正在显示
65 个修改的文件
包含
2614 行增加
和
332 行删除
@@ -3,6 +3,7 @@ package common | @@ -3,6 +3,7 @@ package common | ||
3 | import ( | 3 | import ( |
4 | "oppmg/common/config" | 4 | "oppmg/common/config" |
5 | "oppmg/common/log" | 5 | "oppmg/common/log" |
6 | + "oppmg/common/redis" | ||
6 | 7 | ||
7 | "github.com/astaxie/beego" | 8 | "github.com/astaxie/beego" |
8 | ) | 9 | ) |
@@ -26,4 +27,5 @@ func ResetCommonConfig() { | @@ -26,4 +27,5 @@ func ResetCommonConfig() { | ||
26 | logCallfunc = true | 27 | logCallfunc = true |
27 | } | 28 | } |
28 | log.ResetLog(setlog, logCallfunc) | 29 | log.ResetLog(setlog, logCallfunc) |
30 | + redis.SetRedis(mconfig.RedisAddPort, mconfig.RedisAuth, mconfig.RedisDB) | ||
29 | } | 31 | } |
@@ -12,7 +12,7 @@ type MyConfig struct { | @@ -12,7 +12,7 @@ type MyConfig struct { | ||
12 | SqlConn string //数据库连接 | 12 | SqlConn string //数据库连接 |
13 | RedisAddPort string // | 13 | RedisAddPort string // |
14 | RedisAuth string | 14 | RedisAuth string |
15 | - RedisDB string | 15 | + RedisDB int |
16 | LogOutput string | 16 | LogOutput string |
17 | LogFilename string | 17 | LogFilename string |
18 | LogLevel string | 18 | LogLevel string |
@@ -35,7 +35,7 @@ func RestMyConfig() *MyConfig { | @@ -35,7 +35,7 @@ func RestMyConfig() *MyConfig { | ||
35 | SqlConn: sqlconn, | 35 | SqlConn: sqlconn, |
36 | RedisAddPort: beego.AppConfig.String("redis_add_port"), | 36 | RedisAddPort: beego.AppConfig.String("redis_add_port"), |
37 | RedisAuth: beego.AppConfig.DefaultString("redis_auth", ""), | 37 | RedisAuth: beego.AppConfig.DefaultString("redis_auth", ""), |
38 | - RedisDB: beego.AppConfig.DefaultString("redis_db", "0"), | 38 | + RedisDB: beego.AppConfig.DefaultInt("redis_db", 0), |
39 | LogOutput: beego.AppConfig.DefaultString("log_output", "console"), | 39 | LogOutput: beego.AppConfig.DefaultString("log_output", "console"), |
40 | LogFilename: beego.AppConfig.DefaultString("log_filename", "./log/ability.log"), | 40 | LogFilename: beego.AppConfig.DefaultString("log_filename", "./log/ability.log"), |
41 | LogLevel: beego.AppConfig.DefaultString("log_Level", "debug"), | 41 | LogLevel: beego.AppConfig.DefaultString("log_Level", "debug"), |
@@ -2,12 +2,9 @@ package controllers | @@ -2,12 +2,9 @@ package controllers | ||
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "encoding/json" | 4 | "encoding/json" |
5 | - "fmt" | ||
6 | "oppmg/common/log" | 5 | "oppmg/common/log" |
7 | "oppmg/protocol" | 6 | "oppmg/protocol" |
8 | - "oppmg/services/auth" | ||
9 | - | ||
10 | - "github.com/astaxie/beego/validation" | 7 | + serveauth "oppmg/services/auth" |
11 | ) | 8 | ) |
12 | 9 | ||
13 | type AuthController struct { | 10 | type AuthController struct { |
@@ -16,55 +13,53 @@ type AuthController struct { | @@ -16,55 +13,53 @@ type AuthController struct { | ||
16 | 13 | ||
17 | //URLMapping 实现ControllerInterface中的URLMapping | 14 | //URLMapping 实现ControllerInterface中的URLMapping |
18 | func (c *AuthController) URLMapping() { | 15 | func (c *AuthController) URLMapping() { |
19 | - c.Mapping("AccessToken", c.AccessToken) | ||
20 | 16 | ||
21 | } | 17 | } |
22 | 18 | ||
23 | -// AccessToken .... | ||
24 | -// @router /AccessToken [post] | ||
25 | -func (c *AuthController) AccessToken() { | 19 | +// RefreshToken .... |
20 | +// @router /refresh_token [get] | ||
21 | +func (c *AuthController) RefreshToken() { | ||
26 | log.Debug("运行cotrollers") | 22 | log.Debug("运行cotrollers") |
27 | var msg *protocol.ResponseMessage | 23 | var msg *protocol.ResponseMessage |
28 | defer func() { | 24 | defer func() { |
29 | c.ResposeJson(msg) | 25 | c.ResposeJson(msg) |
30 | }() | 26 | }() |
31 | - var param protocol.RequestCheckSmsCode | ||
32 | - if err := json.Unmarshal(c.Ctx.Input.RequestBody, ¶m); err != nil { | ||
33 | - log.Error("json 解析失败", err) | ||
34 | - msg = protocol.BadRequestParam("00001") | ||
35 | - return | ||
36 | - } | ||
37 | - valid := validation.Validation{} | ||
38 | - ok, err := valid.Valid(¶m) | ||
39 | - if err != nil { | ||
40 | - //TODO | ||
41 | - log.Error("系统错误", err) | ||
42 | - } | ||
43 | - if !ok { | ||
44 | - //TODO | ||
45 | - log.Error("参数错误") | ||
46 | - } | ||
47 | - data, commErr := auth.GetAccessToken(param) | ||
48 | - msg = protocol.NewReturnResponse(data, commErr) | 27 | + |
28 | + // data, commErr := auth.GetAccessToken(param) | ||
29 | + // msg = protocol.NewReturnResponse(data, commErr) | ||
49 | return | 30 | return |
50 | } | 31 | } |
51 | 32 | ||
52 | -// Demo 登录 | ||
53 | -// @router /demo [post] | ||
54 | -func (c *AuthController) Demo() { | 33 | +// Login 登录 |
34 | +// @router /login [post] | ||
35 | +func (c *AuthController) Login() { | ||
55 | var msg *protocol.ResponseMessage | 36 | var msg *protocol.ResponseMessage |
56 | defer func() { | 37 | defer func() { |
57 | c.ResposeJson(msg) | 38 | c.ResposeJson(msg) |
58 | }() | 39 | }() |
59 | - type Parameter struct { | ||
60 | - } | ||
61 | - var param Parameter | 40 | + |
41 | + var param protocol.RequestLogin | ||
62 | if err := json.Unmarshal(c.Ctx.Input.RequestBody, ¶m); err != nil { | 42 | if err := json.Unmarshal(c.Ctx.Input.RequestBody, ¶m); err != nil { |
63 | - fmt.Println("json 解析失败", err) | ||
64 | - // msg.Code = "1" | 43 | + log.Error("json 解析失败", err) |
44 | + msg = protocol.BadRequestParam("1") | ||
65 | return | 45 | return |
66 | } | 46 | } |
67 | - /**/ | ||
68 | - | 47 | + if len(param.Account) == 0 || len(param.Password) == 0 { |
48 | + msg = protocol.BadRequestParam("10021") | ||
49 | + return | ||
50 | + } | ||
51 | + logintoken, err := serveauth.LoginAuthByPassword(param.Account, param.Password) | ||
52 | + if err != nil { | ||
53 | + msg = protocol.NewReturnResponse(nil, err) | ||
54 | + return | ||
55 | + } | ||
56 | + err = serveauth.ResetLoginToken(logintoken) | ||
57 | + msg = protocol.NewReturnResponse(logintoken, err) | ||
69 | return | 58 | return |
70 | } | 59 | } |
60 | + | ||
61 | +//ChangeCompany 切换公司 | ||
62 | +//@Router /change_company [post] | ||
63 | +func (c *AuthController) ChangeCompany() { | ||
64 | + | ||
65 | +} |
@@ -3,7 +3,7 @@ module oppmg | @@ -3,7 +3,7 @@ module oppmg | ||
3 | go 1.13 | 3 | go 1.13 |
4 | 4 | ||
5 | require ( | 5 | require ( |
6 | - github.com/astaxie/beego v1.10.0 | 6 | + github.com/astaxie/beego v1.11.1 |
7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible | 7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible |
8 | github.com/go-redis/redis v6.15.6+incompatible | 8 | github.com/go-redis/redis v6.15.6+incompatible |
9 | github.com/go-sql-driver/mysql v1.4.1 | 9 | github.com/go-sql-driver/mysql v1.4.1 |
@@ -2,10 +2,13 @@ github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8L | @@ -2,10 +2,13 @@ github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8L | ||
2 | github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM= | 2 | github.com/OwnLocal/goes v1.0.0/go.mod h1:8rIFjBGTue3lCU0wplczcUgt9Gxgrkkrw7etMIcn8TM= |
3 | github.com/astaxie/beego v1.10.0 h1:s0OZ1iUO0rl8+lwWZfPK/0GhQi1tFUcIClTevyz48Pg= | 3 | github.com/astaxie/beego v1.10.0 h1:s0OZ1iUO0rl8+lwWZfPK/0GhQi1tFUcIClTevyz48Pg= |
4 | github.com/astaxie/beego v1.10.0/go.mod h1:0R4++1tUqERR0WYFWdfkcrsyoVBCG4DgpDGokT3yb+U= | 4 | github.com/astaxie/beego v1.10.0/go.mod h1:0R4++1tUqERR0WYFWdfkcrsyoVBCG4DgpDGokT3yb+U= |
5 | +github.com/astaxie/beego v1.11.1 h1:6DESefxW5oMcRLFRKi53/6exzup/IR6N4EzzS1n6CnQ= | ||
6 | +github.com/astaxie/beego v1.11.1/go.mod h1:i69hVzgauOPSw5qeyF4GVZhn7Od0yG5bbCGzmhbWxgQ= | ||
5 | github.com/astaxie/beego v1.12.0 h1:MRhVoeeye5N+Flul5PoVfD9CslfdoH+xqC/xvSQ5u2Y= | 7 | github.com/astaxie/beego v1.12.0 h1:MRhVoeeye5N+Flul5PoVfD9CslfdoH+xqC/xvSQ5u2Y= |
6 | github.com/astaxie/beego v1.12.0/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o= | 8 | github.com/astaxie/beego v1.12.0/go.mod h1:fysx+LZNZKnvh4GED/xND7jWtjCR6HzydR2Hh2Im57o= |
7 | github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= | 9 | github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= |
8 | github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= | 10 | github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= |
11 | +github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff/go.mod h1:PhH1ZhyCzHKt4uAasyx+ljRCgoezetRNf59CUtwUkqY= | ||
9 | github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= | 12 | github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= |
10 | github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= | 13 | github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= |
11 | github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= | 14 | github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= |
1 | package middleware | 1 | package middleware |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | - "crypto/sha256" | ||
5 | - "encoding/hex" | ||
6 | "fmt" | 4 | "fmt" |
7 | "oppmg/common/log" | 5 | "oppmg/common/log" |
8 | "oppmg/protocol" | 6 | "oppmg/protocol" |
9 | - "strings" | 7 | + serveauth "oppmg/services/auth" |
8 | + "oppmg/storage/redisdata" | ||
10 | 9 | ||
11 | "github.com/astaxie/beego/plugins/cors" | 10 | "github.com/astaxie/beego/plugins/cors" |
12 | 11 | ||
@@ -24,34 +23,86 @@ var LogRequestData = func(ctx *context.Context) { | @@ -24,34 +23,86 @@ var LogRequestData = func(ctx *context.Context) { | ||
24 | } | 23 | } |
25 | 24 | ||
26 | //CheckSign Before Router | 25 | //CheckSign Before Router |
27 | -var CheckSign = func(ctx *context.Context) { | 26 | +// var CheckSign = func(ctx *context.Context) { |
27 | +// var ( | ||
28 | +// headTimeStamp string | ||
29 | +// headUuid string | ||
30 | +// headAccessToken string | ||
31 | +// headSign string | ||
32 | +// signHex string | ||
33 | +// ) | ||
34 | +// headTimeStamp = ctx.Input.Header(protocol.HeaderTimestamp) | ||
35 | +// headUuid = ctx.Input.Header(protocol.HeaderUUID) | ||
36 | +// headSign = ctx.Input.Header(protocol.HeaderSign) | ||
37 | +// setsign := fmt.Sprintf("v!(MmM%v%v%vMmM)i^", headTimeStamp, headUuid, headAccessToken) | ||
38 | +// sha256 := sha256.New() | ||
39 | +// sha256.Write([]byte(setsign)) | ||
40 | +// signHex = hex.EncodeToString(sha256.Sum(nil)) | ||
41 | +// if strings.Compare(signHex, headSign) != 0 { | ||
42 | +// msg := protocol.BadRequestParam("113") | ||
43 | +// ctx.Output.JSON(msg, false, false) | ||
44 | +// return | ||
45 | +// } | ||
46 | +// return | ||
47 | +// } | ||
48 | + | ||
49 | +//AuthToken Before Router | ||
50 | +var AuthToken = func(ctx *context.Context) { | ||
51 | + log.Debug("执行中间件AuthToken") | ||
28 | var ( | 52 | var ( |
29 | - headTimeStamp string | ||
30 | - headUuid string | ||
31 | - headAccessToken string | ||
32 | - headSign string | ||
33 | - signHex string | 53 | + storetoken protocol.LoginAuthToken |
54 | + msg *protocol.ResponseMessage | ||
55 | + err error | ||
56 | + mtoken *serveauth.MyToken | ||
34 | ) | 57 | ) |
35 | - headTimeStamp = ctx.Input.Header(protocol.HeaderTimestamp) | ||
36 | - headUuid = ctx.Input.Header(protocol.HeaderUUID) | ||
37 | - headSign = ctx.Input.Header(protocol.HeaderSign) | ||
38 | - setsign := fmt.Sprintf("v!(MmM%v%v%vMmM)i^", headTimeStamp, headUuid, headAccessToken) | ||
39 | - sha256 := sha256.New() | ||
40 | - sha256.Write([]byte(setsign)) | ||
41 | - signHex = hex.EncodeToString(sha256.Sum(nil)) | ||
42 | - if strings.Compare(signHex, headSign) != 0 { | ||
43 | - msg := protocol.BadRequestParam("113") | 58 | + accesstoken := ctx.Input.Header(protocol.HeaderAccessToken) |
59 | + refreshToken := ctx.Input.Header(protocol.HeaderRefreshToken) | ||
60 | + mtoken, err = serveauth.ValidJWTToken(accesstoken) | ||
61 | + if err == nil { | ||
62 | + storetoken, err = redisdata.GetLoginToken(mtoken.UID) | ||
63 | + if err != nil { | ||
64 | + log.Error("err:%s", err) | ||
65 | + msg = protocol.NewMesage("10024") | ||
66 | + ctx.Output.JSON(msg, false, false) | ||
67 | + return | ||
68 | + } | ||
69 | + if storetoken.AccessToken != accesstoken { | ||
70 | + msg = protocol.NewMesage("10025") | ||
71 | + ctx.Output.JSON(msg, false, false) | ||
72 | + return | ||
73 | + } | ||
74 | + ctx.Input.SetData(protocol.HeaderCompanyid, mtoken.CompanyID) | ||
75 | + ctx.Input.SetData(protocol.HeaderUserid, mtoken.UID) | ||
76 | + return | ||
77 | + } | ||
78 | + if ok := serveauth.IsJwtErrorExpired(err); ok { | ||
79 | + //token过期 | ||
80 | + mtoken, err = serveauth.ValidJWTToken(refreshToken) | ||
81 | + if err != nil { | ||
82 | + msg = protocol.NewMesage("10024") | ||
83 | + ctx.Output.JSON(msg, false, false) | ||
84 | + return | ||
85 | + } | ||
86 | + storetoken, err = redisdata.GetLoginToken(mtoken.UID) | ||
87 | + if err != nil { | ||
88 | + log.Error("err:%s", err) | ||
89 | + msg = protocol.NewMesage("10024") | ||
90 | + ctx.Output.JSON(msg, false, false) | ||
91 | + return | ||
92 | + } | ||
93 | + if storetoken.AccessToken != accesstoken { | ||
94 | + msg = protocol.NewMesage("10025") | ||
95 | + ctx.Output.JSON(msg, false, false) | ||
96 | + return | ||
97 | + } | ||
98 | + logintoken, _ := serveauth.GenerateAuthToken(mtoken.UID, mtoken.CompanyID) | ||
99 | + serveauth.ResetLoginTokenRedis(logintoken) | ||
100 | + msg = protocol.NewReturnResponse(logintoken, nil) | ||
44 | ctx.Output.JSON(msg, false, false) | 101 | ctx.Output.JSON(msg, false, false) |
45 | return | 102 | return |
46 | } | 103 | } |
47 | - return | ||
48 | -} | ||
49 | - | ||
50 | -//AuthToken Before Router | ||
51 | -var AuthToken = func(ctx *context.Context) { | ||
52 | - log.Debug("执行中间件AuthToken") | ||
53 | - | ||
54 | - // ctx.Output.Body([]byte("{}")) | 104 | + msg = protocol.NewMesage("10024") |
105 | + ctx.Output.JSON(msg, false, false) | ||
55 | return | 106 | return |
56 | } | 107 | } |
57 | 108 |
@@ -2,6 +2,7 @@ package models | @@ -2,6 +2,7 @@ package models | ||
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "fmt" | 4 | "fmt" |
5 | + "oppmg/common/log" | ||
5 | "time" | 6 | "time" |
6 | 7 | ||
7 | "github.com/astaxie/beego/orm" | 8 | "github.com/astaxie/beego/orm" |
@@ -40,13 +41,6 @@ const ( | @@ -40,13 +41,6 @@ const ( | ||
40 | DEVICE_TYPE_WEB | 41 | DEVICE_TYPE_WEB |
41 | ) | 42 | ) |
42 | 43 | ||
43 | -//过期时长设置,单位:秒 | ||
44 | -const ( | ||
45 | - REFRESH_TIME int64 = 60 * 60 * 4 //4小时 | ||
46 | - ACCESS_TIME int64 = 60 * 60 * 2 //2小时 | ||
47 | - AUTHCODE_TIME int64 = 60 * 60 * 2 //2小时 | ||
48 | -) | ||
49 | - | ||
50 | // AddUserAuth insert a new UserAuth into database and returns | 44 | // AddUserAuth insert a new UserAuth into database and returns |
51 | // last inserted Id on success. | 45 | // last inserted Id on success. |
52 | func AddUserAuth(m *UserAuth) (id int64, err error) { | 46 | func AddUserAuth(m *UserAuth) (id int64, err error) { |
@@ -68,15 +62,12 @@ func GetUserAuthById(id int64) (v *UserAuth, err error) { | @@ -68,15 +62,12 @@ func GetUserAuthById(id int64) (v *UserAuth, err error) { | ||
68 | 62 | ||
69 | // UpdateUserAuth updates UserAuth by Id and returns error if | 63 | // UpdateUserAuth updates UserAuth by Id and returns error if |
70 | // the record to be updated doesn't exist | 64 | // the record to be updated doesn't exist |
71 | -func UpdateUserAuthById(m *UserAuth) (err error) { | 65 | +func UpdateUserAuthById(m *UserAuth, col []string) (err error) { |
72 | o := orm.NewOrm() | 66 | o := orm.NewOrm() |
73 | - v := UserAuth{Id: m.Id} | ||
74 | // ascertain id exists in the database | 67 | // ascertain id exists in the database |
75 | - if err = o.Read(&v); err == nil { | ||
76 | - var num int64 | ||
77 | - if num, err = o.Update(m); err == nil { | ||
78 | - fmt.Println("Number of records updated in database:", num) | ||
79 | - } | 68 | + var num int64 |
69 | + if num, err = o.Update(m, col...); err == nil { | ||
70 | + log.Info("Number of records updated in database:", num) | ||
80 | } | 71 | } |
81 | return | 72 | return |
82 | } | 73 | } |
@@ -4,12 +4,18 @@ package protocol | @@ -4,12 +4,18 @@ package protocol | ||
4 | const ( | 4 | const ( |
5 | HeaderAccessToken string = "x-mmm-accesstoken" | 5 | HeaderAccessToken string = "x-mmm-accesstoken" |
6 | HeaderRefreshToken string = "x-mmm-refreshtoken" | 6 | HeaderRefreshToken string = "x-mmm-refreshtoken" |
7 | - HeaderUID string = "x-mmm-uid" | ||
8 | - HeaderUUID string = "x-mmm-uuid" | ||
9 | - HeaderTimestamp string = "x-mmm-timestamp" | ||
10 | - HeaderDevicetype string = "x-mmm-devicetype" | ||
11 | - HeaderAppproject string = "x-mmm-appproject" | ||
12 | - HeaderSign string = "x-mmm-sign" | 7 | + // HeaderUID string = "x-mmm-uid" |
8 | + // HeaderUUID string = "x-mmm-uuid" | ||
9 | + // HeaderTimestamp string = "x-mmm-timestamp" | ||
10 | + // HeaderDevicetype string = "x-mmm-devicetype" | ||
11 | + // HeaderAppproject string = "x-mmm-appproject" | ||
12 | + // HeaderSign string = "x-mmm-sign" | ||
13 | +) | ||
14 | + | ||
15 | +//用来存储从token中解析出来的内容对应的键名 | ||
16 | +const ( | ||
17 | + HeaderCompanyid string = "header_companyid" | ||
18 | + HeaderUserid string = "header_userid" | ||
13 | ) | 19 | ) |
14 | 20 | ||
15 | //BaseHeader 请求的header数据 | 21 | //BaseHeader 请求的header数据 |
@@ -16,4 +16,8 @@ var errmessge ErrorMap = map[string]string{ | @@ -16,4 +16,8 @@ var errmessge ErrorMap = map[string]string{ | ||
16 | //安全认证相关 | 16 | //安全认证相关 |
17 | "10021": "账号或密码不正确", | 17 | "10021": "账号或密码不正确", |
18 | "10022": "账号已被禁用", | 18 | "10022": "账号已被禁用", |
19 | + "10023": "用户无使用权限", | ||
20 | + "10024": "登录凭证失效", | ||
21 | + "10025": "该账号已在其他地方登录", | ||
22 | + "10026": "登录凭证过期", | ||
19 | } | 23 | } |
@@ -11,7 +11,7 @@ import ( | @@ -11,7 +11,7 @@ import ( | ||
11 | func init() { | 11 | func init() { |
12 | nsV1 := beego.NewNamespace("v1", | 12 | nsV1 := beego.NewNamespace("v1", |
13 | beego.NSBefore(middleware.AllowOption), | 13 | beego.NSBefore(middleware.AllowOption), |
14 | - beego.NSBefore(middleware.LogRequestData, middleware.AuthToken), | 14 | + beego.NSBefore(middleware.LogRequestData), |
15 | beego.NSNamespace("/company", | 15 | beego.NSNamespace("/company", |
16 | beego.NSRouter("/:companyid([0-9]+)/department", &controllers.CompanyController{}, "get:DepartmentList"), | 16 | beego.NSRouter("/:companyid([0-9]+)/department", &controllers.CompanyController{}, "get:DepartmentList"), |
17 | beego.NSRouter("/department/:id([0-9]+)", &controllers.CompanyController{}, "get:DepartmentOne"), | 17 | beego.NSRouter("/department/:id([0-9]+)", &controllers.CompanyController{}, "get:DepartmentOne"), |
@@ -32,12 +32,17 @@ func init() { | @@ -32,12 +32,17 @@ func init() { | ||
32 | beego.NSRouter("/role_group", &controllers.RbacController{}, "delete:RoleGroupDelete"), | 32 | beego.NSRouter("/role_group", &controllers.RbacController{}, "delete:RoleGroupDelete"), |
33 | beego.NSRouter("/:companyid([0-9]+)/role", &controllers.RbacController{}, "get:RoleList"), | 33 | beego.NSRouter("/:companyid([0-9]+)/role", &controllers.RbacController{}, "get:RoleList"), |
34 | ), | 34 | ), |
35 | - beego.NSAfter(middleware.LogRouter), | ||
36 | ) | 35 | ) |
37 | 36 | ||
38 | nsAuth := beego.NewNamespace("/auth", | 37 | nsAuth := beego.NewNamespace("/auth", |
39 | - beego.NSBefore(middleware.LogRequestData, middleware.AuthToken), | ||
40 | - beego.NSRouter("/accessToken", &controllers.AuthController{}, "post:AccessToken"), | 38 | + beego.NSBefore(middleware.AllowOption), |
39 | + beego.NSBefore(middleware.LogRequestData), | ||
40 | + beego.NSRouter("/login", &controllers.AuthController{}, "post:Login"), | ||
41 | + beego.NSNamespace("/token", | ||
42 | + beego.NSBefore(middleware.AuthToken), | ||
43 | + beego.NSRouter("/change_company", &controllers.AuthController{}, "post:ChangeCompany"), | ||
44 | + beego.NSRouter("/refresh_token", &controllers.AuthController{}, "post:RefreshToken"), | ||
45 | + ), | ||
41 | ) | 46 | ) |
42 | beego.AddNamespace(nsV1) | 47 | beego.AddNamespace(nsV1) |
43 | beego.AddNamespace(nsAuth) | 48 | beego.AddNamespace(nsAuth) |
@@ -9,6 +9,7 @@ import ( | @@ -9,6 +9,7 @@ import ( | ||
9 | "oppmg/common/log" | 9 | "oppmg/common/log" |
10 | "oppmg/models" | 10 | "oppmg/models" |
11 | "oppmg/protocol" | 11 | "oppmg/protocol" |
12 | + "oppmg/storage/redisdata" | ||
12 | "strings" | 13 | "strings" |
13 | "time" | 14 | "time" |
14 | 15 | ||
@@ -38,10 +39,9 @@ func validatePassword(from, to string) bool { | @@ -38,10 +39,9 @@ func validatePassword(from, to string) bool { | ||
38 | } | 39 | } |
39 | 40 | ||
40 | //LoginAuth 登录认证 | 41 | //LoginAuth 登录认证 |
41 | -func LoginAuthByPassword(account, password string) error { | 42 | +func LoginAuthByPassword(account, password string) (protocol.LoginAuthToken, error) { |
42 | var ( | 43 | var ( |
43 | user *models.User | 44 | user *models.User |
44 | - uAuth *models.UserAuth | ||
45 | companys []models.Company | 45 | companys []models.Company |
46 | mcompany models.Company | 46 | mcompany models.Company |
47 | loginToken protocol.LoginAuthToken | 47 | loginToken protocol.LoginAuthToken |
@@ -50,82 +50,56 @@ func LoginAuthByPassword(account, password string) error { | @@ -50,82 +50,56 @@ func LoginAuthByPassword(account, password string) error { | ||
50 | user, err = models.GetUserByPhone(account) | 50 | user, err = models.GetUserByPhone(account) |
51 | if err != nil { | 51 | if err != nil { |
52 | log.Error(err.Error()) | 52 | log.Error(err.Error()) |
53 | - return protocol.NewErrWithMessage("10021", err) | 53 | + return loginToken, protocol.NewErrWithMessage("10021", err) |
54 | } | 54 | } |
55 | if ok := validatePassword(password, user.Passwd); !ok { | 55 | if ok := validatePassword(password, user.Passwd); !ok { |
56 | - return protocol.NewErrWithMessage("10021", err) | 56 | + return loginToken, protocol.NewErrWithMessage("10021", err) |
57 | } | 57 | } |
58 | if ok := user.IsEnable(); !ok { | 58 | if ok := user.IsEnable(); !ok { |
59 | - return protocol.NewErrWithMessage("10022") | 59 | + return loginToken, protocol.NewErrWithMessage("10022") |
60 | } | 60 | } |
61 | companys, err = models.GetCompanyByUser(user.Id) | 61 | companys, err = models.GetCompanyByUser(user.Id) |
62 | if err != nil { | 62 | if err != nil { |
63 | e := fmt.Errorf("GetCompanyByUser(%d) err:%s", user.Id, err) | 63 | e := fmt.Errorf("GetCompanyByUser(%d) err:%s", user.Id, err) |
64 | log.Error(e.Error()) | 64 | log.Error(e.Error()) |
65 | - return protocol.NewErrWithMessage("1") | 65 | + return loginToken, protocol.NewErrWithMessage("1") |
66 | } | 66 | } |
67 | if len(companys) <= 0 { | 67 | if len(companys) <= 0 { |
68 | log.Error("can not found company") | 68 | log.Error("can not found company") |
69 | - return protocol.NewErrWithMessage("1") | 69 | + return loginToken, protocol.NewErrWithMessage("1") |
70 | } | 70 | } |
71 | mcompany = companys[0] | 71 | mcompany = companys[0] |
72 | loginToken, err = GenerateAuthToken(user.Id, mcompany.Id) | 72 | loginToken, err = GenerateAuthToken(user.Id, mcompany.Id) |
73 | if err != nil { | 73 | if err != nil { |
74 | e := fmt.Errorf("GenerateAuthToken err:%s", err) | 74 | e := fmt.Errorf("GenerateAuthToken err:%s", err) |
75 | log.Error(e.Error()) | 75 | log.Error(e.Error()) |
76 | - return protocol.NewErrWithMessage("1") | 76 | + return loginToken, protocol.NewErrWithMessage("1") |
77 | } | 77 | } |
78 | - // uAuth, err = models.ReadUserAuthByDevice(user.Id, models.DEVICE_TYPE_WEB) | ||
79 | - // if err != nil && err != orm.ErrNoRows { | ||
80 | - // e := fmt.Errorf("ReadUserAuthByDevice(%d,%d) err:%s", user.Id, models.DEVICE_TYPE_WEB, err) | ||
81 | - // log.Error(e.Error()) | ||
82 | - // return protocol.NewErrWithMessage("1", e) | ||
83 | - // } | ||
84 | - // var ( | ||
85 | - // authcode string | ||
86 | - // authcodeExp time.Time | ||
87 | - // ) | ||
88 | - // authcode = utils.GenerateIDByUUID() | ||
89 | - // authcodeExp = time.Now().Add(time.Duration(models.AUTHCODE_TIME) * time.Second) | ||
90 | - // if err == orm.ErrNoRows { | ||
91 | - // uAuth := &models.UserAuth{ | ||
92 | - // UserId: user.Id, | ||
93 | - // AuthCode: authcode, | ||
94 | - // AuthCodeExp: authcodeExp, | ||
95 | - // CreateAt: time.Now(), | ||
96 | - // } | ||
97 | - // _, err = models.AddUserAuth(uAuth) | ||
98 | - // if err != nil { | ||
99 | - // e := fmt.Errorf("AddUserAuth err:%s", err) | ||
100 | - // log.Error(e.Error()) | ||
101 | - // return protocol.NewErrWithMessage("1", e) | ||
102 | - // } | ||
103 | - // } | ||
104 | - // if err == nil { | ||
105 | - // uAuth.AuthCode = authcode | ||
106 | - // uAuth.AuthCodeExp = authcodeExp | ||
107 | - // uAuth.UpdateAt = time.Now() | ||
108 | - // err = models.UpdateUserAuthById(uAuth) | ||
109 | - // if err != nil { | ||
110 | - // e := fmt.Errorf("UpdateUserAuthById err:%s", err) | ||
111 | - // log.Error(e.Error()) | ||
112 | - // return protocol.NewErrWithMessage("1", e) | ||
113 | - // } | ||
114 | - // } | ||
115 | - | ||
116 | - return nil | 78 | + return loginToken, nil |
117 | } | 79 | } |
118 | 80 | ||
119 | //ResetLoginToken token存数据库 | 81 | //ResetLoginToken token存数据库 |
120 | -func ResetLoginToken(loginToken protocol.LoginAuthToken, userid int64, companyid int64) error { | 82 | +func ResetLoginToken(loginToken protocol.LoginAuthToken) error { |
121 | var ( | 83 | var ( |
122 | - uAuth *models.UserAuth | ||
123 | - err error | ||
124 | - nowTime = time.Now() | 84 | + uAuth *models.UserAuth |
85 | + err error | ||
86 | + nowTime = time.Now() | ||
87 | + userid int64 | ||
88 | + companyid int64 | ||
89 | + mtoken *MyToken | ||
125 | ) | 90 | ) |
91 | + mtoken, err = ValidJWTToken(loginToken.AccessToken) | ||
92 | + if err != nil { | ||
93 | + log.Error("jwt err:%s", err) | ||
94 | + return protocol.NewErrWithMessage("1") | ||
95 | + } | ||
96 | + userid = mtoken.UID | ||
97 | + companyid = mtoken.CompanyID | ||
126 | uAuth, err = models.GetUserAuthByUser(userid) | 98 | uAuth, err = models.GetUserAuthByUser(userid) |
127 | if err != nil && err != orm.ErrNoRows { | 99 | if err != nil && err != orm.ErrNoRows { |
128 | - return err | 100 | + e := fmt.Errorf("GetUserAuthByUser err:%s", err) |
101 | + log.Error(e.Error()) | ||
102 | + return protocol.NewErrWithMessage("1", e) | ||
129 | } | 103 | } |
130 | if err == orm.ErrNoRows { | 104 | if err == orm.ErrNoRows { |
131 | //添加用户授权 | 105 | //添加用户授权 |
@@ -141,11 +115,43 @@ func ResetLoginToken(loginToken protocol.LoginAuthToken, userid int64, companyid | @@ -141,11 +115,43 @@ func ResetLoginToken(loginToken protocol.LoginAuthToken, userid int64, companyid | ||
141 | DeviceType: models.DEVICE_TYPE_WEB, | 115 | DeviceType: models.DEVICE_TYPE_WEB, |
142 | } | 116 | } |
143 | _, err = models.AddUserAuth(uAuth) | 117 | _, err = models.AddUserAuth(uAuth) |
144 | - return err | 118 | + if err != nil { |
119 | + e := fmt.Errorf("AddUserAuth err:%s", err) | ||
120 | + log.Error(e.Error()) | ||
121 | + return protocol.NewErrWithMessage("1", e) | ||
122 | + } | ||
145 | } | 123 | } |
146 | //更新用户授权 | 124 | //更新用户授权 |
147 | - | 125 | + uAuth.CurrentCompanyId = companyid |
126 | + uAuth.AccessToken = loginToken.AccessToken | ||
127 | + uAuth.AccessTokenExp = time.Unix(loginToken.ExpiresIn, 0) | ||
128 | + uAuth.RefreshToken = loginToken.RefreshToken | ||
129 | + uAuth.RefreshTokenExp = time.Unix(loginToken.RefreshExpires, 0) | ||
130 | + uAuth.UpdateAt = nowTime | ||
131 | + upCol := []string{"CurrentCompanyId", "AccessToken", "RefreshToken", "RefreshTokenExp", "UpdateAt"} | ||
132 | + if err = models.UpdateUserAuthById(uAuth, upCol); err != nil { | ||
133 | + e := fmt.Errorf("UpdateUserAuthById err:%s", err) | ||
134 | + log.Error(e.Error()) | ||
135 | + return protocol.NewErrWithMessage("1", e) | ||
136 | + } | ||
148 | return nil | 137 | return nil |
149 | } | 138 | } |
150 | 139 | ||
151 | -//TODO token 存redis | 140 | +// token 存redis |
141 | +func ResetLoginTokenRedis(loginToken protocol.LoginAuthToken) error { | ||
142 | + var ( | ||
143 | + mtoken *MyToken | ||
144 | + err error | ||
145 | + ) | ||
146 | + mtoken, err = ValidJWTToken(loginToken.AccessToken) | ||
147 | + if err != nil { | ||
148 | + log.Error("jwt err:%s", err) | ||
149 | + return protocol.NewErrWithMessage("1") | ||
150 | + } | ||
151 | + err = redisdata.SetLoginToken(loginToken, mtoken.UID) | ||
152 | + if err != nil { | ||
153 | + log.Error(" redisdata.SetLoginToken err:%s", err) | ||
154 | + } | ||
155 | + return nil | ||
156 | + | ||
157 | +} |
@@ -56,6 +56,14 @@ func ValidJWTToken(tokenString string) (*MyToken, error) { | @@ -56,6 +56,14 @@ func ValidJWTToken(tokenString string) (*MyToken, error) { | ||
56 | return nil, fmt.Errorf("token Valid fail") | 56 | return nil, fmt.Errorf("token Valid fail") |
57 | } | 57 | } |
58 | 58 | ||
59 | +func IsJwtErrorExpired(err error) bool { | ||
60 | + ve, ok := err.(*jwt.ValidationError) | ||
61 | + if ok && ve.Errors == jwt.ValidationErrorExpired { | ||
62 | + return true | ||
63 | + } | ||
64 | + return false | ||
65 | +} | ||
66 | + | ||
59 | func GenerateAuthToken(uid int64, companyid int64) (protocol.LoginAuthToken, error) { | 67 | func GenerateAuthToken(uid int64, companyid int64) (protocol.LoginAuthToken, error) { |
60 | var ( | 68 | var ( |
61 | authToken protocol.LoginAuthToken | 69 | authToken protocol.LoginAuthToken |
@@ -66,11 +74,11 @@ func GenerateAuthToken(uid int64, companyid int64) (protocol.LoginAuthToken, err | @@ -66,11 +74,11 @@ func GenerateAuthToken(uid int64, companyid int64) (protocol.LoginAuthToken, err | ||
66 | err error | 74 | err error |
67 | nowtime = time.Now() | 75 | nowtime = time.Now() |
68 | ) | 76 | ) |
69 | - accesstoken, err = CreateJWTToken(uid, companyid, expiresIn+10) | 77 | + accesstoken, err = CreateJWTToken(uid, companyid, expiresIn+2) |
70 | if err != nil { | 78 | if err != nil { |
71 | return authToken, err | 79 | return authToken, err |
72 | } | 80 | } |
73 | - refreshtoken, err = CreateJWTToken(uid, companyid, refreshExpires+10) | 81 | + refreshtoken, err = CreateJWTToken(uid, companyid, refreshExpires+2) |
74 | if err != nil { | 82 | if err != nil { |
75 | return authToken, err | 83 | return authToken, err |
76 | } | 84 | } |
services/ucenter/models.go
0 → 100644
1 | +package ucenter | ||
2 | + | ||
3 | +import ( | ||
4 | + "encoding/json" | ||
5 | + "errors" | ||
6 | +) | ||
7 | + | ||
8 | +type RequesLogin struct { | ||
9 | + Password string `json:"password"` | ||
10 | + Phone string `json:"phone"` | ||
11 | +} | ||
12 | + | ||
13 | +//Format 实现IUCenterParam接口 | ||
14 | +func (r RequesLogin) Format() []byte { | ||
15 | + var v []byte | ||
16 | + v, _ = json.Marshal(r) | ||
17 | + return v | ||
18 | +} | ||
19 | + | ||
20 | +//Format 实现IUCenterParam接口 | ||
21 | +func (r RequesLogin) GetPath() (string, string) { | ||
22 | + return "/auth/login", "POST" | ||
23 | +} | ||
24 | + | ||
25 | +//Format 实现IUCenterParam接口 | ||
26 | +func (r RequesLogin) Valid() error { | ||
27 | + if len(r.Password) == 0 { | ||
28 | + return errors.New("len(r.Password) == 0") | ||
29 | + } | ||
30 | + if len(r.Phone) == 0 { | ||
31 | + return errors.New("len(r.Phone == 0") | ||
32 | + } | ||
33 | + return nil | ||
34 | +} | ||
35 | + | ||
36 | +type RequestAddUser struct { | ||
37 | + Phone string `json:"phone"` | ||
38 | + RegIm bool `json:"regIm"` | ||
39 | + Nickname string `json:"nickname"` | ||
40 | + Avatar string `json:"avatar"` | ||
41 | + Password string `json:"password"` | ||
42 | +} | ||
43 | + | ||
44 | +//Format 实现IUCenterParam接口 | ||
45 | +func (r RequestAddUser) Format() []byte { | ||
46 | + var v []byte | ||
47 | + v, _ = json.Marshal(r) | ||
48 | + return v | ||
49 | +} | ||
50 | + | ||
51 | +//Format 实现IUCenterParam接口 | ||
52 | +func (r RequestAddUser) GetPath() (string, string) { | ||
53 | + return "/users", "POST" | ||
54 | +} | ||
55 | + | ||
56 | +//Format 实现IUCenterParam接口 | ||
57 | +func (r RequestAddUser) Valid() error { | ||
58 | + if len(r.Password) == 0 { | ||
59 | + return errors.New("len(r.Password) == 0") | ||
60 | + } | ||
61 | + if len(r.Phone) == 0 { | ||
62 | + return errors.New("len(r.Phone == 0") | ||
63 | + } | ||
64 | + return nil | ||
65 | +} |
services/ucenter/request.go
0 → 100644
1 | +package ucenter | ||
2 | + | ||
3 | +import ( | ||
4 | + "bytes" | ||
5 | + "io/ioutil" | ||
6 | + "net/http" | ||
7 | + "time" | ||
8 | +) | ||
9 | + | ||
10 | +type IUCenterParam interface { | ||
11 | + Format() []byte | ||
12 | + GetPath() (string, string) //返回请求路径path,请求方式mathod | ||
13 | + Valid() error | ||
14 | +} | ||
15 | + | ||
16 | +type UCenterClient struct { | ||
17 | + appKey string | ||
18 | + baseUrl string | ||
19 | +} | ||
20 | + | ||
21 | +func (client UCenterClient) buildHeader() http.Header { | ||
22 | + var h = http.Header{} | ||
23 | + h.Set("Content-Type", "application/json") | ||
24 | + h.Set("appKey", client.appKey) | ||
25 | + return h | ||
26 | +} | ||
27 | + | ||
28 | +//httpDo post发送json | ||
29 | +func (client UCenterClient) httpDo(path string, mathod string, posts []byte) ([]byte, error) { | ||
30 | + httpclient := http.Client{ | ||
31 | + Timeout: 5 * time.Second, //请求超时时间5秒 | ||
32 | + } | ||
33 | + reqURL := client.baseUrl + path | ||
34 | + req, err := http.NewRequest(mathod, reqURL, bytes.NewReader(posts)) | ||
35 | + if err != nil { | ||
36 | + return nil, err | ||
37 | + } | ||
38 | + req.Header = client.buildHeader() | ||
39 | + resp, err := httpclient.Do(req) | ||
40 | + if err != nil { | ||
41 | + return nil, err | ||
42 | + } | ||
43 | + defer resp.Body.Close() | ||
44 | + body, err := ioutil.ReadAll(resp.Body) | ||
45 | + if err != nil { | ||
46 | + return nil, err | ||
47 | + } | ||
48 | + | ||
49 | + return body, nil | ||
50 | +} | ||
51 | + | ||
52 | +func (client UCenterClient) Call(param IUCenterParam) ([]byte, error) { | ||
53 | + path, mathod := param.GetPath() | ||
54 | + return client.httpDo(path, mathod, param.Format()) | ||
55 | +} |
1 | package redisdata | 1 | package redisdata |
2 | + | ||
3 | +import ( | ||
4 | + "encoding/json" | ||
5 | + "errors" | ||
6 | + "fmt" | ||
7 | + "oppmg/common/log" | ||
8 | + "oppmg/common/redis" | ||
9 | + "oppmg/protocol" | ||
10 | + "time" | ||
11 | +) | ||
12 | + | ||
13 | +func SetLoginToken(param protocol.LoginAuthToken, userid int64) error { | ||
14 | + client := redis.GetRedis() | ||
15 | + var ( | ||
16 | + key string | ||
17 | + value []byte | ||
18 | + err error | ||
19 | + exp int64 | ||
20 | + nowTime = time.Now().Unix() | ||
21 | + ) | ||
22 | + exp = param.RefreshExpires - nowTime | ||
23 | + key = fmt.Sprintf("%s%s:%d", KEY_PREFIX, KEY_USER_TOKEN, userid) | ||
24 | + value, _ = json.Marshal(param) | ||
25 | + err = client.Set(key, string(value), time.Duration(exp)*time.Second).Err() | ||
26 | + return err | ||
27 | +} | ||
28 | + | ||
29 | +func GetLoginToken(userid int64) (protocol.LoginAuthToken, error) { | ||
30 | + client := redis.GetRedis() | ||
31 | + var ( | ||
32 | + key string | ||
33 | + value string | ||
34 | + err error | ||
35 | + result protocol.LoginAuthToken | ||
36 | + ) | ||
37 | + key = fmt.Sprintf("%s%s:%d", KEY_PREFIX, KEY_USER_TOKEN, userid) | ||
38 | + value = client.Get(key).String() | ||
39 | + if len(value) == 0 { | ||
40 | + return result, errors.New("Token not found") | ||
41 | + } | ||
42 | + err = json.Unmarshal([]byte(value), &result) | ||
43 | + if err != nil { | ||
44 | + log.Error("Unmarshal redis value:%s err:%s", value, err) | ||
45 | + return result, err | ||
46 | + } | ||
47 | + return result, nil | ||
48 | +} | ||
49 | + | ||
50 | +func ExistLoginToken(userid int64) bool { | ||
51 | + client := redis.GetRedis() | ||
52 | + key := fmt.Sprintf("%s%s:%d", KEY_PREFIX, KEY_USER_TOKEN, userid) | ||
53 | + value := client.Exists(key).Val() | ||
54 | + if value > 0 { | ||
55 | + return true | ||
56 | + } | ||
57 | + return false | ||
58 | +} |
1 | language: go | 1 | language: go |
2 | 2 | ||
3 | go: | 3 | go: |
4 | - - "1.9.2" | ||
5 | - - "1.10.3" | 4 | + - "1.10.x" |
5 | + - "1.11.x" | ||
6 | services: | 6 | services: |
7 | - redis-server | 7 | - redis-server |
8 | - mysql | 8 | - mysql |
@@ -34,6 +34,7 @@ install: | @@ -34,6 +34,7 @@ install: | ||
34 | - go get github.com/gogo/protobuf/proto | 34 | - go get github.com/gogo/protobuf/proto |
35 | - go get github.com/Knetic/govaluate | 35 | - go get github.com/Knetic/govaluate |
36 | - go get github.com/casbin/casbin | 36 | - go get github.com/casbin/casbin |
37 | + - go get github.com/elazarl/go-bindata-assetfs | ||
37 | - go get -u honnef.co/go/tools/cmd/gosimple | 38 | - go get -u honnef.co/go/tools/cmd/gosimple |
38 | - go get -u github.com/mdempsky/unconvert | 39 | - go get -u github.com/mdempsky/unconvert |
39 | - go get -u github.com/gordonklaus/ineffassign | 40 | - go get -u github.com/gordonklaus/ineffassign |
@@ -44,12 +45,12 @@ before_script: | @@ -44,12 +45,12 @@ before_script: | ||
44 | - sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi" | 45 | - sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi" |
45 | - sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi" | 46 | - sh -c "if [ '$ORM_DRIVER' = 'mysql' ]; then mysql -u root -e 'create database orm_test;'; fi" |
46 | - sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi" | 47 | - sh -c "if [ '$ORM_DRIVER' = 'sqlite' ]; then touch $TRAVIS_BUILD_DIR/orm_test.db; fi" |
47 | - - sh -c "if [ $(go version) == *1.[5-9]* ]; then go get github.com/golang/lint/golint; golint ./...; fi" | ||
48 | - - sh -c "if [ $(go version) == *1.[5-9]* ]; then go tool vet .; fi" | 48 | + - sh -c "go get github.com/golang/lint/golint; golint ./...;" |
49 | + - sh -c "go list ./... | grep -v vendor | xargs go vet -v" | ||
49 | - mkdir -p res/var | 50 | - mkdir -p res/var |
50 | - ./ssdb/ssdb-server ./ssdb/ssdb.conf -d | 51 | - ./ssdb/ssdb-server ./ssdb/ssdb.conf -d |
51 | after_script: | 52 | after_script: |
52 | - -killall -w ssdb-server | 53 | + - killall -w ssdb-server |
53 | - rm -rf ./res/var/* | 54 | - rm -rf ./res/var/* |
54 | script: | 55 | script: |
55 | - go test -v ./... | 56 | - go test -v ./... |
@@ -59,4 +60,4 @@ script: | @@ -59,4 +60,4 @@ script: | ||
59 | - find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s | 60 | - find . ! \( -path './vendor' -prune \) -type f -name '*.go' -print0 | xargs -0 gofmt -l -s |
60 | - golint ./... | 61 | - golint ./... |
61 | addons: | 62 | addons: |
62 | - postgresql: "9.4" | 63 | + postgresql: "9.6" |
@@ -4,6 +4,8 @@ | @@ -4,6 +4,8 @@ | ||
4 | beego is used for rapid development of RESTful APIs, web apps and backend services in Go. | 4 | beego is used for rapid development of RESTful APIs, web apps and backend services in Go. |
5 | It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding. | 5 | It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding. |
6 | 6 | ||
7 | + Response time ranking: [web-frameworks](https://github.com/the-benchmarker/web-frameworks). | ||
8 | + | ||
7 | ###### More info at [beego.me](http://beego.me). | 9 | ###### More info at [beego.me](http://beego.me). |
8 | 10 | ||
9 | ## Quick Start | 11 | ## Quick Start |
@@ -20,11 +20,10 @@ import ( | @@ -20,11 +20,10 @@ import ( | ||
20 | "fmt" | 20 | "fmt" |
21 | "net/http" | 21 | "net/http" |
22 | "os" | 22 | "os" |
23 | + "reflect" | ||
23 | "text/template" | 24 | "text/template" |
24 | "time" | 25 | "time" |
25 | 26 | ||
26 | - "reflect" | ||
27 | - | ||
28 | "github.com/astaxie/beego/grace" | 27 | "github.com/astaxie/beego/grace" |
29 | "github.com/astaxie/beego/logs" | 28 | "github.com/astaxie/beego/logs" |
30 | "github.com/astaxie/beego/toolbox" | 29 | "github.com/astaxie/beego/toolbox" |
@@ -35,7 +34,7 @@ import ( | @@ -35,7 +34,7 @@ import ( | ||
35 | var beeAdminApp *adminApp | 34 | var beeAdminApp *adminApp |
36 | 35 | ||
37 | // FilterMonitorFunc is default monitor filter when admin module is enable. | 36 | // FilterMonitorFunc is default monitor filter when admin module is enable. |
38 | -// if this func returns, admin module records qbs for this request by condition of this function logic. | 37 | +// if this func returns, admin module records qps for this request by condition of this function logic. |
39 | // usage: | 38 | // usage: |
40 | // func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool { | 39 | // func MyFilterMonitor(method, requestPath string, t time.Duration, pattern string, statusCode int) bool { |
41 | // if method == "POST" { | 40 | // if method == "POST" { |
@@ -67,18 +66,18 @@ func init() { | @@ -67,18 +66,18 @@ func init() { | ||
67 | 66 | ||
68 | // AdminIndex is the default http.Handler for admin module. | 67 | // AdminIndex is the default http.Handler for admin module. |
69 | // it matches url pattern "/". | 68 | // it matches url pattern "/". |
70 | -func adminIndex(rw http.ResponseWriter, r *http.Request) { | 69 | +func adminIndex(rw http.ResponseWriter, _ *http.Request) { |
71 | execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl) | 70 | execTpl(rw, map[interface{}]interface{}{}, indexTpl, defaultScriptsTpl) |
72 | } | 71 | } |
73 | 72 | ||
74 | -// QpsIndex is the http.Handler for writing qbs statistics map result info in http.ResponseWriter. | ||
75 | -// it's registered with url pattern "/qbs" in admin module. | ||
76 | -func qpsIndex(rw http.ResponseWriter, r *http.Request) { | 73 | +// QpsIndex is the http.Handler for writing qps statistics map result info in http.ResponseWriter. |
74 | +// it's registered with url pattern "/qps" in admin module. | ||
75 | +func qpsIndex(rw http.ResponseWriter, _ *http.Request) { | ||
77 | data := make(map[interface{}]interface{}) | 76 | data := make(map[interface{}]interface{}) |
78 | data["Content"] = toolbox.StatisticsMap.GetMap() | 77 | data["Content"] = toolbox.StatisticsMap.GetMap() |
79 | 78 | ||
80 | // do html escape before display path, avoid xss | 79 | // do html escape before display path, avoid xss |
81 | - if content, ok := (data["Content"]).(map[string]interface{}); ok { | 80 | + if content, ok := (data["Content"]).(M); ok { |
82 | if resultLists, ok := (content["Data"]).([][]string); ok { | 81 | if resultLists, ok := (content["Data"]).([][]string); ok { |
83 | for i := range resultLists { | 82 | for i := range resultLists { |
84 | if len(resultLists[i]) > 0 { | 83 | if len(resultLists[i]) > 0 { |
@@ -104,7 +103,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) { | @@ -104,7 +103,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) { | ||
104 | data := make(map[interface{}]interface{}) | 103 | data := make(map[interface{}]interface{}) |
105 | switch command { | 104 | switch command { |
106 | case "conf": | 105 | case "conf": |
107 | - m := make(map[string]interface{}) | 106 | + m := make(M) |
108 | list("BConfig", BConfig, m) | 107 | list("BConfig", BConfig, m) |
109 | m["AppConfigPath"] = appConfigPath | 108 | m["AppConfigPath"] = appConfigPath |
110 | m["AppConfigProvider"] = appConfigProvider | 109 | m["AppConfigProvider"] = appConfigProvider |
@@ -128,14 +127,14 @@ func listConf(rw http.ResponseWriter, r *http.Request) { | @@ -128,14 +127,14 @@ func listConf(rw http.ResponseWriter, r *http.Request) { | ||
128 | execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl) | 127 | execTpl(rw, data, routerAndFilterTpl, defaultScriptsTpl) |
129 | case "filter": | 128 | case "filter": |
130 | var ( | 129 | var ( |
131 | - content = map[string]interface{}{ | 130 | + content = M{ |
132 | "Fields": []string{ | 131 | "Fields": []string{ |
133 | "Router Pattern", | 132 | "Router Pattern", |
134 | "Filter Function", | 133 | "Filter Function", |
135 | }, | 134 | }, |
136 | } | 135 | } |
137 | filterTypes = []string{} | 136 | filterTypes = []string{} |
138 | - filterTypeData = make(map[string]interface{}) | 137 | + filterTypeData = make(M) |
139 | ) | 138 | ) |
140 | 139 | ||
141 | if BeeApp.Handlers.enableFilter { | 140 | if BeeApp.Handlers.enableFilter { |
@@ -173,7 +172,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) { | @@ -173,7 +172,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) { | ||
173 | } | 172 | } |
174 | } | 173 | } |
175 | 174 | ||
176 | -func list(root string, p interface{}, m map[string]interface{}) { | 175 | +func list(root string, p interface{}, m M) { |
177 | pt := reflect.TypeOf(p) | 176 | pt := reflect.TypeOf(p) |
178 | pv := reflect.ValueOf(p) | 177 | pv := reflect.ValueOf(p) |
179 | if pt.Kind() == reflect.Ptr { | 178 | if pt.Kind() == reflect.Ptr { |
@@ -196,11 +195,11 @@ func list(root string, p interface{}, m map[string]interface{}) { | @@ -196,11 +195,11 @@ func list(root string, p interface{}, m map[string]interface{}) { | ||
196 | } | 195 | } |
197 | 196 | ||
198 | // PrintTree prints all registered routers. | 197 | // PrintTree prints all registered routers. |
199 | -func PrintTree() map[string]interface{} { | 198 | +func PrintTree() M { |
200 | var ( | 199 | var ( |
201 | - content = map[string]interface{}{} | 200 | + content = M{} |
202 | methods = []string{} | 201 | methods = []string{} |
203 | - methodsData = make(map[string]interface{}) | 202 | + methodsData = make(M) |
204 | ) | 203 | ) |
205 | for method, t := range BeeApp.Handlers.routers { | 204 | for method, t := range BeeApp.Handlers.routers { |
206 | 205 | ||
@@ -291,12 +290,12 @@ func profIndex(rw http.ResponseWriter, r *http.Request) { | @@ -291,12 +290,12 @@ func profIndex(rw http.ResponseWriter, r *http.Request) { | ||
291 | 290 | ||
292 | // Healthcheck is a http.Handler calling health checking and showing the result. | 291 | // Healthcheck is a http.Handler calling health checking and showing the result. |
293 | // it's in "/healthcheck" pattern in admin module. | 292 | // it's in "/healthcheck" pattern in admin module. |
294 | -func healthcheck(rw http.ResponseWriter, req *http.Request) { | 293 | +func healthcheck(rw http.ResponseWriter, _ *http.Request) { |
295 | var ( | 294 | var ( |
296 | result []string | 295 | result []string |
297 | data = make(map[interface{}]interface{}) | 296 | data = make(map[interface{}]interface{}) |
298 | resultList = new([][]string) | 297 | resultList = new([][]string) |
299 | - content = map[string]interface{}{ | 298 | + content = M{ |
300 | "Fields": []string{"Name", "Message", "Status"}, | 299 | "Fields": []string{"Name", "Message", "Status"}, |
301 | } | 300 | } |
302 | ) | 301 | ) |
@@ -344,7 +343,7 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) { | @@ -344,7 +343,7 @@ func taskStatus(rw http.ResponseWriter, req *http.Request) { | ||
344 | } | 343 | } |
345 | 344 | ||
346 | // List Tasks | 345 | // List Tasks |
347 | - content := make(map[string]interface{}) | 346 | + content := make(M) |
348 | resultList := new([][]string) | 347 | resultList := new([][]string) |
349 | var fields = []string{ | 348 | var fields = []string{ |
350 | "Task Name", | 349 | "Task Name", |
@@ -23,7 +23,7 @@ import ( | @@ -23,7 +23,7 @@ import ( | ||
23 | 23 | ||
24 | const ( | 24 | const ( |
25 | // VERSION represent beego web framework version. | 25 | // VERSION represent beego web framework version. |
26 | - VERSION = "1.10.0" | 26 | + VERSION = "1.11.1" |
27 | 27 | ||
28 | // DEV is for develop | 28 | // DEV is for develop |
29 | DEV = "dev" | 29 | DEV = "dev" |
@@ -31,7 +31,10 @@ const ( | @@ -31,7 +31,10 @@ const ( | ||
31 | PROD = "prod" | 31 | PROD = "prod" |
32 | ) | 32 | ) |
33 | 33 | ||
34 | -//hook function to run | 34 | +// M is Map shortcut |
35 | +type M map[string]interface{} | ||
36 | + | ||
37 | +// Hook function to run | ||
35 | type hookfunc func() error | 38 | type hookfunc func() error |
36 | 39 | ||
37 | var ( | 40 | var ( |
@@ -78,15 +78,37 @@ func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, e | @@ -78,15 +78,37 @@ func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, e | ||
78 | } | 78 | } |
79 | } | 79 | } |
80 | section := defaultSection | 80 | section := defaultSection |
81 | + tmpBuf := bytes.NewBuffer(nil) | ||
81 | for { | 82 | for { |
82 | - line, _, err := buf.ReadLine() | ||
83 | - if err == io.EOF { | ||
84 | - break | 83 | + tmpBuf.Reset() |
84 | + | ||
85 | + shouldBreak := false | ||
86 | + for { | ||
87 | + tmp, isPrefix, err := buf.ReadLine() | ||
88 | + if err == io.EOF { | ||
89 | + shouldBreak = true | ||
90 | + break | ||
91 | + } | ||
92 | + | ||
93 | + //It might be a good idea to throw a error on all unknonw errors? | ||
94 | + if _, ok := err.(*os.PathError); ok { | ||
95 | + return nil, err | ||
96 | + } | ||
97 | + | ||
98 | + tmpBuf.Write(tmp) | ||
99 | + if isPrefix { | ||
100 | + continue | ||
101 | + } | ||
102 | + | ||
103 | + if !isPrefix { | ||
104 | + break | ||
105 | + } | ||
85 | } | 106 | } |
86 | - //It might be a good idea to throw a error on all unknonw errors? | ||
87 | - if _, ok := err.(*os.PathError); ok { | ||
88 | - return nil, err | 107 | + if shouldBreak { |
108 | + break | ||
89 | } | 109 | } |
110 | + | ||
111 | + line := tmpBuf.Bytes() | ||
90 | line = bytes.TrimSpace(line) | 112 | line = bytes.TrimSpace(line) |
91 | if bytes.Equal(line, bEmpty) { | 113 | if bytes.Equal(line, bEmpty) { |
92 | continue | 114 | continue |
@@ -38,6 +38,14 @@ import ( | @@ -38,6 +38,14 @@ import ( | ||
38 | "github.com/astaxie/beego/utils" | 38 | "github.com/astaxie/beego/utils" |
39 | ) | 39 | ) |
40 | 40 | ||
41 | +//commonly used mime-types | ||
42 | +const ( | ||
43 | + ApplicationJSON = "application/json" | ||
44 | + ApplicationXML = "application/xml" | ||
45 | + ApplicationYAML = "application/x-yaml" | ||
46 | + TextXML = "text/xml" | ||
47 | +) | ||
48 | + | ||
41 | // NewContext return the Context with Input and Output | 49 | // NewContext return the Context with Input and Output |
42 | func NewContext() *Context { | 50 | func NewContext() *Context { |
43 | return &Context{ | 51 | return &Context{ |
@@ -193,6 +201,7 @@ type Response struct { | @@ -193,6 +201,7 @@ type Response struct { | ||
193 | http.ResponseWriter | 201 | http.ResponseWriter |
194 | Started bool | 202 | Started bool |
195 | Status int | 203 | Status int |
204 | + Elapsed time.Duration | ||
196 | } | 205 | } |
197 | 206 | ||
198 | func (r *Response) reset(rw http.ResponseWriter) { | 207 | func (r *Response) reset(rw http.ResponseWriter) { |
@@ -244,3 +253,11 @@ func (r *Response) CloseNotify() <-chan bool { | @@ -244,3 +253,11 @@ func (r *Response) CloseNotify() <-chan bool { | ||
244 | } | 253 | } |
245 | return nil | 254 | return nil |
246 | } | 255 | } |
256 | + | ||
257 | +// Pusher http.Pusher | ||
258 | +func (r *Response) Pusher() (pusher http.Pusher) { | ||
259 | + if pusher, ok := r.ResponseWriter.(http.Pusher); ok { | ||
260 | + return pusher | ||
261 | + } | ||
262 | + return nil | ||
263 | +} |
@@ -206,7 +206,7 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) | @@ -206,7 +206,7 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) | ||
206 | 206 | ||
207 | // YAML writes yaml to response body. | 207 | // YAML writes yaml to response body. |
208 | func (output *BeegoOutput) YAML(data interface{}) error { | 208 | func (output *BeegoOutput) YAML(data interface{}) error { |
209 | - output.Header("Content-Type", "application/application/x-yaml; charset=utf-8") | 209 | + output.Header("Content-Type", "application/x-yaml; charset=utf-8") |
210 | var content []byte | 210 | var content []byte |
211 | var err error | 211 | var err error |
212 | content, err = yaml.Marshal(data) | 212 | content, err = yaml.Marshal(data) |
@@ -260,6 +260,19 @@ func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error { | @@ -260,6 +260,19 @@ func (output *BeegoOutput) XML(data interface{}, hasIndent bool) error { | ||
260 | return output.Body(content) | 260 | return output.Body(content) |
261 | } | 261 | } |
262 | 262 | ||
263 | +// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header | ||
264 | +func (output *BeegoOutput) ServeFormatted(data interface{}, hasIndent bool, hasEncode ...bool) { | ||
265 | + accept := output.Context.Input.Header("Accept") | ||
266 | + switch accept { | ||
267 | + case ApplicationYAML: | ||
268 | + output.YAML(data) | ||
269 | + case ApplicationXML, TextXML: | ||
270 | + output.XML(data, hasIndent) | ||
271 | + default: | ||
272 | + output.JSON(data, hasIndent, len(hasEncode) > 0 && hasEncode[0]) | ||
273 | + } | ||
274 | +} | ||
275 | + | ||
263 | // Download forces response for download file. | 276 | // Download forces response for download file. |
264 | // it prepares the download response header automatically. | 277 | // it prepares the download response header automatically. |
265 | func (output *BeegoOutput) Download(file string, filename ...string) { | 278 | func (output *BeegoOutput) Download(file string, filename ...string) { |
@@ -32,14 +32,6 @@ import ( | @@ -32,14 +32,6 @@ import ( | ||
32 | "github.com/astaxie/beego/session" | 32 | "github.com/astaxie/beego/session" |
33 | ) | 33 | ) |
34 | 34 | ||
35 | -//commonly used mime-types | ||
36 | -const ( | ||
37 | - applicationJSON = "application/json" | ||
38 | - applicationXML = "application/xml" | ||
39 | - applicationYAML = "application/x-yaml" | ||
40 | - textXML = "text/xml" | ||
41 | -) | ||
42 | - | ||
43 | var ( | 35 | var ( |
44 | // ErrAbort custom error when user stop request handler manually. | 36 | // ErrAbort custom error when user stop request handler manually. |
45 | ErrAbort = errors.New("User stop run") | 37 | ErrAbort = errors.New("User stop run") |
@@ -47,10 +39,37 @@ var ( | @@ -47,10 +39,37 @@ var ( | ||
47 | GlobalControllerRouter = make(map[string][]ControllerComments) | 39 | GlobalControllerRouter = make(map[string][]ControllerComments) |
48 | ) | 40 | ) |
49 | 41 | ||
42 | +// ControllerFilter store the filter for controller | ||
43 | +type ControllerFilter struct { | ||
44 | + Pattern string | ||
45 | + Pos int | ||
46 | + Filter FilterFunc | ||
47 | + ReturnOnOutput bool | ||
48 | + ResetParams bool | ||
49 | +} | ||
50 | + | ||
51 | +// ControllerFilterComments store the comment for controller level filter | ||
52 | +type ControllerFilterComments struct { | ||
53 | + Pattern string | ||
54 | + Pos int | ||
55 | + Filter string // NOQA | ||
56 | + ReturnOnOutput bool | ||
57 | + ResetParams bool | ||
58 | +} | ||
59 | + | ||
60 | +// ControllerImportComments store the import comment for controller needed | ||
61 | +type ControllerImportComments struct { | ||
62 | + ImportPath string | ||
63 | + ImportAlias string | ||
64 | +} | ||
65 | + | ||
50 | // ControllerComments store the comment for the controller method | 66 | // ControllerComments store the comment for the controller method |
51 | type ControllerComments struct { | 67 | type ControllerComments struct { |
52 | Method string | 68 | Method string |
53 | Router string | 69 | Router string |
70 | + Filters []*ControllerFilter | ||
71 | + ImportComments []*ControllerImportComments | ||
72 | + FilterComments []*ControllerFilterComments | ||
54 | AllowHTTPMethods []string | 73 | AllowHTTPMethods []string |
55 | Params []map[string]string | 74 | Params []map[string]string |
56 | MethodParams []*param.MethodParam | 75 | MethodParams []*param.MethodParam |
@@ -275,16 +294,15 @@ func (c *Controller) viewPath() string { | @@ -275,16 +294,15 @@ func (c *Controller) viewPath() string { | ||
275 | func (c *Controller) Redirect(url string, code int) { | 294 | func (c *Controller) Redirect(url string, code int) { |
276 | logAccess(c.Ctx, nil, code) | 295 | logAccess(c.Ctx, nil, code) |
277 | c.Ctx.Redirect(code, url) | 296 | c.Ctx.Redirect(code, url) |
278 | - panic(ErrAbort) | ||
279 | } | 297 | } |
280 | 298 | ||
281 | -// Set the data depending on the accepted | 299 | +// SetData set the data depending on the accepted |
282 | func (c *Controller) SetData(data interface{}) { | 300 | func (c *Controller) SetData(data interface{}) { |
283 | accept := c.Ctx.Input.Header("Accept") | 301 | accept := c.Ctx.Input.Header("Accept") |
284 | switch accept { | 302 | switch accept { |
285 | - case applicationJSON: | ||
286 | - c.Data["json"] = data | ||
287 | - case applicationXML, textXML: | 303 | + case context.ApplicationYAML: |
304 | + c.Data["yaml"] = data | ||
305 | + case context.ApplicationXML, context.TextXML: | ||
288 | c.Data["xml"] = data | 306 | c.Data["xml"] = data |
289 | default: | 307 | default: |
290 | c.Data["json"] = data | 308 | c.Data["json"] = data |
@@ -333,54 +351,35 @@ func (c *Controller) URLFor(endpoint string, values ...interface{}) string { | @@ -333,54 +351,35 @@ func (c *Controller) URLFor(endpoint string, values ...interface{}) string { | ||
333 | // ServeJSON sends a json response with encoding charset. | 351 | // ServeJSON sends a json response with encoding charset. |
334 | func (c *Controller) ServeJSON(encoding ...bool) { | 352 | func (c *Controller) ServeJSON(encoding ...bool) { |
335 | var ( | 353 | var ( |
336 | - hasIndent = true | ||
337 | - hasEncoding = false | 354 | + hasIndent = BConfig.RunMode != PROD |
355 | + hasEncoding = len(encoding) > 0 && encoding[0] | ||
338 | ) | 356 | ) |
339 | - if BConfig.RunMode == PROD { | ||
340 | - hasIndent = false | ||
341 | - } | ||
342 | - if len(encoding) > 0 && encoding[0] { | ||
343 | - hasEncoding = true | ||
344 | - } | 357 | + |
345 | c.Ctx.Output.JSON(c.Data["json"], hasIndent, hasEncoding) | 358 | c.Ctx.Output.JSON(c.Data["json"], hasIndent, hasEncoding) |
346 | } | 359 | } |
347 | 360 | ||
348 | // ServeJSONP sends a jsonp response. | 361 | // ServeJSONP sends a jsonp response. |
349 | func (c *Controller) ServeJSONP() { | 362 | func (c *Controller) ServeJSONP() { |
350 | - hasIndent := true | ||
351 | - if BConfig.RunMode == PROD { | ||
352 | - hasIndent = false | ||
353 | - } | 363 | + hasIndent := BConfig.RunMode != PROD |
354 | c.Ctx.Output.JSONP(c.Data["jsonp"], hasIndent) | 364 | c.Ctx.Output.JSONP(c.Data["jsonp"], hasIndent) |
355 | } | 365 | } |
356 | 366 | ||
357 | // ServeXML sends xml response. | 367 | // ServeXML sends xml response. |
358 | func (c *Controller) ServeXML() { | 368 | func (c *Controller) ServeXML() { |
359 | - hasIndent := true | ||
360 | - if BConfig.RunMode == PROD { | ||
361 | - hasIndent = false | ||
362 | - } | 369 | + hasIndent := BConfig.RunMode != PROD |
363 | c.Ctx.Output.XML(c.Data["xml"], hasIndent) | 370 | c.Ctx.Output.XML(c.Data["xml"], hasIndent) |
364 | } | 371 | } |
365 | 372 | ||
366 | -// ServeXML sends xml response. | 373 | +// ServeYAML sends yaml response. |
367 | func (c *Controller) ServeYAML() { | 374 | func (c *Controller) ServeYAML() { |
368 | c.Ctx.Output.YAML(c.Data["yaml"]) | 375 | c.Ctx.Output.YAML(c.Data["yaml"]) |
369 | } | 376 | } |
370 | 377 | ||
371 | -// ServeFormatted serve Xml OR Json, depending on the value of the Accept header | ||
372 | -func (c *Controller) ServeFormatted() { | ||
373 | - accept := c.Ctx.Input.Header("Accept") | ||
374 | - switch accept { | ||
375 | - case applicationJSON: | ||
376 | - c.ServeJSON() | ||
377 | - case applicationXML, textXML: | ||
378 | - c.ServeXML() | ||
379 | - case applicationYAML: | ||
380 | - c.ServeYAML() | ||
381 | - default: | ||
382 | - c.ServeJSON() | ||
383 | - } | 378 | +// ServeFormatted serve YAML, XML OR JSON, depending on the value of the Accept header |
379 | +func (c *Controller) ServeFormatted(encoding ...bool) { | ||
380 | + hasIndent := BConfig.RunMode != PROD | ||
381 | + hasEncoding := len(encoding) > 0 && encoding[0] | ||
382 | + c.Ctx.Output.ServeFormatted(c.Data, hasIndent, hasEncoding) | ||
384 | } | 383 | } |
385 | 384 | ||
386 | // Input returns the input data map from POST or PUT request body and query string. | 385 | // Input returns the input data map from POST or PUT request body and query string. |
@@ -361,7 +361,7 @@ func gatewayTimeout(rw http.ResponseWriter, r *http.Request) { | @@ -361,7 +361,7 @@ func gatewayTimeout(rw http.ResponseWriter, r *http.Request) { | ||
361 | 361 | ||
362 | func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) { | 362 | func responseError(rw http.ResponseWriter, r *http.Request, errCode int, errContent string) { |
363 | t, _ := template.New("beegoerrortemp").Parse(errtpl) | 363 | t, _ := template.New("beegoerrortemp").Parse(errtpl) |
364 | - data := map[string]interface{}{ | 364 | + data := M{ |
365 | "Title": http.StatusText(errCode), | 365 | "Title": http.StatusText(errCode), |
366 | "BeegoVersion": VERSION, | 366 | "BeegoVersion": VERSION, |
367 | "Content": template.HTML(errContent), | 367 | "Content": template.HTML(errContent), |
vendor/github.com/astaxie/beego/fs.go
0 → 100644
1 | +package beego | ||
2 | + | ||
3 | +import ( | ||
4 | + "net/http" | ||
5 | + "os" | ||
6 | + "path/filepath" | ||
7 | +) | ||
8 | + | ||
9 | +type FileSystem struct { | ||
10 | +} | ||
11 | + | ||
12 | +func (d FileSystem) Open(name string) (http.File, error) { | ||
13 | + return os.Open(name) | ||
14 | +} | ||
15 | + | ||
16 | +// Walk walks the file tree rooted at root in filesystem, calling walkFn for each file or | ||
17 | +// directory in the tree, including root. All errors that arise visiting files | ||
18 | +// and directories are filtered by walkFn. | ||
19 | +func Walk(fs http.FileSystem, root string, walkFn filepath.WalkFunc) error { | ||
20 | + | ||
21 | + f, err := fs.Open(root) | ||
22 | + if err != nil { | ||
23 | + return err | ||
24 | + } | ||
25 | + info, err := f.Stat() | ||
26 | + if err != nil { | ||
27 | + err = walkFn(root, nil, err) | ||
28 | + } else { | ||
29 | + err = walk(fs, root, info, walkFn) | ||
30 | + } | ||
31 | + if err == filepath.SkipDir { | ||
32 | + return nil | ||
33 | + } | ||
34 | + return err | ||
35 | +} | ||
36 | + | ||
37 | +// walk recursively descends path, calling walkFn. | ||
38 | +func walk(fs http.FileSystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error { | ||
39 | + var err error | ||
40 | + if !info.IsDir() { | ||
41 | + return walkFn(path, info, nil) | ||
42 | + } | ||
43 | + | ||
44 | + dir, err := fs.Open(path) | ||
45 | + defer dir.Close() | ||
46 | + if err != nil { | ||
47 | + if err1 := walkFn(path, info, err); err1 != nil { | ||
48 | + return err1 | ||
49 | + } | ||
50 | + return err | ||
51 | + } | ||
52 | + dirs, err := dir.Readdir(-1) | ||
53 | + err1 := walkFn(path, info, err) | ||
54 | + // If err != nil, walk can't walk into this directory. | ||
55 | + // err1 != nil means walkFn want walk to skip this directory or stop walking. | ||
56 | + // Therefore, if one of err and err1 isn't nil, walk will return. | ||
57 | + if err != nil || err1 != nil { | ||
58 | + // The caller's behavior is controlled by the return value, which is decided | ||
59 | + // by walkFn. walkFn may ignore err and return nil. | ||
60 | + // If walkFn returns SkipDir, it will be handled by the caller. | ||
61 | + // So walk should return whatever walkFn returns. | ||
62 | + return err1 | ||
63 | + } | ||
64 | + | ||
65 | + for _, fileInfo := range dirs { | ||
66 | + filename := filepath.Join(path, fileInfo.Name()) | ||
67 | + if err = walk(fs, filename, fileInfo, walkFn); err != nil { | ||
68 | + if !fileInfo.IsDir() || err != filepath.SkipDir { | ||
69 | + return err | ||
70 | + } | ||
71 | + } | ||
72 | + } | ||
73 | + return nil | ||
74 | +} |
vendor/github.com/astaxie/beego/go.mod
0 → 100644
1 | +module github.com/astaxie/beego | ||
2 | + | ||
3 | +require ( | ||
4 | + github.com/Knetic/govaluate v3.0.0+incompatible // indirect | ||
5 | + github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd | ||
6 | + github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542 | ||
7 | + github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff | ||
8 | + github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 | ||
9 | + github.com/casbin/casbin v1.7.0 | ||
10 | + github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 | ||
11 | + github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb | ||
12 | + github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c // indirect | ||
13 | + github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a // indirect | ||
14 | + github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 // indirect | ||
15 | + github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect | ||
16 | + github.com/elazarl/go-bindata-assetfs v1.0.0 | ||
17 | + github.com/go-redis/redis v6.14.2+incompatible | ||
18 | + github.com/go-sql-driver/mysql v1.4.1 | ||
19 | + github.com/gogo/protobuf v1.1.1 | ||
20 | + github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect | ||
21 | + github.com/gomodule/redigo v2.0.0+incompatible | ||
22 | + github.com/lib/pq v1.0.0 | ||
23 | + github.com/mattn/go-sqlite3 v1.10.0 | ||
24 | + github.com/pelletier/go-toml v1.2.0 // indirect | ||
25 | + github.com/pkg/errors v0.8.0 // indirect | ||
26 | + github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect | ||
27 | + github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373 | ||
28 | + github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d // indirect | ||
29 | + github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec | ||
30 | + github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c // indirect | ||
31 | + github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b // indirect | ||
32 | + golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 | ||
33 | + golang.org/x/net v0.0.0-20181114220301-adae6a3d119a // indirect | ||
34 | + gopkg.in/yaml.v2 v2.2.1 | ||
35 | +) | ||
36 | + | ||
37 | +replace golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 => github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85 | ||
38 | + | ||
39 | +replace gopkg.in/yaml.v2 v2.2.1 => github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d | ||
40 | + | ||
41 | +go 1.13 |
vendor/github.com/astaxie/beego/go.sum
0 → 100644
1 | +github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= | ||
2 | +github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= | ||
3 | +github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd h1:jZtX5jh5IOMu0fpOTC3ayh6QGSPJ/KWOv1lgPvbRw1M= | ||
4 | +github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ= | ||
5 | +github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542 h1:nYXb+3jF6Oq/j8R/y90XrKpreCxIalBWfeyeKymgOPk= | ||
6 | +github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU= | ||
7 | +github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff h1:/kO0p2RTGLB8R5gub7ps0GmYpB2O8LXEoPq8tzFDCUI= | ||
8 | +github.com/belogik/goes v0.0.0-20151229125003-e54d722c3aff/go.mod h1:PhH1ZhyCzHKt4uAasyx+ljRCgoezetRNf59CUtwUkqY= | ||
9 | +github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g= | ||
10 | +github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= | ||
11 | +github.com/casbin/casbin v1.7.0 h1:PuzlE8w0JBg/DhIqnkF1Dewf3z+qmUZMVN07PonvVUQ= | ||
12 | +github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE= | ||
13 | +github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg= | ||
14 | +github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80= | ||
15 | +github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb h1:w3RapLhkA5+km9Z8vUkC6VCaskduJXvXwJg5neKnfDU= | ||
16 | +github.com/couchbase/go-couchbase v0.0.0-20181122212707-3e9b6e1258bb/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U= | ||
17 | +github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c h1:K4FIibkr4//ziZKOKmt4RL0YImuTjLLBtwElf+F2lSQ= | ||
18 | +github.com/couchbase/gomemcached v0.0.0-20181122193126-5125a94a666c/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c= | ||
19 | +github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a h1:Y5XsLCEhtEI8qbD9RP3Qlv5FXdTDHxZM9UPUnMRgBp8= | ||
20 | +github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= | ||
21 | +github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ= | ||
22 | +github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= | ||
23 | +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8= | ||
24 | +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= | ||
25 | +github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= | ||
26 | +github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= | ||
27 | +github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0= | ||
28 | +github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= | ||
29 | +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= | ||
30 | +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= | ||
31 | +github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d h1:xy93KVe+KrIIwWDEAfQBdIfsiHJkepbYsDr+VY3g9/o= | ||
32 | +github.com/go-yaml/yaml v0.0.0-20180328195020-5420a8b6744d/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||
33 | +github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= | ||
34 | +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | ||
35 | +github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:B7ZbAFz7NOmvpUE5RGtu3u0WIizy5GdvbNpEf4RPnWs= | ||
36 | +github.com/golang/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:uZvAcrsnNaCxlh1HorK5dUQHGmEKPh2H/Rl1kehswPo= | ||
37 | +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= | ||
38 | +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= | ||
39 | +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= | ||
40 | +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= | ||
41 | +github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= | ||
42 | +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | ||
43 | +github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= | ||
44 | +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= | ||
45 | +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= | ||
46 | +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= | ||
47 | +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= | ||
48 | +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||
49 | +github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM= | ||
50 | +github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= | ||
51 | +github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373 h1:p6IxqQMjab30l4lb9mmkIkkcE1yv6o0SKbPhW5pxqHI= | ||
52 | +github.com/siddontang/ledisdb v0.0.0-20181029004158-becf5f38d373/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= | ||
53 | +github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d h1:NVwnfyR3rENtlz62bcrkXME3INVUa4lcdGt+opvxExs= | ||
54 | +github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= | ||
55 | +github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec h1:q6XVwXmKvCRHRqesF3cSv6lNqqHi0QWOvgDlSohg8UA= | ||
56 | +github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE= | ||
57 | +github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c h1:3eGShk3EQf5gJCYW+WzA0TEJQd37HLOmlYF7N0YJwv0= | ||
58 | +github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= | ||
59 | +github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b h1:0Ve0/CCjiAiyKddUMUn3RwIGlq2iTW4GuVzyoKBYO/8= | ||
60 | +github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc= | ||
61 | +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI= | ||
62 | +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||
63 | +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= | ||
64 | +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
65 | +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
66 | +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= | ||
67 | +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
@@ -11,7 +11,7 @@ import ( | @@ -11,7 +11,7 @@ import ( | ||
11 | "github.com/astaxie/beego/session" | 11 | "github.com/astaxie/beego/session" |
12 | ) | 12 | ) |
13 | 13 | ||
14 | -// | 14 | +// register MIME type with content type |
15 | func registerMime() error { | 15 | func registerMime() error { |
16 | for k, v := range mimemaps { | 16 | for k, v := range mimemaps { |
17 | mime.AddExtensionType(k, v) | 17 | mime.AddExtensionType(k, v) |
@@ -54,6 +54,12 @@ type fileLogWriter struct { | @@ -54,6 +54,12 @@ type fileLogWriter struct { | ||
54 | dailyOpenDate int | 54 | dailyOpenDate int |
55 | dailyOpenTime time.Time | 55 | dailyOpenTime time.Time |
56 | 56 | ||
57 | + // Rotate hourly | ||
58 | + Hourly bool `json:"hourly"` | ||
59 | + MaxHours int64 `json:"maxhours"` | ||
60 | + hourlyOpenDate int | ||
61 | + hourlyOpenTime time.Time | ||
62 | + | ||
57 | Rotate bool `json:"rotate"` | 63 | Rotate bool `json:"rotate"` |
58 | 64 | ||
59 | Level int `json:"level"` | 65 | Level int `json:"level"` |
@@ -70,6 +76,8 @@ func newFileWriter() Logger { | @@ -70,6 +76,8 @@ func newFileWriter() Logger { | ||
70 | w := &fileLogWriter{ | 76 | w := &fileLogWriter{ |
71 | Daily: true, | 77 | Daily: true, |
72 | MaxDays: 7, | 78 | MaxDays: 7, |
79 | + Hourly: false, | ||
80 | + MaxHours: 168, | ||
73 | Rotate: true, | 81 | Rotate: true, |
74 | RotatePerm: "0440", | 82 | RotatePerm: "0440", |
75 | Level: LevelTrace, | 83 | Level: LevelTrace, |
@@ -83,15 +91,15 @@ func newFileWriter() Logger { | @@ -83,15 +91,15 @@ func newFileWriter() Logger { | ||
83 | 91 | ||
84 | // Init file logger with json config. | 92 | // Init file logger with json config. |
85 | // jsonConfig like: | 93 | // jsonConfig like: |
86 | -// { | ||
87 | -// "filename":"logs/beego.log", | ||
88 | -// "maxLines":10000, | ||
89 | -// "maxsize":1024, | ||
90 | -// "daily":true, | ||
91 | -// "maxDays":15, | ||
92 | -// "rotate":true, | ||
93 | -// "perm":"0600" | ||
94 | -// } | 94 | +// { |
95 | +// "filename":"logs/beego.log", | ||
96 | +// "maxLines":10000, | ||
97 | +// "maxsize":1024, | ||
98 | +// "daily":true, | ||
99 | +// "maxDays":15, | ||
100 | +// "rotate":true, | ||
101 | +// "perm":"0600" | ||
102 | +// } | ||
95 | func (w *fileLogWriter) Init(jsonConfig string) error { | 103 | func (w *fileLogWriter) Init(jsonConfig string) error { |
96 | err := json.Unmarshal([]byte(jsonConfig), w) | 104 | err := json.Unmarshal([]byte(jsonConfig), w) |
97 | if err != nil { | 105 | if err != nil { |
@@ -122,10 +130,16 @@ func (w *fileLogWriter) startLogger() error { | @@ -122,10 +130,16 @@ func (w *fileLogWriter) startLogger() error { | ||
122 | return w.initFd() | 130 | return w.initFd() |
123 | } | 131 | } |
124 | 132 | ||
125 | -func (w *fileLogWriter) needRotate(size int, day int) bool { | 133 | +func (w *fileLogWriter) needRotateDaily(size int, day int) bool { |
126 | return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) || | 134 | return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) || |
127 | (w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) || | 135 | (w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) || |
128 | (w.Daily && day != w.dailyOpenDate) | 136 | (w.Daily && day != w.dailyOpenDate) |
137 | +} | ||
138 | + | ||
139 | +func (w *fileLogWriter) needRotateHourly(size int, hour int) bool { | ||
140 | + return (w.MaxLines > 0 && w.maxLinesCurLines >= w.MaxLines) || | ||
141 | + (w.MaxSize > 0 && w.maxSizeCurSize >= w.MaxSize) || | ||
142 | + (w.Hourly && hour != w.hourlyOpenDate) | ||
129 | 143 | ||
130 | } | 144 | } |
131 | 145 | ||
@@ -134,14 +148,23 @@ func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error { | @@ -134,14 +148,23 @@ func (w *fileLogWriter) WriteMsg(when time.Time, msg string, level int) error { | ||
134 | if level > w.Level { | 148 | if level > w.Level { |
135 | return nil | 149 | return nil |
136 | } | 150 | } |
137 | - h, d := formatTimeHeader(when) | ||
138 | - msg = string(h) + msg + "\n" | 151 | + hd, d, h := formatTimeHeader(when) |
152 | + msg = string(hd) + msg + "\n" | ||
139 | if w.Rotate { | 153 | if w.Rotate { |
140 | w.RLock() | 154 | w.RLock() |
141 | - if w.needRotate(len(msg), d) { | 155 | + if w.needRotateHourly(len(msg), h) { |
156 | + w.RUnlock() | ||
157 | + w.Lock() | ||
158 | + if w.needRotateHourly(len(msg), h) { | ||
159 | + if err := w.doRotate(when); err != nil { | ||
160 | + fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) | ||
161 | + } | ||
162 | + } | ||
163 | + w.Unlock() | ||
164 | + } else if w.needRotateDaily(len(msg), d) { | ||
142 | w.RUnlock() | 165 | w.RUnlock() |
143 | w.Lock() | 166 | w.Lock() |
144 | - if w.needRotate(len(msg), d) { | 167 | + if w.needRotateDaily(len(msg), d) { |
145 | if err := w.doRotate(when); err != nil { | 168 | if err := w.doRotate(when); err != nil { |
146 | fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) | 169 | fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) |
147 | } | 170 | } |
@@ -189,8 +212,12 @@ func (w *fileLogWriter) initFd() error { | @@ -189,8 +212,12 @@ func (w *fileLogWriter) initFd() error { | ||
189 | w.maxSizeCurSize = int(fInfo.Size()) | 212 | w.maxSizeCurSize = int(fInfo.Size()) |
190 | w.dailyOpenTime = time.Now() | 213 | w.dailyOpenTime = time.Now() |
191 | w.dailyOpenDate = w.dailyOpenTime.Day() | 214 | w.dailyOpenDate = w.dailyOpenTime.Day() |
215 | + w.hourlyOpenTime = time.Now() | ||
216 | + w.hourlyOpenDate = w.hourlyOpenTime.Hour() | ||
192 | w.maxLinesCurLines = 0 | 217 | w.maxLinesCurLines = 0 |
193 | - if w.Daily { | 218 | + if w.Hourly { |
219 | + go w.hourlyRotate(w.hourlyOpenTime) | ||
220 | + } else if w.Daily { | ||
194 | go w.dailyRotate(w.dailyOpenTime) | 221 | go w.dailyRotate(w.dailyOpenTime) |
195 | } | 222 | } |
196 | if fInfo.Size() > 0 && w.MaxLines > 0 { | 223 | if fInfo.Size() > 0 && w.MaxLines > 0 { |
@@ -209,7 +236,22 @@ func (w *fileLogWriter) dailyRotate(openTime time.Time) { | @@ -209,7 +236,22 @@ func (w *fileLogWriter) dailyRotate(openTime time.Time) { | ||
209 | tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100)) | 236 | tm := time.NewTimer(time.Duration(nextDay.UnixNano() - openTime.UnixNano() + 100)) |
210 | <-tm.C | 237 | <-tm.C |
211 | w.Lock() | 238 | w.Lock() |
212 | - if w.needRotate(0, time.Now().Day()) { | 239 | + if w.needRotateDaily(0, time.Now().Day()) { |
240 | + if err := w.doRotate(time.Now()); err != nil { | ||
241 | + fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) | ||
242 | + } | ||
243 | + } | ||
244 | + w.Unlock() | ||
245 | +} | ||
246 | + | ||
247 | +func (w *fileLogWriter) hourlyRotate(openTime time.Time) { | ||
248 | + y, m, d := openTime.Add(1 * time.Hour).Date() | ||
249 | + h, _, _ := openTime.Add(1 * time.Hour).Clock() | ||
250 | + nextHour := time.Date(y, m, d, h, 0, 0, 0, openTime.Location()) | ||
251 | + tm := time.NewTimer(time.Duration(nextHour.UnixNano() - openTime.UnixNano() + 100)) | ||
252 | + <-tm.C | ||
253 | + w.Lock() | ||
254 | + if w.needRotateHourly(0, time.Now().Hour()) { | ||
213 | if err := w.doRotate(time.Now()); err != nil { | 255 | if err := w.doRotate(time.Now()); err != nil { |
214 | fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) | 256 | fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.Filename, err) |
215 | } | 257 | } |
@@ -251,6 +293,8 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error { | @@ -251,6 +293,8 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error { | ||
251 | // Find the next available number | 293 | // Find the next available number |
252 | num := w.MaxFilesCurFiles + 1 | 294 | num := w.MaxFilesCurFiles + 1 |
253 | fName := "" | 295 | fName := "" |
296 | + format := "" | ||
297 | + var openTime time.Time | ||
254 | rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64) | 298 | rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64) |
255 | if err != nil { | 299 | if err != nil { |
256 | return err | 300 | return err |
@@ -262,17 +306,26 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error { | @@ -262,17 +306,26 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error { | ||
262 | goto RESTART_LOGGER | 306 | goto RESTART_LOGGER |
263 | } | 307 | } |
264 | 308 | ||
309 | + if w.Hourly { | ||
310 | + format = "2006010215" | ||
311 | + openTime = w.hourlyOpenTime | ||
312 | + } else if w.Daily { | ||
313 | + format = "2006-01-02" | ||
314 | + openTime = w.dailyOpenTime | ||
315 | + } | ||
316 | + | ||
265 | // only when one of them be setted, then the file would be splited | 317 | // only when one of them be setted, then the file would be splited |
266 | if w.MaxLines > 0 || w.MaxSize > 0 { | 318 | if w.MaxLines > 0 || w.MaxSize > 0 { |
267 | for ; err == nil && num <= w.MaxFiles; num++ { | 319 | for ; err == nil && num <= w.MaxFiles; num++ { |
268 | - fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix) | 320 | + fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format(format), num, w.suffix) |
269 | _, err = os.Lstat(fName) | 321 | _, err = os.Lstat(fName) |
270 | } | 322 | } |
271 | } else { | 323 | } else { |
272 | - fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format("2006-01-02"), num, w.suffix) | 324 | + fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", openTime.Format(format), num, w.suffix) |
273 | _, err = os.Lstat(fName) | 325 | _, err = os.Lstat(fName) |
274 | w.MaxFilesCurFiles = num | 326 | w.MaxFilesCurFiles = num |
275 | } | 327 | } |
328 | + | ||
276 | // return error if the last file checked still existed | 329 | // return error if the last file checked still existed |
277 | if err == nil { | 330 | if err == nil { |
278 | return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename) | 331 | return fmt.Errorf("Rotate: Cannot find free log number to rename %s", w.Filename) |
@@ -316,13 +369,21 @@ func (w *fileLogWriter) deleteOldLog() { | @@ -316,13 +369,21 @@ func (w *fileLogWriter) deleteOldLog() { | ||
316 | if info == nil { | 369 | if info == nil { |
317 | return | 370 | return |
318 | } | 371 | } |
319 | - | ||
320 | - if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) { | ||
321 | - if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && | ||
322 | - strings.HasSuffix(filepath.Base(path), w.suffix) { | ||
323 | - os.Remove(path) | ||
324 | - } | ||
325 | - } | 372 | + if w.Hourly { |
373 | + if !info.IsDir() && info.ModTime().Add(1 * time.Hour * time.Duration(w.MaxHours)).Before(time.Now()) { | ||
374 | + if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && | ||
375 | + strings.HasSuffix(filepath.Base(path), w.suffix) { | ||
376 | + os.Remove(path) | ||
377 | + } | ||
378 | + } | ||
379 | + } else if w.Daily { | ||
380 | + if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) { | ||
381 | + if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && | ||
382 | + strings.HasSuffix(filepath.Base(path), w.suffix) { | ||
383 | + os.Remove(path) | ||
384 | + } | ||
385 | + } | ||
386 | + } | ||
326 | return | 387 | return |
327 | }) | 388 | }) |
328 | } | 389 | } |
@@ -33,7 +33,7 @@ func newLogWriter(wr io.Writer) *logWriter { | @@ -33,7 +33,7 @@ func newLogWriter(wr io.Writer) *logWriter { | ||
33 | 33 | ||
34 | func (lg *logWriter) println(when time.Time, msg string) { | 34 | func (lg *logWriter) println(when time.Time, msg string) { |
35 | lg.Lock() | 35 | lg.Lock() |
36 | - h, _ := formatTimeHeader(when) | 36 | + h, _, _:= formatTimeHeader(when) |
37 | lg.writer.Write(append(append(h, msg...), '\n')) | 37 | lg.writer.Write(append(append(h, msg...), '\n')) |
38 | lg.Unlock() | 38 | lg.Unlock() |
39 | } | 39 | } |
@@ -90,7 +90,7 @@ const ( | @@ -90,7 +90,7 @@ const ( | ||
90 | ns1 = `0123456789` | 90 | ns1 = `0123456789` |
91 | ) | 91 | ) |
92 | 92 | ||
93 | -func formatTimeHeader(when time.Time) ([]byte, int) { | 93 | +func formatTimeHeader(when time.Time) ([]byte, int, int) { |
94 | y, mo, d := when.Date() | 94 | y, mo, d := when.Date() |
95 | h, mi, s := when.Clock() | 95 | h, mi, s := when.Clock() |
96 | ns := when.Nanosecond() / 1000000 | 96 | ns := when.Nanosecond() / 1000000 |
@@ -123,7 +123,7 @@ func formatTimeHeader(when time.Time) ([]byte, int) { | @@ -123,7 +123,7 @@ func formatTimeHeader(when time.Time) ([]byte, int) { | ||
123 | 123 | ||
124 | buf[23] = ' ' | 124 | buf[23] = ' ' |
125 | 125 | ||
126 | - return buf[0:], d | 126 | + return buf[0:], d, h |
127 | } | 127 | } |
128 | 128 | ||
129 | var ( | 129 | var ( |
@@ -536,6 +536,8 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a | @@ -536,6 +536,8 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a | ||
536 | updates := make([]string, len(names)) | 536 | updates := make([]string, len(names)) |
537 | var conflitValue interface{} | 537 | var conflitValue interface{} |
538 | for i, v := range names { | 538 | for i, v := range names { |
539 | + // identifier in database may not be case-sensitive, so quote it | ||
540 | + v = fmt.Sprintf("%s%s%s", Q, v, Q) | ||
539 | marks[i] = "?" | 541 | marks[i] = "?" |
540 | valueStr := argsMap[strings.ToLower(v)] | 542 | valueStr := argsMap[strings.ToLower(v)] |
541 | if v == args0 { | 543 | if v == args0 { |
@@ -760,7 +762,13 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con | @@ -760,7 +762,13 @@ func (d *dbBase) UpdateBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con | ||
760 | } | 762 | } |
761 | 763 | ||
762 | d.ins.ReplaceMarks(&query) | 764 | d.ins.ReplaceMarks(&query) |
763 | - res, err := q.Exec(query, values...) | 765 | + var err error |
766 | + var res sql.Result | ||
767 | + if qs != nil && qs.forContext { | ||
768 | + res, err = q.ExecContext(qs.ctx, query, values...) | ||
769 | + } else { | ||
770 | + res, err = q.Exec(query, values...) | ||
771 | + } | ||
764 | if err == nil { | 772 | if err == nil { |
765 | return res.RowsAffected() | 773 | return res.RowsAffected() |
766 | } | 774 | } |
@@ -849,11 +857,16 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con | @@ -849,11 +857,16 @@ func (d *dbBase) DeleteBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Con | ||
849 | for i := range marks { | 857 | for i := range marks { |
850 | marks[i] = "?" | 858 | marks[i] = "?" |
851 | } | 859 | } |
852 | - sql := fmt.Sprintf("IN (%s)", strings.Join(marks, ", ")) | ||
853 | - query = fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s %s", Q, mi.table, Q, Q, mi.fields.pk.column, Q, sql) | 860 | + sqlIn := fmt.Sprintf("IN (%s)", strings.Join(marks, ", ")) |
861 | + query = fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s %s", Q, mi.table, Q, Q, mi.fields.pk.column, Q, sqlIn) | ||
854 | 862 | ||
855 | d.ins.ReplaceMarks(&query) | 863 | d.ins.ReplaceMarks(&query) |
856 | - res, err := q.Exec(query, args...) | 864 | + var res sql.Result |
865 | + if qs != nil && qs.forContext { | ||
866 | + res, err = q.ExecContext(qs.ctx, query, args...) | ||
867 | + } else { | ||
868 | + res, err = q.Exec(query, args...) | ||
869 | + } | ||
857 | if err == nil { | 870 | if err == nil { |
858 | num, err := res.RowsAffected() | 871 | num, err := res.RowsAffected() |
859 | if err != nil { | 872 | if err != nil { |
@@ -926,7 +939,7 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi | @@ -926,7 +939,7 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi | ||
926 | maps[fi.column] = true | 939 | maps[fi.column] = true |
927 | } | 940 | } |
928 | } else { | 941 | } else { |
929 | - panic(fmt.Errorf("wrong field/column name `%s`", col)) | 942 | + return 0, fmt.Errorf("wrong field/column name `%s`", col) |
930 | } | 943 | } |
931 | } | 944 | } |
932 | if hasRel { | 945 | if hasRel { |
@@ -976,11 +989,18 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi | @@ -976,11 +989,18 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi | ||
976 | d.ins.ReplaceMarks(&query) | 989 | d.ins.ReplaceMarks(&query) |
977 | 990 | ||
978 | var rs *sql.Rows | 991 | var rs *sql.Rows |
979 | - r, err := q.Query(query, args...) | ||
980 | - if err != nil { | ||
981 | - return 0, err | 992 | + var err error |
993 | + if qs != nil && qs.forContext { | ||
994 | + rs, err = q.QueryContext(qs.ctx, query, args...) | ||
995 | + if err != nil { | ||
996 | + return 0, err | ||
997 | + } | ||
998 | + } else { | ||
999 | + rs, err = q.Query(query, args...) | ||
1000 | + if err != nil { | ||
1001 | + return 0, err | ||
1002 | + } | ||
982 | } | 1003 | } |
983 | - rs = r | ||
984 | 1004 | ||
985 | refs := make([]interface{}, colsNum) | 1005 | refs := make([]interface{}, colsNum) |
986 | for i := range refs { | 1006 | for i := range refs { |
@@ -1109,8 +1129,12 @@ func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition | @@ -1109,8 +1129,12 @@ func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition | ||
1109 | 1129 | ||
1110 | d.ins.ReplaceMarks(&query) | 1130 | d.ins.ReplaceMarks(&query) |
1111 | 1131 | ||
1112 | - row := q.QueryRow(query, args...) | ||
1113 | - | 1132 | + var row *sql.Row |
1133 | + if qs != nil && qs.forContext { | ||
1134 | + row = q.QueryRowContext(qs.ctx, query, args...) | ||
1135 | + } else { | ||
1136 | + row = q.QueryRow(query, args...) | ||
1137 | + } | ||
1114 | err = row.Scan(&cnt) | 1138 | err = row.Scan(&cnt) |
1115 | return | 1139 | return |
1116 | } | 1140 | } |
@@ -372,7 +372,13 @@ func (t *dbTables) getCondSQL(cond *Condition, sub bool, tz *time.Location) (whe | @@ -372,7 +372,13 @@ func (t *dbTables) getCondSQL(cond *Condition, sub bool, tz *time.Location) (whe | ||
372 | operator = "exact" | 372 | operator = "exact" |
373 | } | 373 | } |
374 | 374 | ||
375 | - operSQL, args := t.base.GenerateOperatorSQL(mi, fi, operator, p.args, tz) | 375 | + var operSQL string |
376 | + var args []interface{} | ||
377 | + if p.isRaw { | ||
378 | + operSQL = p.sql | ||
379 | + } else { | ||
380 | + operSQL, args = t.base.GenerateOperatorSQL(mi, fi, operator, p.args, tz) | ||
381 | + } | ||
376 | 382 | ||
377 | leftCol := fmt.Sprintf("%s.%s%s%s", index, Q, fi.column, Q) | 383 | leftCol := fmt.Sprintf("%s.%s%s%s", index, Q, fi.column, Q) |
378 | t.base.GenerateOperatorLeftCol(fi, operator, &leftCol) | 384 | t.base.GenerateOperatorLeftCol(fi, operator, &leftCol) |
@@ -109,7 +109,7 @@ func getTableUnique(val reflect.Value) [][]string { | @@ -109,7 +109,7 @@ func getTableUnique(val reflect.Value) [][]string { | ||
109 | func getColumnName(ft int, addrField reflect.Value, sf reflect.StructField, col string) string { | 109 | func getColumnName(ft int, addrField reflect.Value, sf reflect.StructField, col string) string { |
110 | column := col | 110 | column := col |
111 | if col == "" { | 111 | if col == "" { |
112 | - column = snakeString(sf.Name) | 112 | + column = nameStrategyMap[nameStrategy](sf.Name) |
113 | } | 113 | } |
114 | switch ft { | 114 | switch ft { |
115 | case RelForeignKey, RelOneToOne: | 115 | case RelForeignKey, RelOneToOne: |
@@ -12,6 +12,8 @@ | @@ -12,6 +12,8 @@ | ||
12 | // See the License for the specific language governing permissions and | 12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. | 13 | // limitations under the License. |
14 | 14 | ||
15 | +// +build go1.8 | ||
16 | + | ||
15 | // Package orm provide ORM for MySQL/PostgreSQL/sqlite | 17 | // Package orm provide ORM for MySQL/PostgreSQL/sqlite |
16 | // Simple Usage | 18 | // Simple Usage |
17 | // | 19 | // |
@@ -52,6 +54,7 @@ | @@ -52,6 +54,7 @@ | ||
52 | package orm | 54 | package orm |
53 | 55 | ||
54 | import ( | 56 | import ( |
57 | + "context" | ||
55 | "database/sql" | 58 | "database/sql" |
56 | "errors" | 59 | "errors" |
57 | "fmt" | 60 | "fmt" |
@@ -422,7 +425,7 @@ func (o *orm) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet { | @@ -422,7 +425,7 @@ func (o *orm) getRelQs(md interface{}, mi *modelInfo, fi *fieldInfo) *querySet { | ||
422 | func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) { | 425 | func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) { |
423 | var name string | 426 | var name string |
424 | if table, ok := ptrStructOrTableName.(string); ok { | 427 | if table, ok := ptrStructOrTableName.(string); ok { |
425 | - name = snakeString(table) | 428 | + name = nameStrategyMap[defaultNameStrategy](table) |
426 | if mi, ok := modelCache.get(name); ok { | 429 | if mi, ok := modelCache.get(name); ok { |
427 | qs = newQuerySet(o, mi) | 430 | qs = newQuerySet(o, mi) |
428 | } | 431 | } |
@@ -458,11 +461,15 @@ func (o *orm) Using(name string) error { | @@ -458,11 +461,15 @@ func (o *orm) Using(name string) error { | ||
458 | 461 | ||
459 | // begin transaction | 462 | // begin transaction |
460 | func (o *orm) Begin() error { | 463 | func (o *orm) Begin() error { |
464 | + return o.BeginTx(context.Background(), nil) | ||
465 | +} | ||
466 | + | ||
467 | +func (o *orm) BeginTx(ctx context.Context, opts *sql.TxOptions) error { | ||
461 | if o.isTx { | 468 | if o.isTx { |
462 | return ErrTxHasBegan | 469 | return ErrTxHasBegan |
463 | } | 470 | } |
464 | var tx *sql.Tx | 471 | var tx *sql.Tx |
465 | - tx, err := o.db.(txer).Begin() | 472 | + tx, err := o.db.(txer).BeginTx(ctx, opts) |
466 | if err != nil { | 473 | if err != nil { |
467 | return err | 474 | return err |
468 | } | 475 | } |
@@ -541,6 +548,9 @@ func NewOrmWithDB(driverName, aliasName string, db *sql.DB) (Ormer, error) { | @@ -541,6 +548,9 @@ func NewOrmWithDB(driverName, aliasName string, db *sql.DB) (Ormer, error) { | ||
541 | 548 | ||
542 | al.Name = aliasName | 549 | al.Name = aliasName |
543 | al.DriverName = driverName | 550 | al.DriverName = driverName |
551 | + al.DB = db | ||
552 | + | ||
553 | + detectTZ(al) | ||
544 | 554 | ||
545 | o := new(orm) | 555 | o := new(orm) |
546 | o.alias = al | 556 | o.alias = al |
@@ -31,6 +31,8 @@ type condValue struct { | @@ -31,6 +31,8 @@ type condValue struct { | ||
31 | isOr bool | 31 | isOr bool |
32 | isNot bool | 32 | isNot bool |
33 | isCond bool | 33 | isCond bool |
34 | + isRaw bool | ||
35 | + sql string | ||
34 | } | 36 | } |
35 | 37 | ||
36 | // Condition struct. | 38 | // Condition struct. |
@@ -45,6 +47,15 @@ func NewCondition() *Condition { | @@ -45,6 +47,15 @@ func NewCondition() *Condition { | ||
45 | return c | 47 | return c |
46 | } | 48 | } |
47 | 49 | ||
50 | +// Raw add raw sql to condition | ||
51 | +func (c Condition) Raw(expr string, sql string) *Condition { | ||
52 | + if len(sql) == 0 { | ||
53 | + panic(fmt.Errorf("<Condition.Raw> sql cannot empty")) | ||
54 | + } | ||
55 | + c.params = append(c.params, condValue{exprs: strings.Split(expr, ExprSep), sql: sql, isRaw: true}) | ||
56 | + return &c | ||
57 | +} | ||
58 | + | ||
48 | // And add expression to condition | 59 | // And add expression to condition |
49 | func (c Condition) And(expr string, args ...interface{}) *Condition { | 60 | func (c Condition) And(expr string, args ...interface{}) *Condition { |
50 | if expr == "" || len(args) == 0 { | 61 | if expr == "" || len(args) == 0 { |
@@ -15,6 +15,7 @@ | @@ -15,6 +15,7 @@ | ||
15 | package orm | 15 | package orm |
16 | 16 | ||
17 | import ( | 17 | import ( |
18 | + "context" | ||
18 | "database/sql" | 19 | "database/sql" |
19 | "fmt" | 20 | "fmt" |
20 | "io" | 21 | "io" |
@@ -122,6 +123,13 @@ func (d *dbQueryLog) Prepare(query string) (*sql.Stmt, error) { | @@ -122,6 +123,13 @@ func (d *dbQueryLog) Prepare(query string) (*sql.Stmt, error) { | ||
122 | return stmt, err | 123 | return stmt, err |
123 | } | 124 | } |
124 | 125 | ||
126 | +func (d *dbQueryLog) PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) { | ||
127 | + a := time.Now() | ||
128 | + stmt, err := d.db.PrepareContext(ctx, query) | ||
129 | + debugLogQueies(d.alias, "db.Prepare", query, a, err) | ||
130 | + return stmt, err | ||
131 | +} | ||
132 | + | ||
125 | func (d *dbQueryLog) Exec(query string, args ...interface{}) (sql.Result, error) { | 133 | func (d *dbQueryLog) Exec(query string, args ...interface{}) (sql.Result, error) { |
126 | a := time.Now() | 134 | a := time.Now() |
127 | res, err := d.db.Exec(query, args...) | 135 | res, err := d.db.Exec(query, args...) |
@@ -129,6 +137,13 @@ func (d *dbQueryLog) Exec(query string, args ...interface{}) (sql.Result, error) | @@ -129,6 +137,13 @@ func (d *dbQueryLog) Exec(query string, args ...interface{}) (sql.Result, error) | ||
129 | return res, err | 137 | return res, err |
130 | } | 138 | } |
131 | 139 | ||
140 | +func (d *dbQueryLog) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { | ||
141 | + a := time.Now() | ||
142 | + res, err := d.db.ExecContext(ctx, query, args...) | ||
143 | + debugLogQueies(d.alias, "db.Exec", query, a, err, args...) | ||
144 | + return res, err | ||
145 | +} | ||
146 | + | ||
132 | func (d *dbQueryLog) Query(query string, args ...interface{}) (*sql.Rows, error) { | 147 | func (d *dbQueryLog) Query(query string, args ...interface{}) (*sql.Rows, error) { |
133 | a := time.Now() | 148 | a := time.Now() |
134 | res, err := d.db.Query(query, args...) | 149 | res, err := d.db.Query(query, args...) |
@@ -136,6 +151,13 @@ func (d *dbQueryLog) Query(query string, args ...interface{}) (*sql.Rows, error) | @@ -136,6 +151,13 @@ func (d *dbQueryLog) Query(query string, args ...interface{}) (*sql.Rows, error) | ||
136 | return res, err | 151 | return res, err |
137 | } | 152 | } |
138 | 153 | ||
154 | +func (d *dbQueryLog) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { | ||
155 | + a := time.Now() | ||
156 | + res, err := d.db.QueryContext(ctx, query, args...) | ||
157 | + debugLogQueies(d.alias, "db.Query", query, a, err, args...) | ||
158 | + return res, err | ||
159 | +} | ||
160 | + | ||
139 | func (d *dbQueryLog) QueryRow(query string, args ...interface{}) *sql.Row { | 161 | func (d *dbQueryLog) QueryRow(query string, args ...interface{}) *sql.Row { |
140 | a := time.Now() | 162 | a := time.Now() |
141 | res := d.db.QueryRow(query, args...) | 163 | res := d.db.QueryRow(query, args...) |
@@ -143,6 +165,13 @@ func (d *dbQueryLog) QueryRow(query string, args ...interface{}) *sql.Row { | @@ -143,6 +165,13 @@ func (d *dbQueryLog) QueryRow(query string, args ...interface{}) *sql.Row { | ||
143 | return res | 165 | return res |
144 | } | 166 | } |
145 | 167 | ||
168 | +func (d *dbQueryLog) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { | ||
169 | + a := time.Now() | ||
170 | + res := d.db.QueryRowContext(ctx, query, args...) | ||
171 | + debugLogQueies(d.alias, "db.QueryRow", query, a, nil, args...) | ||
172 | + return res | ||
173 | +} | ||
174 | + | ||
146 | func (d *dbQueryLog) Begin() (*sql.Tx, error) { | 175 | func (d *dbQueryLog) Begin() (*sql.Tx, error) { |
147 | a := time.Now() | 176 | a := time.Now() |
148 | tx, err := d.db.(txer).Begin() | 177 | tx, err := d.db.(txer).Begin() |
@@ -150,6 +179,13 @@ func (d *dbQueryLog) Begin() (*sql.Tx, error) { | @@ -150,6 +179,13 @@ func (d *dbQueryLog) Begin() (*sql.Tx, error) { | ||
150 | return tx, err | 179 | return tx, err |
151 | } | 180 | } |
152 | 181 | ||
182 | +func (d *dbQueryLog) BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) { | ||
183 | + a := time.Now() | ||
184 | + tx, err := d.db.(txer).BeginTx(ctx, opts) | ||
185 | + debugLogQueies(d.alias, "db.BeginTx", "START TRANSACTION", a, err) | ||
186 | + return tx, err | ||
187 | +} | ||
188 | + | ||
153 | func (d *dbQueryLog) Commit() error { | 189 | func (d *dbQueryLog) Commit() error { |
154 | a := time.Now() | 190 | a := time.Now() |
155 | err := d.db.(txEnder).Commit() | 191 | err := d.db.(txEnder).Commit() |
@@ -15,6 +15,7 @@ | @@ -15,6 +15,7 @@ | ||
15 | package orm | 15 | package orm |
16 | 16 | ||
17 | import ( | 17 | import ( |
18 | + "context" | ||
18 | "fmt" | 19 | "fmt" |
19 | ) | 20 | ) |
20 | 21 | ||
@@ -55,17 +56,19 @@ func ColValue(opt operator, value interface{}) interface{} { | @@ -55,17 +56,19 @@ func ColValue(opt operator, value interface{}) interface{} { | ||
55 | 56 | ||
56 | // real query struct | 57 | // real query struct |
57 | type querySet struct { | 58 | type querySet struct { |
58 | - mi *modelInfo | ||
59 | - cond *Condition | ||
60 | - related []string | ||
61 | - relDepth int | ||
62 | - limit int64 | ||
63 | - offset int64 | ||
64 | - groups []string | ||
65 | - orders []string | ||
66 | - distinct bool | ||
67 | - forupdate bool | ||
68 | - orm *orm | 59 | + mi *modelInfo |
60 | + cond *Condition | ||
61 | + related []string | ||
62 | + relDepth int | ||
63 | + limit int64 | ||
64 | + offset int64 | ||
65 | + groups []string | ||
66 | + orders []string | ||
67 | + distinct bool | ||
68 | + forupdate bool | ||
69 | + orm *orm | ||
70 | + ctx context.Context | ||
71 | + forContext bool | ||
69 | } | 72 | } |
70 | 73 | ||
71 | var _ QuerySeter = new(querySet) | 74 | var _ QuerySeter = new(querySet) |
@@ -79,6 +82,15 @@ func (o querySet) Filter(expr string, args ...interface{}) QuerySeter { | @@ -79,6 +82,15 @@ func (o querySet) Filter(expr string, args ...interface{}) QuerySeter { | ||
79 | return &o | 82 | return &o |
80 | } | 83 | } |
81 | 84 | ||
85 | +// add raw sql to querySeter. | ||
86 | +func (o querySet) FilterRaw(expr string, sql string) QuerySeter { | ||
87 | + if o.cond == nil { | ||
88 | + o.cond = NewCondition() | ||
89 | + } | ||
90 | + o.cond = o.cond.Raw(expr, sql) | ||
91 | + return &o | ||
92 | +} | ||
93 | + | ||
82 | // add NOT condition to querySeter. | 94 | // add NOT condition to querySeter. |
83 | func (o querySet) Exclude(expr string, args ...interface{}) QuerySeter { | 95 | func (o querySet) Exclude(expr string, args ...interface{}) QuerySeter { |
84 | if o.cond == nil { | 96 | if o.cond == nil { |
@@ -198,11 +210,7 @@ func (o *querySet) PrepareInsert() (Inserter, error) { | @@ -198,11 +210,7 @@ func (o *querySet) PrepareInsert() (Inserter, error) { | ||
198 | // query all data and map to containers. | 210 | // query all data and map to containers. |
199 | // cols means the columns when querying. | 211 | // cols means the columns when querying. |
200 | func (o *querySet) All(container interface{}, cols ...string) (int64, error) { | 212 | func (o *querySet) All(container interface{}, cols ...string) (int64, error) { |
201 | - num, err := o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols) | ||
202 | - if num == 0 { | ||
203 | - return 0, ErrNoRows | ||
204 | - } | ||
205 | - return num, err | 213 | + return o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols) |
206 | } | 214 | } |
207 | 215 | ||
208 | // query one row data and map to containers. | 216 | // query one row data and map to containers. |
@@ -270,6 +278,13 @@ func (o *querySet) RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) | @@ -270,6 +278,13 @@ func (o *querySet) RowsToStruct(ptrStruct interface{}, keyCol, valueCol string) | ||
270 | panic(ErrNotImplement) | 278 | panic(ErrNotImplement) |
271 | } | 279 | } |
272 | 280 | ||
281 | +// set context to QuerySeter. | ||
282 | +func (o querySet) WithContext(ctx context.Context) QuerySeter { | ||
283 | + o.ctx = ctx | ||
284 | + o.forContext = true | ||
285 | + return &o | ||
286 | +} | ||
287 | + | ||
273 | // create new QuerySeter. | 288 | // create new QuerySeter. |
274 | func newQuerySet(orm *orm, mi *modelInfo) QuerySeter { | 289 | func newQuerySet(orm *orm, mi *modelInfo) QuerySeter { |
275 | o := new(querySet) | 290 | o := new(querySet) |
@@ -358,7 +358,7 @@ func (o *rawSet) QueryRow(containers ...interface{}) error { | @@ -358,7 +358,7 @@ func (o *rawSet) QueryRow(containers ...interface{}) error { | ||
358 | _, tags := parseStructTag(fe.Tag.Get(defaultStructTagName)) | 358 | _, tags := parseStructTag(fe.Tag.Get(defaultStructTagName)) |
359 | var col string | 359 | var col string |
360 | if col = tags["column"]; col == "" { | 360 | if col = tags["column"]; col == "" { |
361 | - col = snakeString(fe.Name) | 361 | + col = nameStrategyMap[nameStrategy](fe.Name) |
362 | } | 362 | } |
363 | if v, ok := columnsMp[col]; ok { | 363 | if v, ok := columnsMp[col]; ok { |
364 | value := reflect.ValueOf(v).Elem().Interface() | 364 | value := reflect.ValueOf(v).Elem().Interface() |
@@ -509,7 +509,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) { | @@ -509,7 +509,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) { | ||
509 | _, tags := parseStructTag(fe.Tag.Get(defaultStructTagName)) | 509 | _, tags := parseStructTag(fe.Tag.Get(defaultStructTagName)) |
510 | var col string | 510 | var col string |
511 | if col = tags["column"]; col == "" { | 511 | if col = tags["column"]; col == "" { |
512 | - col = snakeString(fe.Name) | 512 | + col = nameStrategyMap[nameStrategy](fe.Name) |
513 | } | 513 | } |
514 | if v, ok := columnsMp[col]; ok { | 514 | if v, ok := columnsMp[col]; ok { |
515 | value := reflect.ValueOf(v).Elem().Interface() | 515 | value := reflect.ValueOf(v).Elem().Interface() |
@@ -15,6 +15,7 @@ | @@ -15,6 +15,7 @@ | ||
15 | package orm | 15 | package orm |
16 | 16 | ||
17 | import ( | 17 | import ( |
18 | + "context" | ||
18 | "database/sql" | 19 | "database/sql" |
19 | "reflect" | 20 | "reflect" |
20 | "time" | 21 | "time" |
@@ -106,6 +107,17 @@ type Ormer interface { | @@ -106,6 +107,17 @@ type Ormer interface { | ||
106 | // ... | 107 | // ... |
107 | // err = o.Rollback() | 108 | // err = o.Rollback() |
108 | Begin() error | 109 | Begin() error |
110 | + // begin transaction with provided context and option | ||
111 | + // the provided context is used until the transaction is committed or rolled back. | ||
112 | + // if the context is canceled, the transaction will be rolled back. | ||
113 | + // the provided TxOptions is optional and may be nil if defaults should be used. | ||
114 | + // if a non-default isolation level is used that the driver doesn't support, an error will be returned. | ||
115 | + // for example: | ||
116 | + // o := NewOrm() | ||
117 | + // err := o.BeginTx(context.Background(), &sql.TxOptions{Isolation: sql.LevelRepeatableRead}) | ||
118 | + // ... | ||
119 | + // err = o.Rollback() | ||
120 | + BeginTx(ctx context.Context, opts *sql.TxOptions) error | ||
109 | // commit transaction | 121 | // commit transaction |
110 | Commit() error | 122 | Commit() error |
111 | // rollback transaction | 123 | // rollback transaction |
@@ -135,6 +147,11 @@ type QuerySeter interface { | @@ -135,6 +147,11 @@ type QuerySeter interface { | ||
135 | // // time compare | 147 | // // time compare |
136 | // qs.Filter("created", time.Now()) | 148 | // qs.Filter("created", time.Now()) |
137 | Filter(string, ...interface{}) QuerySeter | 149 | Filter(string, ...interface{}) QuerySeter |
150 | + // add raw sql to querySeter. | ||
151 | + // for example: | ||
152 | + // qs.FilterRaw("user_id IN (SELECT id FROM profile WHERE age>=18)") | ||
153 | + // //sql-> WHERE user_id IN (SELECT id FROM profile WHERE age>=18) | ||
154 | + FilterRaw(string, string) QuerySeter | ||
138 | // add NOT condition to querySeter. | 155 | // add NOT condition to querySeter. |
139 | // have the same usage as Filter | 156 | // have the same usage as Filter |
140 | Exclude(string, ...interface{}) QuerySeter | 157 | Exclude(string, ...interface{}) QuerySeter |
@@ -378,16 +395,23 @@ type RawSeter interface { | @@ -378,16 +395,23 @@ type RawSeter interface { | ||
378 | type stmtQuerier interface { | 395 | type stmtQuerier interface { |
379 | Close() error | 396 | Close() error |
380 | Exec(args ...interface{}) (sql.Result, error) | 397 | Exec(args ...interface{}) (sql.Result, error) |
398 | + //ExecContext(ctx context.Context, args ...interface{}) (sql.Result, error) | ||
381 | Query(args ...interface{}) (*sql.Rows, error) | 399 | Query(args ...interface{}) (*sql.Rows, error) |
400 | + //QueryContext(args ...interface{}) (*sql.Rows, error) | ||
382 | QueryRow(args ...interface{}) *sql.Row | 401 | QueryRow(args ...interface{}) *sql.Row |
402 | + //QueryRowContext(ctx context.Context, args ...interface{}) *sql.Row | ||
383 | } | 403 | } |
384 | 404 | ||
385 | // db querier | 405 | // db querier |
386 | type dbQuerier interface { | 406 | type dbQuerier interface { |
387 | Prepare(query string) (*sql.Stmt, error) | 407 | Prepare(query string) (*sql.Stmt, error) |
408 | + PrepareContext(ctx context.Context, query string) (*sql.Stmt, error) | ||
388 | Exec(query string, args ...interface{}) (sql.Result, error) | 409 | Exec(query string, args ...interface{}) (sql.Result, error) |
410 | + ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) | ||
389 | Query(query string, args ...interface{}) (*sql.Rows, error) | 411 | Query(query string, args ...interface{}) (*sql.Rows, error) |
412 | + QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) | ||
390 | QueryRow(query string, args ...interface{}) *sql.Row | 413 | QueryRow(query string, args ...interface{}) *sql.Row |
414 | + QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row | ||
391 | } | 415 | } |
392 | 416 | ||
393 | // type DB interface { | 417 | // type DB interface { |
@@ -401,6 +425,7 @@ type dbQuerier interface { | @@ -401,6 +425,7 @@ type dbQuerier interface { | ||
401 | // transaction beginner | 425 | // transaction beginner |
402 | type txer interface { | 426 | type txer interface { |
403 | Begin() (*sql.Tx, error) | 427 | Begin() (*sql.Tx, error) |
428 | + BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error) | ||
404 | } | 429 | } |
405 | 430 | ||
406 | // transaction ending | 431 | // transaction ending |
@@ -23,6 +23,18 @@ import ( | @@ -23,6 +23,18 @@ import ( | ||
23 | "time" | 23 | "time" |
24 | ) | 24 | ) |
25 | 25 | ||
26 | +type fn func(string) string | ||
27 | + | ||
28 | +var ( | ||
29 | + nameStrategyMap = map[string]fn{ | ||
30 | + defaultNameStrategy: snakeString, | ||
31 | + SnakeAcronymNameStrategy: snakeStringWithAcronym, | ||
32 | + } | ||
33 | + defaultNameStrategy = "snakeString" | ||
34 | + SnakeAcronymNameStrategy = "snakeStringWithAcronym" | ||
35 | + nameStrategy = defaultNameStrategy | ||
36 | +) | ||
37 | + | ||
26 | // StrTo is the target string | 38 | // StrTo is the target string |
27 | type StrTo string | 39 | type StrTo string |
28 | 40 | ||
@@ -198,7 +210,28 @@ func ToInt64(value interface{}) (d int64) { | @@ -198,7 +210,28 @@ func ToInt64(value interface{}) (d int64) { | ||
198 | return | 210 | return |
199 | } | 211 | } |
200 | 212 | ||
201 | -// snake string, XxYy to xx_yy , XxYY to xx_yy | 213 | +func snakeStringWithAcronym(s string) string { |
214 | + data := make([]byte, 0, len(s)*2) | ||
215 | + num := len(s) | ||
216 | + for i := 0; i < num; i++ { | ||
217 | + d := s[i] | ||
218 | + before := false | ||
219 | + after := false | ||
220 | + if i > 0 { | ||
221 | + before = s[i-1] >= 'a' && s[i-1] <= 'z' | ||
222 | + } | ||
223 | + if i+1 < num { | ||
224 | + after = s[i+1] >= 'a' && s[i+1] <= 'z' | ||
225 | + } | ||
226 | + if i > 0 && d >= 'A' && d <= 'Z' && (before || after) { | ||
227 | + data = append(data, '_') | ||
228 | + } | ||
229 | + data = append(data, d) | ||
230 | + } | ||
231 | + return strings.ToLower(string(data[:])) | ||
232 | +} | ||
233 | + | ||
234 | +// snake string, XxYy to xx_yy , XxYY to xx_y_y | ||
202 | func snakeString(s string) string { | 235 | func snakeString(s string) string { |
203 | data := make([]byte, 0, len(s)*2) | 236 | data := make([]byte, 0, len(s)*2) |
204 | j := false | 237 | j := false |
@@ -216,6 +249,14 @@ func snakeString(s string) string { | @@ -216,6 +249,14 @@ func snakeString(s string) string { | ||
216 | return strings.ToLower(string(data[:])) | 249 | return strings.ToLower(string(data[:])) |
217 | } | 250 | } |
218 | 251 | ||
252 | +// SetNameStrategy set different name strategy | ||
253 | +func SetNameStrategy(s string) { | ||
254 | + if SnakeAcronymNameStrategy != s { | ||
255 | + nameStrategy = defaultNameStrategy | ||
256 | + } | ||
257 | + nameStrategy = s | ||
258 | +} | ||
259 | + | ||
219 | // camel string, xx_yy to XxYy | 260 | // camel string, xx_yy to XxYy |
220 | func camelString(s string) string { | 261 | func camelString(s string) string { |
221 | data := make([]byte, 0, len(s)) | 262 | data := make([]byte, 0, len(s)) |
@@ -39,7 +39,7 @@ var globalRouterTemplate = `package routers | @@ -39,7 +39,7 @@ var globalRouterTemplate = `package routers | ||
39 | 39 | ||
40 | import ( | 40 | import ( |
41 | "github.com/astaxie/beego" | 41 | "github.com/astaxie/beego" |
42 | - "github.com/astaxie/beego/context/param" | 42 | + "github.com/astaxie/beego/context/param"{{.globalimport}} |
43 | ) | 43 | ) |
44 | 44 | ||
45 | func init() { | 45 | func init() { |
@@ -52,6 +52,22 @@ var ( | @@ -52,6 +52,22 @@ var ( | ||
52 | commentFilename string | 52 | commentFilename string |
53 | pkgLastupdate map[string]int64 | 53 | pkgLastupdate map[string]int64 |
54 | genInfoList map[string][]ControllerComments | 54 | genInfoList map[string][]ControllerComments |
55 | + | ||
56 | + routerHooks = map[string]int{ | ||
57 | + "beego.BeforeStatic": BeforeStatic, | ||
58 | + "beego.BeforeRouter": BeforeRouter, | ||
59 | + "beego.BeforeExec": BeforeExec, | ||
60 | + "beego.AfterExec": AfterExec, | ||
61 | + "beego.FinishRouter": FinishRouter, | ||
62 | + } | ||
63 | + | ||
64 | + routerHooksMapping = map[int]string{ | ||
65 | + BeforeStatic: "beego.BeforeStatic", | ||
66 | + BeforeRouter: "beego.BeforeRouter", | ||
67 | + BeforeExec: "beego.BeforeExec", | ||
68 | + AfterExec: "beego.AfterExec", | ||
69 | + FinishRouter: "beego.FinishRouter", | ||
70 | + } | ||
55 | ) | 71 | ) |
56 | 72 | ||
57 | const commentPrefix = "commentsRouter_" | 73 | const commentPrefix = "commentsRouter_" |
@@ -102,6 +118,20 @@ type parsedComment struct { | @@ -102,6 +118,20 @@ type parsedComment struct { | ||
102 | routerPath string | 118 | routerPath string |
103 | methods []string | 119 | methods []string |
104 | params map[string]parsedParam | 120 | params map[string]parsedParam |
121 | + filters []parsedFilter | ||
122 | + imports []parsedImport | ||
123 | +} | ||
124 | + | ||
125 | +type parsedImport struct { | ||
126 | + importPath string | ||
127 | + importAlias string | ||
128 | +} | ||
129 | + | ||
130 | +type parsedFilter struct { | ||
131 | + pattern string | ||
132 | + pos int | ||
133 | + filter string | ||
134 | + params []bool | ||
105 | } | 135 | } |
106 | 136 | ||
107 | type parsedParam struct { | 137 | type parsedParam struct { |
@@ -126,6 +156,8 @@ func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error { | @@ -126,6 +156,8 @@ func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error { | ||
126 | cc.Router = parsedComment.routerPath | 156 | cc.Router = parsedComment.routerPath |
127 | cc.AllowHTTPMethods = parsedComment.methods | 157 | cc.AllowHTTPMethods = parsedComment.methods |
128 | cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment) | 158 | cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment) |
159 | + cc.FilterComments = buildFilters(parsedComment.filters) | ||
160 | + cc.ImportComments = buildImports(parsedComment.imports) | ||
129 | genInfoList[key] = append(genInfoList[key], cc) | 161 | genInfoList[key] = append(genInfoList[key], cc) |
130 | } | 162 | } |
131 | } | 163 | } |
@@ -133,6 +165,48 @@ func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error { | @@ -133,6 +165,48 @@ func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error { | ||
133 | return nil | 165 | return nil |
134 | } | 166 | } |
135 | 167 | ||
168 | +func buildImports(pis []parsedImport) []*ControllerImportComments { | ||
169 | + var importComments []*ControllerImportComments | ||
170 | + | ||
171 | + for _, pi := range pis { | ||
172 | + importComments = append(importComments, &ControllerImportComments{ | ||
173 | + ImportPath: pi.importPath, | ||
174 | + ImportAlias: pi.importAlias, | ||
175 | + }) | ||
176 | + } | ||
177 | + | ||
178 | + return importComments | ||
179 | +} | ||
180 | + | ||
181 | +func buildFilters(pfs []parsedFilter) []*ControllerFilterComments { | ||
182 | + var filterComments []*ControllerFilterComments | ||
183 | + | ||
184 | + for _, pf := range pfs { | ||
185 | + var ( | ||
186 | + returnOnOutput bool | ||
187 | + resetParams bool | ||
188 | + ) | ||
189 | + | ||
190 | + if len(pf.params) >= 1 { | ||
191 | + returnOnOutput = pf.params[0] | ||
192 | + } | ||
193 | + | ||
194 | + if len(pf.params) >= 2 { | ||
195 | + resetParams = pf.params[1] | ||
196 | + } | ||
197 | + | ||
198 | + filterComments = append(filterComments, &ControllerFilterComments{ | ||
199 | + Filter: pf.filter, | ||
200 | + Pattern: pf.pattern, | ||
201 | + Pos: pf.pos, | ||
202 | + ReturnOnOutput: returnOnOutput, | ||
203 | + ResetParams: resetParams, | ||
204 | + }) | ||
205 | + } | ||
206 | + | ||
207 | + return filterComments | ||
208 | +} | ||
209 | + | ||
136 | func buildMethodParams(funcParams []*ast.Field, pc *parsedComment) []*param.MethodParam { | 210 | func buildMethodParams(funcParams []*ast.Field, pc *parsedComment) []*param.MethodParam { |
137 | result := make([]*param.MethodParam, 0, len(funcParams)) | 211 | result := make([]*param.MethodParam, 0, len(funcParams)) |
138 | for _, fparam := range funcParams { | 212 | for _, fparam := range funcParams { |
@@ -181,6 +255,8 @@ var routeRegex = regexp.MustCompile(`@router\s+(\S+)(?:\s+\[(\S+)\])?`) | @@ -181,6 +255,8 @@ var routeRegex = regexp.MustCompile(`@router\s+(\S+)(?:\s+\[(\S+)\])?`) | ||
181 | func parseComment(lines []*ast.Comment) (pcs []*parsedComment, err error) { | 255 | func parseComment(lines []*ast.Comment) (pcs []*parsedComment, err error) { |
182 | pcs = []*parsedComment{} | 256 | pcs = []*parsedComment{} |
183 | params := map[string]parsedParam{} | 257 | params := map[string]parsedParam{} |
258 | + filters := []parsedFilter{} | ||
259 | + imports := []parsedImport{} | ||
184 | 260 | ||
185 | for _, c := range lines { | 261 | for _, c := range lines { |
186 | t := strings.TrimSpace(strings.TrimLeft(c.Text, "//")) | 262 | t := strings.TrimSpace(strings.TrimLeft(c.Text, "//")) |
@@ -210,8 +286,68 @@ func parseComment(lines []*ast.Comment) (pcs []*parsedComment, err error) { | @@ -210,8 +286,68 @@ func parseComment(lines []*ast.Comment) (pcs []*parsedComment, err error) { | ||
210 | } | 286 | } |
211 | 287 | ||
212 | for _, c := range lines { | 288 | for _, c := range lines { |
289 | + t := strings.TrimSpace(strings.TrimLeft(c.Text, "//")) | ||
290 | + if strings.HasPrefix(t, "@Import") { | ||
291 | + iv := getparams(strings.TrimSpace(strings.TrimLeft(t, "@Import"))) | ||
292 | + if len(iv) == 0 || len(iv) > 2 { | ||
293 | + logs.Error("Invalid @Import format. Only accepts 1 or 2 parameters") | ||
294 | + continue | ||
295 | + } | ||
296 | + | ||
297 | + p := parsedImport{} | ||
298 | + p.importPath = iv[0] | ||
299 | + | ||
300 | + if len(iv) == 2 { | ||
301 | + p.importAlias = iv[1] | ||
302 | + } | ||
303 | + | ||
304 | + imports = append(imports, p) | ||
305 | + } | ||
306 | + } | ||
307 | + | ||
308 | +filterLoop: | ||
309 | + for _, c := range lines { | ||
310 | + t := strings.TrimSpace(strings.TrimLeft(c.Text, "//")) | ||
311 | + if strings.HasPrefix(t, "@Filter") { | ||
312 | + fv := getparams(strings.TrimSpace(strings.TrimLeft(t, "@Filter"))) | ||
313 | + if len(fv) < 3 { | ||
314 | + logs.Error("Invalid @Filter format. Needs at least 3 parameters") | ||
315 | + continue filterLoop | ||
316 | + } | ||
317 | + | ||
318 | + p := parsedFilter{} | ||
319 | + p.pattern = fv[0] | ||
320 | + posName := fv[1] | ||
321 | + if pos, exists := routerHooks[posName]; exists { | ||
322 | + p.pos = pos | ||
323 | + } else { | ||
324 | + logs.Error("Invalid @Filter pos: ", posName) | ||
325 | + continue filterLoop | ||
326 | + } | ||
327 | + | ||
328 | + p.filter = fv[2] | ||
329 | + fvParams := fv[3:] | ||
330 | + for _, fvParam := range fvParams { | ||
331 | + switch fvParam { | ||
332 | + case "true": | ||
333 | + p.params = append(p.params, true) | ||
334 | + case "false": | ||
335 | + p.params = append(p.params, false) | ||
336 | + default: | ||
337 | + logs.Error("Invalid @Filter param: ", fvParam) | ||
338 | + continue filterLoop | ||
339 | + } | ||
340 | + } | ||
341 | + | ||
342 | + filters = append(filters, p) | ||
343 | + } | ||
344 | + } | ||
345 | + | ||
346 | + for _, c := range lines { | ||
213 | var pc = &parsedComment{} | 347 | var pc = &parsedComment{} |
214 | pc.params = params | 348 | pc.params = params |
349 | + pc.filters = filters | ||
350 | + pc.imports = imports | ||
215 | 351 | ||
216 | t := strings.TrimSpace(strings.TrimLeft(c.Text, "//")) | 352 | t := strings.TrimSpace(strings.TrimLeft(c.Text, "//")) |
217 | if strings.HasPrefix(t, "@router") { | 353 | if strings.HasPrefix(t, "@router") { |
@@ -276,8 +412,9 @@ func genRouterCode(pkgRealpath string) { | @@ -276,8 +412,9 @@ func genRouterCode(pkgRealpath string) { | ||
276 | os.Mkdir(getRouterDir(pkgRealpath), 0755) | 412 | os.Mkdir(getRouterDir(pkgRealpath), 0755) |
277 | logs.Info("generate router from comments") | 413 | logs.Info("generate router from comments") |
278 | var ( | 414 | var ( |
279 | - globalinfo string | ||
280 | - sortKey []string | 415 | + globalinfo string |
416 | + globalimport string | ||
417 | + sortKey []string | ||
281 | ) | 418 | ) |
282 | for k := range genInfoList { | 419 | for k := range genInfoList { |
283 | sortKey = append(sortKey, k) | 420 | sortKey = append(sortKey, k) |
@@ -295,6 +432,7 @@ func genRouterCode(pkgRealpath string) { | @@ -295,6 +432,7 @@ func genRouterCode(pkgRealpath string) { | ||
295 | } | 432 | } |
296 | allmethod = strings.TrimRight(allmethod, ",") + "}" | 433 | allmethod = strings.TrimRight(allmethod, ",") + "}" |
297 | } | 434 | } |
435 | + | ||
298 | params := "nil" | 436 | params := "nil" |
299 | if len(c.Params) > 0 { | 437 | if len(c.Params) > 0 { |
300 | params = "[]map[string]string{" | 438 | params = "[]map[string]string{" |
@@ -305,6 +443,7 @@ func genRouterCode(pkgRealpath string) { | @@ -305,6 +443,7 @@ func genRouterCode(pkgRealpath string) { | ||
305 | } | 443 | } |
306 | params = strings.TrimRight(params, ",") + "}" | 444 | params = strings.TrimRight(params, ",") + "}" |
307 | } | 445 | } |
446 | + | ||
308 | methodParams := "param.Make(" | 447 | methodParams := "param.Make(" |
309 | if len(c.MethodParams) > 0 { | 448 | if len(c.MethodParams) > 0 { |
310 | lines := make([]string, 0, len(c.MethodParams)) | 449 | lines := make([]string, 0, len(c.MethodParams)) |
@@ -316,24 +455,66 @@ func genRouterCode(pkgRealpath string) { | @@ -316,24 +455,66 @@ func genRouterCode(pkgRealpath string) { | ||
316 | ",\n " | 455 | ",\n " |
317 | } | 456 | } |
318 | methodParams += ")" | 457 | methodParams += ")" |
458 | + | ||
459 | + imports := "" | ||
460 | + if len(c.ImportComments) > 0 { | ||
461 | + for _, i := range c.ImportComments { | ||
462 | + if i.ImportAlias != "" { | ||
463 | + imports += fmt.Sprintf(` | ||
464 | + %s "%s"`, i.ImportAlias, i.ImportPath) | ||
465 | + } else { | ||
466 | + imports += fmt.Sprintf(` | ||
467 | + "%s"`, i.ImportPath) | ||
468 | + } | ||
469 | + } | ||
470 | + } | ||
471 | + | ||
472 | + filters := "" | ||
473 | + if len(c.FilterComments) > 0 { | ||
474 | + for _, f := range c.FilterComments { | ||
475 | + filters += fmt.Sprintf(` &beego.ControllerFilter{ | ||
476 | + Pattern: "%s", | ||
477 | + Pos: %s, | ||
478 | + Filter: %s, | ||
479 | + ReturnOnOutput: %v, | ||
480 | + ResetParams: %v, | ||
481 | + },`, f.Pattern, routerHooksMapping[f.Pos], f.Filter, f.ReturnOnOutput, f.ResetParams) | ||
482 | + } | ||
483 | + } | ||
484 | + | ||
485 | + if filters == "" { | ||
486 | + filters = "nil" | ||
487 | + } else { | ||
488 | + filters = fmt.Sprintf(`[]*beego.ControllerFilter{ | ||
489 | +%s | ||
490 | + }`, filters) | ||
491 | + } | ||
492 | + | ||
493 | + globalimport = imports | ||
494 | + | ||
319 | globalinfo = globalinfo + ` | 495 | globalinfo = globalinfo + ` |
320 | - beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"], | ||
321 | - beego.ControllerComments{ | ||
322 | - Method: "` + strings.TrimSpace(c.Method) + `", | ||
323 | - ` + "Router: `" + c.Router + "`" + `, | ||
324 | - AllowHTTPMethods: ` + allmethod + `, | ||
325 | - MethodParams: ` + methodParams + `, | ||
326 | - Params: ` + params + `}) | 496 | + beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"], |
497 | + beego.ControllerComments{ | ||
498 | + Method: "` + strings.TrimSpace(c.Method) + `", | ||
499 | + ` + "Router: `" + c.Router + "`" + `, | ||
500 | + AllowHTTPMethods: ` + allmethod + `, | ||
501 | + MethodParams: ` + methodParams + `, | ||
502 | + Filters: ` + filters + `, | ||
503 | + Params: ` + params + `}) | ||
327 | ` | 504 | ` |
328 | } | 505 | } |
329 | } | 506 | } |
507 | + | ||
330 | if globalinfo != "" { | 508 | if globalinfo != "" { |
331 | f, err := os.Create(filepath.Join(getRouterDir(pkgRealpath), commentFilename)) | 509 | f, err := os.Create(filepath.Join(getRouterDir(pkgRealpath), commentFilename)) |
332 | if err != nil { | 510 | if err != nil { |
333 | panic(err) | 511 | panic(err) |
334 | } | 512 | } |
335 | defer f.Close() | 513 | defer f.Close() |
336 | - f.WriteString(strings.Replace(globalRouterTemplate, "{{.globalinfo}}", globalinfo, -1)) | 514 | + |
515 | + content := strings.Replace(globalRouterTemplate, "{{.globalinfo}}", globalinfo, -1) | ||
516 | + content = strings.Replace(content, "{{.globalimport}}", globalimport, -1) | ||
517 | + f.WriteString(content) | ||
337 | } | 518 | } |
338 | } | 519 | } |
339 | 520 |
1 | +// Copyright 2014 beego Author. All Rights Reserved. | ||
2 | +// | ||
3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | ||
4 | +// you may not use this file except in compliance with the License. | ||
5 | +// You may obtain a copy of the License at | ||
6 | +// | ||
7 | +// http://www.apache.org/licenses/LICENSE-2.0 | ||
8 | +// | ||
9 | +// Unless required by applicable law or agreed to in writing, software | ||
10 | +// distributed under the License is distributed on an "AS IS" BASIS, | ||
11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
12 | +// See the License for the specific language governing permissions and | ||
13 | +// limitations under the License. | ||
14 | + | ||
15 | +// Package cors provides handlers to enable CORS support. | ||
16 | +// Usage | ||
17 | +// import ( | ||
18 | +// "github.com/astaxie/beego" | ||
19 | +// "github.com/astaxie/beego/plugins/cors" | ||
20 | +// ) | ||
21 | +// | ||
22 | +// func main() { | ||
23 | +// // CORS for https://foo.* origins, allowing: | ||
24 | +// // - PUT and PATCH methods | ||
25 | +// // - Origin header | ||
26 | +// // - Credentials share | ||
27 | +// beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{ | ||
28 | +// AllowOrigins: []string{"https://*.foo.com"}, | ||
29 | +// AllowMethods: []string{"PUT", "PATCH"}, | ||
30 | +// AllowHeaders: []string{"Origin"}, | ||
31 | +// ExposeHeaders: []string{"Content-Length"}, | ||
32 | +// AllowCredentials: true, | ||
33 | +// })) | ||
34 | +// beego.Run() | ||
35 | +// } | ||
36 | +package cors | ||
37 | + | ||
38 | +import ( | ||
39 | + "net/http" | ||
40 | + "regexp" | ||
41 | + "strconv" | ||
42 | + "strings" | ||
43 | + "time" | ||
44 | + | ||
45 | + "github.com/astaxie/beego" | ||
46 | + "github.com/astaxie/beego/context" | ||
47 | +) | ||
48 | + | ||
49 | +const ( | ||
50 | + headerAllowOrigin = "Access-Control-Allow-Origin" | ||
51 | + headerAllowCredentials = "Access-Control-Allow-Credentials" | ||
52 | + headerAllowHeaders = "Access-Control-Allow-Headers" | ||
53 | + headerAllowMethods = "Access-Control-Allow-Methods" | ||
54 | + headerExposeHeaders = "Access-Control-Expose-Headers" | ||
55 | + headerMaxAge = "Access-Control-Max-Age" | ||
56 | + | ||
57 | + headerOrigin = "Origin" | ||
58 | + headerRequestMethod = "Access-Control-Request-Method" | ||
59 | + headerRequestHeaders = "Access-Control-Request-Headers" | ||
60 | +) | ||
61 | + | ||
62 | +var ( | ||
63 | + defaultAllowHeaders = []string{"Origin", "Accept", "Content-Type", "Authorization"} | ||
64 | + // Regex patterns are generated from AllowOrigins. These are used and generated internally. | ||
65 | + allowOriginPatterns = []string{} | ||
66 | +) | ||
67 | + | ||
68 | +// Options represents Access Control options. | ||
69 | +type Options struct { | ||
70 | + // If set, all origins are allowed. | ||
71 | + AllowAllOrigins bool | ||
72 | + // A list of allowed origins. Wild cards and FQDNs are supported. | ||
73 | + AllowOrigins []string | ||
74 | + // If set, allows to share auth credentials such as cookies. | ||
75 | + AllowCredentials bool | ||
76 | + // A list of allowed HTTP methods. | ||
77 | + AllowMethods []string | ||
78 | + // A list of allowed HTTP headers. | ||
79 | + AllowHeaders []string | ||
80 | + // A list of exposed HTTP headers. | ||
81 | + ExposeHeaders []string | ||
82 | + // Max age of the CORS headers. | ||
83 | + MaxAge time.Duration | ||
84 | +} | ||
85 | + | ||
86 | +// Header converts options into CORS headers. | ||
87 | +func (o *Options) Header(origin string) (headers map[string]string) { | ||
88 | + headers = make(map[string]string) | ||
89 | + // if origin is not allowed, don't extend the headers | ||
90 | + // with CORS headers. | ||
91 | + if !o.AllowAllOrigins && !o.IsOriginAllowed(origin) { | ||
92 | + return | ||
93 | + } | ||
94 | + | ||
95 | + // add allow origin | ||
96 | + if o.AllowAllOrigins { | ||
97 | + headers[headerAllowOrigin] = "*" | ||
98 | + } else { | ||
99 | + headers[headerAllowOrigin] = origin | ||
100 | + } | ||
101 | + | ||
102 | + // add allow credentials | ||
103 | + headers[headerAllowCredentials] = strconv.FormatBool(o.AllowCredentials) | ||
104 | + | ||
105 | + // add allow methods | ||
106 | + if len(o.AllowMethods) > 0 { | ||
107 | + headers[headerAllowMethods] = strings.Join(o.AllowMethods, ",") | ||
108 | + } | ||
109 | + | ||
110 | + // add allow headers | ||
111 | + if len(o.AllowHeaders) > 0 { | ||
112 | + headers[headerAllowHeaders] = strings.Join(o.AllowHeaders, ",") | ||
113 | + } | ||
114 | + | ||
115 | + // add exposed header | ||
116 | + if len(o.ExposeHeaders) > 0 { | ||
117 | + headers[headerExposeHeaders] = strings.Join(o.ExposeHeaders, ",") | ||
118 | + } | ||
119 | + // add a max age header | ||
120 | + if o.MaxAge > time.Duration(0) { | ||
121 | + headers[headerMaxAge] = strconv.FormatInt(int64(o.MaxAge/time.Second), 10) | ||
122 | + } | ||
123 | + return | ||
124 | +} | ||
125 | + | ||
126 | +// PreflightHeader converts options into CORS headers for a preflight response. | ||
127 | +func (o *Options) PreflightHeader(origin, rMethod, rHeaders string) (headers map[string]string) { | ||
128 | + headers = make(map[string]string) | ||
129 | + if !o.AllowAllOrigins && !o.IsOriginAllowed(origin) { | ||
130 | + return | ||
131 | + } | ||
132 | + // verify if requested method is allowed | ||
133 | + for _, method := range o.AllowMethods { | ||
134 | + if method == rMethod { | ||
135 | + headers[headerAllowMethods] = strings.Join(o.AllowMethods, ",") | ||
136 | + break | ||
137 | + } | ||
138 | + } | ||
139 | + | ||
140 | + // verify if requested headers are allowed | ||
141 | + var allowed []string | ||
142 | + for _, rHeader := range strings.Split(rHeaders, ",") { | ||
143 | + rHeader = strings.TrimSpace(rHeader) | ||
144 | + lookupLoop: | ||
145 | + for _, allowedHeader := range o.AllowHeaders { | ||
146 | + if strings.ToLower(rHeader) == strings.ToLower(allowedHeader) { | ||
147 | + allowed = append(allowed, rHeader) | ||
148 | + break lookupLoop | ||
149 | + } | ||
150 | + } | ||
151 | + } | ||
152 | + | ||
153 | + headers[headerAllowCredentials] = strconv.FormatBool(o.AllowCredentials) | ||
154 | + // add allow origin | ||
155 | + if o.AllowAllOrigins { | ||
156 | + headers[headerAllowOrigin] = "*" | ||
157 | + } else { | ||
158 | + headers[headerAllowOrigin] = origin | ||
159 | + } | ||
160 | + | ||
161 | + // add allowed headers | ||
162 | + if len(allowed) > 0 { | ||
163 | + headers[headerAllowHeaders] = strings.Join(allowed, ",") | ||
164 | + } | ||
165 | + | ||
166 | + // add exposed headers | ||
167 | + if len(o.ExposeHeaders) > 0 { | ||
168 | + headers[headerExposeHeaders] = strings.Join(o.ExposeHeaders, ",") | ||
169 | + } | ||
170 | + // add a max age header | ||
171 | + if o.MaxAge > time.Duration(0) { | ||
172 | + headers[headerMaxAge] = strconv.FormatInt(int64(o.MaxAge/time.Second), 10) | ||
173 | + } | ||
174 | + return | ||
175 | +} | ||
176 | + | ||
177 | +// IsOriginAllowed looks up if the origin matches one of the patterns | ||
178 | +// generated from Options.AllowOrigins patterns. | ||
179 | +func (o *Options) IsOriginAllowed(origin string) (allowed bool) { | ||
180 | + for _, pattern := range allowOriginPatterns { | ||
181 | + allowed, _ = regexp.MatchString(pattern, origin) | ||
182 | + if allowed { | ||
183 | + return | ||
184 | + } | ||
185 | + } | ||
186 | + return | ||
187 | +} | ||
188 | + | ||
189 | +// Allow enables CORS for requests those match the provided options. | ||
190 | +func Allow(opts *Options) beego.FilterFunc { | ||
191 | + // Allow default headers if nothing is specified. | ||
192 | + if len(opts.AllowHeaders) == 0 { | ||
193 | + opts.AllowHeaders = defaultAllowHeaders | ||
194 | + } | ||
195 | + | ||
196 | + for _, origin := range opts.AllowOrigins { | ||
197 | + pattern := regexp.QuoteMeta(origin) | ||
198 | + pattern = strings.Replace(pattern, "\\*", ".*", -1) | ||
199 | + pattern = strings.Replace(pattern, "\\?", ".", -1) | ||
200 | + allowOriginPatterns = append(allowOriginPatterns, "^"+pattern+"$") | ||
201 | + } | ||
202 | + | ||
203 | + return func(ctx *context.Context) { | ||
204 | + var ( | ||
205 | + origin = ctx.Input.Header(headerOrigin) | ||
206 | + requestedMethod = ctx.Input.Header(headerRequestMethod) | ||
207 | + requestedHeaders = ctx.Input.Header(headerRequestHeaders) | ||
208 | + // additional headers to be added | ||
209 | + // to the response. | ||
210 | + headers map[string]string | ||
211 | + ) | ||
212 | + | ||
213 | + if ctx.Input.Method() == "OPTIONS" && | ||
214 | + (requestedMethod != "" || requestedHeaders != "") { | ||
215 | + headers = opts.PreflightHeader(origin, requestedMethod, requestedHeaders) | ||
216 | + for key, value := range headers { | ||
217 | + ctx.Output.Header(key, value) | ||
218 | + } | ||
219 | + ctx.ResponseWriter.WriteHeader(http.StatusOK) | ||
220 | + return | ||
221 | + } | ||
222 | + headers = opts.Header(origin) | ||
223 | + | ||
224 | + for key, value := range headers { | ||
225 | + ctx.Output.Header(key, value) | ||
226 | + } | ||
227 | + } | ||
228 | +} |
@@ -43,7 +43,7 @@ const ( | @@ -43,7 +43,7 @@ const ( | ||
43 | ) | 43 | ) |
44 | 44 | ||
45 | const ( | 45 | const ( |
46 | - routerTypeBeego = iota | 46 | + routerTypeBeego = iota |
47 | routerTypeRESTFul | 47 | routerTypeRESTFul |
48 | routerTypeHandler | 48 | routerTypeHandler |
49 | ) | 49 | ) |
@@ -133,14 +133,15 @@ type ControllerRegister struct { | @@ -133,14 +133,15 @@ type ControllerRegister struct { | ||
133 | 133 | ||
134 | // NewControllerRegister returns a new ControllerRegister. | 134 | // NewControllerRegister returns a new ControllerRegister. |
135 | func NewControllerRegister() *ControllerRegister { | 135 | func NewControllerRegister() *ControllerRegister { |
136 | - cr := &ControllerRegister{ | 136 | + return &ControllerRegister{ |
137 | routers: make(map[string]*Tree), | 137 | routers: make(map[string]*Tree), |
138 | policies: make(map[string]*Tree), | 138 | policies: make(map[string]*Tree), |
139 | + pool: sync.Pool{ | ||
140 | + New: func() interface{} { | ||
141 | + return beecontext.NewContext() | ||
142 | + }, | ||
143 | + }, | ||
139 | } | 144 | } |
140 | - cr.pool.New = func() interface{} { | ||
141 | - return beecontext.NewContext() | ||
142 | - } | ||
143 | - return cr | ||
144 | } | 145 | } |
145 | 146 | ||
146 | // Add controller handler and pattern rules to ControllerRegister. | 147 | // Add controller handler and pattern rules to ControllerRegister. |
@@ -277,6 +278,10 @@ func (p *ControllerRegister) Include(cList ...ControllerInterface) { | @@ -277,6 +278,10 @@ func (p *ControllerRegister) Include(cList ...ControllerInterface) { | ||
277 | key := t.PkgPath() + ":" + t.Name() | 278 | key := t.PkgPath() + ":" + t.Name() |
278 | if comm, ok := GlobalControllerRouter[key]; ok { | 279 | if comm, ok := GlobalControllerRouter[key]; ok { |
279 | for _, a := range comm { | 280 | for _, a := range comm { |
281 | + for _, f := range a.Filters { | ||
282 | + p.InsertFilter(f.Pattern, f.Pos, f.Filter, f.ReturnOnOutput, f.ResetParams) | ||
283 | + } | ||
284 | + | ||
280 | p.addWithMethodParams(a.Router, c, a.MethodParams, strings.Join(a.AllowHTTPMethods, ",")+":"+a.Method) | 285 | p.addWithMethodParams(a.Router, c, a.MethodParams, strings.Join(a.AllowHTTPMethods, ",")+":"+a.Method) |
281 | } | 286 | } |
282 | } | 287 | } |
@@ -794,7 +799,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) | @@ -794,7 +799,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) | ||
794 | if !isRunnable { | 799 | if !isRunnable { |
795 | //Invoke the request handler | 800 | //Invoke the request handler |
796 | var execController ControllerInterface | 801 | var execController ControllerInterface |
797 | - if routerInfo.initialize != nil { | 802 | + if routerInfo != nil && routerInfo.initialize != nil { |
798 | execController = routerInfo.initialize() | 803 | execController = routerInfo.initialize() |
799 | } else { | 804 | } else { |
800 | vc := reflect.New(runRouter) | 805 | vc := reflect.New(runRouter) |
@@ -877,7 +882,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) | @@ -877,7 +882,7 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) | ||
877 | } | 882 | } |
878 | 883 | ||
879 | Admin: | 884 | Admin: |
880 | -//admin module record QPS | 885 | + //admin module record QPS |
881 | 886 | ||
882 | statusCode := context.ResponseWriter.Status | 887 | statusCode := context.ResponseWriter.Status |
883 | if statusCode == 0 { | 888 | if statusCode == 0 { |
@@ -886,8 +891,9 @@ Admin: | @@ -886,8 +891,9 @@ Admin: | ||
886 | 891 | ||
887 | logAccess(context, &startTime, statusCode) | 892 | logAccess(context, &startTime, statusCode) |
888 | 893 | ||
894 | + timeDur := time.Since(startTime) | ||
895 | + context.ResponseWriter.Elapsed = timeDur | ||
889 | if BConfig.Listen.EnableAdmin { | 896 | if BConfig.Listen.EnableAdmin { |
890 | - timeDur := time.Since(startTime) | ||
891 | pattern := "" | 897 | pattern := "" |
892 | if routerInfo != nil { | 898 | if routerInfo != nil { |
893 | pattern = routerInfo.pattern | 899 | pattern = routerInfo.pattern |
@@ -904,7 +910,6 @@ Admin: | @@ -904,7 +910,6 @@ Admin: | ||
904 | 910 | ||
905 | if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs { | 911 | if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs { |
906 | var devInfo string | 912 | var devInfo string |
907 | - timeDur := time.Since(startTime) | ||
908 | iswin := (runtime.GOOS == "windows") | 913 | iswin := (runtime.GOOS == "windows") |
909 | statusColor := logs.ColorByStatus(iswin, statusCode) | 914 | statusColor := logs.ColorByStatus(iswin, statusCode) |
910 | methodColor := logs.ColorByMethod(iswin, r.Method) | 915 | methodColor := logs.ColorByMethod(iswin, r.Method) |
@@ -21,6 +21,7 @@ import ( | @@ -21,6 +21,7 @@ import ( | ||
21 | "os" | 21 | "os" |
22 | "path" | 22 | "path" |
23 | "path/filepath" | 23 | "path/filepath" |
24 | + "strings" | ||
24 | "sync" | 25 | "sync" |
25 | "time" | 26 | "time" |
26 | ) | 27 | ) |
@@ -127,6 +128,9 @@ func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error { | @@ -127,6 +128,9 @@ func (fp *FileProvider) SessionInit(maxlifetime int64, savePath string) error { | ||
127 | // if file is not exist, create it. | 128 | // if file is not exist, create it. |
128 | // the file path is generated from sid string. | 129 | // the file path is generated from sid string. |
129 | func (fp *FileProvider) SessionRead(sid string) (Store, error) { | 130 | func (fp *FileProvider) SessionRead(sid string) (Store, error) { |
131 | + if strings.ContainsAny(sid, "./") { | ||
132 | + return nil, nil | ||
133 | + } | ||
130 | filepder.lock.Lock() | 134 | filepder.lock.Lock() |
131 | defer filepder.lock.Unlock() | 135 | defer filepder.lock.Unlock() |
132 | 136 |
@@ -96,6 +96,7 @@ type ManagerConfig struct { | @@ -96,6 +96,7 @@ type ManagerConfig struct { | ||
96 | EnableSidInHTTPHeader bool `json:"EnableSidInHTTPHeader"` | 96 | EnableSidInHTTPHeader bool `json:"EnableSidInHTTPHeader"` |
97 | SessionNameInHTTPHeader string `json:"SessionNameInHTTPHeader"` | 97 | SessionNameInHTTPHeader string `json:"SessionNameInHTTPHeader"` |
98 | EnableSidInURLQuery bool `json:"EnableSidInURLQuery"` | 98 | EnableSidInURLQuery bool `json:"EnableSidInURLQuery"` |
99 | + SessionIDPrefix string `json:"sessionIDPrefix"` | ||
99 | } | 100 | } |
100 | 101 | ||
101 | // Manager contains Provider and its configuration. | 102 | // Manager contains Provider and its configuration. |
@@ -153,6 +154,11 @@ func NewManager(provideName string, cf *ManagerConfig) (*Manager, error) { | @@ -153,6 +154,11 @@ func NewManager(provideName string, cf *ManagerConfig) (*Manager, error) { | ||
153 | }, nil | 154 | }, nil |
154 | } | 155 | } |
155 | 156 | ||
157 | +// GetProvider return current manager's provider | ||
158 | +func (manager *Manager) GetProvider() Provider { | ||
159 | + return manager.provider | ||
160 | +} | ||
161 | + | ||
156 | // getSid retrieves session identifier from HTTP Request. | 162 | // getSid retrieves session identifier from HTTP Request. |
157 | // First try to retrieve id by reading from cookie, session cookie name is configurable, | 163 | // First try to retrieve id by reading from cookie, session cookie name is configurable, |
158 | // if not exist, then retrieve id from querying parameters. | 164 | // if not exist, then retrieve id from querying parameters. |
@@ -331,7 +337,7 @@ func (manager *Manager) sessionID() (string, error) { | @@ -331,7 +337,7 @@ func (manager *Manager) sessionID() (string, error) { | ||
331 | if n != len(b) || err != nil { | 337 | if n != len(b) || err != nil { |
332 | return "", fmt.Errorf("Could not successfully read from the system CSPRNG") | 338 | return "", fmt.Errorf("Could not successfully read from the system CSPRNG") |
333 | } | 339 | } |
334 | - return hex.EncodeToString(b), nil | 340 | + return manager.config.SessionIDPrefix + hex.EncodeToString(b), nil |
335 | } | 341 | } |
336 | 342 | ||
337 | // Set cookie with https. | 343 | // Set cookie with https. |
@@ -178,7 +178,7 @@ func searchFile(ctx *context.Context) (string, os.FileInfo, error) { | @@ -178,7 +178,7 @@ func searchFile(ctx *context.Context) (string, os.FileInfo, error) { | ||
178 | if !strings.Contains(requestPath, prefix) { | 178 | if !strings.Contains(requestPath, prefix) { |
179 | continue | 179 | continue |
180 | } | 180 | } |
181 | - if len(requestPath) > len(prefix) && requestPath[len(prefix)] != '/' { | 181 | + if prefix != "/" && len(requestPath) > len(prefix) && requestPath[len(prefix)] != '/' { |
182 | continue | 182 | continue |
183 | } | 183 | } |
184 | filePath := path.Join(staticDir, requestPath[len(prefix):]) | 184 | filePath := path.Join(staticDir, requestPath[len(prefix):]) |
@@ -20,6 +20,7 @@ import ( | @@ -20,6 +20,7 @@ import ( | ||
20 | "html/template" | 20 | "html/template" |
21 | "io" | 21 | "io" |
22 | "io/ioutil" | 22 | "io/ioutil" |
23 | + "net/http" | ||
23 | "os" | 24 | "os" |
24 | "path/filepath" | 25 | "path/filepath" |
25 | "regexp" | 26 | "regexp" |
@@ -40,6 +41,7 @@ var ( | @@ -40,6 +41,7 @@ var ( | ||
40 | beeTemplateExt = []string{"tpl", "html"} | 41 | beeTemplateExt = []string{"tpl", "html"} |
41 | // beeTemplatePreprocessors stores associations of extension -> preprocessor handler | 42 | // beeTemplatePreprocessors stores associations of extension -> preprocessor handler |
42 | beeTemplateEngines = map[string]templatePreProcessor{} | 43 | beeTemplateEngines = map[string]templatePreProcessor{} |
44 | + beeTemplateFS = defaultFSFunc | ||
43 | ) | 45 | ) |
44 | 46 | ||
45 | // ExecuteTemplate applies the template with name to the specified data object, | 47 | // ExecuteTemplate applies the template with name to the specified data object, |
@@ -181,12 +183,17 @@ func lockViewPaths() { | @@ -181,12 +183,17 @@ func lockViewPaths() { | ||
181 | // BuildTemplate will build all template files in a directory. | 183 | // BuildTemplate will build all template files in a directory. |
182 | // it makes beego can render any template file in view directory. | 184 | // it makes beego can render any template file in view directory. |
183 | func BuildTemplate(dir string, files ...string) error { | 185 | func BuildTemplate(dir string, files ...string) error { |
184 | - if _, err := os.Stat(dir); err != nil { | 186 | + var err error |
187 | + fs := beeTemplateFS() | ||
188 | + f, err := fs.Open(dir) | ||
189 | + if err != nil { | ||
185 | if os.IsNotExist(err) { | 190 | if os.IsNotExist(err) { |
186 | return nil | 191 | return nil |
187 | } | 192 | } |
188 | return errors.New("dir open err") | 193 | return errors.New("dir open err") |
189 | } | 194 | } |
195 | + defer f.Close() | ||
196 | + | ||
190 | beeTemplates, ok := beeViewPathTemplates[dir] | 197 | beeTemplates, ok := beeViewPathTemplates[dir] |
191 | if !ok { | 198 | if !ok { |
192 | panic("Unknown view path: " + dir) | 199 | panic("Unknown view path: " + dir) |
@@ -195,11 +202,11 @@ func BuildTemplate(dir string, files ...string) error { | @@ -195,11 +202,11 @@ func BuildTemplate(dir string, files ...string) error { | ||
195 | root: dir, | 202 | root: dir, |
196 | files: make(map[string][]string), | 203 | files: make(map[string][]string), |
197 | } | 204 | } |
198 | - err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error { | 205 | + err = Walk(fs, dir, func(path string, f os.FileInfo, err error) error { |
199 | return self.visit(path, f, err) | 206 | return self.visit(path, f, err) |
200 | }) | 207 | }) |
201 | if err != nil { | 208 | if err != nil { |
202 | - fmt.Printf("filepath.Walk() returned %v\n", err) | 209 | + fmt.Printf("Walk() returned %v\n", err) |
203 | return err | 210 | return err |
204 | } | 211 | } |
205 | buildAllFiles := len(files) == 0 | 212 | buildAllFiles := len(files) == 0 |
@@ -210,11 +217,11 @@ func BuildTemplate(dir string, files ...string) error { | @@ -210,11 +217,11 @@ func BuildTemplate(dir string, files ...string) error { | ||
210 | ext := filepath.Ext(file) | 217 | ext := filepath.Ext(file) |
211 | var t *template.Template | 218 | var t *template.Template |
212 | if len(ext) == 0 { | 219 | if len(ext) == 0 { |
213 | - t, err = getTemplate(self.root, file, v...) | 220 | + t, err = getTemplate(self.root, fs, file, v...) |
214 | } else if fn, ok := beeTemplateEngines[ext[1:]]; ok { | 221 | } else if fn, ok := beeTemplateEngines[ext[1:]]; ok { |
215 | t, err = fn(self.root, file, beegoTplFuncMap) | 222 | t, err = fn(self.root, file, beegoTplFuncMap) |
216 | } else { | 223 | } else { |
217 | - t, err = getTemplate(self.root, file, v...) | 224 | + t, err = getTemplate(self.root, fs, file, v...) |
218 | } | 225 | } |
219 | if err != nil { | 226 | if err != nil { |
220 | logs.Error("parse template err:", file, err) | 227 | logs.Error("parse template err:", file, err) |
@@ -229,9 +236,10 @@ func BuildTemplate(dir string, files ...string) error { | @@ -229,9 +236,10 @@ func BuildTemplate(dir string, files ...string) error { | ||
229 | return nil | 236 | return nil |
230 | } | 237 | } |
231 | 238 | ||
232 | -func getTplDeep(root, file, parent string, t *template.Template) (*template.Template, [][]string, error) { | 239 | +func getTplDeep(root string, fs http.FileSystem, file string, parent string, t *template.Template) (*template.Template, [][]string, error) { |
233 | var fileAbsPath string | 240 | var fileAbsPath string |
234 | var rParent string | 241 | var rParent string |
242 | + var err error | ||
235 | if filepath.HasPrefix(file, "../") { | 243 | if filepath.HasPrefix(file, "../") { |
236 | rParent = filepath.Join(filepath.Dir(parent), file) | 244 | rParent = filepath.Join(filepath.Dir(parent), file) |
237 | fileAbsPath = filepath.Join(root, filepath.Dir(parent), file) | 245 | fileAbsPath = filepath.Join(root, filepath.Dir(parent), file) |
@@ -239,10 +247,12 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp | @@ -239,10 +247,12 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp | ||
239 | rParent = file | 247 | rParent = file |
240 | fileAbsPath = filepath.Join(root, file) | 248 | fileAbsPath = filepath.Join(root, file) |
241 | } | 249 | } |
242 | - if e := utils.FileExists(fileAbsPath); !e { | 250 | + f, err := fs.Open(fileAbsPath) |
251 | + defer f.Close() | ||
252 | + if err != nil { | ||
243 | panic("can't find template file:" + file) | 253 | panic("can't find template file:" + file) |
244 | } | 254 | } |
245 | - data, err := ioutil.ReadFile(fileAbsPath) | 255 | + data, err := ioutil.ReadAll(f) |
246 | if err != nil { | 256 | if err != nil { |
247 | return nil, [][]string{}, err | 257 | return nil, [][]string{}, err |
248 | } | 258 | } |
@@ -261,7 +271,7 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp | @@ -261,7 +271,7 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp | ||
261 | if !HasTemplateExt(m[1]) { | 271 | if !HasTemplateExt(m[1]) { |
262 | continue | 272 | continue |
263 | } | 273 | } |
264 | - _, _, err = getTplDeep(root, m[1], rParent, t) | 274 | + _, _, err = getTplDeep(root, fs, m[1], rParent, t) |
265 | if err != nil { | 275 | if err != nil { |
266 | return nil, [][]string{}, err | 276 | return nil, [][]string{}, err |
267 | } | 277 | } |
@@ -270,14 +280,14 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp | @@ -270,14 +280,14 @@ func getTplDeep(root, file, parent string, t *template.Template) (*template.Temp | ||
270 | return t, allSub, nil | 280 | return t, allSub, nil |
271 | } | 281 | } |
272 | 282 | ||
273 | -func getTemplate(root, file string, others ...string) (t *template.Template, err error) { | 283 | +func getTemplate(root string, fs http.FileSystem, file string, others ...string) (t *template.Template, err error) { |
274 | t = template.New(file).Delims(BConfig.WebConfig.TemplateLeft, BConfig.WebConfig.TemplateRight).Funcs(beegoTplFuncMap) | 284 | t = template.New(file).Delims(BConfig.WebConfig.TemplateLeft, BConfig.WebConfig.TemplateRight).Funcs(beegoTplFuncMap) |
275 | var subMods [][]string | 285 | var subMods [][]string |
276 | - t, subMods, err = getTplDeep(root, file, "", t) | 286 | + t, subMods, err = getTplDeep(root, fs, file, "", t) |
277 | if err != nil { | 287 | if err != nil { |
278 | return nil, err | 288 | return nil, err |
279 | } | 289 | } |
280 | - t, err = _getTemplate(t, root, subMods, others...) | 290 | + t, err = _getTemplate(t, root, fs, subMods, others...) |
281 | 291 | ||
282 | if err != nil { | 292 | if err != nil { |
283 | return nil, err | 293 | return nil, err |
@@ -285,7 +295,7 @@ func getTemplate(root, file string, others ...string) (t *template.Template, err | @@ -285,7 +295,7 @@ func getTemplate(root, file string, others ...string) (t *template.Template, err | ||
285 | return | 295 | return |
286 | } | 296 | } |
287 | 297 | ||
288 | -func _getTemplate(t0 *template.Template, root string, subMods [][]string, others ...string) (t *template.Template, err error) { | 298 | +func _getTemplate(t0 *template.Template, root string, fs http.FileSystem, subMods [][]string, others ...string) (t *template.Template, err error) { |
289 | t = t0 | 299 | t = t0 |
290 | for _, m := range subMods { | 300 | for _, m := range subMods { |
291 | if len(m) == 2 { | 301 | if len(m) == 2 { |
@@ -297,11 +307,11 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others | @@ -297,11 +307,11 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others | ||
297 | for _, otherFile := range others { | 307 | for _, otherFile := range others { |
298 | if otherFile == m[1] { | 308 | if otherFile == m[1] { |
299 | var subMods1 [][]string | 309 | var subMods1 [][]string |
300 | - t, subMods1, err = getTplDeep(root, otherFile, "", t) | 310 | + t, subMods1, err = getTplDeep(root, fs, otherFile, "", t) |
301 | if err != nil { | 311 | if err != nil { |
302 | logs.Trace("template parse file err:", err) | 312 | logs.Trace("template parse file err:", err) |
303 | } else if len(subMods1) > 0 { | 313 | } else if len(subMods1) > 0 { |
304 | - t, err = _getTemplate(t, root, subMods1, others...) | 314 | + t, err = _getTemplate(t, root, fs, subMods1, others...) |
305 | } | 315 | } |
306 | break | 316 | break |
307 | } | 317 | } |
@@ -310,8 +320,16 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others | @@ -310,8 +320,16 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others | ||
310 | for _, otherFile := range others { | 320 | for _, otherFile := range others { |
311 | var data []byte | 321 | var data []byte |
312 | fileAbsPath := filepath.Join(root, otherFile) | 322 | fileAbsPath := filepath.Join(root, otherFile) |
313 | - data, err = ioutil.ReadFile(fileAbsPath) | 323 | + f, err := fs.Open(fileAbsPath) |
324 | + if err != nil { | ||
325 | + f.Close() | ||
326 | + logs.Trace("template file parse error, not success open file:", err) | ||
327 | + continue | ||
328 | + } | ||
329 | + data, err = ioutil.ReadAll(f) | ||
330 | + f.Close() | ||
314 | if err != nil { | 331 | if err != nil { |
332 | + logs.Trace("template file parse error, not success read file:", err) | ||
315 | continue | 333 | continue |
316 | } | 334 | } |
317 | reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*define[ ]+\"([^\"]+)\"") | 335 | reg := regexp.MustCompile(BConfig.WebConfig.TemplateLeft + "[ ]*define[ ]+\"([^\"]+)\"") |
@@ -319,11 +337,14 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others | @@ -319,11 +337,14 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others | ||
319 | for _, sub := range allSub { | 337 | for _, sub := range allSub { |
320 | if len(sub) == 2 && sub[1] == m[1] { | 338 | if len(sub) == 2 && sub[1] == m[1] { |
321 | var subMods1 [][]string | 339 | var subMods1 [][]string |
322 | - t, subMods1, err = getTplDeep(root, otherFile, "", t) | 340 | + t, subMods1, err = getTplDeep(root, fs, otherFile, "", t) |
323 | if err != nil { | 341 | if err != nil { |
324 | logs.Trace("template parse file err:", err) | 342 | logs.Trace("template parse file err:", err) |
325 | } else if len(subMods1) > 0 { | 343 | } else if len(subMods1) > 0 { |
326 | - t, err = _getTemplate(t, root, subMods1, others...) | 344 | + t, err = _getTemplate(t, root, fs, subMods1, others...) |
345 | + if err != nil { | ||
346 | + logs.Trace("template parse file err:", err) | ||
347 | + } | ||
327 | } | 348 | } |
328 | break | 349 | break |
329 | } | 350 | } |
@@ -335,6 +356,17 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others | @@ -335,6 +356,17 @@ func _getTemplate(t0 *template.Template, root string, subMods [][]string, others | ||
335 | return | 356 | return |
336 | } | 357 | } |
337 | 358 | ||
359 | +type templateFSFunc func() http.FileSystem | ||
360 | + | ||
361 | +func defaultFSFunc() http.FileSystem { | ||
362 | + return FileSystem{} | ||
363 | +} | ||
364 | + | ||
365 | +// SetTemplateFSFunc set default filesystem function | ||
366 | +func SetTemplateFSFunc(fnt templateFSFunc) { | ||
367 | + beeTemplateFS = fnt | ||
368 | +} | ||
369 | + | ||
338 | // SetViewsPath sets view directory path in beego application. | 370 | // SetViewsPath sets view directory path in beego application. |
339 | func SetViewsPath(path string) *App { | 371 | func SetViewsPath(path string) *App { |
340 | BConfig.WebConfig.ViewsPath = path | 372 | BConfig.WebConfig.ViewsPath = path |
@@ -692,7 +692,7 @@ func ge(arg1, arg2 interface{}) (bool, error) { | @@ -692,7 +692,7 @@ func ge(arg1, arg2 interface{}) (bool, error) { | ||
692 | 692 | ||
693 | // MapGet getting value from map by keys | 693 | // MapGet getting value from map by keys |
694 | // usage: | 694 | // usage: |
695 | -// Data["m"] = map[string]interface{} { | 695 | +// Data["m"] = M{ |
696 | // "a": 1, | 696 | // "a": 1, |
697 | // "1": map[string]float64{ | 697 | // "1": map[string]float64{ |
698 | // "c": 4, | 698 | // "c": 4, |
@@ -428,6 +428,9 @@ func run() { | @@ -428,6 +428,9 @@ func run() { | ||
428 | continue | 428 | continue |
429 | case <-changed: | 429 | case <-changed: |
430 | now = time.Now().Local() | 430 | now = time.Now().Local() |
431 | + for _, t := range AdminTaskList { | ||
432 | + t.SetNext(now) | ||
433 | + } | ||
431 | continue | 434 | continue |
432 | case <-stop: | 435 | case <-stop: |
433 | return | 436 | return |
@@ -446,6 +449,7 @@ func StopTask() { | @@ -446,6 +449,7 @@ func StopTask() { | ||
446 | 449 | ||
447 | // AddTask add task with name | 450 | // AddTask add task with name |
448 | func AddTask(taskname string, t Tasker) { | 451 | func AddTask(taskname string, t Tasker) { |
452 | + t.SetNext(time.Now().Local()) | ||
449 | AdminTaskList[taskname] = t | 453 | AdminTaskList[taskname] = t |
450 | if isstart { | 454 | if isstart { |
451 | changed <- true | 455 | changed <- true |
vendor/github.com/satori/go.uuid/.travis.yml
0 → 100644
1 | +language: go | ||
2 | +sudo: false | ||
3 | +go: | ||
4 | + - 1.2 | ||
5 | + - 1.3 | ||
6 | + - 1.4 | ||
7 | + - 1.5 | ||
8 | + - 1.6 | ||
9 | + - 1.7 | ||
10 | + - 1.8 | ||
11 | + - 1.9 | ||
12 | + - tip | ||
13 | +matrix: | ||
14 | + allow_failures: | ||
15 | + - go: tip | ||
16 | + fast_finish: true | ||
17 | +before_install: | ||
18 | + - go get github.com/mattn/goveralls | ||
19 | + - go get golang.org/x/tools/cmd/cover | ||
20 | +script: | ||
21 | + - $HOME/gopath/bin/goveralls -service=travis-ci | ||
22 | +notifications: | ||
23 | + email: false |
vendor/github.com/satori/go.uuid/LICENSE
0 → 100644
1 | +Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru> | ||
2 | + | ||
3 | +Permission is hereby granted, free of charge, to any person obtaining | ||
4 | +a copy of this software and associated documentation files (the | ||
5 | +"Software"), to deal in the Software without restriction, including | ||
6 | +without limitation the rights to use, copy, modify, merge, publish, | ||
7 | +distribute, sublicense, and/or sell copies of the Software, and to | ||
8 | +permit persons to whom the Software is furnished to do so, subject to | ||
9 | +the following conditions: | ||
10 | + | ||
11 | +The above copyright notice and this permission notice shall be | ||
12 | +included in all copies or substantial portions of the Software. | ||
13 | + | ||
14 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
15 | +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
16 | +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
17 | +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
18 | +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
19 | +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
20 | +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
vendor/github.com/satori/go.uuid/README.md
0 → 100644
1 | +# UUID package for Go language | ||
2 | + | ||
3 | +[![Build Status](https://travis-ci.org/satori/go.uuid.png?branch=master)](https://travis-ci.org/satori/go.uuid) | ||
4 | +[![Coverage Status](https://coveralls.io/repos/github/satori/go.uuid/badge.svg?branch=master)](https://coveralls.io/github/satori/go.uuid) | ||
5 | +[![GoDoc](http://godoc.org/github.com/satori/go.uuid?status.png)](http://godoc.org/github.com/satori/go.uuid) | ||
6 | + | ||
7 | +This package provides pure Go implementation of Universally Unique Identifier (UUID). Supported both creation and parsing of UUIDs. | ||
8 | + | ||
9 | +With 100% test coverage and benchmarks out of box. | ||
10 | + | ||
11 | +Supported versions: | ||
12 | +* Version 1, based on timestamp and MAC address (RFC 4122) | ||
13 | +* Version 2, based on timestamp, MAC address and POSIX UID/GID (DCE 1.1) | ||
14 | +* Version 3, based on MD5 hashing (RFC 4122) | ||
15 | +* Version 4, based on random numbers (RFC 4122) | ||
16 | +* Version 5, based on SHA-1 hashing (RFC 4122) | ||
17 | + | ||
18 | +## Installation | ||
19 | + | ||
20 | +Use the `go` command: | ||
21 | + | ||
22 | + $ go get github.com/satori/go.uuid | ||
23 | + | ||
24 | +## Requirements | ||
25 | + | ||
26 | +UUID package requires Go >= 1.2. | ||
27 | + | ||
28 | +## Example | ||
29 | + | ||
30 | +```go | ||
31 | +package main | ||
32 | + | ||
33 | +import ( | ||
34 | + "fmt" | ||
35 | + "github.com/satori/go.uuid" | ||
36 | +) | ||
37 | + | ||
38 | +func main() { | ||
39 | + // Creating UUID Version 4 | ||
40 | + u1 := uuid.NewV4() | ||
41 | + fmt.Printf("UUIDv4: %s\n", u1) | ||
42 | + | ||
43 | + // Parsing UUID from string input | ||
44 | + u2, err := uuid.FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8") | ||
45 | + if err != nil { | ||
46 | + fmt.Printf("Something gone wrong: %s", err) | ||
47 | + } | ||
48 | + fmt.Printf("Successfully parsed: %s", u2) | ||
49 | +} | ||
50 | +``` | ||
51 | + | ||
52 | +## Documentation | ||
53 | + | ||
54 | +[Documentation](http://godoc.org/github.com/satori/go.uuid) is hosted at GoDoc project. | ||
55 | + | ||
56 | +## Links | ||
57 | +* [RFC 4122](http://tools.ietf.org/html/rfc4122) | ||
58 | +* [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01) | ||
59 | + | ||
60 | +## Copyright | ||
61 | + | ||
62 | +Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>. | ||
63 | + | ||
64 | +UUID package released under MIT License. | ||
65 | +See [LICENSE](https://github.com/satori/go.uuid/blob/master/LICENSE) for details. |
vendor/github.com/satori/go.uuid/codec.go
0 → 100644
1 | +// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru> | ||
2 | +// | ||
3 | +// Permission is hereby granted, free of charge, to any person obtaining | ||
4 | +// a copy of this software and associated documentation files (the | ||
5 | +// "Software"), to deal in the Software without restriction, including | ||
6 | +// without limitation the rights to use, copy, modify, merge, publish, | ||
7 | +// distribute, sublicense, and/or sell copies of the Software, and to | ||
8 | +// permit persons to whom the Software is furnished to do so, subject to | ||
9 | +// the following conditions: | ||
10 | +// | ||
11 | +// The above copyright notice and this permission notice shall be | ||
12 | +// included in all copies or substantial portions of the Software. | ||
13 | +// | ||
14 | +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
15 | +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
16 | +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
17 | +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
18 | +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
19 | +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
20 | +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
21 | + | ||
22 | +package uuid | ||
23 | + | ||
24 | +import ( | ||
25 | + "bytes" | ||
26 | + "encoding/hex" | ||
27 | + "fmt" | ||
28 | +) | ||
29 | + | ||
30 | +// FromBytes returns UUID converted from raw byte slice input. | ||
31 | +// It will return error if the slice isn't 16 bytes long. | ||
32 | +func FromBytes(input []byte) (u UUID, err error) { | ||
33 | + err = u.UnmarshalBinary(input) | ||
34 | + return | ||
35 | +} | ||
36 | + | ||
37 | +// FromBytesOrNil returns UUID converted from raw byte slice input. | ||
38 | +// Same behavior as FromBytes, but returns a Nil UUID on error. | ||
39 | +func FromBytesOrNil(input []byte) UUID { | ||
40 | + uuid, err := FromBytes(input) | ||
41 | + if err != nil { | ||
42 | + return Nil | ||
43 | + } | ||
44 | + return uuid | ||
45 | +} | ||
46 | + | ||
47 | +// FromString returns UUID parsed from string input. | ||
48 | +// Input is expected in a form accepted by UnmarshalText. | ||
49 | +func FromString(input string) (u UUID, err error) { | ||
50 | + err = u.UnmarshalText([]byte(input)) | ||
51 | + return | ||
52 | +} | ||
53 | + | ||
54 | +// FromStringOrNil returns UUID parsed from string input. | ||
55 | +// Same behavior as FromString, but returns a Nil UUID on error. | ||
56 | +func FromStringOrNil(input string) UUID { | ||
57 | + uuid, err := FromString(input) | ||
58 | + if err != nil { | ||
59 | + return Nil | ||
60 | + } | ||
61 | + return uuid | ||
62 | +} | ||
63 | + | ||
64 | +// MarshalText implements the encoding.TextMarshaler interface. | ||
65 | +// The encoding is the same as returned by String. | ||
66 | +func (u UUID) MarshalText() (text []byte, err error) { | ||
67 | + text = []byte(u.String()) | ||
68 | + return | ||
69 | +} | ||
70 | + | ||
71 | +// UnmarshalText implements the encoding.TextUnmarshaler interface. | ||
72 | +// Following formats are supported: | ||
73 | +// "6ba7b810-9dad-11d1-80b4-00c04fd430c8", | ||
74 | +// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}", | ||
75 | +// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" | ||
76 | +// "6ba7b8109dad11d180b400c04fd430c8" | ||
77 | +// ABNF for supported UUID text representation follows: | ||
78 | +// uuid := canonical | hashlike | braced | urn | ||
79 | +// plain := canonical | hashlike | ||
80 | +// canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct | ||
81 | +// hashlike := 12hexoct | ||
82 | +// braced := '{' plain '}' | ||
83 | +// urn := URN ':' UUID-NID ':' plain | ||
84 | +// URN := 'urn' | ||
85 | +// UUID-NID := 'uuid' | ||
86 | +// 12hexoct := 6hexoct 6hexoct | ||
87 | +// 6hexoct := 4hexoct 2hexoct | ||
88 | +// 4hexoct := 2hexoct 2hexoct | ||
89 | +// 2hexoct := hexoct hexoct | ||
90 | +// hexoct := hexdig hexdig | ||
91 | +// hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | | ||
92 | +// 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | | ||
93 | +// 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | ||
94 | +func (u *UUID) UnmarshalText(text []byte) (err error) { | ||
95 | + switch len(text) { | ||
96 | + case 32: | ||
97 | + return u.decodeHashLike(text) | ||
98 | + case 36: | ||
99 | + return u.decodeCanonical(text) | ||
100 | + case 38: | ||
101 | + return u.decodeBraced(text) | ||
102 | + case 41: | ||
103 | + fallthrough | ||
104 | + case 45: | ||
105 | + return u.decodeURN(text) | ||
106 | + default: | ||
107 | + return fmt.Errorf("uuid: incorrect UUID length: %s", text) | ||
108 | + } | ||
109 | +} | ||
110 | + | ||
111 | +// decodeCanonical decodes UUID string in format | ||
112 | +// "6ba7b810-9dad-11d1-80b4-00c04fd430c8". | ||
113 | +func (u *UUID) decodeCanonical(t []byte) (err error) { | ||
114 | + if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' { | ||
115 | + return fmt.Errorf("uuid: incorrect UUID format %s", t) | ||
116 | + } | ||
117 | + | ||
118 | + src := t[:] | ||
119 | + dst := u[:] | ||
120 | + | ||
121 | + for i, byteGroup := range byteGroups { | ||
122 | + if i > 0 { | ||
123 | + src = src[1:] // skip dash | ||
124 | + } | ||
125 | + _, err = hex.Decode(dst[:byteGroup/2], src[:byteGroup]) | ||
126 | + if err != nil { | ||
127 | + return | ||
128 | + } | ||
129 | + src = src[byteGroup:] | ||
130 | + dst = dst[byteGroup/2:] | ||
131 | + } | ||
132 | + | ||
133 | + return | ||
134 | +} | ||
135 | + | ||
136 | +// decodeHashLike decodes UUID string in format | ||
137 | +// "6ba7b8109dad11d180b400c04fd430c8". | ||
138 | +func (u *UUID) decodeHashLike(t []byte) (err error) { | ||
139 | + src := t[:] | ||
140 | + dst := u[:] | ||
141 | + | ||
142 | + if _, err = hex.Decode(dst, src); err != nil { | ||
143 | + return err | ||
144 | + } | ||
145 | + return | ||
146 | +} | ||
147 | + | ||
148 | +// decodeBraced decodes UUID string in format | ||
149 | +// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" or in format | ||
150 | +// "{6ba7b8109dad11d180b400c04fd430c8}". | ||
151 | +func (u *UUID) decodeBraced(t []byte) (err error) { | ||
152 | + l := len(t) | ||
153 | + | ||
154 | + if t[0] != '{' || t[l-1] != '}' { | ||
155 | + return fmt.Errorf("uuid: incorrect UUID format %s", t) | ||
156 | + } | ||
157 | + | ||
158 | + return u.decodePlain(t[1 : l-1]) | ||
159 | +} | ||
160 | + | ||
161 | +// decodeURN decodes UUID string in format | ||
162 | +// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in format | ||
163 | +// "urn:uuid:6ba7b8109dad11d180b400c04fd430c8". | ||
164 | +func (u *UUID) decodeURN(t []byte) (err error) { | ||
165 | + total := len(t) | ||
166 | + | ||
167 | + urn_uuid_prefix := t[:9] | ||
168 | + | ||
169 | + if !bytes.Equal(urn_uuid_prefix, urnPrefix) { | ||
170 | + return fmt.Errorf("uuid: incorrect UUID format: %s", t) | ||
171 | + } | ||
172 | + | ||
173 | + return u.decodePlain(t[9:total]) | ||
174 | +} | ||
175 | + | ||
176 | +// decodePlain decodes UUID string in canonical format | ||
177 | +// "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format | ||
178 | +// "6ba7b8109dad11d180b400c04fd430c8". | ||
179 | +func (u *UUID) decodePlain(t []byte) (err error) { | ||
180 | + switch len(t) { | ||
181 | + case 32: | ||
182 | + return u.decodeHashLike(t) | ||
183 | + case 36: | ||
184 | + return u.decodeCanonical(t) | ||
185 | + default: | ||
186 | + return fmt.Errorf("uuid: incorrrect UUID length: %s", t) | ||
187 | + } | ||
188 | +} | ||
189 | + | ||
190 | +// MarshalBinary implements the encoding.BinaryMarshaler interface. | ||
191 | +func (u UUID) MarshalBinary() (data []byte, err error) { | ||
192 | + data = u.Bytes() | ||
193 | + return | ||
194 | +} | ||
195 | + | ||
196 | +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. | ||
197 | +// It will return error if the slice isn't 16 bytes long. | ||
198 | +func (u *UUID) UnmarshalBinary(data []byte) (err error) { | ||
199 | + if len(data) != Size { | ||
200 | + err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data)) | ||
201 | + return | ||
202 | + } | ||
203 | + copy(u[:], data) | ||
204 | + | ||
205 | + return | ||
206 | +} |
1 | +// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru> | ||
2 | +// | ||
3 | +// Permission is hereby granted, free of charge, to any person obtaining | ||
4 | +// a copy of this software and associated documentation files (the | ||
5 | +// "Software"), to deal in the Software without restriction, including | ||
6 | +// without limitation the rights to use, copy, modify, merge, publish, | ||
7 | +// distribute, sublicense, and/or sell copies of the Software, and to | ||
8 | +// permit persons to whom the Software is furnished to do so, subject to | ||
9 | +// the following conditions: | ||
10 | +// | ||
11 | +// The above copyright notice and this permission notice shall be | ||
12 | +// included in all copies or substantial portions of the Software. | ||
13 | +// | ||
14 | +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
15 | +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
16 | +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
17 | +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
18 | +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
19 | +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
20 | +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
21 | + | ||
22 | +package uuid | ||
23 | + | ||
24 | +import ( | ||
25 | + "crypto/md5" | ||
26 | + "crypto/rand" | ||
27 | + "crypto/sha1" | ||
28 | + "encoding/binary" | ||
29 | + "hash" | ||
30 | + "net" | ||
31 | + "os" | ||
32 | + "sync" | ||
33 | + "time" | ||
34 | +) | ||
35 | + | ||
36 | +// Difference in 100-nanosecond intervals between | ||
37 | +// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970). | ||
38 | +const epochStart = 122192928000000000 | ||
39 | + | ||
40 | +var ( | ||
41 | + global = newDefaultGenerator() | ||
42 | + | ||
43 | + epochFunc = unixTimeFunc | ||
44 | + posixUID = uint32(os.Getuid()) | ||
45 | + posixGID = uint32(os.Getgid()) | ||
46 | +) | ||
47 | + | ||
48 | +// NewV1 returns UUID based on current timestamp and MAC address. | ||
49 | +func NewV1() UUID { | ||
50 | + return global.NewV1() | ||
51 | +} | ||
52 | + | ||
53 | +// NewV2 returns DCE Security UUID based on POSIX UID/GID. | ||
54 | +func NewV2(domain byte) UUID { | ||
55 | + return global.NewV2(domain) | ||
56 | +} | ||
57 | + | ||
58 | +// NewV3 returns UUID based on MD5 hash of namespace UUID and name. | ||
59 | +func NewV3(ns UUID, name string) UUID { | ||
60 | + return global.NewV3(ns, name) | ||
61 | +} | ||
62 | + | ||
63 | +// NewV4 returns random generated UUID. | ||
64 | +func NewV4() UUID { | ||
65 | + return global.NewV4() | ||
66 | +} | ||
67 | + | ||
68 | +// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. | ||
69 | +func NewV5(ns UUID, name string) UUID { | ||
70 | + return global.NewV5(ns, name) | ||
71 | +} | ||
72 | + | ||
73 | +// Generator provides interface for generating UUIDs. | ||
74 | +type Generator interface { | ||
75 | + NewV1() UUID | ||
76 | + NewV2(domain byte) UUID | ||
77 | + NewV3(ns UUID, name string) UUID | ||
78 | + NewV4() UUID | ||
79 | + NewV5(ns UUID, name string) UUID | ||
80 | +} | ||
81 | + | ||
82 | +// Default generator implementation. | ||
83 | +type generator struct { | ||
84 | + storageOnce sync.Once | ||
85 | + storageMutex sync.Mutex | ||
86 | + | ||
87 | + lastTime uint64 | ||
88 | + clockSequence uint16 | ||
89 | + hardwareAddr [6]byte | ||
90 | +} | ||
91 | + | ||
92 | +func newDefaultGenerator() Generator { | ||
93 | + return &generator{} | ||
94 | +} | ||
95 | + | ||
96 | +// NewV1 returns UUID based on current timestamp and MAC address. | ||
97 | +func (g *generator) NewV1() UUID { | ||
98 | + u := UUID{} | ||
99 | + | ||
100 | + timeNow, clockSeq, hardwareAddr := g.getStorage() | ||
101 | + | ||
102 | + binary.BigEndian.PutUint32(u[0:], uint32(timeNow)) | ||
103 | + binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) | ||
104 | + binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) | ||
105 | + binary.BigEndian.PutUint16(u[8:], clockSeq) | ||
106 | + | ||
107 | + copy(u[10:], hardwareAddr) | ||
108 | + | ||
109 | + u.SetVersion(V1) | ||
110 | + u.SetVariant(VariantRFC4122) | ||
111 | + | ||
112 | + return u | ||
113 | +} | ||
114 | + | ||
115 | +// NewV2 returns DCE Security UUID based on POSIX UID/GID. | ||
116 | +func (g *generator) NewV2(domain byte) UUID { | ||
117 | + u := UUID{} | ||
118 | + | ||
119 | + timeNow, clockSeq, hardwareAddr := g.getStorage() | ||
120 | + | ||
121 | + switch domain { | ||
122 | + case DomainPerson: | ||
123 | + binary.BigEndian.PutUint32(u[0:], posixUID) | ||
124 | + case DomainGroup: | ||
125 | + binary.BigEndian.PutUint32(u[0:], posixGID) | ||
126 | + } | ||
127 | + | ||
128 | + binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32)) | ||
129 | + binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48)) | ||
130 | + binary.BigEndian.PutUint16(u[8:], clockSeq) | ||
131 | + u[9] = domain | ||
132 | + | ||
133 | + copy(u[10:], hardwareAddr) | ||
134 | + | ||
135 | + u.SetVersion(V2) | ||
136 | + u.SetVariant(VariantRFC4122) | ||
137 | + | ||
138 | + return u | ||
139 | +} | ||
140 | + | ||
141 | +// NewV3 returns UUID based on MD5 hash of namespace UUID and name. | ||
142 | +func (g *generator) NewV3(ns UUID, name string) UUID { | ||
143 | + u := newFromHash(md5.New(), ns, name) | ||
144 | + u.SetVersion(V3) | ||
145 | + u.SetVariant(VariantRFC4122) | ||
146 | + | ||
147 | + return u | ||
148 | +} | ||
149 | + | ||
150 | +// NewV4 returns random generated UUID. | ||
151 | +func (g *generator) NewV4() UUID { | ||
152 | + u := UUID{} | ||
153 | + g.safeRandom(u[:]) | ||
154 | + u.SetVersion(V4) | ||
155 | + u.SetVariant(VariantRFC4122) | ||
156 | + | ||
157 | + return u | ||
158 | +} | ||
159 | + | ||
160 | +// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name. | ||
161 | +func (g *generator) NewV5(ns UUID, name string) UUID { | ||
162 | + u := newFromHash(sha1.New(), ns, name) | ||
163 | + u.SetVersion(V5) | ||
164 | + u.SetVariant(VariantRFC4122) | ||
165 | + | ||
166 | + return u | ||
167 | +} | ||
168 | + | ||
169 | +func (g *generator) initStorage() { | ||
170 | + g.initClockSequence() | ||
171 | + g.initHardwareAddr() | ||
172 | +} | ||
173 | + | ||
174 | +func (g *generator) initClockSequence() { | ||
175 | + buf := make([]byte, 2) | ||
176 | + g.safeRandom(buf) | ||
177 | + g.clockSequence = binary.BigEndian.Uint16(buf) | ||
178 | +} | ||
179 | + | ||
180 | +func (g *generator) initHardwareAddr() { | ||
181 | + interfaces, err := net.Interfaces() | ||
182 | + if err == nil { | ||
183 | + for _, iface := range interfaces { | ||
184 | + if len(iface.HardwareAddr) >= 6 { | ||
185 | + copy(g.hardwareAddr[:], iface.HardwareAddr) | ||
186 | + return | ||
187 | + } | ||
188 | + } | ||
189 | + } | ||
190 | + | ||
191 | + // Initialize hardwareAddr randomly in case | ||
192 | + // of real network interfaces absence | ||
193 | + g.safeRandom(g.hardwareAddr[:]) | ||
194 | + | ||
195 | + // Set multicast bit as recommended in RFC 4122 | ||
196 | + g.hardwareAddr[0] |= 0x01 | ||
197 | +} | ||
198 | + | ||
199 | +func (g *generator) safeRandom(dest []byte) { | ||
200 | + if _, err := rand.Read(dest); err != nil { | ||
201 | + panic(err) | ||
202 | + } | ||
203 | +} | ||
204 | + | ||
205 | +// Returns UUID v1/v2 storage state. | ||
206 | +// Returns epoch timestamp, clock sequence, and hardware address. | ||
207 | +func (g *generator) getStorage() (uint64, uint16, []byte) { | ||
208 | + g.storageOnce.Do(g.initStorage) | ||
209 | + | ||
210 | + g.storageMutex.Lock() | ||
211 | + defer g.storageMutex.Unlock() | ||
212 | + | ||
213 | + timeNow := epochFunc() | ||
214 | + // Clock changed backwards since last UUID generation. | ||
215 | + // Should increase clock sequence. | ||
216 | + if timeNow <= g.lastTime { | ||
217 | + g.clockSequence++ | ||
218 | + } | ||
219 | + g.lastTime = timeNow | ||
220 | + | ||
221 | + return timeNow, g.clockSequence, g.hardwareAddr[:] | ||
222 | +} | ||
223 | + | ||
224 | +// Returns difference in 100-nanosecond intervals between | ||
225 | +// UUID epoch (October 15, 1582) and current time. | ||
226 | +// This is default epoch calculation function. | ||
227 | +func unixTimeFunc() uint64 { | ||
228 | + return epochStart + uint64(time.Now().UnixNano()/100) | ||
229 | +} | ||
230 | + | ||
231 | +// Returns UUID based on hashing of namespace UUID and name. | ||
232 | +func newFromHash(h hash.Hash, ns UUID, name string) UUID { | ||
233 | + u := UUID{} | ||
234 | + h.Write(ns[:]) | ||
235 | + h.Write([]byte(name)) | ||
236 | + copy(u[:], h.Sum(nil)) | ||
237 | + | ||
238 | + return u | ||
239 | +} |
vendor/github.com/satori/go.uuid/sql.go
0 → 100644
1 | +// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru> | ||
2 | +// | ||
3 | +// Permission is hereby granted, free of charge, to any person obtaining | ||
4 | +// a copy of this software and associated documentation files (the | ||
5 | +// "Software"), to deal in the Software without restriction, including | ||
6 | +// without limitation the rights to use, copy, modify, merge, publish, | ||
7 | +// distribute, sublicense, and/or sell copies of the Software, and to | ||
8 | +// permit persons to whom the Software is furnished to do so, subject to | ||
9 | +// the following conditions: | ||
10 | +// | ||
11 | +// The above copyright notice and this permission notice shall be | ||
12 | +// included in all copies or substantial portions of the Software. | ||
13 | +// | ||
14 | +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
15 | +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
16 | +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
17 | +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
18 | +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
19 | +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
20 | +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
21 | + | ||
22 | +package uuid | ||
23 | + | ||
24 | +import ( | ||
25 | + "database/sql/driver" | ||
26 | + "fmt" | ||
27 | +) | ||
28 | + | ||
29 | +// Value implements the driver.Valuer interface. | ||
30 | +func (u UUID) Value() (driver.Value, error) { | ||
31 | + return u.String(), nil | ||
32 | +} | ||
33 | + | ||
34 | +// Scan implements the sql.Scanner interface. | ||
35 | +// A 16-byte slice is handled by UnmarshalBinary, while | ||
36 | +// a longer byte slice or a string is handled by UnmarshalText. | ||
37 | +func (u *UUID) Scan(src interface{}) error { | ||
38 | + switch src := src.(type) { | ||
39 | + case []byte: | ||
40 | + if len(src) == Size { | ||
41 | + return u.UnmarshalBinary(src) | ||
42 | + } | ||
43 | + return u.UnmarshalText(src) | ||
44 | + | ||
45 | + case string: | ||
46 | + return u.UnmarshalText([]byte(src)) | ||
47 | + } | ||
48 | + | ||
49 | + return fmt.Errorf("uuid: cannot convert %T to UUID", src) | ||
50 | +} | ||
51 | + | ||
52 | +// NullUUID can be used with the standard sql package to represent a | ||
53 | +// UUID value that can be NULL in the database | ||
54 | +type NullUUID struct { | ||
55 | + UUID UUID | ||
56 | + Valid bool | ||
57 | +} | ||
58 | + | ||
59 | +// Value implements the driver.Valuer interface. | ||
60 | +func (u NullUUID) Value() (driver.Value, error) { | ||
61 | + if !u.Valid { | ||
62 | + return nil, nil | ||
63 | + } | ||
64 | + // Delegate to UUID Value function | ||
65 | + return u.UUID.Value() | ||
66 | +} | ||
67 | + | ||
68 | +// Scan implements the sql.Scanner interface. | ||
69 | +func (u *NullUUID) Scan(src interface{}) error { | ||
70 | + if src == nil { | ||
71 | + u.UUID, u.Valid = Nil, false | ||
72 | + return nil | ||
73 | + } | ||
74 | + | ||
75 | + // Delegate to UUID Scan function | ||
76 | + u.Valid = true | ||
77 | + return u.UUID.Scan(src) | ||
78 | +} |
vendor/github.com/satori/go.uuid/uuid.go
0 → 100644
1 | +// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru> | ||
2 | +// | ||
3 | +// Permission is hereby granted, free of charge, to any person obtaining | ||
4 | +// a copy of this software and associated documentation files (the | ||
5 | +// "Software"), to deal in the Software without restriction, including | ||
6 | +// without limitation the rights to use, copy, modify, merge, publish, | ||
7 | +// distribute, sublicense, and/or sell copies of the Software, and to | ||
8 | +// permit persons to whom the Software is furnished to do so, subject to | ||
9 | +// the following conditions: | ||
10 | +// | ||
11 | +// The above copyright notice and this permission notice shall be | ||
12 | +// included in all copies or substantial portions of the Software. | ||
13 | +// | ||
14 | +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
15 | +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
16 | +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
17 | +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
18 | +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
19 | +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
20 | +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
21 | + | ||
22 | +// Package uuid provides implementation of Universally Unique Identifier (UUID). | ||
23 | +// Supported versions are 1, 3, 4 and 5 (as specified in RFC 4122) and | ||
24 | +// version 2 (as specified in DCE 1.1). | ||
25 | +package uuid | ||
26 | + | ||
27 | +import ( | ||
28 | + "bytes" | ||
29 | + "encoding/hex" | ||
30 | +) | ||
31 | + | ||
32 | +// Size of a UUID in bytes. | ||
33 | +const Size = 16 | ||
34 | + | ||
35 | +// UUID representation compliant with specification | ||
36 | +// described in RFC 4122. | ||
37 | +type UUID [Size]byte | ||
38 | + | ||
39 | +// UUID versions | ||
40 | +const ( | ||
41 | + _ byte = iota | ||
42 | + V1 | ||
43 | + V2 | ||
44 | + V3 | ||
45 | + V4 | ||
46 | + V5 | ||
47 | +) | ||
48 | + | ||
49 | +// UUID layout variants. | ||
50 | +const ( | ||
51 | + VariantNCS byte = iota | ||
52 | + VariantRFC4122 | ||
53 | + VariantMicrosoft | ||
54 | + VariantFuture | ||
55 | +) | ||
56 | + | ||
57 | +// UUID DCE domains. | ||
58 | +const ( | ||
59 | + DomainPerson = iota | ||
60 | + DomainGroup | ||
61 | + DomainOrg | ||
62 | +) | ||
63 | + | ||
64 | +// String parse helpers. | ||
65 | +var ( | ||
66 | + urnPrefix = []byte("urn:uuid:") | ||
67 | + byteGroups = []int{8, 4, 4, 4, 12} | ||
68 | +) | ||
69 | + | ||
70 | +// Nil is special form of UUID that is specified to have all | ||
71 | +// 128 bits set to zero. | ||
72 | +var Nil = UUID{} | ||
73 | + | ||
74 | +// Predefined namespace UUIDs. | ||
75 | +var ( | ||
76 | + NamespaceDNS = Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) | ||
77 | + NamespaceURL = Must(FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) | ||
78 | + NamespaceOID = Must(FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) | ||
79 | + NamespaceX500 = Must(FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) | ||
80 | +) | ||
81 | + | ||
82 | +// Equal returns true if u1 and u2 equals, otherwise returns false. | ||
83 | +func Equal(u1 UUID, u2 UUID) bool { | ||
84 | + return bytes.Equal(u1[:], u2[:]) | ||
85 | +} | ||
86 | + | ||
87 | +// Version returns algorithm version used to generate UUID. | ||
88 | +func (u UUID) Version() byte { | ||
89 | + return u[6] >> 4 | ||
90 | +} | ||
91 | + | ||
92 | +// Variant returns UUID layout variant. | ||
93 | +func (u UUID) Variant() byte { | ||
94 | + switch { | ||
95 | + case (u[8] >> 7) == 0x00: | ||
96 | + return VariantNCS | ||
97 | + case (u[8] >> 6) == 0x02: | ||
98 | + return VariantRFC4122 | ||
99 | + case (u[8] >> 5) == 0x06: | ||
100 | + return VariantMicrosoft | ||
101 | + case (u[8] >> 5) == 0x07: | ||
102 | + fallthrough | ||
103 | + default: | ||
104 | + return VariantFuture | ||
105 | + } | ||
106 | +} | ||
107 | + | ||
108 | +// Bytes returns bytes slice representation of UUID. | ||
109 | +func (u UUID) Bytes() []byte { | ||
110 | + return u[:] | ||
111 | +} | ||
112 | + | ||
113 | +// Returns canonical string representation of UUID: | ||
114 | +// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. | ||
115 | +func (u UUID) String() string { | ||
116 | + buf := make([]byte, 36) | ||
117 | + | ||
118 | + hex.Encode(buf[0:8], u[0:4]) | ||
119 | + buf[8] = '-' | ||
120 | + hex.Encode(buf[9:13], u[4:6]) | ||
121 | + buf[13] = '-' | ||
122 | + hex.Encode(buf[14:18], u[6:8]) | ||
123 | + buf[18] = '-' | ||
124 | + hex.Encode(buf[19:23], u[8:10]) | ||
125 | + buf[23] = '-' | ||
126 | + hex.Encode(buf[24:], u[10:]) | ||
127 | + | ||
128 | + return string(buf) | ||
129 | +} | ||
130 | + | ||
131 | +// SetVersion sets version bits. | ||
132 | +func (u *UUID) SetVersion(v byte) { | ||
133 | + u[6] = (u[6] & 0x0f) | (v << 4) | ||
134 | +} | ||
135 | + | ||
136 | +// SetVariant sets variant bits. | ||
137 | +func (u *UUID) SetVariant(v byte) { | ||
138 | + switch v { | ||
139 | + case VariantNCS: | ||
140 | + u[8] = (u[8]&(0xff>>1) | (0x00 << 7)) | ||
141 | + case VariantRFC4122: | ||
142 | + u[8] = (u[8]&(0xff>>2) | (0x02 << 6)) | ||
143 | + case VariantMicrosoft: | ||
144 | + u[8] = (u[8]&(0xff>>3) | (0x06 << 5)) | ||
145 | + case VariantFuture: | ||
146 | + fallthrough | ||
147 | + default: | ||
148 | + u[8] = (u[8]&(0xff>>3) | (0x07 << 5)) | ||
149 | + } | ||
150 | +} | ||
151 | + | ||
152 | +// Must is a helper that wraps a call to a function returning (UUID, error) | ||
153 | +// and panics if the error is non-nil. It is intended for use in variable | ||
154 | +// initializations such as | ||
155 | +// var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000")); | ||
156 | +func Must(u UUID, err error) UUID { | ||
157 | + if err != nil { | ||
158 | + panic(err) | ||
159 | + } | ||
160 | + return u | ||
161 | +} |
vendor/github.com/sony/sonyflake/.gitignore
0 → 100644
1 | +example/sonyflake_server |
vendor/github.com/sony/sonyflake/.travis.yml
0 → 100644
1 | +language: go | ||
2 | +go: | ||
3 | + - 1.10.x | ||
4 | + - 1.11.x | ||
5 | +sudo: false | ||
6 | +before_install: | ||
7 | + - go get -u golang.org/x/lint/golint | ||
8 | + - go get github.com/axw/gocov/gocov | ||
9 | + - go get github.com/mattn/goveralls | ||
10 | +script: | ||
11 | + - test -z "`gofmt -l .`" | ||
12 | + - test -z "`golint ./...`" | ||
13 | + - $GOPATH/bin/goveralls -service=travis-ci | ||
14 | + - cd example && ./linux64_build.sh |
vendor/github.com/sony/sonyflake/LICENSE
0 → 100644
1 | +The MIT License (MIT) | ||
2 | + | ||
3 | +Copyright 2015 Sony Corporation | ||
4 | + | ||
5 | +Permission is hereby granted, free of charge, to any person obtaining a copy | ||
6 | +of this software and associated documentation files (the "Software"), to deal | ||
7 | +in the Software without restriction, including without limitation the rights | ||
8 | +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
9 | +copies of the Software, and to permit persons to whom the Software is | ||
10 | +furnished to do so, subject to the following conditions: | ||
11 | + | ||
12 | +The above copyright notice and this permission notice shall be included in | ||
13 | +all copies or substantial portions of the Software. | ||
14 | + | ||
15 | +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
16 | +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
17 | +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
18 | +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
19 | +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
20 | +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
21 | +THE SOFTWARE. |
vendor/github.com/sony/sonyflake/README.md
0 → 100644
1 | +Sonyflake | ||
2 | +========= | ||
3 | + | ||
4 | +[![GoDoc](https://godoc.org/github.com/sony/sonyflake?status.svg)](http://godoc.org/github.com/sony/sonyflake) | ||
5 | +[![Build Status](https://travis-ci.org/sony/sonyflake.svg?branch=master)](https://travis-ci.org/sony/sonyflake) | ||
6 | +[![Coverage Status](https://coveralls.io/repos/sony/sonyflake/badge.svg?branch=master&service=github)](https://coveralls.io/github/sony/sonyflake?branch=master) | ||
7 | +[![Go Report Card](https://goreportcard.com/badge/github.com/sony/sonyflake)](https://goreportcard.com/report/github.com/sony/sonyflake) | ||
8 | + | ||
9 | +Sonyflake is a distributed unique ID generator inspired by [Twitter's Snowflake](https://blog.twitter.com/2010/announcing-snowflake). | ||
10 | +A Sonyflake ID is composed of | ||
11 | + | ||
12 | + 39 bits for time in units of 10 msec | ||
13 | + 8 bits for a sequence number | ||
14 | + 16 bits for a machine id | ||
15 | + | ||
16 | +Installation | ||
17 | +------------ | ||
18 | + | ||
19 | +``` | ||
20 | +go get github.com/sony/sonyflake | ||
21 | +``` | ||
22 | + | ||
23 | +Usage | ||
24 | +----- | ||
25 | + | ||
26 | +The function NewSonyflake creates a new Sonyflake instance. | ||
27 | + | ||
28 | +```go | ||
29 | +func NewSonyflake(st Settings) *Sonyflake | ||
30 | +``` | ||
31 | + | ||
32 | +You can configure Sonyflake by the struct Settings: | ||
33 | + | ||
34 | +```go | ||
35 | +type Settings struct { | ||
36 | + StartTime time.Time | ||
37 | + MachineID func() (uint16, error) | ||
38 | + CheckMachineID func(uint16) bool | ||
39 | +} | ||
40 | +``` | ||
41 | + | ||
42 | +- StartTime is the time since which the Sonyflake time is defined as the elapsed time. | ||
43 | + If StartTime is 0, the start time of the Sonyflake is set to "2014-09-01 00:00:00 +0000 UTC". | ||
44 | + If StartTime is ahead of the current time, Sonyflake is not created. | ||
45 | + | ||
46 | +- MachineID returns the unique ID of the Sonyflake instance. | ||
47 | + If MachineID returns an error, Sonyflake is not created. | ||
48 | + If MachineID is nil, default MachineID is used. | ||
49 | + Default MachineID returns the lower 16 bits of the private IP address. | ||
50 | + | ||
51 | +- CheckMachineID validates the uniqueness of the machine ID. | ||
52 | + If CheckMachineID returns false, Sonyflake is not created. | ||
53 | + If CheckMachineID is nil, no validation is done. | ||
54 | + | ||
55 | +In order to get a new unique ID, you just have to call the method NextID. | ||
56 | + | ||
57 | +```go | ||
58 | +func (sf *Sonyflake) NextID() (uint64, error) | ||
59 | +``` | ||
60 | + | ||
61 | +NextID can continue to generate IDs for about 174 years from StartTime. | ||
62 | +But after the Sonyflake time is over the limit, NextID returns an error. | ||
63 | + | ||
64 | +AWS VPC and Docker | ||
65 | +------------------ | ||
66 | + | ||
67 | +The [awsutil](https://github.com/sony/sonyflake/blob/master/awsutil) package provides | ||
68 | +the function AmazonEC2MachineID that returns the lower 16-bit private IP address of the Amazon EC2 instance. | ||
69 | +It also works correctly on Docker | ||
70 | +by retrieving [instance metadata](http://docs.aws.amazon.com/en_us/AWSEC2/latest/UserGuide/ec2-instance-metadata.html). | ||
71 | + | ||
72 | +[AWS VPC](http://docs.aws.amazon.com/en_us/AmazonVPC/latest/UserGuide/VPC_Subnets.html) | ||
73 | +is assigned a single CIDR with a netmask between /28 and /16. | ||
74 | +So if each EC2 instance has a unique private IP address in AWS VPC, | ||
75 | +the lower 16 bits of the address is also unique. | ||
76 | +In this common case, you can use AmazonEC2MachineID as Settings.MachineID. | ||
77 | + | ||
78 | +See [example](https://github.com/sony/sonyflake/blob/master/example) that runs Sonyflake on AWS Elastic Beanstalk. | ||
79 | + | ||
80 | +License | ||
81 | +------- | ||
82 | + | ||
83 | +The MIT License (MIT) | ||
84 | + | ||
85 | +See [LICENSE](https://github.com/sony/sonyflake/blob/master/LICENSE) for details. |
vendor/github.com/sony/sonyflake/go.mod
0 → 100644
vendor/github.com/sony/sonyflake/go.sum
0 → 100644
1 | +// Package sonyflake implements Sonyflake, a distributed unique ID generator inspired by Twitter's Snowflake. | ||
2 | +// | ||
3 | +// A Sonyflake ID is composed of | ||
4 | +// 39 bits for time in units of 10 msec | ||
5 | +// 8 bits for a sequence number | ||
6 | +// 16 bits for a machine id | ||
7 | +package sonyflake | ||
8 | + | ||
9 | +import ( | ||
10 | + "errors" | ||
11 | + "net" | ||
12 | + "sync" | ||
13 | + "time" | ||
14 | +) | ||
15 | + | ||
16 | +// These constants are the bit lengths of Sonyflake ID parts. | ||
17 | +const ( | ||
18 | + BitLenTime = 39 // bit length of time | ||
19 | + BitLenSequence = 8 // bit length of sequence number | ||
20 | + BitLenMachineID = 63 - BitLenTime - BitLenSequence // bit length of machine id | ||
21 | +) | ||
22 | + | ||
23 | +// Settings configures Sonyflake: | ||
24 | +// | ||
25 | +// StartTime is the time since which the Sonyflake time is defined as the elapsed time. | ||
26 | +// If StartTime is 0, the start time of the Sonyflake is set to "2014-09-01 00:00:00 +0000 UTC". | ||
27 | +// If StartTime is ahead of the current time, Sonyflake is not created. | ||
28 | +// | ||
29 | +// MachineID returns the unique ID of the Sonyflake instance. | ||
30 | +// If MachineID returns an error, Sonyflake is not created. | ||
31 | +// If MachineID is nil, default MachineID is used. | ||
32 | +// Default MachineID returns the lower 16 bits of the private IP address. | ||
33 | +// | ||
34 | +// CheckMachineID validates the uniqueness of the machine ID. | ||
35 | +// If CheckMachineID returns false, Sonyflake is not created. | ||
36 | +// If CheckMachineID is nil, no validation is done. | ||
37 | +type Settings struct { | ||
38 | + StartTime time.Time | ||
39 | + MachineID func() (uint16, error) | ||
40 | + CheckMachineID func(uint16) bool | ||
41 | +} | ||
42 | + | ||
43 | +// Sonyflake is a distributed unique ID generator. | ||
44 | +type Sonyflake struct { | ||
45 | + mutex *sync.Mutex | ||
46 | + startTime int64 | ||
47 | + elapsedTime int64 | ||
48 | + sequence uint16 | ||
49 | + machineID uint16 | ||
50 | +} | ||
51 | + | ||
52 | +// NewSonyflake returns a new Sonyflake configured with the given Settings. | ||
53 | +// NewSonyflake returns nil in the following cases: | ||
54 | +// - Settings.StartTime is ahead of the current time. | ||
55 | +// - Settings.MachineID returns an error. | ||
56 | +// - Settings.CheckMachineID returns false. | ||
57 | +func NewSonyflake(st Settings) *Sonyflake { | ||
58 | + sf := new(Sonyflake) | ||
59 | + sf.mutex = new(sync.Mutex) | ||
60 | + sf.sequence = uint16(1<<BitLenSequence - 1) | ||
61 | + | ||
62 | + if st.StartTime.After(time.Now()) { | ||
63 | + return nil | ||
64 | + } | ||
65 | + if st.StartTime.IsZero() { | ||
66 | + sf.startTime = toSonyflakeTime(time.Date(2014, 9, 1, 0, 0, 0, 0, time.UTC)) | ||
67 | + } else { | ||
68 | + sf.startTime = toSonyflakeTime(st.StartTime) | ||
69 | + } | ||
70 | + | ||
71 | + var err error | ||
72 | + if st.MachineID == nil { | ||
73 | + sf.machineID, err = lower16BitPrivateIP() | ||
74 | + } else { | ||
75 | + sf.machineID, err = st.MachineID() | ||
76 | + } | ||
77 | + if err != nil || (st.CheckMachineID != nil && !st.CheckMachineID(sf.machineID)) { | ||
78 | + return nil | ||
79 | + } | ||
80 | + | ||
81 | + return sf | ||
82 | +} | ||
83 | + | ||
84 | +// NextID generates a next unique ID. | ||
85 | +// After the Sonyflake time overflows, NextID returns an error. | ||
86 | +func (sf *Sonyflake) NextID() (uint64, error) { | ||
87 | + const maskSequence = uint16(1<<BitLenSequence - 1) | ||
88 | + | ||
89 | + sf.mutex.Lock() | ||
90 | + defer sf.mutex.Unlock() | ||
91 | + | ||
92 | + current := currentElapsedTime(sf.startTime) | ||
93 | + if sf.elapsedTime < current { | ||
94 | + sf.elapsedTime = current | ||
95 | + sf.sequence = 0 | ||
96 | + } else { // sf.elapsedTime >= current | ||
97 | + sf.sequence = (sf.sequence + 1) & maskSequence | ||
98 | + if sf.sequence == 0 { | ||
99 | + sf.elapsedTime++ | ||
100 | + overtime := sf.elapsedTime - current | ||
101 | + time.Sleep(sleepTime((overtime))) | ||
102 | + } | ||
103 | + } | ||
104 | + | ||
105 | + return sf.toID() | ||
106 | +} | ||
107 | + | ||
108 | +const sonyflakeTimeUnit = 1e7 // nsec, i.e. 10 msec | ||
109 | + | ||
110 | +func toSonyflakeTime(t time.Time) int64 { | ||
111 | + return t.UTC().UnixNano() / sonyflakeTimeUnit | ||
112 | +} | ||
113 | + | ||
114 | +func currentElapsedTime(startTime int64) int64 { | ||
115 | + return toSonyflakeTime(time.Now()) - startTime | ||
116 | +} | ||
117 | + | ||
118 | +func sleepTime(overtime int64) time.Duration { | ||
119 | + return time.Duration(overtime)*10*time.Millisecond - | ||
120 | + time.Duration(time.Now().UTC().UnixNano()%sonyflakeTimeUnit)*time.Nanosecond | ||
121 | +} | ||
122 | + | ||
123 | +func (sf *Sonyflake) toID() (uint64, error) { | ||
124 | + if sf.elapsedTime >= 1<<BitLenTime { | ||
125 | + return 0, errors.New("over the time limit") | ||
126 | + } | ||
127 | + | ||
128 | + return uint64(sf.elapsedTime)<<(BitLenSequence+BitLenMachineID) | | ||
129 | + uint64(sf.sequence)<<BitLenMachineID | | ||
130 | + uint64(sf.machineID), nil | ||
131 | +} | ||
132 | + | ||
133 | +func privateIPv4() (net.IP, error) { | ||
134 | + as, err := net.InterfaceAddrs() | ||
135 | + if err != nil { | ||
136 | + return nil, err | ||
137 | + } | ||
138 | + | ||
139 | + for _, a := range as { | ||
140 | + ipnet, ok := a.(*net.IPNet) | ||
141 | + if !ok || ipnet.IP.IsLoopback() { | ||
142 | + continue | ||
143 | + } | ||
144 | + | ||
145 | + ip := ipnet.IP.To4() | ||
146 | + if isPrivateIPv4(ip) { | ||
147 | + return ip, nil | ||
148 | + } | ||
149 | + } | ||
150 | + return nil, errors.New("no private ip address") | ||
151 | +} | ||
152 | + | ||
153 | +func isPrivateIPv4(ip net.IP) bool { | ||
154 | + return ip != nil && | ||
155 | + (ip[0] == 10 || ip[0] == 172 && (ip[1] >= 16 && ip[1] < 32) || ip[0] == 192 && ip[1] == 168) | ||
156 | +} | ||
157 | + | ||
158 | +func lower16BitPrivateIP() (uint16, error) { | ||
159 | + ip, err := privateIPv4() | ||
160 | + if err != nil { | ||
161 | + return 0, err | ||
162 | + } | ||
163 | + | ||
164 | + return uint16(ip[2])<<8 + uint16(ip[3]), nil | ||
165 | +} | ||
166 | + | ||
167 | +// Decompose returns a set of Sonyflake ID parts. | ||
168 | +func Decompose(id uint64) map[string]uint64 { | ||
169 | + const maskSequence = uint64((1<<BitLenSequence - 1) << BitLenMachineID) | ||
170 | + const maskMachineID = uint64(1<<BitLenMachineID - 1) | ||
171 | + | ||
172 | + msb := id >> 63 | ||
173 | + time := id >> (BitLenSequence + BitLenMachineID) | ||
174 | + sequence := id & maskSequence >> BitLenMachineID | ||
175 | + machineID := id & maskMachineID | ||
176 | + return map[string]uint64{ | ||
177 | + "id": id, | ||
178 | + "msb": msb, | ||
179 | + "time": time, | ||
180 | + "sequence": sequence, | ||
181 | + "machine-id": machineID, | ||
182 | + } | ||
183 | +} |
1 | -# github.com/astaxie/beego v1.10.0 | 1 | +# github.com/astaxie/beego v1.11.1 |
2 | github.com/astaxie/beego | 2 | github.com/astaxie/beego |
3 | github.com/astaxie/beego/config | 3 | github.com/astaxie/beego/config |
4 | github.com/astaxie/beego/context | 4 | github.com/astaxie/beego/context |
@@ -6,6 +6,7 @@ github.com/astaxie/beego/context/param | @@ -6,6 +6,7 @@ github.com/astaxie/beego/context/param | ||
6 | github.com/astaxie/beego/grace | 6 | github.com/astaxie/beego/grace |
7 | github.com/astaxie/beego/logs | 7 | github.com/astaxie/beego/logs |
8 | github.com/astaxie/beego/orm | 8 | github.com/astaxie/beego/orm |
9 | +github.com/astaxie/beego/plugins/cors | ||
9 | github.com/astaxie/beego/session | 10 | github.com/astaxie/beego/session |
10 | github.com/astaxie/beego/toolbox | 11 | github.com/astaxie/beego/toolbox |
11 | github.com/astaxie/beego/utils | 12 | github.com/astaxie/beego/utils |
@@ -22,6 +23,10 @@ github.com/go-redis/redis/internal/proto | @@ -22,6 +23,10 @@ github.com/go-redis/redis/internal/proto | ||
22 | github.com/go-redis/redis/internal/util | 23 | github.com/go-redis/redis/internal/util |
23 | # github.com/go-sql-driver/mysql v1.4.1 | 24 | # github.com/go-sql-driver/mysql v1.4.1 |
24 | github.com/go-sql-driver/mysql | 25 | github.com/go-sql-driver/mysql |
26 | +# github.com/satori/go.uuid v1.2.0 | ||
27 | +github.com/satori/go.uuid | ||
28 | +# github.com/sony/sonyflake v1.0.0 | ||
29 | +github.com/sony/sonyflake | ||
25 | # golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba | 30 | # golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba |
26 | golang.org/x/crypto/acme | 31 | golang.org/x/crypto/acme |
27 | golang.org/x/crypto/acme/autocert | 32 | golang.org/x/crypto/acme/autocert |
-
请 注册 或 登录 后发表评论