From b36afadbdca6ddb7cf7258db08e0c3683c862b84 Mon Sep 17 00:00:00 2001 From: redfoxli Date: Fri, 22 Apr 2016 13:54:55 +0800 Subject: [PATCH 01/96] close socket when http client request over To avoid a large number of TIME_WAIT in server which http client talk with --- httplib/httplib.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/httplib/httplib.go b/httplib/httplib.go index 76984122..505bb45b 100644 --- a/httplib/httplib.go +++ b/httplib/httplib.go @@ -409,9 +409,10 @@ func (b *BeegoHTTPRequest) DoRequest() (*http.Response, error) { if trans == nil { // create default transport trans = &http.Transport{ - TLSClientConfig: b.setting.TLSClientConfig, - Proxy: b.setting.Proxy, - Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), + TLSClientConfig: b.setting.TLSClientConfig, + Proxy: b.setting.Proxy, + Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), + MaxIdleConnsPerHost: -1, } } else { // if b.transport is *http.Transport then set the settings. From 8f0749ddeea0d16e3ed68966f611893a7f2d58ca Mon Sep 17 00:00:00 2001 From: qAison <77456702@qq.com> Date: Fri, 27 May 2016 13:51:49 +0800 Subject: [PATCH 02/96] fix fk field null value --- orm/orm_raw.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/orm/orm_raw.go b/orm/orm_raw.go index 5f88121c..3b945833 100644 --- a/orm/orm_raw.go +++ b/orm/orm_raw.go @@ -342,7 +342,13 @@ func (o *rawSet) QueryRow(containers ...interface{}) error { for _, col := range columns { if fi := sMi.fields.GetByColumn(col); fi != nil { value := reflect.ValueOf(columnsMp[col]).Elem().Interface() - o.setFieldValue(ind.FieldByIndex(fi.fieldIndex), value) + field := ind.FieldByIndex(fi.fieldIndex) + if fi.fieldType&IsRelField > 0 { + mf := reflect.New(fi.relModelInfo.addrField.Elem().Type()) + field.Set(mf) + field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex) + } + o.setFieldValue(field, value) } } } else { @@ -480,7 +486,13 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) { for _, col := range columns { if fi := sMi.fields.GetByColumn(col); fi != nil { value := reflect.ValueOf(columnsMp[col]).Elem().Interface() - o.setFieldValue(ind.FieldByIndex(fi.fieldIndex), value) + field := ind.FieldByIndex(fi.fieldIndex) + if fi.fieldType&IsRelField > 0 { + mf := reflect.New(fi.relModelInfo.addrField.Elem().Type()) + field.Set(mf) + field = mf.Elem().FieldByIndex(fi.relModelInfo.fields.pk.fieldIndex) + } + o.setFieldValue(field, value) } } } else { From 761b6c129cee9c2a2582d8d8b4dfe2300c9308d4 Mon Sep 17 00:00:00 2001 From: qAison <77456702@qq.com> Date: Fri, 27 May 2016 14:34:22 +0800 Subject: [PATCH 03/96] count func add support group by --- orm/db.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/orm/db.go b/orm/db.go index 314c3535..d5951f45 100644 --- a/orm/db.go +++ b/orm/db.go @@ -927,12 +927,17 @@ func (d *dbBase) Count(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condition tables.parseRelated(qs.related, qs.relDepth) where, args := tables.getCondSQL(cond, false, tz) + groupBy := tables.getGroupSQL(qs.groups) tables.getOrderSQL(qs.orders) join := tables.getJoinSQL() Q := d.ins.TableQuote() - query := fmt.Sprintf("SELECT COUNT(*) FROM %s%s%s T0 %s%s", Q, mi.table, Q, join, where) + query := fmt.Sprintf("SELECT COUNT(*) FROM %s%s%s T0 %s%s%s", Q, mi.table, Q, join, where, groupBy) + + if groupBy != "" { + query = fmt.Sprintf("SELECT COUNT(*) FROM (%s) AS T", query) + } d.ins.ReplaceMarks(&query) From d528fafd43a2bd202024a9986ef8bb9617623bd1 Mon Sep 17 00:00:00 2001 From: ysqi Date: Fri, 3 Jun 2016 22:06:43 +0800 Subject: [PATCH 04/96] ignore case of tag and fixed bug for columName --- orm/models_utils.go | 6 +++--- orm/orm_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ orm/utils.go | 32 +++++++++++++++++++++++++------- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/orm/models_utils.go b/orm/models_utils.go index ec11d516..1dc17f81 100644 --- a/orm/models_utils.go +++ b/orm/models_utils.go @@ -192,10 +192,10 @@ func parseStructTag(data string, attrs *map[string]bool, tags *map[string]string tag := make(map[string]string) for _, v := range strings.Split(data, defaultStructTagDelim) { v = strings.TrimSpace(v) - if supportTag[v] == 1 { - attr[v] = true + if t := strings.ToLower(v); supportTag[t] == 1 { + attr[t] = true } else if i := strings.Index(v, "("); i > 0 && strings.Index(v, ")") == len(v)-1 { - name := v[:i] + name := t[:i] if supportTag[name] == 2 { v = v[i+1 : len(v)-1] tag[name] = v diff --git a/orm/orm_test.go b/orm/orm_test.go index 11f6bd56..c029999f 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -2117,3 +2117,47 @@ func TestUintPk(t *testing.T) { dORM.Delete(u) } + +func TestSnake(t *testing.T) { + cases := map[string]string{ + "i": "i", + "I": "i", + "iD": "i_d", //01 + "ID": "id", //11 pre+curent=2 + "NO": "no", //11 + "NOO": "noo", //111 10 + "NOOooOOoo": "noo_oo_oo_oo", //111_00_11_00 + "OrderNO": "order_no", //10000_11 + "tagName": "tag_name", //000_1000 + "tag_Name": "tag_name", + "tag_name": "tag_name", + "_tag_name": "_tag_name", + "tag_666name": "tag_666name", + "tag_666Name": "tag_666_name", + } + for name, want := range cases { + got := snakeString(name) + throwFail(t, AssertIs(got, want)) + } +} + +func TestIgnoreCaseTag(t *testing.T) { + type testTagModel struct { + ID int `orm:"pk"` + NOO string `orm:"column(n)"` + Name01 string `orm:"NULL"` + Name02 string `orm:"COLUMN(Name)"` + Name03 string `orm:"Column(name)"` + } + RegisterModel(&testTagModel{}) + info, ok := modelCache.get("test_tag_model") + throwFail(t, AssertIs(ok, true)) + throwFail(t, AssertNot(info, nil)) + if t == nil { + return + } + throwFail(t, AssertIs(info.fields.GetByName("NOO").column, "n")) + throwFail(t, AssertIs(info.fields.GetByName("Name01").null, true)) + throwFail(t, AssertIs(info.fields.GetByName("Name02").column, "Name")) + throwFail(t, AssertIs(info.fields.GetByName("Name03").column, "name")) +} diff --git a/orm/utils.go b/orm/utils.go index 99437c7b..e3cd8ad6 100644 --- a/orm/utils.go +++ b/orm/utils.go @@ -181,18 +181,36 @@ func ToInt64(value interface{}) (d int64) { return } -// snake string, XxYy to xx_yy +// snake string, XxYy to xx_yy , XxYY to xx_yy func snakeString(s string) string { data := make([]byte, 0, len(s)*2) - j := false num := len(s) for i := 0; i < num; i++ { d := s[i] - if i > 0 && d >= 'A' && d <= 'Z' && j { - data = append(data, '_') - } - if d != '_' { - j = true + if i > 0 && d != '_' && s[i-1] != '_' { + need := false + // upper as 1, lower as 0 + // XX -> 11 -> 11 + // Xx -> 10 -> 10 + // XxYyZZ -> 101011 -> 10_10_11 + isUpper := d >= 'A' && d <= 'Z' + preIsUpper := s[i-1] >= 'A' && s[i-1] <= 'Z' + if isUpper { + // like : xxYy + if !preIsUpper { + need = true + } + } else { + if preIsUpper { + // ignore "Xy" in "xxXyy" + if i-2 >= 0 && s[i-2] >= 'A' && s[i-2] <= 'Z' { + need = true + } + } + } + if need { + data = append(data, '_') + } } data = append(data, d) } From 3f016840db8da93a98705527e502a8fa19469704 Mon Sep 17 00:00:00 2001 From: ysqi Date: Sat, 4 Jun 2016 10:41:55 +0800 Subject: [PATCH 05/96] fixed test error --- orm/orm_test.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/orm/orm_test.go b/orm/orm_test.go index c029999f..e1ecdfd1 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -2122,13 +2122,13 @@ func TestSnake(t *testing.T) { cases := map[string]string{ "i": "i", "I": "i", - "iD": "i_d", //01 - "ID": "id", //11 pre+curent=2 - "NO": "no", //11 - "NOO": "noo", //111 10 - "NOOooOOoo": "noo_oo_oo_oo", //111_00_11_00 - "OrderNO": "order_no", //10000_11 - "tagName": "tag_name", //000_1000 + "iD": "i_d", + "ID": "id", + "NO": "no", + "NOO": "noo", + "NOOooOOoo": "noo_oo_oo_oo", + "OrderNO": "order_no", + "tagName": "tag_name", "tag_Name": "tag_name", "tag_name": "tag_name", "_tag_name": "_tag_name", @@ -2149,6 +2149,7 @@ func TestIgnoreCaseTag(t *testing.T) { Name02 string `orm:"COLUMN(Name)"` Name03 string `orm:"Column(name)"` } + modelCache.clean() RegisterModel(&testTagModel{}) info, ok := modelCache.get("test_tag_model") throwFail(t, AssertIs(ok, true)) From 0d3a806c238ebd9f37e5bc28d36037223480c980 Mon Sep 17 00:00:00 2001 From: Joshua Santos Date: Wed, 15 Jun 2016 17:17:50 -0700 Subject: [PATCH 06/96] Add meta fields with required flag --- templatefunc.go | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/templatefunc.go b/templatefunc.go index 8558733f..0f9af481 100644 --- a/templatefunc.go +++ b/templatefunc.go @@ -422,17 +422,18 @@ func RenderForm(obj interface{}) template.HTML { fieldT := objT.Field(i) label, name, fType, id, class, ignored := parseFormTag(fieldT) + required := parseMetaTag(fieldT) if ignored { continue } - raw = append(raw, renderFormField(label, name, fType, fieldV.Interface(), id, class)) + raw = append(raw, renderFormField(label, name, fType, fieldV.Interface(), id, class, required)) } return template.HTML(strings.Join(raw, "
")) } // renderFormField returns a string containing HTML of a single form field. -func renderFormField(label, name, fType string, value interface{}, id string, class string) string { +func renderFormField(label, name, fType string, value interface{}, id string, class string, required bool) string { if id != "" { id = " id=\"" + id + "\"" } @@ -441,8 +442,13 @@ func renderFormField(label, name, fType string, value interface{}, id string, cl class = " class=\"" + class + "\"" } + requiredString := "" + if required { + requiredString = " required" + } + if isValidForInput(fType) { - return fmt.Sprintf(`%v`, label, id, class, name, fType, value) + return fmt.Sprintf(`%v`, label, id, class, name, fType, value, requiredString) } return fmt.Sprintf(`%v<%v%v%v name="%v">%v`, label, fType, id, class, name, value, fType) @@ -496,6 +502,21 @@ func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id str label = tags[2] } } + + return +} + +// parseMetaTag takes the stuct-tag of a StructField and parses the `meta` value. +// returned is the boolean of whether the field is required +func parseMetaTag(fieldT reflect.StructField) (required bool) { + meta := strings.Split(fieldT.Tag.Get("meta"), ",") + required = false + switch len(meta) { + case 1: + if len(meta[0]) > 0 && meta[0] != "-" { + required = true + } + } return } From 2bd743fcffbd80f1584f1fea87ac931ff08dfe52 Mon Sep 17 00:00:00 2001 From: Faissal Elamraoui Date: Fri, 17 Jun 2016 15:39:16 +0200 Subject: [PATCH 07/96] Enhanced logging during DEV mode --- logs/logger.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ router.go | 21 +++++++++++++++------ 2 files changed, 61 insertions(+), 6 deletions(-) diff --git a/logs/logger.go b/logs/logger.go index 2f47e569..aff7e992 100644 --- a/logs/logger.go +++ b/logs/logger.go @@ -18,6 +18,7 @@ import ( "io" "sync" "time" + "net/http" ) type logWriter struct { @@ -80,3 +81,48 @@ func formatTimeHeader(when time.Time) ([]byte, int) { return buf[0:], d } + +var ( + green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) + white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) + yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) + red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) + blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) + magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) + cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) + reset = string([]byte{27, 91, 48, 109}) +) + +func ColorByStatus(code int) string { + switch { + case code >= 200 && code < 300: + return green + case code >= 300 && code < 400: + return white + case code >= 400 && code < 500: + return yellow + default: + return red + } +} + +func ColorByMethod(method string) string { + switch method { + case http.MethodGet: + return blue + case http.MethodPost: + return cyan + case http.MethodPut: + return yellow + case http.MethodDelete: + return red + case http.MethodPatch: + return green + case http.MethodHead: + return magenta + case http.MethodOptions: + return white + default: + return reset + } +} diff --git a/router.go b/router.go index 960cd104..07abad2c 100644 --- a/router.go +++ b/router.go @@ -816,18 +816,27 @@ Admin: if BConfig.RunMode == DEV || BConfig.Log.AccessLogs { timeDur := time.Since(startTime) var devInfo string + + statusCode := context.ResponseWriter.Status + if statusCode == 0 { statusCode = 200 } + + statusColor := logs.ColorByStatus(statusCode) + methodColor := logs.ColorByMethod(r.Method) + resetColor := logs.ColorByMethod("") + if findRouter { if routerInfo != nil { - devInfo = fmt.Sprintf("| % -10s | % -40s | % -16s | % -10s | % -40s |", r.Method, r.URL.Path, timeDur.String(), "match", routerInfo.pattern) + devInfo = fmt.Sprintf("|%s %3d %s|%7s|%8s|%s %s %-7s %-3s r:%s", statusColor, statusCode, resetColor, + timeDur.String(), "match", methodColor, resetColor, r.Method, r.URL.Path, routerInfo.pattern) } else { - devInfo = fmt.Sprintf("| % -10s | % -40s | % -16s | % -10s |", r.Method, r.URL.Path, timeDur.String(), "match") + devInfo = fmt.Sprintf("|%s %3d %s|%7s|%8s|%s %s %-7s %-3s", statusColor, statusCode, resetColor, + timeDur.String(), "match", methodColor, resetColor, r.Method, r.URL.Path) } } else { - devInfo = fmt.Sprintf("| % -10s | % -40s | % -16s | % -10s |", r.Method, r.URL.Path, timeDur.String(), "notmatch") - } - if DefaultAccessLogFilter == nil || !DefaultAccessLogFilter.Filter(context) { - logs.Debug(devInfo) + devInfo = fmt.Sprintf("|%s %3d %s|%7s|%8s|%s %s %-7s %-3s", statusColor, statusCode, resetColor, + timeDur.String(), "nomatch", methodColor, resetColor, r.Method, r.URL.Path) } + logs.Debug(devInfo) } // Call WriteHeader if status code has been set changed From 448be6e58cb746015d02a3f1ae0cb882e1f112da Mon Sep 17 00:00:00 2001 From: Faissal Elamraoui Date: Fri, 17 Jun 2016 17:39:32 +0200 Subject: [PATCH 08/96] added compatibility for go1.4 --- logs/logger.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/logs/logger.go b/logs/logger.go index aff7e992..db6d2ce9 100644 --- a/logs/logger.go +++ b/logs/logger.go @@ -108,19 +108,19 @@ func ColorByStatus(code int) string { func ColorByMethod(method string) string { switch method { - case http.MethodGet: + case "GET": return blue - case http.MethodPost: + case "POST": return cyan - case http.MethodPut: + case "PUT": return yellow - case http.MethodDelete: + case "DELETE": return red - case http.MethodPatch: + case "PATCH": return green - case http.MethodHead: + case "HEAD": return magenta - case http.MethodOptions: + case "OPTIONS": return white default: return reset From 844a3b0ffd598c58fdd0bfdb313ca80b64851ccd Mon Sep 17 00:00:00 2001 From: Faissal Elamraoui Date: Fri, 17 Jun 2016 17:47:12 +0200 Subject: [PATCH 09/96] removed unused import --- logs/logger.go | 1 - 1 file changed, 1 deletion(-) diff --git a/logs/logger.go b/logs/logger.go index db6d2ce9..e31dc083 100644 --- a/logs/logger.go +++ b/logs/logger.go @@ -18,7 +18,6 @@ import ( "io" "sync" "time" - "net/http" ) type logWriter struct { From cb3f240f4466ee9db61d67353ff2e08c982ba1d1 Mon Sep 17 00:00:00 2001 From: GuyCheung Date: Tue, 21 Jun 2016 15:52:31 +0800 Subject: [PATCH 10/96] add os.Chmod when create log file --- logs/file.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/logs/file.go b/logs/file.go index 7798a221..beaa9171 100644 --- a/logs/file.go +++ b/logs/file.go @@ -159,6 +159,10 @@ func (w *fileLogWriter) createLogFile() (*os.File, error) { return nil, err } fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm)) + if err == nil { + // Make sure file perm is user set perm cause of `os.OpenFile` will obey umask + os.Chmod(w.Filename, w.Perm) + } return fd, err } From 9572fdcf9a3f8cbedc9786e8ffbcc40cae2fb804 Mon Sep 17 00:00:00 2001 From: GuyCheung Date: Wed, 22 Jun 2016 09:57:16 +0800 Subject: [PATCH 11/96] update error on type of w.Perm; change unit test perm value --- logs/file.go | 2 +- logs/file_test.go | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/logs/file.go b/logs/file.go index beaa9171..42146dae 100644 --- a/logs/file.go +++ b/logs/file.go @@ -161,7 +161,7 @@ func (w *fileLogWriter) createLogFile() (*os.File, error) { fd, err := os.OpenFile(w.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.FileMode(perm)) if err == nil { // Make sure file perm is user set perm cause of `os.OpenFile` will obey umask - os.Chmod(w.Filename, w.Perm) + os.Chmod(w.Filename, os.FileMode(perm)) } return fd, err } diff --git a/logs/file_test.go b/logs/file_test.go index 23370947..69a66d84 100644 --- a/logs/file_test.go +++ b/logs/file_test.go @@ -26,7 +26,8 @@ import ( func TestFilePerm(t *testing.T) { log := NewLogger(10000) - log.SetLogger("file", `{"filename":"test.log", "perm": "0600"}`) + // use 0666 as test perm cause the default umask is 022 + log.SetLogger("file", `{"filename":"test.log", "perm": "0666"}`) log.Debug("debug") log.Informational("info") log.Notice("notice") @@ -39,7 +40,7 @@ func TestFilePerm(t *testing.T) { if err != nil { t.Fatal(err) } - if file.Mode() != 0600 { + if file.Mode() != 0666 { t.Fatal("unexpected log file permission") } os.Remove("test.log") From 415b9cf310e73a12a73f59f7d63439e0702df5b0 Mon Sep 17 00:00:00 2001 From: Victor Popkov Date: Wed, 22 Jun 2016 16:32:37 +0300 Subject: [PATCH 12/96] add support for time.Time pointer in struct types Allow to use pointer *time.Time as a type in combination with orm tags in struct. This enables to treat them as "empty" in JSON marshaling/unmarshaling when using 'json:"null,omitempty'. --- orm/db.go | 11 ++++++++++- orm/models_test.go | 3 +++ orm/models_utils.go | 2 ++ orm/orm_test.go | 12 ++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/orm/db.go b/orm/db.go index 78c72e87..9964e263 100644 --- a/orm/db.go +++ b/orm/db.go @@ -243,6 +243,9 @@ func (d *dbBase) collectFieldValue(mi *modelInfo, fi *fieldInfo, ind reflect.Val if fi.isFielder { f := field.Addr().Interface().(Fielder) f.SetRaw(tnow.In(DefaultTimeLoc)) + } else if field.Kind() == reflect.Ptr { + v := tnow.In(DefaultTimeLoc) + field.Set(reflect.ValueOf(&v)) } else { field.Set(reflect.ValueOf(tnow.In(DefaultTimeLoc))) } @@ -1273,8 +1276,14 @@ setValue: if isNative { if value == nil { value = time.Time{} + } else if field.Kind() == reflect.Ptr { + if value != nil { + v := value.(time.Time) + field.Set(reflect.ValueOf(&v)) + } + } else { + field.Set(reflect.ValueOf(value)) } - field.Set(reflect.ValueOf(value)) } case fieldType == TypePositiveBitField && field.Kind() == reflect.Ptr: if value != nil { diff --git a/orm/models_test.go b/orm/models_test.go index c68c7339..462370b2 100644 --- a/orm/models_test.go +++ b/orm/models_test.go @@ -181,6 +181,9 @@ type DataNull struct { Float32Ptr *float32 `orm:"null"` Float64Ptr *float64 `orm:"null"` DecimalPtr *float64 `orm:"digits(8);decimals(4);null"` + TimePtr *time.Time `orm:"null;type(time)"` + DatePtr *time.Time `orm:"null;type(date)"` + DateTimePtr *time.Time `orm:"null"` } type String string diff --git a/orm/models_utils.go b/orm/models_utils.go index 1dc17f81..4c4b0f24 100644 --- a/orm/models_utils.go +++ b/orm/models_utils.go @@ -137,6 +137,8 @@ func getFieldType(val reflect.Value) (ft int, err error) { ft = TypeBooleanField case reflect.TypeOf(new(string)): ft = TypeCharField + case reflect.TypeOf(new(time.Time)): + ft = TypeDateTimeField default: elm := reflect.Indirect(val) switch elm.Kind() { diff --git a/orm/orm_test.go b/orm/orm_test.go index e1ecdfd1..b5973448 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -350,6 +350,9 @@ func TestNullDataTypes(t *testing.T) { throwFail(t, AssertIs(d.Float32Ptr, nil)) throwFail(t, AssertIs(d.Float64Ptr, nil)) throwFail(t, AssertIs(d.DecimalPtr, nil)) + throwFail(t, AssertIs(d.TimePtr, nil)) + throwFail(t, AssertIs(d.DatePtr, nil)) + throwFail(t, AssertIs(d.DateTimePtr, nil)) _, err = dORM.Raw(`INSERT INTO data_null (boolean) VALUES (?)`, nil).Exec() throwFail(t, err) @@ -376,6 +379,9 @@ func TestNullDataTypes(t *testing.T) { float32Ptr := float32(42.0) float64Ptr := float64(42.0) decimalPtr := float64(42.0) + timePtr := time.Now() + datePtr := time.Now() + dateTimePtr := time.Now() d = DataNull{ DateTime: time.Now(), @@ -401,6 +407,9 @@ func TestNullDataTypes(t *testing.T) { Float32Ptr: &float32Ptr, Float64Ptr: &float64Ptr, DecimalPtr: &decimalPtr, + TimePtr: &timePtr, + DatePtr: &datePtr, + DateTimePtr: &dateTimePtr, } id, err = dORM.Insert(&d) @@ -441,6 +450,9 @@ func TestNullDataTypes(t *testing.T) { throwFail(t, AssertIs(*d.Float32Ptr, float32Ptr)) throwFail(t, AssertIs(*d.Float64Ptr, float64Ptr)) throwFail(t, AssertIs(*d.DecimalPtr, decimalPtr)) + throwFail(t, AssertIs((*d.TimePtr).Format(testTime), timePtr.Format(testTime))) + throwFail(t, AssertIs((*d.DatePtr).Format(testDate), datePtr.Format(testDate))) + throwFail(t, AssertIs((*d.DateTimePtr).Format(testDateTime), dateTimePtr.Format(testDateTime))) } func TestDataCustomTypes(t *testing.T) { From 479dfdbd40454c0331feec8cb31eac9edaa52bfb Mon Sep 17 00:00:00 2001 From: Faissal Elamraoui Date: Fri, 24 Jun 2016 15:11:21 +0200 Subject: [PATCH 13/96] added support for Windows terminals --- logs/logger.go | 80 +++++++++++++++++++++++++++++++++----------------- router.go | 22 +++++++++----- 2 files changed, 67 insertions(+), 35 deletions(-) diff --git a/logs/logger.go b/logs/logger.go index e31dc083..3cdf7237 100644 --- a/logs/logger.go +++ b/logs/logger.go @@ -18,6 +18,9 @@ import ( "io" "sync" "time" + "github.com/shiena/ansicolor" + "fmt" + "os" ) type logWriter struct { @@ -59,14 +62,14 @@ func formatTimeHeader(when time.Time) ([]byte, int) { buf[0] = '2' //change to '1' after 84 years, LOL buf[1] = '0' - buf[2] = y1[y-2000] - buf[3] = y2[y-2000] + buf[2] = y1[y - 2000] + buf[3] = y2[y - 2000] buf[4] = '/' - buf[5] = mo1[mo-1] - buf[6] = mo2[mo-1] + buf[5] = mo1[mo - 1] + buf[6] = mo2[mo - 1] buf[7] = '/' - buf[8] = d1[d-1] - buf[9] = d2[d-1] + buf[8] = d1[d - 1] + buf[9] = d2[d - 1] buf[10] = ' ' buf[11] = h1[h] buf[12] = h2[h] @@ -82,46 +85,69 @@ func formatTimeHeader(when time.Time) ([]byte, int) { } var ( - green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) - white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) - yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) - red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) - blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) - magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) - cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) - reset = string([]byte{27, 91, 48, 109}) + green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) + white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) + yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) + red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) + blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) + magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) + cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) + + w32Green = string([]byte{27, 91, 52, 50, 109}) + w32White = string([]byte{27, 91, 52, 55, 109}) + w32Yellow = string([]byte{27, 91, 52, 51, 109}) + w32Red = string([]byte{27, 91, 52, 49, 109}) + w32Blue = string([]byte{27, 91, 52, 52, 109}) + w32Magenta = string([]byte{27, 91, 52, 53, 109}) + w32Cyan = string([]byte{27, 91, 52, 54, 109}) + + reset = string([]byte{27, 91, 48, 109}) ) -func ColorByStatus(code int) string { +func ColorByStatus(cond bool, code int) string { switch { case code >= 200 && code < 300: - return green + return map[bool]string{true: green, false: w32Green}[cond] case code >= 300 && code < 400: - return white + return map[bool]string{true: white, false: w32White}[cond] case code >= 400 && code < 500: - return yellow + return map[bool]string{true: yellow, false: w32Yellow}[cond] default: - return red + return map[bool]string{true: red, false: w32Red}[cond] } } -func ColorByMethod(method string) string { +func ColorByMethod(cond bool, method string) string { switch method { case "GET": - return blue + return map[bool]string{true: blue, false: w32Blue}[cond] case "POST": - return cyan + return map[bool]string{true: cyan, false: w32Cyan}[cond] case "PUT": - return yellow + return map[bool]string{true: yellow, false: w32Yellow}[cond] case "DELETE": - return red + return map[bool]string{true: red, false: w32Red}[cond] case "PATCH": - return green + return map[bool]string{true: green, false: w32Green}[cond] case "HEAD": - return magenta + return map[bool]string{true: magenta, false: w32Magenta}[cond] case "OPTIONS": - return white + return map[bool]string{true: white, false: w32White}[cond] default: return reset } } + +var mu sync.Mutex + +// Helper method to output colored logs in Windows terminals +// using ansicolor (https://github.com/shiena/ansicolor) +func W32Debug(msg string) { + mu.Lock() + defer mu.Unlock() + + current := time.Now() + w := ansicolor.NewAnsiColorWriter(os.Stdout) + + fmt.Fprintf(w, "[beego] %v %s\n", current.Format("2006/01/02 - 15:04:05"), msg) +} diff --git a/router.go b/router.go index 07abad2c..296100d9 100644 --- a/router.go +++ b/router.go @@ -820,23 +820,29 @@ Admin: statusCode := context.ResponseWriter.Status if statusCode == 0 { statusCode = 200 } - statusColor := logs.ColorByStatus(statusCode) - methodColor := logs.ColorByMethod(r.Method) - resetColor := logs.ColorByMethod("") + iswin := (runtime.GOOS == "windows") + statusColor := logs.ColorByStatus(iswin, statusCode) + methodColor := logs.ColorByMethod(iswin, r.Method) + resetColor := logs.ColorByMethod(iswin, "") if findRouter { if routerInfo != nil { - devInfo = fmt.Sprintf("|%s %3d %s|%7s|%8s|%s %s %-7s %-3s r:%s", statusColor, statusCode, resetColor, - timeDur.String(), "match", methodColor, resetColor, r.Method, r.URL.Path, routerInfo.pattern) + devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-3s r:%s", statusColor, statusCode, + resetColor, timeDur.String(), "match", methodColor, resetColor, r.Method, r.URL.Path, + routerInfo.pattern) } else { - devInfo = fmt.Sprintf("|%s %3d %s|%7s|%8s|%s %s %-7s %-3s", statusColor, statusCode, resetColor, + devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-3s", statusColor, statusCode, resetColor, timeDur.String(), "match", methodColor, resetColor, r.Method, r.URL.Path) } } else { - devInfo = fmt.Sprintf("|%s %3d %s|%7s|%8s|%s %s %-7s %-3s", statusColor, statusCode, resetColor, + devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-3s", statusColor, statusCode, resetColor, timeDur.String(), "nomatch", methodColor, resetColor, r.Method, r.URL.Path) } - logs.Debug(devInfo) + if iswin { + logs.W32Debug(devInfo) + } else { + logs.Debug(devInfo) + } } // Call WriteHeader if status code has been set changed From c893b3472cbb17524505f14c8849a678aa6f55f2 Mon Sep 17 00:00:00 2001 From: Faissal Elamraoui Date: Fri, 24 Jun 2016 15:51:59 +0200 Subject: [PATCH 14/96] added dep to .travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 93536488..fbba71eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ install: - go get github.com/siddontang/ledisdb/config - go get github.com/siddontang/ledisdb/ledis - go get github.com/ssdb/gossdb/ssdb + - go get github.com/shiena/ansicolor before_script: - psql --version - sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi" From f9f92b4f61709fc673e9c081cc4f5740a7b13d72 Mon Sep 17 00:00:00 2001 From: Joshua Santos Date: Tue, 28 Jun 2016 15:19:58 -0700 Subject: [PATCH 15/96] Merge meta tag into parseFormField --- templatefunc.go | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/templatefunc.go b/templatefunc.go index 0f9af481..827c9b6b 100644 --- a/templatefunc.go +++ b/templatefunc.go @@ -421,8 +421,7 @@ func RenderForm(obj interface{}) template.HTML { fieldT := objT.Field(i) - label, name, fType, id, class, ignored := parseFormTag(fieldT) - required := parseMetaTag(fieldT) + label, name, fType, id, class, ignored, required := parseFormTag(fieldT) if ignored { continue } @@ -467,7 +466,7 @@ func isValidForInput(fType string) bool { // parseFormTag takes the stuct-tag of a StructField and parses the `form` value. // returned are the form label, name-property, type and wether the field should be ignored. -func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id string, class string, ignored bool) { +func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id string, class string, ignored bool, required bool) { tags := strings.Split(fieldT.Tag.Get("form"), ",") label = fieldT.Name + ": " name = fieldT.Name @@ -476,6 +475,15 @@ func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id str id = fieldT.Tag.Get("id") class = fieldT.Tag.Get("class") + meta := strings.Split(fieldT.Tag.Get("meta"), ",") + required = false + switch len(meta) { + case 1: + if len(meta[0]) > 0 && meta[0] != "-" { + required = true + } + } + switch len(tags) { case 1: if tags[0] == "-" { @@ -506,20 +514,6 @@ func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id str return } -// parseMetaTag takes the stuct-tag of a StructField and parses the `meta` value. -// returned is the boolean of whether the field is required -func parseMetaTag(fieldT reflect.StructField) (required bool) { - meta := strings.Split(fieldT.Tag.Get("meta"), ",") - required = false - switch len(meta) { - case 1: - if len(meta[0]) > 0 && meta[0] != "-" { - required = true - } - } - return -} - func isStructPtr(t reflect.Type) bool { return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct } From 21c78216923d0a507d7f7740a931f495f8f31cc5 Mon Sep 17 00:00:00 2001 From: Joshua Santos Date: Tue, 28 Jun 2016 16:24:32 -0700 Subject: [PATCH 16/96] Add test function --- templatefunc_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/templatefunc_test.go b/templatefunc_test.go index 98fbf7ab..a4a2c237 100644 --- a/templatefunc_test.go +++ b/templatefunc_test.go @@ -195,15 +195,20 @@ func TestRenderForm(t *testing.T) { } func TestRenderFormField(t *testing.T) { - html := renderFormField("Label: ", "Name", "text", "Value", "", "") + html := renderFormField("Label: ", "Name", "text", "Value", "", "", false) if html != `Label: ` { t.Errorf("Wrong html output for input[type=text]: %v ", html) } - html = renderFormField("Label: ", "Name", "textarea", "Value", "", "") + html = renderFormField("Label: ", "Name", "textarea", "Value", "", "", false) if html != `Label: ` { t.Errorf("Wrong html output for textarea: %v ", html) } + + html = renderFormField("Label: ", "Name", "textarea", "Value", "", "", true) + if html != `Label: ` { + t.Errorf("Wrong html output for textarea: %v ", html) + } } func TestParseFormTag(t *testing.T) { From 8917fe44a91505457494631f267757b8b7e57508 Mon Sep 17 00:00:00 2001 From: Joshua Santos Date: Tue, 28 Jun 2016 16:31:45 -0700 Subject: [PATCH 17/96] Add test functions --- templatefunc_test.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/templatefunc_test.go b/templatefunc_test.go index a4a2c237..6f337928 100644 --- a/templatefunc_test.go +++ b/templatefunc_test.go @@ -219,35 +219,42 @@ func TestParseFormTag(t *testing.T) { OnlyLabel int `form:",,年龄:"` OnlyName int `form:"name" id:"name" class:"form-name"` Ignored int `form:"-"` + Required int `form:"name" meta:"true"` } objT := reflect.TypeOf(&user{}).Elem() - label, name, fType, id, class, ignored := parseFormTag(objT.Field(0)) + label, name, fType, id, class, ignored, required := parseFormTag(objT.Field(0)) if !(name == "name" && label == "年龄:" && fType == "text" && ignored == false) { t.Errorf("Form Tag with name, label and type was not correctly parsed.") } - label, name, fType, id, class, ignored = parseFormTag(objT.Field(1)) + label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(1)) if !(name == "NoName" && label == "年龄:" && fType == "hidden" && ignored == false) { t.Errorf("Form Tag with label and type but without name was not correctly parsed.") } - label, name, fType, id, class, ignored = parseFormTag(objT.Field(2)) + label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(2)) if !(name == "OnlyLabel" && label == "年龄:" && fType == "text" && ignored == false) { t.Errorf("Form Tag containing only label was not correctly parsed.") } - label, name, fType, id, class, ignored = parseFormTag(objT.Field(3)) + label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(3)) if !(name == "name" && label == "OnlyName: " && fType == "text" && ignored == false && id == "name" && class == "form-name") { t.Errorf("Form Tag containing only name was not correctly parsed.") } - label, name, fType, id, class, ignored = parseFormTag(objT.Field(4)) + label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(4)) if ignored == false { t.Errorf("Form Tag that should be ignored was not correctly parsed.") } + + label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(5)) + if !(name == "name" && required == true) { + t.Errorf("Form Tag containing only name and required was not correctly parsed.") + } + } func TestMapGet(t *testing.T) { From 84b6bef7d064371365822d60928e163f54988b0d Mon Sep 17 00:00:00 2001 From: Joshua Santos Date: Tue, 28 Jun 2016 16:39:09 -0700 Subject: [PATCH 18/96] Required field useful for not only input --- templatefunc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templatefunc.go b/templatefunc.go index 827c9b6b..d6afaf15 100644 --- a/templatefunc.go +++ b/templatefunc.go @@ -450,7 +450,7 @@ func renderFormField(label, name, fType string, value interface{}, id string, cl return fmt.Sprintf(`%v`, label, id, class, name, fType, value, requiredString) } - return fmt.Sprintf(`%v<%v%v%v name="%v">%v`, label, fType, id, class, name, value, fType) + return fmt.Sprintf(`%v<%v%v%v name="%v"%v>%v`, label, fType, id, class, name, requiredString, value, fType) } // isValidForInput checks if fType is a valid value for the `type` property of an HTML input element. From e0393b721ced07de32ae3d57e20b1fcd214d97e6 Mon Sep 17 00:00:00 2001 From: Joshua Santos Date: Thu, 30 Jun 2016 10:32:53 -0700 Subject: [PATCH 19/96] Change meta to required, and refactor a bit to cover edge cases --- templatefunc.go | 9 +++------ templatefunc_test.go | 24 ++++++++++++++++++------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/templatefunc.go b/templatefunc.go index d6afaf15..36442984 100644 --- a/templatefunc.go +++ b/templatefunc.go @@ -475,13 +475,10 @@ func parseFormTag(fieldT reflect.StructField) (label, name, fType string, id str id = fieldT.Tag.Get("id") class = fieldT.Tag.Get("class") - meta := strings.Split(fieldT.Tag.Get("meta"), ",") required = false - switch len(meta) { - case 1: - if len(meta[0]) > 0 && meta[0] != "-" { - required = true - } + required_field := fieldT.Tag.Get("required") + if required_field != "-" && required_field != "" { + required, _ = strconv.ParseBool(required_field) } switch len(tags) { diff --git a/templatefunc_test.go b/templatefunc_test.go index 6f337928..86de37ae 100644 --- a/templatefunc_test.go +++ b/templatefunc_test.go @@ -214,12 +214,14 @@ func TestRenderFormField(t *testing.T) { func TestParseFormTag(t *testing.T) { // create struct to contain field with different types of struct-tag `form` type user struct { - All int `form:"name,text,年龄:"` - NoName int `form:",hidden,年龄:"` - OnlyLabel int `form:",,年龄:"` - OnlyName int `form:"name" id:"name" class:"form-name"` - Ignored int `form:"-"` - Required int `form:"name" meta:"true"` + All int `form:"name,text,年龄:"` + NoName int `form:",hidden,年龄:"` + OnlyLabel int `form:",,年龄:"` + OnlyName int `form:"name" id:"name" class:"form-name"` + Ignored int `form:"-"` + Required int `form:"name" required:"true"` + IgnoreRequired int `form:"name"` + NotRequired int `form:"name" required:"false"` } objT := reflect.TypeOf(&user{}).Elem() @@ -255,6 +257,16 @@ func TestParseFormTag(t *testing.T) { t.Errorf("Form Tag containing only name and required was not correctly parsed.") } + label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(6)) + if !(name == "name" && required == false) { + t.Errorf("Form Tag containing only name and ignore required was not correctly parsed.") + } + + label, name, fType, id, class, ignored, required = parseFormTag(objT.Field(7)) + if !(name == "name" && required == false) { + t.Errorf("Form Tag containing only name and not required was not correctly parsed.") + } + } func TestMapGet(t *testing.T) { From fee06a23bd74fcb537e4122efbbd78d3f9c8ee32 Mon Sep 17 00:00:00 2001 From: saturn4er Date: Sun, 3 Jul 2016 14:44:11 +0300 Subject: [PATCH 20/96] Add template prefix field to controller --- controller.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/controller.go b/controller.go index 85894275..df186731 100644 --- a/controller.go +++ b/controller.go @@ -71,6 +71,7 @@ type Controller struct { TplName string Layout string LayoutSections map[string]string // the key is the section name and the value is the template name + TplPrefix string TplExt string EnableRender bool @@ -226,6 +227,9 @@ func (c *Controller) renderTemplate() (bytes.Buffer, error) { var buf bytes.Buffer if c.TplName == "" { c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt + if c.TplPrefix != "" { + c.TplName = c.TplPrefix + c.TplName + } } if BConfig.RunMode == DEV { buildFiles := []string{c.TplName} From 9ab5f6d8083b06a325ea1d3cc0018dad664ea168 Mon Sep 17 00:00:00 2001 From: shev_yan Date: Wed, 6 Jul 2016 12:53:47 +0800 Subject: [PATCH 21/96] use keyed fields to pass go vet --- parser.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/parser.go b/parser.go index 46d02320..1719cd34 100644 --- a/parser.go +++ b/parser.go @@ -164,10 +164,10 @@ func genRouterCode() { globalinfo = globalinfo + ` beego.GlobalControllerRouter["` + k + `"] = append(beego.GlobalControllerRouter["` + k + `"], beego.ControllerComments{ - "` + strings.TrimSpace(c.Method) + `", - ` + "`" + c.Router + "`" + `, - ` + allmethod + `, - ` + params + `}) + Method: "` + strings.TrimSpace(c.Method) + `", + ` + "Router: `" + c.Router + "`" + `, + AllowHTTPMethods: ` + allmethod + `, + Params: ` + params + `}) ` } } From 0943ef9e7403357288ef9d864a3d353748e9b35c Mon Sep 17 00:00:00 2001 From: saturn4er Date: Thu, 14 Jul 2016 11:48:49 +0300 Subject: [PATCH 22/96] Add TplPrefix to TplName also. --- controller.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/controller.go b/controller.go index df186731..4cfbf5be 100644 --- a/controller.go +++ b/controller.go @@ -227,9 +227,9 @@ func (c *Controller) renderTemplate() (bytes.Buffer, error) { var buf bytes.Buffer if c.TplName == "" { c.TplName = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt - if c.TplPrefix != "" { - c.TplName = c.TplPrefix + c.TplName - } + } + if c.TplPrefix != "" { + c.TplName = c.TplPrefix + c.TplName } if BConfig.RunMode == DEV { buildFiles := []string{c.TplName} From 8099a81b7a5418d96626349149a5626646811a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?maxin=5B=E9=A9=AC=E9=91=AB=5D?= Date: Fri, 15 Jul 2016 19:13:35 +0800 Subject: [PATCH 23/96] avoid error when the callback function not exisit --- context/output.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/context/output.go b/context/output.go index 17404702..3437b2c4 100644 --- a/context/output.go +++ b/context/output.go @@ -208,7 +208,8 @@ func (output *BeegoOutput) JSONP(data interface{}, hasIndent bool) error { if callback == "" { return errors.New(`"callback" parameter required`) } - callbackContent := bytes.NewBufferString(" " + template.JSEscapeString(callback)) + callback = template.JSEscapeString(callback) + callbackContent := bytes.NewBufferString(" if(window." + callback + ")" + callback) callbackContent.WriteString("(") callbackContent.Write(content) callbackContent.WriteString(");\r\n") From ee2627931190249840e1bcd3c84df6c94add17a4 Mon Sep 17 00:00:00 2001 From: simsun Date: Mon, 18 Jul 2016 22:57:13 +0800 Subject: [PATCH 24/96] fix(context): retrieve scheme from X-Forwarded-Proto when it isn't none --- context/input.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/context/input.go b/context/input.go index edfdf530..c47996c9 100644 --- a/context/input.go +++ b/context/input.go @@ -89,6 +89,9 @@ func (input *BeegoInput) Site() string { // Scheme returns request scheme as "http" or "https". func (input *BeegoInput) Scheme() string { + if scheme := input.Header("X-Forwarded-Proto"); scheme != "" { + return scheme + } if input.Context.Request.URL.Scheme != "" { return input.Context.Request.URL.Scheme } From 4b8ecced83fda708d4bcdfab10ec6b2cda3b66d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cfudali113=E2=80=9D?= <“fudali113@gmail.com”> Date: Wed, 20 Jul 2016 14:37:05 +0800 Subject: [PATCH 25/96] orm insert or update --- orm/db.go | 106 ++++++++++++++++++++++++++++++++++++++++++++++++ orm/orm.go | 13 ++++++ orm/orm_test.go | 73 +++++++++++++++++++++++++++++++++ orm/types.go | 6 +++ 4 files changed, 198 insertions(+) diff --git a/orm/db.go b/orm/db.go index 9964e263..c4b0e046 100644 --- a/orm/db.go +++ b/orm/db.go @@ -488,6 +488,112 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s return id, err } +//insert or update a row +//If your primary key or unique column conflict will update +//if no will insert +func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, dn string, args ...string) (int64, error) { + iouStr := "" + mysql := "mysql" + postgres := "postgres" + argsMap := map[string]string{} + args0 := "" + if dn == mysql { + iouStr = "ON DUPLICATE KEY UPDATE" + } else if dn == postgres { + if len(args) == 0 || (len(strings.Split(args0, "=")) != 1) { + return 0, fmt.Errorf("`%s` use insert or update must have a conflict column arg in first", dn) + } else { + args0 = args[0] + iouStr = fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET", args0) + } + } else { + return 0, fmt.Errorf("`%s` nonsupport insert or update in beego", dn) + } + //Get on the key-value pairs + for _, v := range args { + kv := strings.Split(v, "=") + if len(kv) == 2 { + argsMap[kv[0]] = kv[1] + } + } + + isMulti := false + names := make([]string, 0, len(mi.fields.dbcols)-1) + Q := d.ins.TableQuote() + values, _, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, tz) + + if err != nil { + return 0, err + } + + marks := make([]string, len(names)) + updateValues := make([]interface{}, 0) + updates := make([]string, len(names)) + var conflitValue interface{} + for i, v := range names { + marks[i] = "?" + valueStr := argsMap[v] + if v == args0 { + conflitValue = values[i] + } + if valueStr != "" { + switch dn { + case mysql: + updates[i] = v + "=" + valueStr + break + case postgres: + if conflitValue != nil { + //postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values + updates[i] = fmt.Sprintf("%s=(select %s from %s where %s = ? )", v, valueStr, mi.table, args[0]) + updateValues = append(updateValues, conflitValue) + } else { + return 0, fmt.Errorf("`%s` must be in front of `%s` in your struct", args[0], v) + } + break + } + } else { + updates[i] = v + "=?" + updateValues = append(updateValues, values[i]) + } + } + + values = append(values, updateValues...) + + sep := fmt.Sprintf("%s, %s", Q, Q) + qmarks := strings.Join(marks, ", ") + qupdates := strings.Join(updates, ", ") + columns := strings.Join(names, sep) + + multi := len(values) / len(names) + + if isMulti { + qmarks = strings.Repeat(qmarks+"), (", multi-1) + qmarks + } + //conflitValue maybe is a int,can`t use fmt.Sprintf + query := fmt.Sprintf("INSERT INTO %s%s%s (%s%s%s) VALUES (%s) %s "+qupdates, Q, mi.table, Q, Q, columns, Q, qmarks, iouStr) + + d.ins.ReplaceMarks(&query) + + if isMulti || !d.ins.HasReturningID(mi, &query) { + res, err := q.Exec(query, values...) + if err == nil { + if isMulti { + return res.RowsAffected() + } + return res.LastInsertId() + } + return 0, err + } + + row := q.QueryRow(query, values...) + var id int64 + err = row.Scan(&id) + if err.Error() == `pq: syntax error at or near "ON"` { + err = fmt.Errorf("postgres version must 9.5 or higher") + } + return id, err +} + // execute update sql dbQuerier with given struct reflect.Value. func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) { pkName, pkValue, ok := getExistPk(mi, ind) diff --git a/orm/orm.go b/orm/orm.go index 5e43ae59..fe189037 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -209,6 +209,19 @@ func (o *orm) InsertMulti(bulk int, mds interface{}) (int64, error) { return cnt, nil } +//insert or update data to database +func (o *orm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) { + mi, ind := o.getMiInd(md, true) + id, err := o.alias.DbBaser.InsertOrUpdate(o.db, mi, ind, o.alias.TZ, o.alias.DriverName, colConflitAndArgs...) + if err != nil { + return id, err + } + + o.setPk(mi, ind, id) + + return id, nil +} + // update model to database. // cols set the columns those want to update. func (o *orm) Update(md interface{}, cols ...string) (int64, error) { diff --git a/orm/orm_test.go b/orm/orm_test.go index b5973448..aac9fef8 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -2174,3 +2174,76 @@ func TestIgnoreCaseTag(t *testing.T) { throwFail(t, AssertIs(info.fields.GetByName("Name02").column, "Name")) throwFail(t, AssertIs(info.fields.GetByName("Name03").column, "name")) } +func TestInsertOrUpdate(t *testing.T) { + user := User{UserName: "unique_username133", Status: 1, Password: "o"} + user1 := User{UserName: "unique_username133", Status: 2, Password: "o"} + user2 := User{UserName: "unique_username133", Status: 3, Password: "oo"} + dORM.Insert(&user) + fmt.Println(dORM.Driver().Name()) + if dORM.Driver().Name() == "sqlite3" { + fmt.Println("sqlite3 is nonsupport") + return + } + //test1 普通操作 + _, err := dORM.InsertOrUpdate(&user1, "UserName") + if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { + fmt.Println(err) + } else { + throwFailNow(t, err) + } + test := User{UserName: "unique_username133"} + time.Sleep(time.Second * 1) + dORM.Read(&test, "UserName") + throwFailNow(t, AssertIs(user1.Status, test.Status)) + //test2 普通操作 + _, err = dORM.InsertOrUpdate(&user2, "UserName") + if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { + fmt.Println(err) + } else { + throwFailNow(t, err) + time.Sleep(time.Second * 1) + dORM.Read(&test, "UserName") + throwFailNow(t, AssertIs(user2.Status, test.Status)) + throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password))) + } + //test3 数字 + 操作 + _, err = dORM.InsertOrUpdate(&user2, "UserName", "Status=Status+1") + if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { + fmt.Println(err) + } else { + throwFailNow(t, err) + time.Sleep(time.Second * 1) + dORM.Read(&test, "UserName") + throwFailNow(t, AssertIs(user2.Status+1, test.Status)) + } + //test4 数字 - 操作 + _, err = dORM.InsertOrUpdate(&user2, "UserName", "Status=Status-1") + if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { + fmt.Println(err) + } else { + throwFailNow(t, err) + time.Sleep(time.Second * 1) + dORM.Read(&test, "UserName") + throwFailNow(t, AssertIs((user2.Status+1)-1, test.Status)) + } + //test5 数字 * 操作 + _, err = dORM.InsertOrUpdate(&user2, "UserName", "Status=Status*3") + if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { + fmt.Println(err) + } else { + throwFailNow(t, err) + time.Sleep(time.Second * 1) + dORM.Read(&test, "UserName") + throwFailNow(t, AssertIs(((user2.Status+1)-1)*3, test.Status)) + } + //test6 数字 / 操作 + _, err = dORM.InsertOrUpdate(&user2, "UserName", "Status=Status/3") + if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { + fmt.Println(err) + } else { + throwFailNow(t, err) + time.Sleep(time.Second * 1) + dORM.Read(&test, "UserName") + throwFailNow(t, AssertIs((((user2.Status+1)-1)*3)/3, test.Status)) + } +} diff --git a/orm/types.go b/orm/types.go index cb55e71a..7864e315 100644 --- a/orm/types.go +++ b/orm/types.go @@ -53,6 +53,11 @@ type Ormer interface { // id, err = Ormer.Insert(user) // user must a pointer and Insert will set user's pk field Insert(interface{}) (int64, error) + //mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value") + //if colu type is integer : can use(+-*/), string : convert(colu,"value") + //postgres: InsertOrUpdate(model,"conflictColumnName") or InsertOrUpdate(model,"conflictColumnName","colu=colu+value") + //if colu type is integer : can use(+-*/), string : colu || "value" + InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) // insert some models to database InsertMulti(bulk int, mds interface{}) (int64, error) // update model to database. @@ -391,6 +396,7 @@ type txEnder interface { type dbBaser interface { Read(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) error Insert(dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error) + InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *time.Location, string, ...string) (int64, error) InsertMulti(dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error) InsertValue(dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error) InsertStmt(stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error) From ec521ad166bdeab116ca296773c3f1776bcd2870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cfudali113=E2=80=9D?= <“fudali113@gmail.com”> Date: Wed, 20 Jul 2016 15:13:18 +0800 Subject: [PATCH 26/96] update --- orm/orm_test.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/orm/orm_test.go b/orm/orm_test.go index aac9fef8..d2d10a6d 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -2175,10 +2175,12 @@ func TestIgnoreCaseTag(t *testing.T) { throwFail(t, AssertIs(info.fields.GetByName("Name03").column, "name")) } func TestInsertOrUpdate(t *testing.T) { + RegisterModel(new(User)) user := User{UserName: "unique_username133", Status: 1, Password: "o"} user1 := User{UserName: "unique_username133", Status: 2, Password: "o"} user2 := User{UserName: "unique_username133", Status: 3, Password: "oo"} dORM.Insert(&user) + test := User{UserName: "unique_username133"} fmt.Println(dORM.Driver().Name()) if dORM.Driver().Name() == "sqlite3" { fmt.Println("sqlite3 is nonsupport") @@ -2190,18 +2192,15 @@ func TestInsertOrUpdate(t *testing.T) { fmt.Println(err) } else { throwFailNow(t, err) + dORM.Read(&test, "UserName") + throwFailNow(t, AssertIs(user1.Status, test.Status)) } - test := User{UserName: "unique_username133"} - time.Sleep(time.Second * 1) - dORM.Read(&test, "UserName") - throwFailNow(t, AssertIs(user1.Status, test.Status)) //test2 普通操作 _, err = dORM.InsertOrUpdate(&user2, "UserName") if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { fmt.Println(err) } else { throwFailNow(t, err) - time.Sleep(time.Second * 1) dORM.Read(&test, "UserName") throwFailNow(t, AssertIs(user2.Status, test.Status)) throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password))) @@ -2212,7 +2211,6 @@ func TestInsertOrUpdate(t *testing.T) { fmt.Println(err) } else { throwFailNow(t, err) - time.Sleep(time.Second * 1) dORM.Read(&test, "UserName") throwFailNow(t, AssertIs(user2.Status+1, test.Status)) } @@ -2222,7 +2220,6 @@ func TestInsertOrUpdate(t *testing.T) { fmt.Println(err) } else { throwFailNow(t, err) - time.Sleep(time.Second * 1) dORM.Read(&test, "UserName") throwFailNow(t, AssertIs((user2.Status+1)-1, test.Status)) } @@ -2232,7 +2229,6 @@ func TestInsertOrUpdate(t *testing.T) { fmt.Println(err) } else { throwFailNow(t, err) - time.Sleep(time.Second * 1) dORM.Read(&test, "UserName") throwFailNow(t, AssertIs(((user2.Status+1)-1)*3, test.Status)) } @@ -2242,7 +2238,6 @@ func TestInsertOrUpdate(t *testing.T) { fmt.Println(err) } else { throwFailNow(t, err) - time.Sleep(time.Second * 1) dORM.Read(&test, "UserName") throwFailNow(t, AssertIs((((user2.Status+1)-1)*3)/3, test.Status)) } From 530c32017cb04cd9d790f382ade868321193d08d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cfudali113=E2=80=9D?= <“fudali113@gmail.com”> Date: Wed, 20 Jul 2016 15:33:30 +0800 Subject: [PATCH 27/96] update --- orm/db.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orm/db.go b/orm/db.go index c4b0e046..5fbb8ab3 100644 --- a/orm/db.go +++ b/orm/db.go @@ -533,7 +533,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, t for i, v := range names { marks[i] = "?" valueStr := argsMap[v] - if v == args0 { + if strings.ToLower(v) == strings.ToLower(args0) { conflitValue = values[i] } if valueStr != "" { From 50c5df32b1bdfe6cd2ba7ada6527b5cc312a49ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cfudali113=E2=80=9D?= <“fudali113@gmail.com”> Date: Wed, 20 Jul 2016 16:26:02 +0800 Subject: [PATCH 28/96] update --- orm/db.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/orm/db.go b/orm/db.go index 5fbb8ab3..871f4a5d 100644 --- a/orm/db.go +++ b/orm/db.go @@ -503,7 +503,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, t if len(args) == 0 || (len(strings.Split(args0, "=")) != 1) { return 0, fmt.Errorf("`%s` use insert or update must have a conflict column arg in first", dn) } else { - args0 = args[0] + args0 = strings.ToLower(args[0]) iouStr = fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET", args0) } } else { @@ -513,7 +513,8 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, t for _, v := range args { kv := strings.Split(v, "=") if len(kv) == 2 { - argsMap[kv[0]] = kv[1] + k := strings.ToLower(kv[0]) + argsMap[k] = kv[1] } } @@ -532,8 +533,9 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, t var conflitValue interface{} for i, v := range names { marks[i] = "?" - valueStr := argsMap[v] - if strings.ToLower(v) == strings.ToLower(args0) { + vtl := strings.ToLower(v) + valueStr := argsMap[vtl] + if vtl == args0 { conflitValue = values[i] } if valueStr != "" { @@ -544,10 +546,10 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, t case postgres: if conflitValue != nil { //postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values - updates[i] = fmt.Sprintf("%s=(select %s from %s where %s = ? )", v, valueStr, mi.table, args[0]) + updates[i] = fmt.Sprintf("%s=(select %s from %s where %s = ? )", v, valueStr, mi.table, args0) updateValues = append(updateValues, conflitValue) } else { - return 0, fmt.Errorf("`%s` must be in front of `%s` in your struct", args[0], v) + return 0, fmt.Errorf("`%s` must be in front of `%s` in your struct", args0, v) } break } From 6d1b203bcabe9b3e79435617ecf05ecc4c05c956 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cfudali113=E2=80=9D?= <“fudali113@gmail.com”> Date: Wed, 20 Jul 2016 16:52:14 +0800 Subject: [PATCH 29/96] update --- orm/orm_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/orm/orm_test.go b/orm/orm_test.go index d2d10a6d..c4f2d8da 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -2187,58 +2187,58 @@ func TestInsertOrUpdate(t *testing.T) { return } //test1 普通操作 - _, err := dORM.InsertOrUpdate(&user1, "UserName") + _, err := dORM.InsertOrUpdate(&user1, "user_name") if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { fmt.Println(err) } else { throwFailNow(t, err) - dORM.Read(&test, "UserName") + dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user1.Status, test.Status)) } //test2 普通操作 - _, err = dORM.InsertOrUpdate(&user2, "UserName") + _, err = dORM.InsertOrUpdate(&user2, "user_name") if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { fmt.Println(err) } else { throwFailNow(t, err) - dORM.Read(&test, "UserName") + dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user2.Status, test.Status)) throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password))) } //test3 数字 + 操作 - _, err = dORM.InsertOrUpdate(&user2, "UserName", "Status=Status+1") + _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status+1") if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { fmt.Println(err) } else { throwFailNow(t, err) - dORM.Read(&test, "UserName") + dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user2.Status+1, test.Status)) } //test4 数字 - 操作 - _, err = dORM.InsertOrUpdate(&user2, "UserName", "Status=Status-1") + _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status-1") if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { fmt.Println(err) } else { throwFailNow(t, err) - dORM.Read(&test, "UserName") + dORM.Read(&test, "user_name") throwFailNow(t, AssertIs((user2.Status+1)-1, test.Status)) } //test5 数字 * 操作 - _, err = dORM.InsertOrUpdate(&user2, "UserName", "Status=Status*3") + _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status*3") if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { fmt.Println(err) } else { throwFailNow(t, err) - dORM.Read(&test, "UserName") + dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(((user2.Status+1)-1)*3, test.Status)) } //test6 数字 / 操作 - _, err = dORM.InsertOrUpdate(&user2, "UserName", "Status=Status/3") + _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status/3") if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { fmt.Println(err) } else { throwFailNow(t, err) - dORM.Read(&test, "UserName") + dORM.Read(&test, "user_name") throwFailNow(t, AssertIs((((user2.Status+1)-1)*3)/3, test.Status)) } } From e2316c4b9ececa12b2b48078c8d8d192eeb4f756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cfudali113=E2=80=9D?= <“fudali113@gmail.com”> Date: Wed, 20 Jul 2016 17:28:26 +0800 Subject: [PATCH 30/96] update --- orm/orm_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/orm/orm_test.go b/orm/orm_test.go index c4f2d8da..5b44f286 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -2186,7 +2186,7 @@ func TestInsertOrUpdate(t *testing.T) { fmt.Println("sqlite3 is nonsupport") return } - //test1 普通操作 + //test1 _, err := dORM.InsertOrUpdate(&user1, "user_name") if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { fmt.Println(err) @@ -2195,7 +2195,7 @@ func TestInsertOrUpdate(t *testing.T) { dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user1.Status, test.Status)) } - //test2 普通操作 + //test2 _, err = dORM.InsertOrUpdate(&user2, "user_name") if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { fmt.Println(err) @@ -2205,7 +2205,7 @@ func TestInsertOrUpdate(t *testing.T) { throwFailNow(t, AssertIs(user2.Status, test.Status)) throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password))) } - //test3 数字 + 操作 + //test3 + _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status+1") if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { fmt.Println(err) @@ -2214,7 +2214,7 @@ func TestInsertOrUpdate(t *testing.T) { dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user2.Status+1, test.Status)) } - //test4 数字 - 操作 + //test4 - _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status-1") if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { fmt.Println(err) @@ -2223,7 +2223,7 @@ func TestInsertOrUpdate(t *testing.T) { dORM.Read(&test, "user_name") throwFailNow(t, AssertIs((user2.Status+1)-1, test.Status)) } - //test5 数字 * 操作 + //test5 * _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status*3") if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { fmt.Println(err) @@ -2232,7 +2232,7 @@ func TestInsertOrUpdate(t *testing.T) { dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(((user2.Status+1)-1)*3, test.Status)) } - //test6 数字 / 操作 + //test6 / _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status/3") if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { fmt.Println(err) From 3583ad8cc0784cf208ccce1272440f004e60cf5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cfudali113=E2=80=9D?= <“fudali113@gmail.com”> Date: Thu, 21 Jul 2016 15:49:55 +0800 Subject: [PATCH 31/96] update annotation --- orm/db.go | 6 +++--- orm/orm.go | 2 +- orm/types.go | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/orm/db.go b/orm/db.go index 871f4a5d..40e71d12 100644 --- a/orm/db.go +++ b/orm/db.go @@ -488,9 +488,9 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s return id, err } -//insert or update a row -//If your primary key or unique column conflict will update -//if no will insert +// InsertOrUpdate a row +// If your primary key or unique column conflict will update +// If no will insert func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, dn string, args ...string) (int64, error) { iouStr := "" mysql := "mysql" diff --git a/orm/orm.go b/orm/orm.go index fe189037..c84dddea 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -209,7 +209,7 @@ func (o *orm) InsertMulti(bulk int, mds interface{}) (int64, error) { return cnt, nil } -//insert or update data to database +// InsertOrUpdate data to database func (o *orm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) { mi, ind := o.getMiInd(md, true) id, err := o.alias.DbBaser.InsertOrUpdate(o.db, mi, ind, o.alias.TZ, o.alias.DriverName, colConflitAndArgs...) diff --git a/orm/types.go b/orm/types.go index 7864e315..b375fd65 100644 --- a/orm/types.go +++ b/orm/types.go @@ -53,10 +53,10 @@ type Ormer interface { // id, err = Ormer.Insert(user) // user must a pointer and Insert will set user's pk field Insert(interface{}) (int64, error) - //mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value") - //if colu type is integer : can use(+-*/), string : convert(colu,"value") - //postgres: InsertOrUpdate(model,"conflictColumnName") or InsertOrUpdate(model,"conflictColumnName","colu=colu+value") - //if colu type is integer : can use(+-*/), string : colu || "value" + // mysql:InsertOrUpdate(model) or InsertOrUpdate(model,"colu=colu+value") + // if colu type is integer : can use(+-*/), string : convert(colu,"value") + // postgres: InsertOrUpdate(model,"conflictColumnName") or InsertOrUpdate(model,"conflictColumnName","colu=colu+value") + // if colu type is integer : can use(+-*/), string : colu || "value" InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) // insert some models to database InsertMulti(bulk int, mds interface{}) (int64, error) From e0e888ab8f2050cbe4aedbdd91b391e644ce3d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cfudali113=E2=80=9D?= <“fudali113@gmail.com”> Date: Fri, 22 Jul 2016 12:10:37 +0800 Subject: [PATCH 32/96] =?UTF-8?q?update=20=E2=80=9Cmodification=20hardcode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- orm/db.go | 16 ++++++++-------- orm/orm.go | 2 +- orm/types.go | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/orm/db.go b/orm/db.go index 40e71d12..29c1c825 100644 --- a/orm/db.go +++ b/orm/db.go @@ -491,23 +491,23 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s // InsertOrUpdate a row // If your primary key or unique column conflict will update // If no will insert -func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, dn string, args ...string) (int64, error) { +func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, driver DriverType, args ...string) (int64, error) { iouStr := "" - mysql := "mysql" - postgres := "postgres" + mysql := DRMySQL + postgres := DRPostgres argsMap := map[string]string{} args0 := "" - if dn == mysql { + if driver == mysql { iouStr = "ON DUPLICATE KEY UPDATE" - } else if dn == postgres { + } else if driver == postgres { if len(args) == 0 || (len(strings.Split(args0, "=")) != 1) { - return 0, fmt.Errorf("`%s` use insert or update must have a conflict column arg in first", dn) + return 0, fmt.Errorf("`%s` use insert or update must have a conflict column arg in first", driver) } else { args0 = strings.ToLower(args[0]) iouStr = fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET", args0) } } else { - return 0, fmt.Errorf("`%s` nonsupport insert or update in beego", dn) + return 0, fmt.Errorf("`%s` nonsupport insert or update in beego", driver) } //Get on the key-value pairs for _, v := range args { @@ -539,7 +539,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, t conflitValue = values[i] } if valueStr != "" { - switch dn { + switch driver { case mysql: updates[i] = v + "=" + valueStr break diff --git a/orm/orm.go b/orm/orm.go index c84dddea..d7ebb371 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -212,7 +212,7 @@ func (o *orm) InsertMulti(bulk int, mds interface{}) (int64, error) { // InsertOrUpdate data to database func (o *orm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) { mi, ind := o.getMiInd(md, true) - id, err := o.alias.DbBaser.InsertOrUpdate(o.db, mi, ind, o.alias.TZ, o.alias.DriverName, colConflitAndArgs...) + id, err := o.alias.DbBaser.InsertOrUpdate(o.db, mi, ind, o.alias.TZ, o.alias.Driver, colConflitAndArgs...) if err != nil { return id, err } diff --git a/orm/types.go b/orm/types.go index b375fd65..32d8c233 100644 --- a/orm/types.go +++ b/orm/types.go @@ -396,7 +396,7 @@ type txEnder interface { type dbBaser interface { Read(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) error Insert(dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error) - InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *time.Location, string, ...string) (int64, error) + InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *time.Location, DriverType, ...string) (int64, error) InsertMulti(dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error) InsertValue(dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error) InsertStmt(stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error) From bf17558d066d5bc0c25d807e8e6a0c5a78d13a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cfudali113=E2=80=9D?= <“fudali113@gmail.com”> Date: Fri, 22 Jul 2016 12:25:30 +0800 Subject: [PATCH 33/96] =?UTF-8?q?update=20=E2=80=9Cmodification=20hardcode?= =?UTF-8?q?=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- orm/db.go | 9 +++++---- orm/orm.go | 2 +- orm/types.go | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/orm/db.go b/orm/db.go index 29c1c825..613fc8a9 100644 --- a/orm/db.go +++ b/orm/db.go @@ -491,23 +491,24 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s // InsertOrUpdate a row // If your primary key or unique column conflict will update // If no will insert -func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, driver DriverType, args ...string) (int64, error) { +func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) { iouStr := "" mysql := DRMySQL postgres := DRPostgres + driver := a.Driver argsMap := map[string]string{} args0 := "" if driver == mysql { iouStr = "ON DUPLICATE KEY UPDATE" } else if driver == postgres { if len(args) == 0 || (len(strings.Split(args0, "=")) != 1) { - return 0, fmt.Errorf("`%s` use insert or update must have a conflict column arg in first", driver) + return 0, fmt.Errorf("`%s` use insert or update must have a conflict column arg in first", a.DriverName) } else { args0 = strings.ToLower(args[0]) iouStr = fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET", args0) } } else { - return 0, fmt.Errorf("`%s` nonsupport insert or update in beego", driver) + return 0, fmt.Errorf("`%s` nonsupport insert or update in beego", a.DriverName) } //Get on the key-value pairs for _, v := range args { @@ -521,7 +522,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, t isMulti := false names := make([]string, 0, len(mi.fields.dbcols)-1) Q := d.ins.TableQuote() - values, _, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, tz) + values, _, err := d.collectValues(mi, ind, mi.fields.dbcols, true, true, &names, a.TZ) if err != nil { return 0, err diff --git a/orm/orm.go b/orm/orm.go index d7ebb371..994ed7e3 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -212,7 +212,7 @@ func (o *orm) InsertMulti(bulk int, mds interface{}) (int64, error) { // InsertOrUpdate data to database func (o *orm) InsertOrUpdate(md interface{}, colConflitAndArgs ...string) (int64, error) { mi, ind := o.getMiInd(md, true) - id, err := o.alias.DbBaser.InsertOrUpdate(o.db, mi, ind, o.alias.TZ, o.alias.Driver, colConflitAndArgs...) + id, err := o.alias.DbBaser.InsertOrUpdate(o.db, mi, ind, o.alias, colConflitAndArgs...) if err != nil { return id, err } diff --git a/orm/types.go b/orm/types.go index 32d8c233..8c17271d 100644 --- a/orm/types.go +++ b/orm/types.go @@ -396,7 +396,7 @@ type txEnder interface { type dbBaser interface { Read(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) error Insert(dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error) - InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *time.Location, DriverType, ...string) (int64, error) + InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *alias, ...string) (int64, error) InsertMulti(dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error) InsertValue(dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error) InsertStmt(stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error) From 33ef34b95d90f4d9953ba410bf768eb1af8c3f8e Mon Sep 17 00:00:00 2001 From: Maxgis Date: Sun, 24 Jul 2016 15:29:34 +0800 Subject: [PATCH 34/96] =?UTF-8?q?=E5=87=8F=E5=B0=91=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E5=BF=85=E8=A6=81=E7=9A=84=E4=BB=A3=E7=A0=81=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?=E9=9D=99=E6=80=81=E5=89=8D=E7=BC=80=E5=89=8D=E9=9D=A2=E6=9C=89?= =?UTF-8?q?/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.go | 5 ++--- staticfile.go | 5 +---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/config.go b/config.go index 2761e7cb..067f0d1b 100644 --- a/config.go +++ b/config.go @@ -256,13 +256,12 @@ func parseConfig(appConfigPath string) (err error) { sds := strings.Fields(sd) for _, v := range sds { if url2fsmap := strings.SplitN(v, ":", 2); len(url2fsmap) == 2 { - BConfig.WebConfig.StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[1] + BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[1] } else { - BConfig.WebConfig.StaticDir["/"+strings.TrimRight(url2fsmap[0], "/")] = url2fsmap[0] + BConfig.WebConfig.StaticDir["/"+strings.Trim(url2fsmap[0], "/")] = url2fsmap[0] } } } - if sgz := AppConfig.String("StaticExtensionsToGzip"); sgz != "" { extensions := strings.Split(sgz, ",") fileExts := []string{} diff --git a/staticfile.go b/staticfile.go index 0aad2c81..4b19f949 100644 --- a/staticfile.go +++ b/staticfile.go @@ -157,13 +157,10 @@ func searchFile(ctx *context.Context) (string, os.FileInfo, error) { return filePath, fi, nil } } - return "", nil, errors.New(requestPath + " file not find") + return "", nil, errNotStaticRequest } for prefix, staticDir := range BConfig.WebConfig.StaticDir { - if len(prefix) == 0 { - continue - } if !strings.Contains(requestPath, prefix) { continue } From 182a21172fabf88763301622476ce99b09bf57c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cfudali113=E2=80=9D?= <“fudali113@gmail.com”> Date: Tue, 26 Jul 2016 11:15:59 +0800 Subject: [PATCH 35/96] Optimize the code logic --- orm/db.go | 37 ++++++++++++++++--------------------- orm/orm_test.go | 13 +++++++------ 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/orm/db.go b/orm/db.go index 613fc8a9..92b6cfe8 100644 --- a/orm/db.go +++ b/orm/db.go @@ -492,30 +492,28 @@ func (d *dbBase) InsertValue(q dbQuerier, mi *modelInfo, isMulti bool, names []s // If your primary key or unique column conflict will update // If no will insert func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a *alias, args ...string) (int64, error) { - iouStr := "" - mysql := DRMySQL - postgres := DRPostgres - driver := a.Driver - argsMap := map[string]string{} args0 := "" - if driver == mysql { + iouStr := "" + argsMap := map[string]string{} + switch a.Driver { + case DRMySQL: iouStr = "ON DUPLICATE KEY UPDATE" - } else if driver == postgres { - if len(args) == 0 || (len(strings.Split(args0, "=")) != 1) { - return 0, fmt.Errorf("`%s` use insert or update must have a conflict column arg in first", a.DriverName) + case DRPostgres: + if len(args) == 0 { + return 0, fmt.Errorf("`%s` use InsertOrUpdate must have a conflict column", a.DriverName) } else { args0 = strings.ToLower(args[0]) iouStr = fmt.Sprintf("ON CONFLICT (%s) DO UPDATE SET", args0) } - } else { - return 0, fmt.Errorf("`%s` nonsupport insert or update in beego", a.DriverName) + default: + return 0, fmt.Errorf("`%s` nonsupport InsertOrUpdate in beego", a.DriverName) } + //Get on the key-value pairs for _, v := range args { kv := strings.Split(v, "=") if len(kv) == 2 { - k := strings.ToLower(kv[0]) - argsMap[k] = kv[1] + argsMap[strings.ToLower(kv[0])] = kv[1] } } @@ -534,17 +532,15 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a var conflitValue interface{} for i, v := range names { marks[i] = "?" - vtl := strings.ToLower(v) - valueStr := argsMap[vtl] - if vtl == args0 { + valueStr := argsMap[v] + if v == args0 { conflitValue = values[i] } if valueStr != "" { - switch driver { - case mysql: + switch a.Driver { + case DRMySQL: updates[i] = v + "=" + valueStr - break - case postgres: + case DRPostgres: if conflitValue != nil { //postgres ON CONFLICT DO UPDATE SET can`t use colu=colu+values updates[i] = fmt.Sprintf("%s=(select %s from %s where %s = ? )", v, valueStr, mi.table, args0) @@ -552,7 +548,6 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a } else { return 0, fmt.Errorf("`%s` must be in front of `%s` in your struct", args0, v) } - break } } else { updates[i] = v + "=?" diff --git a/orm/orm_test.go b/orm/orm_test.go index 5b44f286..c0e7dacd 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -2188,8 +2188,9 @@ func TestInsertOrUpdate(t *testing.T) { } //test1 _, err := dORM.InsertOrUpdate(&user1, "user_name") - if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { + if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego") { fmt.Println(err) + return } else { throwFailNow(t, err) dORM.Read(&test, "user_name") @@ -2197,7 +2198,7 @@ func TestInsertOrUpdate(t *testing.T) { } //test2 _, err = dORM.InsertOrUpdate(&user2, "user_name") - if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { + if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego") { fmt.Println(err) } else { throwFailNow(t, err) @@ -2207,7 +2208,7 @@ func TestInsertOrUpdate(t *testing.T) { } //test3 + _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status+1") - if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { + if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego") { fmt.Println(err) } else { throwFailNow(t, err) @@ -2216,7 +2217,7 @@ func TestInsertOrUpdate(t *testing.T) { } //test4 - _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status-1") - if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { + if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego") { fmt.Println(err) } else { throwFailNow(t, err) @@ -2225,7 +2226,7 @@ func TestInsertOrUpdate(t *testing.T) { } //test5 * _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status*3") - if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { + if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego") { fmt.Println(err) } else { throwFailNow(t, err) @@ -2234,7 +2235,7 @@ func TestInsertOrUpdate(t *testing.T) { } //test6 / _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status/3") - if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport insert or update in beego") { + if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego") { fmt.Println(err) } else { throwFailNow(t, err) From f471ee90250ffcc5ce1038b835aac8199a2fc2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cfudali113=E2=80=9D?= <“fudali113@gmail.com”> Date: Tue, 26 Jul 2016 12:19:06 +0800 Subject: [PATCH 36/96] update orm_test --- orm/orm_test.go | 43 +++++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/orm/orm_test.go b/orm/orm_test.go index c0e7dacd..eaf78738 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -2187,19 +2187,26 @@ func TestInsertOrUpdate(t *testing.T) { return } //test1 +test1: _, err := dORM.InsertOrUpdate(&user1, "user_name") - if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego") { + if err != nil { fmt.Println(err) - return + if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { + goto test2 + } } else { throwFailNow(t, err) dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user1.Status, test.Status)) } //test2 +test2: _, err = dORM.InsertOrUpdate(&user2, "user_name") - if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego") { + if err != nil { fmt.Println(err) + if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { + goto test3 + } } else { throwFailNow(t, err) dORM.Read(&test, "user_name") @@ -2207,36 +2214,52 @@ func TestInsertOrUpdate(t *testing.T) { throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password))) } //test3 + - _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status+1") - if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego") { +test3: + _, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status+1") + if err != nil { fmt.Println(err) + if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { + goto test4 + } } else { throwFailNow(t, err) dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user2.Status+1, test.Status)) } //test4 - - _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status-1") - if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego") { +test4: + _, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status-1") + if err != nil { fmt.Println(err) + if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { + goto test5 + } } else { throwFailNow(t, err) dORM.Read(&test, "user_name") throwFailNow(t, AssertIs((user2.Status+1)-1, test.Status)) } //test5 * - _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status*3") - if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego") { +test5: + _, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status*3") + if err != nil { fmt.Println(err) + if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { + goto test6 + } } else { throwFailNow(t, err) dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(((user2.Status+1)-1)*3, test.Status)) } //test6 / +test6: _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status/3") - if err != nil && (err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego") { + if err != nil { fmt.Println(err) + if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { + return + } } else { throwFailNow(t, err) dORM.Read(&test, "user_name") From 4e10100575ce5de5fec0d1d39d129b8e9ecedd39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cfudali113=E2=80=9D?= <“fudali113@gmail.com”> Date: Tue, 26 Jul 2016 12:44:16 +0800 Subject: [PATCH 37/96] update orm_test --- orm/orm_test.go | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/orm/orm_test.go b/orm/orm_test.go index eaf78738..1410fd8a 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -2187,81 +2187,75 @@ func TestInsertOrUpdate(t *testing.T) { return } //test1 -test1: _, err := dORM.InsertOrUpdate(&user1, "user_name") if err != nil { fmt.Println(err) if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { - goto test2 + } else { + throwFailNow(t, err) } } else { - throwFailNow(t, err) dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user1.Status, test.Status)) } //test2 -test2: _, err = dORM.InsertOrUpdate(&user2, "user_name") if err != nil { fmt.Println(err) if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { - goto test3 + } else { + throwFailNow(t, err) } } else { - throwFailNow(t, err) dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user2.Status, test.Status)) throwFailNow(t, AssertIs(user2.Password, strings.TrimSpace(test.Password))) } //test3 + -test3: _, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status+1") if err != nil { fmt.Println(err) if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { - goto test4 + } else { + throwFailNow(t, err) } } else { - throwFailNow(t, err) dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(user2.Status+1, test.Status)) } //test4 - -test4: _, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status-1") if err != nil { fmt.Println(err) if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { - goto test5 + } else { + throwFailNow(t, err) } } else { - throwFailNow(t, err) dORM.Read(&test, "user_name") throwFailNow(t, AssertIs((user2.Status+1)-1, test.Status)) } //test5 * -test5: _, err = dORM.InsertOrUpdate(&user2, "user_name", "status=status*3") if err != nil { fmt.Println(err) if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { - goto test6 + } else { + throwFailNow(t, err) } } else { - throwFailNow(t, err) dORM.Read(&test, "user_name") throwFailNow(t, AssertIs(((user2.Status+1)-1)*3, test.Status)) } //test6 / -test6: _, err = dORM.InsertOrUpdate(&user2, "user_name", "Status=Status/3") if err != nil { fmt.Println(err) if err.Error() == "postgres version must 9.5 or higher" || err.Error() == "`sqlite3` nonsupport InsertOrUpdate in beego" { - return + } else { + throwFailNow(t, err) } } else { - throwFailNow(t, err) dORM.Read(&test, "user_name") throwFailNow(t, AssertIs((((user2.Status+1)-1)*3)/3, test.Status)) } From cacf6cde194d909c1c307c0a2f1c284b3943bd9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cfudali113=E2=80=9D?= <“fudali113@gmail.com”> Date: Tue, 26 Jul 2016 14:27:22 +0800 Subject: [PATCH 38/96] update db.go --- orm/db.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orm/db.go b/orm/db.go index 92b6cfe8..ec23e383 100644 --- a/orm/db.go +++ b/orm/db.go @@ -532,7 +532,7 @@ func (d *dbBase) InsertOrUpdate(q dbQuerier, mi *modelInfo, ind reflect.Value, a var conflitValue interface{} for i, v := range names { marks[i] = "?" - valueStr := argsMap[v] + valueStr := argsMap[strings.ToLower(v)] if v == args0 { conflitValue = values[i] } From 4b3ed531581673717a9cfefadae3fd68ba7b6cd9 Mon Sep 17 00:00:00 2001 From: Maxgis Date: Tue, 26 Jul 2016 19:54:10 +0800 Subject: [PATCH 39/96] redirect should add query params --- staticfile.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/staticfile.go b/staticfile.go index 11a2cdcc..7c8b4517 100644 --- a/staticfile.go +++ b/staticfile.go @@ -57,7 +57,11 @@ func serverStaticRouter(ctx *context.Context) { if fileInfo.IsDir() { requestURL := ctx.Input.URL() if requestURL[len(requestURL)-1] != '/' { - ctx.Redirect(302, requestURL+"/") + redirectURL := requestURL + "/" + if ctx.Request.URL.RawQuery != "" { + redirectURL = redirectURL + "?" + ctx.Request.URL.RawQuery + } + ctx.Redirect(302, redirectURL) } else { //serveFile will list dir http.ServeFile(ctx.ResponseWriter, ctx.Request, filePath) From 5485e1334f634ae1b66b7db2e37d6e45ed892640 Mon Sep 17 00:00:00 2001 From: Maxgis Date: Wed, 27 Jul 2016 09:31:53 +0800 Subject: [PATCH 40/96] remove not support encoding --- context/acceptencoder.go | 15 ++++++++------- context/acceptencoder_test.go | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/context/acceptencoder.go b/context/acceptencoder.go index cb735445..350b560d 100644 --- a/context/acceptencoder.go +++ b/context/acceptencoder.go @@ -209,9 +209,13 @@ func parseEncoding(r *http.Request) string { continue } vs := strings.Split(v, ";") + var cf acceptEncoder + var ok bool + if cf, ok = encoderMap[vs[0]]; !ok { + continue + } if len(vs) == 1 { - lastQ = q{vs[0], 1} - break + return cf.name } if len(vs) == 2 { f, _ := strconv.ParseFloat(strings.Replace(vs[1], "q=", "", -1), 64) @@ -219,12 +223,9 @@ func parseEncoding(r *http.Request) string { continue } if f > lastQ.value { - lastQ = q{vs[0], f} + lastQ = q{cf.name, f} } } } - if cf, ok := encoderMap[lastQ.name]; ok { - return cf.name - } - return "" + return lastQ.name } diff --git a/context/acceptencoder_test.go b/context/acceptencoder_test.go index 3afff679..e3d61e27 100644 --- a/context/acceptencoder_test.go +++ b/context/acceptencoder_test.go @@ -41,4 +41,19 @@ func Test_ExtractEncoding(t *testing.T) { if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"*"}}}) != "gzip" { t.Fail() } + if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"x,gzip,deflate"}}}) != "gzip" { + t.Fail() + } + if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip,x,deflate"}}}) != "gzip" { + t.Fail() + } + if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=0.5,x,deflate"}}}) != "deflate" { + t.Fail() + } + if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"x"}}}) != "" { + t.Fail() + } + if parseEncoding(&http.Request{Header: map[string][]string{"Accept-Encoding": {"gzip;q=0.5,x;q=0.8"}}}) != "gzip" { + t.Fail() + } } From 2b867f815268b7cf95cdb5c33f45f4e7bed29bda Mon Sep 17 00:00:00 2001 From: Faissal Elamraoui Date: Wed, 27 Jul 2016 17:42:28 +0200 Subject: [PATCH 41/96] Removed external dependency --- .travis.yml | 1 - logs/color_windows.go | 461 +++++++++++++++++++++++++++++++++++++ logs/color_windows_test.go | 294 +++++++++++++++++++++++ logs/logger.go | 5 +- 4 files changed, 757 insertions(+), 4 deletions(-) create mode 100644 logs/color_windows.go create mode 100644 logs/color_windows_test.go diff --git a/.travis.yml b/.travis.yml index fbba71eb..93536488 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,6 @@ install: - go get github.com/siddontang/ledisdb/config - go get github.com/siddontang/ledisdb/ledis - go get github.com/ssdb/gossdb/ssdb - - go get github.com/shiena/ansicolor before_script: - psql --version - sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi" diff --git a/logs/color_windows.go b/logs/color_windows.go new file mode 100644 index 00000000..f6c6cad3 --- /dev/null +++ b/logs/color_windows.go @@ -0,0 +1,461 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package logs + +import ( + "bytes" + "io" + "strings" + "syscall" + "unsafe" +) + +type ( + outputMode int + csiState int + parseResult int +) + +const ( + outsideCsiCode csiState = iota + firstCsiCode + secondCsiCode +) + +const ( + noConsole parseResult = iota + changedColor + unknown +) + +type ansiColorWriter struct { + w io.Writer + mode outputMode + state csiState + paramStartBuf bytes.Buffer + paramBuf bytes.Buffer +} + +const ( + firstCsiChar byte = '\x1b' + secondeCsiChar byte = '[' + separatorChar byte = ';' + sgrCode byte = 'm' +) + +const ( + foregroundBlue = uint16(0x0001) + foregroundGreen = uint16(0x0002) + foregroundRed = uint16(0x0004) + foregroundIntensity = uint16(0x0008) + backgroundBlue = uint16(0x0010) + backgroundGreen = uint16(0x0020) + backgroundRed = uint16(0x0040) + backgroundIntensity = uint16(0x0080) + underscore = uint16(0x8000) + + foregroundMask = foregroundBlue | foregroundGreen | foregroundRed | foregroundIntensity + backgroundMask = backgroundBlue | backgroundGreen | backgroundRed | backgroundIntensity +) + +const ( + ansiReset = "0" + ansiIntensityOn = "1" + ansiIntensityOff = "21" + ansiUnderlineOn = "4" + ansiUnderlineOff = "24" + ansiBlinkOn = "5" + ansiBlinkOff = "25" + + ansiForegroundBlack = "30" + ansiForegroundRed = "31" + ansiForegroundGreen = "32" + ansiForegroundYellow = "33" + ansiForegroundBlue = "34" + ansiForegroundMagenta = "35" + ansiForegroundCyan = "36" + ansiForegroundWhite = "37" + ansiForegroundDefault = "39" + + ansiBackgroundBlack = "40" + ansiBackgroundRed = "41" + ansiBackgroundGreen = "42" + ansiBackgroundYellow = "43" + ansiBackgroundBlue = "44" + ansiBackgroundMagenta = "45" + ansiBackgroundCyan = "46" + ansiBackgroundWhite = "47" + ansiBackgroundDefault = "49" + + ansiLightForegroundGray = "90" + ansiLightForegroundRed = "91" + ansiLightForegroundGreen = "92" + ansiLightForegroundYellow = "93" + ansiLightForegroundBlue = "94" + ansiLightForegroundMagenta = "95" + ansiLightForegroundCyan = "96" + ansiLightForegroundWhite = "97" + + ansiLightBackgroundGray = "100" + ansiLightBackgroundRed = "101" + ansiLightBackgroundGreen = "102" + ansiLightBackgroundYellow = "103" + ansiLightBackgroundBlue = "104" + ansiLightBackgroundMagenta = "105" + ansiLightBackgroundCyan = "106" + ansiLightBackgroundWhite = "107" +) + +type drawType int + +const ( + foreground drawType = iota + background +) + +type winColor struct { + code uint16 + drawType drawType +} + +var colorMap = map[string]winColor{ + ansiForegroundBlack: {0, foreground}, + ansiForegroundRed: {foregroundRed, foreground}, + ansiForegroundGreen: {foregroundGreen, foreground}, + ansiForegroundYellow: {foregroundRed | foregroundGreen, foreground}, + ansiForegroundBlue: {foregroundBlue, foreground}, + ansiForegroundMagenta: {foregroundRed | foregroundBlue, foreground}, + ansiForegroundCyan: {foregroundGreen | foregroundBlue, foreground}, + ansiForegroundWhite: {foregroundRed | foregroundGreen | foregroundBlue, foreground}, + ansiForegroundDefault: {foregroundRed | foregroundGreen | foregroundBlue, foreground}, + + ansiBackgroundBlack: {0, background}, + ansiBackgroundRed: {backgroundRed, background}, + ansiBackgroundGreen: {backgroundGreen, background}, + ansiBackgroundYellow: {backgroundRed | backgroundGreen, background}, + ansiBackgroundBlue: {backgroundBlue, background}, + ansiBackgroundMagenta: {backgroundRed | backgroundBlue, background}, + ansiBackgroundCyan: {backgroundGreen | backgroundBlue, background}, + ansiBackgroundWhite: {backgroundRed | backgroundGreen | backgroundBlue, background}, + ansiBackgroundDefault: {0, background}, + + ansiLightForegroundGray: {foregroundIntensity, foreground}, + ansiLightForegroundRed: {foregroundIntensity | foregroundRed, foreground}, + ansiLightForegroundGreen: {foregroundIntensity | foregroundGreen, foreground}, + ansiLightForegroundYellow: {foregroundIntensity | foregroundRed | foregroundGreen, foreground}, + ansiLightForegroundBlue: {foregroundIntensity | foregroundBlue, foreground}, + ansiLightForegroundMagenta: {foregroundIntensity | foregroundRed | foregroundBlue, foreground}, + ansiLightForegroundCyan: {foregroundIntensity | foregroundGreen | foregroundBlue, foreground}, + ansiLightForegroundWhite: {foregroundIntensity | foregroundRed | foregroundGreen | foregroundBlue, foreground}, + + ansiLightBackgroundGray: {backgroundIntensity, background}, + ansiLightBackgroundRed: {backgroundIntensity | backgroundRed, background}, + ansiLightBackgroundGreen: {backgroundIntensity | backgroundGreen, background}, + ansiLightBackgroundYellow: {backgroundIntensity | backgroundRed | backgroundGreen, background}, + ansiLightBackgroundBlue: {backgroundIntensity | backgroundBlue, background}, + ansiLightBackgroundMagenta: {backgroundIntensity | backgroundRed | backgroundBlue, background}, + ansiLightBackgroundCyan: {backgroundIntensity | backgroundGreen | backgroundBlue, background}, + ansiLightBackgroundWhite: {backgroundIntensity | backgroundRed | backgroundGreen | backgroundBlue, background}, +} + +var ( + kernel32 = syscall.NewLazyDLL("kernel32.dll") + procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") + defaultAttr *textAttributes +) + +func init() { + screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout)) + if screenInfo != nil { + colorMap[ansiForegroundDefault] = winColor{ + screenInfo.WAttributes & (foregroundRed | foregroundGreen | foregroundBlue), + foreground, + } + colorMap[ansiBackgroundDefault] = winColor{ + screenInfo.WAttributes & (backgroundRed | backgroundGreen | backgroundBlue), + background, + } + defaultAttr = convertTextAttr(screenInfo.WAttributes) + } +} + +type coord struct { + X, Y int16 +} + +type smallRect struct { + Left, Top, Right, Bottom int16 +} + +type consoleScreenBufferInfo struct { + DwSize coord + DwCursorPosition coord + WAttributes uint16 + SrWindow smallRect + DwMaximumWindowSize coord +} + +func getConsoleScreenBufferInfo(hConsoleOutput uintptr) *consoleScreenBufferInfo { + var csbi consoleScreenBufferInfo + ret, _, _ := procGetConsoleScreenBufferInfo.Call( + hConsoleOutput, + uintptr(unsafe.Pointer(&csbi))) + if ret == 0 { + return nil + } + return &csbi +} + +func setConsoleTextAttribute(hConsoleOutput uintptr, wAttributes uint16) bool { + ret, _, _ := procSetConsoleTextAttribute.Call( + hConsoleOutput, + uintptr(wAttributes)) + return ret != 0 +} + +type textAttributes struct { + foregroundColor uint16 + backgroundColor uint16 + foregroundIntensity uint16 + backgroundIntensity uint16 + underscore uint16 + otherAttributes uint16 +} + +func convertTextAttr(winAttr uint16) *textAttributes { + fgColor := winAttr & (foregroundRed | foregroundGreen | foregroundBlue) + bgColor := winAttr & (backgroundRed | backgroundGreen | backgroundBlue) + fgIntensity := winAttr & foregroundIntensity + bgIntensity := winAttr & backgroundIntensity + underline := winAttr & underscore + otherAttributes := winAttr &^ (foregroundMask | backgroundMask | underscore) + return &textAttributes{fgColor, bgColor, fgIntensity, bgIntensity, underline, otherAttributes} +} + +func convertWinAttr(textAttr *textAttributes) uint16 { + var winAttr uint16 + winAttr |= textAttr.foregroundColor + winAttr |= textAttr.backgroundColor + winAttr |= textAttr.foregroundIntensity + winAttr |= textAttr.backgroundIntensity + winAttr |= textAttr.underscore + winAttr |= textAttr.otherAttributes + return winAttr +} + +func changeColor(param []byte) parseResult { + screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout)) + if screenInfo == nil { + return noConsole + } + + winAttr := convertTextAttr(screenInfo.WAttributes) + strParam := string(param) + if len(strParam) <= 0 { + strParam = "0" + } + csiParam := strings.Split(strParam, string(separatorChar)) + for _, p := range csiParam { + c, ok := colorMap[p] + switch { + case !ok: + switch p { + case ansiReset: + winAttr.foregroundColor = defaultAttr.foregroundColor + winAttr.backgroundColor = defaultAttr.backgroundColor + winAttr.foregroundIntensity = defaultAttr.foregroundIntensity + winAttr.backgroundIntensity = defaultAttr.backgroundIntensity + winAttr.underscore = 0 + winAttr.otherAttributes = 0 + case ansiIntensityOn: + winAttr.foregroundIntensity = foregroundIntensity + case ansiIntensityOff: + winAttr.foregroundIntensity = 0 + case ansiUnderlineOn: + winAttr.underscore = underscore + case ansiUnderlineOff: + winAttr.underscore = 0 + case ansiBlinkOn: + winAttr.backgroundIntensity = backgroundIntensity + case ansiBlinkOff: + winAttr.backgroundIntensity = 0 + default: + // unknown code + } + case c.drawType == foreground: + winAttr.foregroundColor = c.code + case c.drawType == background: + winAttr.backgroundColor = c.code + } + } + winTextAttribute := convertWinAttr(winAttr) + setConsoleTextAttribute(uintptr(syscall.Stdout), winTextAttribute) + + return changedColor +} + +func parseEscapeSequence(command byte, param []byte) parseResult { + if defaultAttr == nil { + return noConsole + } + + switch command { + case sgrCode: + return changeColor(param) + default: + return unknown + } +} + +func (cw *ansiColorWriter) flushBuffer() (int, error) { + return cw.flushTo(cw.w) +} + +func (cw *ansiColorWriter) resetBuffer() (int, error) { + return cw.flushTo(nil) +} + +func (cw *ansiColorWriter) flushTo(w io.Writer) (int, error) { + var n1, n2 int + var err error + + startBytes := cw.paramStartBuf.Bytes() + cw.paramStartBuf.Reset() + if w != nil { + n1, err = cw.w.Write(startBytes) + if err != nil { + return n1, err + } + } else { + n1 = len(startBytes) + } + paramBytes := cw.paramBuf.Bytes() + cw.paramBuf.Reset() + if w != nil { + n2, err = cw.w.Write(paramBytes) + if err != nil { + return n1 + n2, err + } + } else { + n2 = len(paramBytes) + } + return n1 + n2, nil +} + +func isParameterChar(b byte) bool { + return ('0' <= b && b <= '9') || b == separatorChar +} + +func (cw *ansiColorWriter) Write(p []byte) (int, error) { + r, nw, first, last := 0, 0, 0, 0 + if cw.mode != DiscardNonColorEscSeq { + cw.state = outsideCsiCode + cw.resetBuffer() + } + + var err error + for i, ch := range p { + switch cw.state { + case outsideCsiCode: + if ch == firstCsiChar { + cw.paramStartBuf.WriteByte(ch) + cw.state = firstCsiCode + } + case firstCsiCode: + switch ch { + case firstCsiChar: + cw.paramStartBuf.WriteByte(ch) + break + case secondeCsiChar: + cw.paramStartBuf.WriteByte(ch) + cw.state = secondCsiCode + last = i - 1 + default: + cw.resetBuffer() + cw.state = outsideCsiCode + } + case secondCsiCode: + if isParameterChar(ch) { + cw.paramBuf.WriteByte(ch) + } else { + nw, err = cw.w.Write(p[first:last]) + r += nw + if err != nil { + return r, err + } + first = i + 1 + result := parseEscapeSequence(ch, cw.paramBuf.Bytes()) + if result == noConsole || (cw.mode == OutputNonColorEscSeq && result == unknown) { + cw.paramBuf.WriteByte(ch) + nw, err := cw.flushBuffer() + if err != nil { + return r, err + } + r += nw + } else { + n, _ := cw.resetBuffer() + // Add one more to the size of the buffer for the last ch + r += n + 1 + } + + cw.state = outsideCsiCode + } + default: + cw.state = outsideCsiCode + } + } + + if cw.mode != DiscardNonColorEscSeq || cw.state == outsideCsiCode { + nw, err = cw.w.Write(p[first:len(p)]) + r += nw + } + + return r, err +} + +// DiscardNonColorEscSeq supports the divided color escape sequence. +// But non-color escape sequence is not output. +// Please use the OutputNonColorEscSeq If you want to output a non-color +// escape sequences such as ncurses. However, it does not support the divided +// color escape sequence. +const ( + _ outputMode = iota + DiscardNonColorEscSeq + OutputNonColorEscSeq +) + +// NewAnsiColorWriter creates and initializes a new ansiColorWriter +// using io.Writer w as its initial contents. +// In the console of Windows, which change the foreground and background +// colors of the text by the escape sequence. +// In the console of other systems, which writes to w all text. +func NewAnsiColorWriter(w io.Writer) io.Writer { + return NewModeAnsiColorWriter(w, DiscardNonColorEscSeq) +} + +// NewModeAnsiColorWriter create and initializes a new ansiColorWriter +// by specifying the outputMode. +func NewModeAnsiColorWriter(w io.Writer, mode outputMode) io.Writer { + if _, ok := w.(*ansiColorWriter); !ok { + return &ansiColorWriter{ + w: w, + mode: mode, + } + } + return w +} \ No newline at end of file diff --git a/logs/color_windows_test.go b/logs/color_windows_test.go new file mode 100644 index 00000000..d87d9798 --- /dev/null +++ b/logs/color_windows_test.go @@ -0,0 +1,294 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package logs + +import ( + "bytes" + "fmt" + "syscall" + "testing" +) + +var GetConsoleScreenBufferInfo = getConsoleScreenBufferInfo + +func ChangeColor(color uint16) { + setConsoleTextAttribute(uintptr(syscall.Stdout), color) +} + +func ResetColor() { + ChangeColor(uint16(0x0007)) +} + +func TestWritePlanText(t *testing.T) { + inner := bytes.NewBufferString("") + w := NewAnsiColorWriter(inner) + expected := "plain text" + fmt.Fprintf(w, expected) + actual := inner.String() + if actual != expected { + t.Errorf("Get %q, want %q", actual, expected) + } +} + +func TestWriteParseText(t *testing.T) { + inner := bytes.NewBufferString("") + w := NewAnsiColorWriter(inner) + + inputTail := "\x1b[0mtail text" + expectedTail := "tail text" + fmt.Fprintf(w, inputTail) + actualTail := inner.String() + inner.Reset() + if actualTail != expectedTail { + t.Errorf("Get %q, want %q", actualTail, expectedTail) + } + + inputHead := "head text\x1b[0m" + expectedHead := "head text" + fmt.Fprintf(w, inputHead) + actualHead := inner.String() + inner.Reset() + if actualHead != expectedHead { + t.Errorf("Get %q, want %q", actualHead, expectedHead) + } + + inputBothEnds := "both ends \x1b[0m text" + expectedBothEnds := "both ends text" + fmt.Fprintf(w, inputBothEnds) + actualBothEnds := inner.String() + inner.Reset() + if actualBothEnds != expectedBothEnds { + t.Errorf("Get %q, want %q", actualBothEnds, expectedBothEnds) + } + + inputManyEsc := "\x1b\x1b\x1b\x1b[0m many esc" + expectedManyEsc := "\x1b\x1b\x1b many esc" + fmt.Fprintf(w, inputManyEsc) + actualManyEsc := inner.String() + inner.Reset() + if actualManyEsc != expectedManyEsc { + t.Errorf("Get %q, want %q", actualManyEsc, expectedManyEsc) + } + + expectedSplit := "split text" + for _, ch := range "split \x1b[0m text" { + fmt.Fprintf(w, string(ch)) + } + actualSplit := inner.String() + inner.Reset() + if actualSplit != expectedSplit { + t.Errorf("Get %q, want %q", actualSplit, expectedSplit) + } +} + +type screenNotFoundError struct { + error +} + +func writeAnsiColor(expectedText, colorCode string) (actualText string, actualAttributes uint16, err error) { + inner := bytes.NewBufferString("") + w := NewAnsiColorWriter(inner) + fmt.Fprintf(w, "\x1b[%sm%s", colorCode, expectedText) + + actualText = inner.String() + screenInfo := GetConsoleScreenBufferInfo(uintptr(syscall.Stdout)) + if screenInfo != nil { + actualAttributes = screenInfo.WAttributes + } else { + err = &screenNotFoundError{} + } + return +} + +type testParam struct { + text string + attributes uint16 + ansiColor string +} + +func TestWriteAnsiColorText(t *testing.T) { + screenInfo := GetConsoleScreenBufferInfo(uintptr(syscall.Stdout)) + if screenInfo == nil { + t.Fatal("Could not get ConsoleScreenBufferInfo") + } + defer ChangeColor(screenInfo.WAttributes) + defaultFgColor := screenInfo.WAttributes & uint16(0x0007) + defaultBgColor := screenInfo.WAttributes & uint16(0x0070) + defaultFgIntensity := screenInfo.WAttributes & uint16(0x0008) + defaultBgIntensity := screenInfo.WAttributes & uint16(0x0080) + + fgParam := []testParam{ + {"foreground black ", uint16(0x0000 | 0x0000), "30"}, + {"foreground red ", uint16(0x0004 | 0x0000), "31"}, + {"foreground green ", uint16(0x0002 | 0x0000), "32"}, + {"foreground yellow ", uint16(0x0006 | 0x0000), "33"}, + {"foreground blue ", uint16(0x0001 | 0x0000), "34"}, + {"foreground magenta", uint16(0x0005 | 0x0000), "35"}, + {"foreground cyan ", uint16(0x0003 | 0x0000), "36"}, + {"foreground white ", uint16(0x0007 | 0x0000), "37"}, + {"foreground default", defaultFgColor | 0x0000, "39"}, + {"foreground light gray ", uint16(0x0000 | 0x0008 | 0x0000), "90"}, + {"foreground light red ", uint16(0x0004 | 0x0008 | 0x0000), "91"}, + {"foreground light green ", uint16(0x0002 | 0x0008 | 0x0000), "92"}, + {"foreground light yellow ", uint16(0x0006 | 0x0008 | 0x0000), "93"}, + {"foreground light blue ", uint16(0x0001 | 0x0008 | 0x0000), "94"}, + {"foreground light magenta", uint16(0x0005 | 0x0008 | 0x0000), "95"}, + {"foreground light cyan ", uint16(0x0003 | 0x0008 | 0x0000), "96"}, + {"foreground light white ", uint16(0x0007 | 0x0008 | 0x0000), "97"}, + } + + bgParam := []testParam{ + {"background black ", uint16(0x0007 | 0x0000), "40"}, + {"background red ", uint16(0x0007 | 0x0040), "41"}, + {"background green ", uint16(0x0007 | 0x0020), "42"}, + {"background yellow ", uint16(0x0007 | 0x0060), "43"}, + {"background blue ", uint16(0x0007 | 0x0010), "44"}, + {"background magenta", uint16(0x0007 | 0x0050), "45"}, + {"background cyan ", uint16(0x0007 | 0x0030), "46"}, + {"background white ", uint16(0x0007 | 0x0070), "47"}, + {"background default", uint16(0x0007) | defaultBgColor, "49"}, + {"background light gray ", uint16(0x0007 | 0x0000 | 0x0080), "100"}, + {"background light red ", uint16(0x0007 | 0x0040 | 0x0080), "101"}, + {"background light green ", uint16(0x0007 | 0x0020 | 0x0080), "102"}, + {"background light yellow ", uint16(0x0007 | 0x0060 | 0x0080), "103"}, + {"background light blue ", uint16(0x0007 | 0x0010 | 0x0080), "104"}, + {"background light magenta", uint16(0x0007 | 0x0050 | 0x0080), "105"}, + {"background light cyan ", uint16(0x0007 | 0x0030 | 0x0080), "106"}, + {"background light white ", uint16(0x0007 | 0x0070 | 0x0080), "107"}, + } + + resetParam := []testParam{ + {"all reset", defaultFgColor | defaultBgColor | defaultFgIntensity | defaultBgIntensity, "0"}, + {"all reset", defaultFgColor | defaultBgColor | defaultFgIntensity | defaultBgIntensity, ""}, + } + + boldParam := []testParam{ + {"bold on", uint16(0x0007 | 0x0008), "1"}, + {"bold off", uint16(0x0007), "21"}, + } + + underscoreParam := []testParam{ + {"underscore on", uint16(0x0007 | 0x8000), "4"}, + {"underscore off", uint16(0x0007), "24"}, + } + + blinkParam := []testParam{ + {"blink on", uint16(0x0007 | 0x0080), "5"}, + {"blink off", uint16(0x0007), "25"}, + } + + mixedParam := []testParam{ + {"both black, bold, underline, blink", uint16(0x0000 | 0x0000 | 0x0008 | 0x8000 | 0x0080), "30;40;1;4;5"}, + {"both red, bold, underline, blink", uint16(0x0004 | 0x0040 | 0x0008 | 0x8000 | 0x0080), "31;41;1;4;5"}, + {"both green, bold, underline, blink", uint16(0x0002 | 0x0020 | 0x0008 | 0x8000 | 0x0080), "32;42;1;4;5"}, + {"both yellow, bold, underline, blink", uint16(0x0006 | 0x0060 | 0x0008 | 0x8000 | 0x0080), "33;43;1;4;5"}, + {"both blue, bold, underline, blink", uint16(0x0001 | 0x0010 | 0x0008 | 0x8000 | 0x0080), "34;44;1;4;5"}, + {"both magenta, bold, underline, blink", uint16(0x0005 | 0x0050 | 0x0008 | 0x8000 | 0x0080), "35;45;1;4;5"}, + {"both cyan, bold, underline, blink", uint16(0x0003 | 0x0030 | 0x0008 | 0x8000 | 0x0080), "36;46;1;4;5"}, + {"both white, bold, underline, blink", uint16(0x0007 | 0x0070 | 0x0008 | 0x8000 | 0x0080), "37;47;1;4;5"}, + {"both default, bold, underline, blink", uint16(defaultFgColor | defaultBgColor | 0x0008 | 0x8000 | 0x0080), "39;49;1;4;5"}, + } + + assertTextAttribute := func(expectedText string, expectedAttributes uint16, ansiColor string) { + actualText, actualAttributes, err := writeAnsiColor(expectedText, ansiColor) + if actualText != expectedText { + t.Errorf("Get %q, want %q", actualText, expectedText) + } + if err != nil { + t.Fatal("Could not get ConsoleScreenBufferInfo") + } + if actualAttributes != expectedAttributes { + t.Errorf("Text: %q, Get 0x%04x, want 0x%04x", expectedText, actualAttributes, expectedAttributes) + } + } + + for _, v := range fgParam { + ResetColor() + assertTextAttribute(v.text, v.attributes, v.ansiColor) + } + + for _, v := range bgParam { + ChangeColor(uint16(0x0070 | 0x0007)) + assertTextAttribute(v.text, v.attributes, v.ansiColor) + } + + for _, v := range resetParam { + ChangeColor(uint16(0x0000 | 0x0070 | 0x0008)) + assertTextAttribute(v.text, v.attributes, v.ansiColor) + } + + ResetColor() + for _, v := range boldParam { + assertTextAttribute(v.text, v.attributes, v.ansiColor) + } + + ResetColor() + for _, v := range underscoreParam { + assertTextAttribute(v.text, v.attributes, v.ansiColor) + } + + ResetColor() + for _, v := range blinkParam { + assertTextAttribute(v.text, v.attributes, v.ansiColor) + } + + for _, v := range mixedParam { + ResetColor() + assertTextAttribute(v.text, v.attributes, v.ansiColor) + } +} + +func TestIgnoreUnknownSequences(t *testing.T) { + inner := bytes.NewBufferString("") + w := NewModeAnsiColorWriter(inner, OutputNonColorEscSeq) + + inputText := "\x1b[=decpath mode" + expectedTail := inputText + fmt.Fprintf(w, inputText) + actualTail := inner.String() + inner.Reset() + if actualTail != expectedTail { + t.Errorf("Get %q, want %q", actualTail, expectedTail) + } + + inputText = "\x1b[=tailing esc and bracket\x1b[" + expectedTail = inputText + fmt.Fprintf(w, inputText) + actualTail = inner.String() + inner.Reset() + if actualTail != expectedTail { + t.Errorf("Get %q, want %q", actualTail, expectedTail) + } + + inputText = "\x1b[?tailing esc\x1b" + expectedTail = inputText + fmt.Fprintf(w, inputText) + actualTail = inner.String() + inner.Reset() + if actualTail != expectedTail { + t.Errorf("Get %q, want %q", actualTail, expectedTail) + } + + inputText = "\x1b[1h;3punended color code invalid\x1b3" + expectedTail = inputText + fmt.Fprintf(w, inputText) + actualTail = inner.String() + inner.Reset() + if actualTail != expectedTail { + t.Errorf("Get %q, want %q", actualTail, expectedTail) + } +} \ No newline at end of file diff --git a/logs/logger.go b/logs/logger.go index 3cdf7237..499a5b39 100644 --- a/logs/logger.go +++ b/logs/logger.go @@ -18,7 +18,6 @@ import ( "io" "sync" "time" - "github.com/shiena/ansicolor" "fmt" "os" ) @@ -138,16 +137,16 @@ func ColorByMethod(cond bool, method string) string { } } +// Guard Mutex to guarantee atomicity of W32Debug(string) function var mu sync.Mutex // Helper method to output colored logs in Windows terminals -// using ansicolor (https://github.com/shiena/ansicolor) func W32Debug(msg string) { mu.Lock() defer mu.Unlock() current := time.Now() - w := ansicolor.NewAnsiColorWriter(os.Stdout) + w := NewAnsiColorWriter(os.Stdout) fmt.Fprintf(w, "[beego] %v %s\n", current.Format("2006/01/02 - 15:04:05"), msg) } From 5d1e60b468a8bb9489bb2c111b1d814dab3e53ba Mon Sep 17 00:00:00 2001 From: Maxgis Date: Thu, 28 Jul 2016 11:56:55 +0800 Subject: [PATCH 42/96] reflection --- template.go | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/template.go b/template.go index 494acc4f..5415f5f0 100644 --- a/template.go +++ b/template.go @@ -50,22 +50,16 @@ func ExecuteTemplate(wr io.Writer, name string, data interface{}) error { defer templatesLock.RUnlock() } if t, ok := beeTemplates[name]; ok { + var err error if t.Lookup(name) != nil { - err := t.ExecuteTemplate(wr, name, data) - if err != nil { - logs.Trace("template Execute err:", err) - } - return err + err = t.ExecuteTemplate(wr, name, data) } else { - err := t.Execute(wr, data) - if err != nil { - if err != nil { - logs.Trace("template Execute err:", err) - } - return err - } + err = t.Execute(wr, data) } - return nil + if err != nil { + logs.Trace("template Execute err:", err) + } + return err } panic("can't find templatefile in the path:" + name) } From d85293b7c0d2704abe692a17b90394670f5defd7 Mon Sep 17 00:00:00 2001 From: Faissal Elamraoui Date: Thu, 28 Jul 2016 11:37:28 +0200 Subject: [PATCH 43/96] Fixed bug and added more tests --- logs/color.go | 28 +++++++++++++++++++ logs/color_windows.go | 33 ----------------------- logs/logger.go | 62 ++++++++++++++++++++++++++++++++++--------- logs/logger_test.go | 18 +++++++++++++ 4 files changed, 95 insertions(+), 46 deletions(-) create mode 100644 logs/color.go diff --git a/logs/color.go b/logs/color.go new file mode 100644 index 00000000..fb21b42e --- /dev/null +++ b/logs/color.go @@ -0,0 +1,28 @@ +// Copyright 2014 beego Author. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !windows + +package logs + +import "io" + +type ansiColorWriter struct { + w io.Writer + mode outputMode +} + +func (cw *ansiColorWriter) Write(p []byte) (int, error) { + return cw.w.Write(p) +} \ No newline at end of file diff --git a/logs/color_windows.go b/logs/color_windows.go index f6c6cad3..47f7b24e 100644 --- a/logs/color_windows.go +++ b/logs/color_windows.go @@ -25,7 +25,6 @@ import ( ) type ( - outputMode int csiState int parseResult int ) @@ -426,36 +425,4 @@ func (cw *ansiColorWriter) Write(p []byte) (int, error) { } return r, err -} - -// DiscardNonColorEscSeq supports the divided color escape sequence. -// But non-color escape sequence is not output. -// Please use the OutputNonColorEscSeq If you want to output a non-color -// escape sequences such as ncurses. However, it does not support the divided -// color escape sequence. -const ( - _ outputMode = iota - DiscardNonColorEscSeq - OutputNonColorEscSeq -) - -// NewAnsiColorWriter creates and initializes a new ansiColorWriter -// using io.Writer w as its initial contents. -// In the console of Windows, which change the foreground and background -// colors of the text by the escape sequence. -// In the console of other systems, which writes to w all text. -func NewAnsiColorWriter(w io.Writer) io.Writer { - return NewModeAnsiColorWriter(w, DiscardNonColorEscSeq) -} - -// NewModeAnsiColorWriter create and initializes a new ansiColorWriter -// by specifying the outputMode. -func NewModeAnsiColorWriter(w io.Writer, mode outputMode) io.Writer { - if _, ok := w.(*ansiColorWriter); !ok { - return &ansiColorWriter{ - w: w, - mode: mode, - } - } - return w } \ No newline at end of file diff --git a/logs/logger.go b/logs/logger.go index 499a5b39..d74df7bf 100644 --- a/logs/logger.go +++ b/logs/logger.go @@ -38,18 +38,54 @@ func (lg *logWriter) println(when time.Time, msg string) { lg.Unlock() } -const y1 = `0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999` -const y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789` -const mo1 = `000000000111` -const mo2 = `123456789012` -const d1 = `0000000001111111111222222222233` -const d2 = `1234567890123456789012345678901` -const h1 = `000000000011111111112222` -const h2 = `012345678901234567890123` -const mi1 = `000000000011111111112222222222333333333344444444445555555555` -const mi2 = `012345678901234567890123456789012345678901234567890123456789` -const s1 = `000000000011111111112222222222333333333344444444445555555555` -const s2 = `012345678901234567890123456789012345678901234567890123456789` +type outputMode int + +// DiscardNonColorEscSeq supports the divided color escape sequence. +// But non-color escape sequence is not output. +// Please use the OutputNonColorEscSeq If you want to output a non-color +// escape sequences such as ncurses. However, it does not support the divided +// color escape sequence. +const ( + _ outputMode = iota + DiscardNonColorEscSeq + OutputNonColorEscSeq +) + +// NewAnsiColorWriter creates and initializes a new ansiColorWriter +// using io.Writer w as its initial contents. +// In the console of Windows, which change the foreground and background +// colors of the text by the escape sequence. +// In the console of other systems, which writes to w all text. +func NewAnsiColorWriter(w io.Writer) io.Writer { + return NewModeAnsiColorWriter(w, DiscardNonColorEscSeq) +} + +// NewModeAnsiColorWriter create and initializes a new ansiColorWriter +// by specifying the outputMode. +func NewModeAnsiColorWriter(w io.Writer, mode outputMode) io.Writer { + if _, ok := w.(*ansiColorWriter); !ok { + return &ansiColorWriter{ + w: w, + mode: mode, + } + } + return w +} + +const ( + y1 = `0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999` + y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789` + mo1 = `000000000111` + mo2 = `123456789012` + d1 = `0000000001111111111222222222233` + d2 = `1234567890123456789012345678901` + h1 = `000000000011111111112222` + h2 = `012345678901234567890123` + mi1 = `000000000011111111112222222222333333333344444444445555555555` + mi2 = `012345678901234567890123456789012345678901234567890123456789` + s1 = `000000000011111111112222222222333333333344444444445555555555` + s2 = `012345678901234567890123456789012345678901234567890123456789` +) func formatTimeHeader(when time.Time) ([]byte, int) { y, mo, d := when.Date() @@ -149,4 +185,4 @@ func W32Debug(msg string) { w := NewAnsiColorWriter(os.Stdout) fmt.Fprintf(w, "[beego] %v %s\n", current.Format("2006/01/02 - 15:04:05"), msg) -} +} \ No newline at end of file diff --git a/logs/logger_test.go b/logs/logger_test.go index 4627853a..c65d70d5 100644 --- a/logs/logger_test.go +++ b/logs/logger_test.go @@ -17,6 +17,7 @@ package logs import ( "testing" "time" + "bytes" ) func TestFormatHeader_0(t *testing.T) { @@ -55,3 +56,20 @@ func TestFormatHeader_1(t *testing.T) { tm = tm.Add(dur) } } + +func TestNewAnsiColor1(t *testing.T) { + inner := bytes.NewBufferString("") + w := NewAnsiColorWriter(inner) + if w == inner { + t.Errorf("Get %#v, want %#v", w, inner) + } +} + +func TestNewAnsiColor2(t *testing.T) { + inner := bytes.NewBufferString("") + w1 := NewAnsiColorWriter(inner) + w2 := NewAnsiColorWriter(w1) + if w1 != w2 { + t.Errorf("Get %#v, want %#v", w1, w2) + } +} \ No newline at end of file From b89bfe76d0b4c66f60f600b7bfa59d491d3ae017 Mon Sep 17 00:00:00 2001 From: maxin Date: Sat, 30 Jul 2016 21:57:23 +0800 Subject: [PATCH 44/96] update route variables should not have underline --- tree.go | 6 ++++-- tree_test.go | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tree.go b/tree.go index 25b78e50..da7690ea 100644 --- a/tree.go +++ b/tree.go @@ -19,6 +19,7 @@ import ( "regexp" "strings" + "github.com/Maxgis/tree" "github.com/astaxie/beego/context" "github.com/astaxie/beego/utils" ) @@ -467,7 +468,7 @@ func splitPath(key string) []string { // ":name:string" -> true, [:name], ([\w]+) // ":id([0-9]+)" -> true, [:id], ([0-9]+) // ":id([0-9]+)_:name" -> true, [:id :name], ([0-9]+)_(.+) -// "cms_:id_:page.html" -> true, [:id_ :page], cms_(.+)(.+).html +// "cms_:id_:page.html" -> true, [:id :page], cms_(.+)_(.+).html // "cms_:id(.+)_:page.html" -> true, [:id :page], cms_(.+)_(.+).html // "*" -> true, [:splat], "" // "*.*" -> true,[. :path :ext], "" . meaning separator @@ -487,7 +488,7 @@ func splitSegment(key string) (bool, []string, string) { var expt []rune var skipnum int params := []string{} - reg := regexp.MustCompile(`[a-zA-Z0-9_]+`) + reg := regexp.MustCompile(`[a-zA-Z0-9]+`) for i, v := range key { if skipnum > 0 { skipnum-- @@ -574,6 +575,7 @@ func splitSegment(key string) (bool, []string, string) { } params = append(params, ":"+string(param)) } + tree.Print(string(out)) return true, params, string(out) } return false, nil, "" diff --git a/tree_test.go b/tree_test.go index 81ff7edd..df7242cb 100644 --- a/tree_test.go +++ b/tree_test.go @@ -74,6 +74,8 @@ func init() { routers = append(routers, testinfo{"/v1/:v(.+)_cms/ttt_:id(.+)_:page(.+).html", "/v1/2_cms/ttt_123_1.html", map[string]string{":v": "2", ":id": "123", ":page": "1"}}) routers = append(routers, testinfo{"/api/projects/:pid/members/?:mid", "/api/projects/1/members", map[string]string{":pid": "1"}}) routers = append(routers, testinfo{"/api/projects/:pid/members/?:mid", "/api/projects/1/members/2", map[string]string{":pid": "1", ":mid": "2"}}) + routers = append(routers, testinfo{"/page/:id_:page", "/page/12_33", map[string]string{":id": "12", ":page": "33"}}) + routers = append(routers, testinfo{"/page/:id_:page.html", "/page/12_33.html", map[string]string{":id": "12", ":page": "33"}}) } func TestTreeRouters(t *testing.T) { From d5c339530aa9e1492a06fb2eb149e4a7f0c4bc80 Mon Sep 17 00:00:00 2001 From: maxin Date: Sat, 30 Jul 2016 22:05:59 +0800 Subject: [PATCH 45/96] remove debug --- tree.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/tree.go b/tree.go index da7690ea..f93dfa64 100644 --- a/tree.go +++ b/tree.go @@ -19,7 +19,6 @@ import ( "regexp" "strings" - "github.com/Maxgis/tree" "github.com/astaxie/beego/context" "github.com/astaxie/beego/utils" ) @@ -575,7 +574,6 @@ func splitSegment(key string) (bool, []string, string) { } params = append(params, ":"+string(param)) } - tree.Print(string(out)) return true, params, string(out) } return false, nil, "" From 15c048d6ca1f638e05eef447ecba68ddad2d2a8a Mon Sep 17 00:00:00 2001 From: maxin Date: Sat, 30 Jul 2016 22:07:35 +0800 Subject: [PATCH 46/96] update test --- tree_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tree_test.go b/tree_test.go index df7242cb..ab16756e 100644 --- a/tree_test.go +++ b/tree_test.go @@ -74,8 +74,8 @@ func init() { routers = append(routers, testinfo{"/v1/:v(.+)_cms/ttt_:id(.+)_:page(.+).html", "/v1/2_cms/ttt_123_1.html", map[string]string{":v": "2", ":id": "123", ":page": "1"}}) routers = append(routers, testinfo{"/api/projects/:pid/members/?:mid", "/api/projects/1/members", map[string]string{":pid": "1"}}) routers = append(routers, testinfo{"/api/projects/:pid/members/?:mid", "/api/projects/1/members/2", map[string]string{":pid": "1", ":mid": "2"}}) - routers = append(routers, testinfo{"/page/:id_:page", "/page/12_33", map[string]string{":id": "12", ":page": "33"}}) - routers = append(routers, testinfo{"/page/:id_:page.html", "/page/12_33.html", map[string]string{":id": "12", ":page": "33"}}) + routers = append(routers, testinfo{"/view/:id_:page", "/page/12_33", map[string]string{":id": "12", ":page": "33"}}) + routers = append(routers, testinfo{"/view/:id_:page.html", "/page/12_33.html", map[string]string{":id": "12", ":page": "33"}}) } func TestTreeRouters(t *testing.T) { From 6f0a985755110e9dab21cade7f4eec79725b574d Mon Sep 17 00:00:00 2001 From: maxin Date: Sat, 30 Jul 2016 23:14:29 +0800 Subject: [PATCH 47/96] update test --- tree_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tree_test.go b/tree_test.go index ab16756e..a20aed84 100644 --- a/tree_test.go +++ b/tree_test.go @@ -74,8 +74,8 @@ func init() { routers = append(routers, testinfo{"/v1/:v(.+)_cms/ttt_:id(.+)_:page(.+).html", "/v1/2_cms/ttt_123_1.html", map[string]string{":v": "2", ":id": "123", ":page": "1"}}) routers = append(routers, testinfo{"/api/projects/:pid/members/?:mid", "/api/projects/1/members", map[string]string{":pid": "1"}}) routers = append(routers, testinfo{"/api/projects/:pid/members/?:mid", "/api/projects/1/members/2", map[string]string{":pid": "1", ":mid": "2"}}) - routers = append(routers, testinfo{"/view/:id_:page", "/page/12_33", map[string]string{":id": "12", ":page": "33"}}) - routers = append(routers, testinfo{"/view/:id_:page.html", "/page/12_33.html", map[string]string{":id": "12", ":page": "33"}}) + routers = append(routers, testinfo{"/view/:id_:page", "/view/12_33", map[string]string{":id": "12", ":page": "33"}}) + routers = append(routers, testinfo{"/view/:id_:page.html", "/view/12_33.html", map[string]string{":id": "12", ":page": "33"}}) } func TestTreeRouters(t *testing.T) { From ce6f19871c3ea1ce6038a56edf2ba8ad5b5c1425 Mon Sep 17 00:00:00 2001 From: maxin Date: Sat, 6 Aug 2016 21:13:58 +0800 Subject: [PATCH 48/96] Refactoring --- context/output.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/context/output.go b/context/output.go index e1ad23e0..78804e42 100644 --- a/context/output.go +++ b/context/output.go @@ -146,18 +146,12 @@ func (output *BeegoOutput) Cookie(name string, value string, others ...interface } // default false. for session cookie default true - httponly := false if len(others) > 4 { if v, ok := others[4].(bool); ok && v { - // HttpOnly = true - httponly = true + fmt.Fprintf(&b, "; HttpOnly") } } - if httponly { - fmt.Fprintf(&b, "; HttpOnly") - } - output.Context.ResponseWriter.Header().Add("Set-Cookie", b.String()) } From 0e786fa4af86343bc2cd31027eafa8ebbf3f3bef Mon Sep 17 00:00:00 2001 From: dan pittman Date: Fri, 5 Aug 2016 16:20:56 -0700 Subject: [PATCH 49/96] adds ability to reset params after a filter runs When a filter is run _after_ the router completes, it's input params, such as `":splat"` will have been overwritten by the filter's router pass. This commit adds the ability to tell the router to revert to the previous input params after running a filter. --- context/input.go | 8 ++++++ filter.go | 1 + router.go | 47 +++++++++++++++++++++--------- router_test.go | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 14 deletions(-) diff --git a/context/input.go b/context/input.go index c47996c9..1683060e 100644 --- a/context/input.go +++ b/context/input.go @@ -301,6 +301,14 @@ func (input *BeegoInput) SetParam(key, val string) { input.pnames = append(input.pnames, key) } +// ResetParams clears any of the input's Params +// This function is used to clear parameters so they may be reset between filter +// passes. +func (input *BeegoInput) ResetParams() { + input.pnames = input.pnames[:0] + input.pvalues = input.pvalues[:0] +} + // Query returns input data item string by a given string. func (input *BeegoInput) Query(key string) string { if val := input.Param(key); val != "" { diff --git a/filter.go b/filter.go index 863223f7..9cc6e913 100644 --- a/filter.go +++ b/filter.go @@ -27,6 +27,7 @@ type FilterRouter struct { tree *Tree pattern string returnOnOutput bool + resetParams bool } // ValidRouter checks if the current request is matched by this filter. diff --git a/router.go b/router.go index d48e509a..6dc94a13 100644 --- a/router.go +++ b/router.go @@ -406,20 +406,27 @@ func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface) } // InsertFilter Add a FilterFunc with pattern rule and action constant. -// The bool params is for setting the returnOnOutput value (false allows multiple filters to execute) +// params is for: +// 1. setting the returnOnOutput value (false allows multiple filters to execute) +// 2. determining whether or not params need to be reset. func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) error { - mr := new(FilterRouter) - mr.tree = NewTree() - mr.pattern = pattern - mr.filterFunc = filter - if !BConfig.RouterCaseSensitive { - pattern = strings.ToLower(pattern) + mr := &FilterRouter{ + tree: NewTree(), + pattern: pattern, + filterFunc: filter, + returnOnOutput: true, } - if len(params) == 0 { - mr.returnOnOutput = true - } else { + if !BConfig.RouterCaseSensitive { + mr.pattern = strings.ToLower(pattern) + } + + paramsLen := len(params) + if paramsLen > 0 { mr.returnOnOutput = params[0] } + if paramsLen > 1 { + mr.resetParams = params[1] + } mr.tree.AddRouter(pattern, true) return p.insertFilterRouter(pos, mr) } @@ -581,12 +588,22 @@ func (p *ControllerRegister) geturl(t *Tree, url, controllName, methodName strin } func (p *ControllerRegister) execFilter(context *beecontext.Context, urlPath string, pos int) (started bool) { + var preFilterParams map[string]string for _, filterR := range p.filters[pos] { if filterR.returnOnOutput && context.ResponseWriter.Started { return true } + if filterR.resetParams { + preFilterParams = context.Input.Params() + } if ok := filterR.ValidRouter(urlPath, context); ok { filterR.filterFunc(context) + if filterR.resetParams { + context.Input.ResetParams() + for k, v := range preFilterParams { + context.Input.SetParam(k, v) + } + } } if filterR.returnOnOutput && context.ResponseWriter.Started { return true @@ -810,7 +827,9 @@ Admin: var devInfo string statusCode := context.ResponseWriter.Status - if statusCode == 0 { statusCode = 200 } + if statusCode == 0 { + statusCode = 200 + } iswin := (runtime.GOOS == "windows") statusColor := logs.ColorByStatus(iswin, statusCode) @@ -819,9 +838,9 @@ Admin: if findRouter { if routerInfo != nil { - devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-3s r:%s", statusColor, statusCode, - resetColor, timeDur.String(), "match", methodColor, resetColor, r.Method, r.URL.Path, - routerInfo.pattern) + devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-3s r:%s", statusColor, statusCode, + resetColor, timeDur.String(), "match", methodColor, resetColor, r.Method, r.URL.Path, + routerInfo.pattern) } else { devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-3s", statusColor, statusCode, resetColor, timeDur.String(), "match", methodColor, resetColor, r.Method, r.URL.Path) diff --git a/router_test.go b/router_test.go index 9f11286c..936fd5e8 100644 --- a/router_test.go +++ b/router_test.go @@ -420,6 +420,74 @@ func testRequest(method, path string) (*httptest.ResponseRecorder, *http.Request return recorder, request } +// Expectation: A Filter with the correct configuration should be created given +// specific parameters. +func TestInsertFilter(t *testing.T) { + testName := "TestInsertFilter" + + mux := NewControllerRegister() + mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}) + if !mux.filters[BeforeRouter][0].returnOnOutput { + t.Errorf( + "%s: passing no variadic params should set returnOnOutput to true", + testName) + } + if mux.filters[BeforeRouter][0].resetParams { + t.Errorf( + "%s: passing no variadic params should set resetParams to false", + testName) + } + + mux = NewControllerRegister() + mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, false) + if mux.filters[BeforeRouter][0].returnOnOutput { + t.Errorf( + "%s: passing false as 1st variadic param should set returnOnOutput to false", + testName) + } + + mux = NewControllerRegister() + mux.InsertFilter("*", BeforeRouter, func(*context.Context) {}, true, true) + if !mux.filters[BeforeRouter][0].resetParams { + t.Errorf( + "%s: passing true as 2nd variadic param should set resetParams to true", + testName) + } +} + +// Expectation: the second variadic arg should cause the execution of the filter +// to preserve the parameters from before its execution. +func TestParamResetFilter(t *testing.T) { + testName := "TestParamResetFilter" + route := "/beego/*" // splat + path := "/beego/routes/routes" + + mux := NewControllerRegister() + + mux.InsertFilter("*", BeforeExec, beegoResetParams, true, true) + + mux.Get(route, beegoHandleResetParams) + + rw, r := testRequest("GET", path) + mux.ServeHTTP(rw, r) + + // The two functions, `beegoResetParams` and `beegoHandleResetParams` add + // a response header of `Splat`. The expectation here is that that Header + // value should match what the _request's_ router set, not the filter's. + + headers := rw.HeaderMap + if len(headers["Splat"]) != 1 { + t.Errorf( + "%s: There was an error in the test. Splat param not set in Header", + testName) + } + if headers["Splat"][0] != "routes/routes" { + t.Errorf( + "%s: expected `:splat` param to be [routes/routes] but it was [%s]", + testName, headers["Splat"][0]) + } +} + // Execution point: BeforeRouter // expectation: only BeforeRouter function is executed, notmatch output as router doesn't handle func TestFilterBeforeRouter(t *testing.T) { @@ -612,3 +680,10 @@ func beegoFinishRouter1(ctx *context.Context) { func beegoFinishRouter2(ctx *context.Context) { ctx.WriteString("|FinishRouter2") } +func beegoResetParams(ctx *context.Context) { + ctx.ResponseWriter.Header().Set("splat", ctx.Input.Param(":splat")) +} + +func beegoHandleResetParams(ctx *context.Context) { + ctx.ResponseWriter.Header().Set("splat", ctx.Input.Param(":splat")) +} From 957db1362f98b4f957d1249dede39ec1e1ef69bd Mon Sep 17 00:00:00 2001 From: astaxie Date: Mon, 8 Aug 2016 16:45:11 +0800 Subject: [PATCH 50/96] update swagger definistion --- swagger/swagger.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/swagger/swagger.go b/swagger/swagger.go index e48dcf1e..069731dd 100644 --- a/swagger/swagger.go +++ b/swagger/swagger.go @@ -29,7 +29,7 @@ type Swagger struct { Schemes []string `json:"schemes,omitempty"` Consumes []string `json:"consumes,omitempty"` Produces []string `json:"produces,omitempty"` - Paths map[string]Item `json:"paths"` + Paths map[string]*Item `json:"paths"` Definitions map[string]Schema `json:"definitions,omitempty"` SecurityDefinitions map[string]Scurity `json:"securityDefinitions,omitempty"` Security map[string][]string `json:"security,omitempty"` @@ -111,6 +111,7 @@ type Schema struct { // Propertie are taken from the JSON Schema definition but their definitions were adjusted to the Swagger Specification type Propertie struct { + Ref string `json:"$ref,omitempty"` Title string `json:"title,omitempty"` Description string `json:"description,omitempty"` Default string `json:"default,omitempty"` From 1b4f30e11e3b73bff73bb91bbbbbc5a378c5629f Mon Sep 17 00:00:00 2001 From: astaxie Date: Sat, 13 Aug 2016 14:43:56 +0800 Subject: [PATCH 51/96] update swagger --- swagger/swagger.go | 57 +++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/swagger/swagger.go b/swagger/swagger.go index 069731dd..d585e9d9 100644 --- a/swagger/swagger.go +++ b/swagger/swagger.go @@ -34,15 +34,15 @@ type Swagger struct { SecurityDefinitions map[string]Scurity `json:"securityDefinitions,omitempty"` Security map[string][]string `json:"security,omitempty"` Tags []Tag `json:"tags,omitempty"` - ExternalDocs ExternalDocs `json:"externalDocs,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` } // Information Provides metadata about the API. The metadata can be used by the clients if needed. type Information struct { - Title string `json:"title,omitempty"` - Description string `json:"description,omitempty"` - Version string `json:"version,omitempty"` - TermsOfServiceURL string `json:"termsOfServiceUrl,omitempty"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + Version string `json:"version,omitempty"` + TermsOfService string `json:"termsOfService,omitempty"` Contact Contact `json:"contact,omitempty"` License License `json:"license,omitempty"` @@ -89,13 +89,13 @@ type Operation struct { // Parameter Describes a single operation parameter. type Parameter struct { - In string `json:"in,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Required bool `json:"required,omitempty"` - Schema Schema `json:"schema,omitempty"` - Type string `json:"type,omitempty"` - Format string `json:"format,omitempty"` + In string `json:"in,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Required bool `json:"required,omitempty"` + Schema *Schema `json:"schema,omitempty"` + Type string `json:"type,omitempty"` + Format string `json:"format,omitempty"` } // Schema Object allows the definition of input and output data types. @@ -111,23 +111,24 @@ type Schema struct { // Propertie are taken from the JSON Schema definition but their definitions were adjusted to the Swagger Specification type Propertie struct { - Ref string `json:"$ref,omitempty"` - Title string `json:"title,omitempty"` - Description string `json:"description,omitempty"` - Default string `json:"default,omitempty"` - Type string `json:"type,omitempty"` - Example string `json:"example,omitempty"` - Required []string `json:"required,omitempty"` - Format string `json:"format,omitempty"` - ReadOnly bool `json:"readOnly,omitempty"` - Properties map[string]Propertie `json:"properties,omitempty"` + Ref string `json:"$ref,omitempty"` + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + Default string `json:"default,omitempty"` + Type string `json:"type,omitempty"` + Example string `json:"example,omitempty"` + Required []string `json:"required,omitempty"` + Format string `json:"format,omitempty"` + ReadOnly bool `json:"readOnly,omitempty"` + Properties map[string]Propertie `json:"properties,omitempty"` + AdditionalProperties *Propertie `json:"additionalProperties,omitempty"` } // Response as they are returned from executing this operation. type Response struct { - Description string `json:"description,omitempty"` - Schema Schema `json:"schema,omitempty"` - Ref string `json:"$ref,omitempty"` + Description string `json:"description,omitempty"` + Schema *Schema `json:"schema,omitempty"` + Ref string `json:"$ref,omitempty"` } // Scurity Allows the definition of a security scheme that can be used by the operations @@ -144,9 +145,9 @@ type Scurity struct { // Tag Allows adding meta data to a single tag that is used by the Operation Object type Tag struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - ExternalDocs ExternalDocs `json:"externalDocs,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` } // ExternalDocs include Additional external documentation From ef0d3d80dcef374f61202f7b045d56e307b66d30 Mon Sep 17 00:00:00 2001 From: maxin Date: Sat, 13 Aug 2016 21:07:27 +0800 Subject: [PATCH 52/96] set session.managerconfig public --- hooks.go | 30 ++++++++++++++---------------- session/sess_cookie_test.go | 13 +++++++++++-- session/sess_mem_test.go | 8 +++++++- session/sess_test.go | 4 ++-- session/session.go | 21 +++++++++------------ 5 files changed, 43 insertions(+), 33 deletions(-) diff --git a/hooks.go b/hooks.go index 3dca1b8d..0c7d05fe 100644 --- a/hooks.go +++ b/hooks.go @@ -45,26 +45,24 @@ func registerSession() error { if BConfig.WebConfig.Session.SessionOn { var err error sessionConfig := AppConfig.String("sessionConfig") + conf := new(session.ManagerConfig) if sessionConfig == "" { - conf := map[string]interface{}{ - "cookieName": BConfig.WebConfig.Session.SessionName, - "gclifetime": BConfig.WebConfig.Session.SessionGCMaxLifetime, - "providerConfig": filepath.ToSlash(BConfig.WebConfig.Session.SessionProviderConfig), - "secure": BConfig.Listen.EnableHTTPS, - "enableSetCookie": BConfig.WebConfig.Session.SessionAutoSetCookie, - "domain": BConfig.WebConfig.Session.SessionDomain, - "cookieLifeTime": BConfig.WebConfig.Session.SessionCookieLifeTime, - "enableSidInHttpHeader": BConfig.WebConfig.Session.EnableSidInHttpHeader, - "sessionNameInHttpHeader": BConfig.WebConfig.Session.SessionNameInHttpHeader, - "enableSidInUrlQuery": BConfig.WebConfig.Session.EnableSidInUrlQuery, - } - confBytes, err := json.Marshal(conf) - if err != nil { + conf.CookieName = BConfig.WebConfig.Session.SessionName + conf.EnableSetCookie = BConfig.WebConfig.Session.SessionAutoSetCookie + conf.Gclifetime = BConfig.WebConfig.Session.SessionGCMaxLifetime + conf.Secure = BConfig.Listen.EnableHTTPS + conf.CookieLifeTime = BConfig.WebConfig.Session.SessionCookieLifeTime + conf.ProviderConfig = filepath.ToSlash(BConfig.WebConfig.Session.SessionProviderConfig) + conf.Domain = BConfig.WebConfig.Session.SessionDomain + conf.EnableSidInHttpHeader = BConfig.WebConfig.Session.EnableSidInHttpHeader + conf.SessionNameInHttpHeader = BConfig.WebConfig.Session.SessionNameInHttpHeader + conf.EnableSidInUrlQuery = BConfig.WebConfig.Session.EnableSidInUrlQuery + } else { + if err = json.Unmarshal([]byte(sessionConfig), conf); err != nil { return err } - sessionConfig = string(confBytes) } - if GlobalSessions, err = session.NewManager(BConfig.WebConfig.Session.SessionProvider, sessionConfig); err != nil { + if GlobalSessions, err = session.NewManager(BConfig.WebConfig.Session.SessionProvider, conf); err != nil { return err } go GlobalSessions.GC() diff --git a/session/sess_cookie_test.go b/session/sess_cookie_test.go index 209e501c..b6726005 100644 --- a/session/sess_cookie_test.go +++ b/session/sess_cookie_test.go @@ -15,6 +15,7 @@ package session import ( + "encoding/json" "net/http" "net/http/httptest" "strings" @@ -23,7 +24,11 @@ import ( func TestCookie(t *testing.T) { config := `{"cookieName":"gosessionid","enableSetCookie":false,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}` - globalSessions, err := NewManager("cookie", config) + conf := new(ManagerConfig) + if err := json.Unmarshal([]byte(config), conf); err != nil { + t.Fatal("json decode error", err) + } + globalSessions, err := NewManager("cookie", conf) if err != nil { t.Fatal("init cookie session err", err) } @@ -56,7 +61,11 @@ func TestCookie(t *testing.T) { func TestDestorySessionCookie(t *testing.T) { config := `{"cookieName":"gosessionid","enableSetCookie":true,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}` - globalSessions, err := NewManager("cookie", config) + conf := new(ManagerConfig) + if err := json.Unmarshal([]byte(config), conf); err != nil { + t.Fatal("json decode error", err) + } + globalSessions, err := NewManager("cookie", conf) if err != nil { t.Fatal("init cookie session err", err) } diff --git a/session/sess_mem_test.go b/session/sess_mem_test.go index 43f5b0a9..2e8934b8 100644 --- a/session/sess_mem_test.go +++ b/session/sess_mem_test.go @@ -15,6 +15,7 @@ package session import ( + "encoding/json" "net/http" "net/http/httptest" "strings" @@ -22,7 +23,12 @@ import ( ) func TestMem(t *testing.T) { - globalSessions, _ := NewManager("memory", `{"cookieName":"gosessionid","gclifetime":10}`) + config := `{"cookieName":"gosessionid","gclifetime":10, "enableSetCookie":true}` + conf := new(ManagerConfig) + if err := json.Unmarshal([]byte(config), conf); err != nil { + t.Fatal("json decode error", err) + } + globalSessions, _ := NewManager("memory", conf) go globalSessions.GC() r, _ := http.NewRequest("GET", "/", nil) w := httptest.NewRecorder() diff --git a/session/sess_test.go b/session/sess_test.go index 5ba910f2..b40865f3 100644 --- a/session/sess_test.go +++ b/session/sess_test.go @@ -89,7 +89,7 @@ func TestCookieEncodeDecode(t *testing.T) { func TestParseConfig(t *testing.T) { s := `{"cookieName":"gosessionid","gclifetime":3600}` - cf := new(managerConfig) + cf := new(ManagerConfig) cf.EnableSetCookie = true err := json.Unmarshal([]byte(s), cf) if err != nil { @@ -103,7 +103,7 @@ func TestParseConfig(t *testing.T) { } cc := `{"cookieName":"gosessionid","enableSetCookie":false,"gclifetime":3600,"ProviderConfig":"{\"cookieName\":\"gosessionid\",\"securityKey\":\"beegocookiehashkey\"}"}` - cf2 := new(managerConfig) + cf2 := new(ManagerConfig) cf2.EnableSetCookie = true err = json.Unmarshal([]byte(cc), cf2) if err != nil { diff --git a/session/session.go b/session/session.go index 73f0d677..3c9d07ab 100644 --- a/session/session.go +++ b/session/session.go @@ -30,7 +30,6 @@ package session import ( "crypto/rand" "encoding/hex" - "encoding/json" "errors" "fmt" "io" @@ -82,7 +81,7 @@ func Register(name string, provide Provider) { provides[name] = provide } -type managerConfig struct { +type ManagerConfig struct { CookieName string `json:"cookieName"` EnableSetCookie bool `json:"enableSetCookie,omitempty"` Gclifetime int64 `json:"gclifetime"` @@ -100,7 +99,7 @@ type managerConfig struct { // Manager contains Provider and its configuration. type Manager struct { provider Provider - config *managerConfig + config *ManagerConfig } // NewManager Create new Manager with provider name and json config string. @@ -115,17 +114,12 @@ type Manager struct { // 2. hashfunc default sha1 // 3. hashkey default beegosessionkey // 4. maxage default is none -func NewManager(provideName, config string) (*Manager, error) { +func NewManager(provideName string, cf *ManagerConfig) (*Manager, error) { provider, ok := provides[provideName] if !ok { return nil, fmt.Errorf("session: unknown provide %q (forgotten import?)", provideName) } - cf := new(managerConfig) - cf.EnableSetCookie = true - err := json.Unmarshal([]byte(config), cf) - if err != nil { - return nil, err - } + if cf.Maxlifetime == 0 { cf.Maxlifetime = cf.Gclifetime } @@ -142,7 +136,7 @@ func NewManager(provideName, config string) (*Manager, error) { } } - err = provider.SessionInit(cf.Maxlifetime, cf.ProviderConfig) + err := provider.SessionInit(cf.Maxlifetime, cf.ProviderConfig) if err != nil { return nil, err } @@ -166,7 +160,7 @@ func NewManager(provideName, config string) (*Manager, error) { // otherwise return an valid session id. func (manager *Manager) getSid(r *http.Request) (string, error) { cookie, errs := r.Cookie(manager.config.CookieName) - if errs != nil || cookie.Value == "" || cookie.MaxAge < 0 { + if errs != nil || cookie.Value == "" { var sid string if manager.config.EnableSidInUrlQuery { errs := r.ParseForm() @@ -211,6 +205,9 @@ func (manager *Manager) SessionStart(w http.ResponseWriter, r *http.Request) (se } session, err = manager.provider.SessionRead(sid) + if err != nil { + return nil, errs + } cookie := &http.Cookie{ Name: manager.config.CookieName, Value: url.QueryEscape(sid), From 3362f83662880d2df4138bd581e15c3bcb86c0fe Mon Sep 17 00:00:00 2001 From: astaxie Date: Tue, 16 Aug 2016 23:54:52 +0800 Subject: [PATCH 53/96] change to v1.7.0 --- beego.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beego.go b/beego.go index 32b64f75..10304994 100644 --- a/beego.go +++ b/beego.go @@ -23,7 +23,7 @@ import ( const ( // VERSION represent beego web framework version. - VERSION = "1.6.1" + VERSION = "1.7.0" // DEV is for develop DEV = "dev" From 44a57e86dd007e6ab76996b1e984c7b85167ef9f Mon Sep 17 00:00:00 2001 From: astaxie Date: Wed, 17 Aug 2016 22:05:54 +0800 Subject: [PATCH 54/96] fix #2090 --- logs/logger.go | 83 +++++++++++++------------------------------------- 1 file changed, 21 insertions(+), 62 deletions(-) diff --git a/logs/logger.go b/logs/logger.go index d74df7bf..a8333089 100644 --- a/logs/logger.go +++ b/logs/logger.go @@ -15,11 +15,11 @@ package logs import ( + "fmt" "io" + "os" "sync" "time" - "fmt" - "os" ) type logWriter struct { @@ -72,71 +72,30 @@ func NewModeAnsiColorWriter(w io.Writer, mode outputMode) io.Writer { return w } -const ( - y1 = `0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999` - y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789` - mo1 = `000000000111` - mo2 = `123456789012` - d1 = `0000000001111111111222222222233` - d2 = `1234567890123456789012345678901` - h1 = `000000000011111111112222` - h2 = `012345678901234567890123` - mi1 = `000000000011111111112222222222333333333344444444445555555555` - mi2 = `012345678901234567890123456789012345678901234567890123456789` - s1 = `000000000011111111112222222222333333333344444444445555555555` - s2 = `012345678901234567890123456789012345678901234567890123456789` -) - func formatTimeHeader(when time.Time) ([]byte, int) { - y, mo, d := when.Date() - h, mi, s := when.Clock() - //len("2006/01/02 15:04:05 ")==20 - var buf [20]byte - - //change to '3' after 984 years, LOL - buf[0] = '2' - //change to '1' after 84 years, LOL - buf[1] = '0' - buf[2] = y1[y - 2000] - buf[3] = y2[y - 2000] - buf[4] = '/' - buf[5] = mo1[mo - 1] - buf[6] = mo2[mo - 1] - buf[7] = '/' - buf[8] = d1[d - 1] - buf[9] = d2[d - 1] - buf[10] = ' ' - buf[11] = h1[h] - buf[12] = h2[h] - buf[13] = ':' - buf[14] = mi1[mi] - buf[15] = mi2[mi] - buf[16] = ':' - buf[17] = s1[s] - buf[18] = s2[s] - buf[19] = ' ' - - return buf[0:], d + _, _, d := when.Date() + s := when.Format("2006/01/02 15:04:05 ") + return []byte(s), d } var ( - green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) - white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) - yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) - red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) - blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) - magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) - cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) + green = string([]byte{27, 91, 57, 55, 59, 52, 50, 109}) + white = string([]byte{27, 91, 57, 48, 59, 52, 55, 109}) + yellow = string([]byte{27, 91, 57, 55, 59, 52, 51, 109}) + red = string([]byte{27, 91, 57, 55, 59, 52, 49, 109}) + blue = string([]byte{27, 91, 57, 55, 59, 52, 52, 109}) + magenta = string([]byte{27, 91, 57, 55, 59, 52, 53, 109}) + cyan = string([]byte{27, 91, 57, 55, 59, 52, 54, 109}) - w32Green = string([]byte{27, 91, 52, 50, 109}) - w32White = string([]byte{27, 91, 52, 55, 109}) - w32Yellow = string([]byte{27, 91, 52, 51, 109}) - w32Red = string([]byte{27, 91, 52, 49, 109}) - w32Blue = string([]byte{27, 91, 52, 52, 109}) - w32Magenta = string([]byte{27, 91, 52, 53, 109}) - w32Cyan = string([]byte{27, 91, 52, 54, 109}) + w32Green = string([]byte{27, 91, 52, 50, 109}) + w32White = string([]byte{27, 91, 52, 55, 109}) + w32Yellow = string([]byte{27, 91, 52, 51, 109}) + w32Red = string([]byte{27, 91, 52, 49, 109}) + w32Blue = string([]byte{27, 91, 52, 52, 109}) + w32Magenta = string([]byte{27, 91, 52, 53, 109}) + w32Cyan = string([]byte{27, 91, 52, 54, 109}) - reset = string([]byte{27, 91, 48, 109}) + reset = string([]byte{27, 91, 48, 109}) ) func ColorByStatus(cond bool, code int) string { @@ -185,4 +144,4 @@ func W32Debug(msg string) { w := NewAnsiColorWriter(os.Stdout) fmt.Fprintf(w, "[beego] %v %s\n", current.Format("2006/01/02 - 15:04:05"), msg) -} \ No newline at end of file +} From bed1d9bd27d2209c7d10d8d5d0d420b4049002e0 Mon Sep 17 00:00:00 2001 From: astaxie Date: Wed, 17 Aug 2016 22:33:36 +0800 Subject: [PATCH 55/96] update the performance --- logs/logger.go | 49 ++++++++++++++++++++++++++++++++++++++++++--- logs/logger_test.go | 4 ++-- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/logs/logger.go b/logs/logger.go index a8333089..1d480c07 100644 --- a/logs/logger.go +++ b/logs/logger.go @@ -72,10 +72,53 @@ func NewModeAnsiColorWriter(w io.Writer, mode outputMode) io.Writer { return w } +const ( + y1 = `0123456789` + y2 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789` + y3 = `0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999` + y4 = `0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789` + mo1 = `000000000111` + mo2 = `123456789012` + d1 = `0000000001111111111222222222233` + d2 = `1234567890123456789012345678901` + h1 = `000000000011111111112222` + h2 = `012345678901234567890123` + mi1 = `000000000011111111112222222222333333333344444444445555555555` + mi2 = `012345678901234567890123456789012345678901234567890123456789` + s1 = `000000000011111111112222222222333333333344444444445555555555` + s2 = `012345678901234567890123456789012345678901234567890123456789` +) + func formatTimeHeader(when time.Time) ([]byte, int) { - _, _, d := when.Date() - s := when.Format("2006/01/02 15:04:05 ") - return []byte(s), d + y, mo, d := when.Date() + h, mi, s := when.Clock() + //len("2006/01/02 15:04:05 ")==20 + var buf [20]byte + + //change to '3' after 984 years, LOL + buf[0] = y1[y/1000%10] + //change to '1' after 84 years, LOL + buf[1] = y2[y/100] + buf[2] = y3[y-y/100*100] + buf[3] = y4[y-y/100*100] + buf[4] = '/' + buf[5] = mo1[mo-1] + buf[6] = mo2[mo-1] + buf[7] = '/' + buf[8] = d1[d-1] + buf[9] = d2[d-1] + buf[10] = ' ' + buf[11] = h1[h] + buf[12] = h2[h] + buf[13] = ':' + buf[14] = mi1[mi] + buf[15] = mi2[mi] + buf[16] = ':' + buf[17] = s1[s] + buf[18] = s2[s] + buf[19] = ' ' + + return buf[0:], d } var ( diff --git a/logs/logger_test.go b/logs/logger_test.go index c65d70d5..119b7bd3 100644 --- a/logs/logger_test.go +++ b/logs/logger_test.go @@ -15,9 +15,9 @@ package logs import ( + "bytes" "testing" "time" - "bytes" ) func TestFormatHeader_0(t *testing.T) { @@ -72,4 +72,4 @@ func TestNewAnsiColor2(t *testing.T) { if w1 != w2 { t.Errorf("Get %#v, want %#v", w1, w2) } -} \ No newline at end of file +} From 68311b286e8b83ec77297237a8f297953dbef48a Mon Sep 17 00:00:00 2001 From: astaxie Date: Wed, 17 Aug 2016 22:49:30 +0800 Subject: [PATCH 56/96] gofmt -s -w . --- httplib/httplib.go | 8 ++++---- logs/color.go | 2 +- logs/color_windows.go | 8 ++++---- logs/color_windows_test.go | 2 +- logs/logger.go | 2 -- orm/orm_test.go | 2 +- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/httplib/httplib.go b/httplib/httplib.go index 505bb45b..7e6f2700 100644 --- a/httplib/httplib.go +++ b/httplib/httplib.go @@ -409,10 +409,10 @@ func (b *BeegoHTTPRequest) DoRequest() (*http.Response, error) { if trans == nil { // create default transport trans = &http.Transport{ - TLSClientConfig: b.setting.TLSClientConfig, - Proxy: b.setting.Proxy, - Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), - MaxIdleConnsPerHost: -1, + TLSClientConfig: b.setting.TLSClientConfig, + Proxy: b.setting.Proxy, + Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), + MaxIdleConnsPerHost: -1, } } else { // if b.transport is *http.Transport then set the settings. diff --git a/logs/color.go b/logs/color.go index fb21b42e..41d23638 100644 --- a/logs/color.go +++ b/logs/color.go @@ -25,4 +25,4 @@ type ansiColorWriter struct { func (cw *ansiColorWriter) Write(p []byte) (int, error) { return cw.w.Write(p) -} \ No newline at end of file +} diff --git a/logs/color_windows.go b/logs/color_windows.go index 47f7b24e..deee4c87 100644 --- a/logs/color_windows.go +++ b/logs/color_windows.go @@ -25,7 +25,7 @@ import ( ) type ( - csiState int + csiState int parseResult int ) @@ -294,7 +294,7 @@ func changeColor(param []byte) parseResult { case ansiBlinkOff: winAttr.backgroundIntensity = 0 default: - // unknown code + // unknown code } case c.drawType == foreground: winAttr.foregroundColor = c.code @@ -420,9 +420,9 @@ func (cw *ansiColorWriter) Write(p []byte) (int, error) { } if cw.mode != DiscardNonColorEscSeq || cw.state == outsideCsiCode { - nw, err = cw.w.Write(p[first:len(p)]) + nw, err = cw.w.Write(p[first:]) r += nw } return r, err -} \ No newline at end of file +} diff --git a/logs/color_windows_test.go b/logs/color_windows_test.go index d87d9798..5074841a 100644 --- a/logs/color_windows_test.go +++ b/logs/color_windows_test.go @@ -291,4 +291,4 @@ func TestIgnoreUnknownSequences(t *testing.T) { if actualTail != expectedTail { t.Errorf("Get %q, want %q", actualTail, expectedTail) } -} \ No newline at end of file +} diff --git a/logs/logger.go b/logs/logger.go index 1d480c07..e0abfdc4 100644 --- a/logs/logger.go +++ b/logs/logger.go @@ -95,9 +95,7 @@ func formatTimeHeader(when time.Time) ([]byte, int) { //len("2006/01/02 15:04:05 ")==20 var buf [20]byte - //change to '3' after 984 years, LOL buf[0] = y1[y/1000%10] - //change to '1' after 84 years, LOL buf[1] = y2[y/100] buf[2] = y3[y-y/100*100] buf[3] = y4[y-y/100*100] diff --git a/orm/orm_test.go b/orm/orm_test.go index 1410fd8a..75a60cb0 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -2062,7 +2062,7 @@ func TestIntegerPk(t *testing.T) { throwFail(t, AssertIs(out.Value, intPk.Value)) } - num, err = dORM.InsertMulti(1, []*IntegerPk{&IntegerPk{ + num, err = dORM.InsertMulti(1, []*IntegerPk{{ ID: 1, Value: "ok", }}) throwFail(t, err) From 3672f96a9d832c044565b5c0a92af8bac7fd237c Mon Sep 17 00:00:00 2001 From: astaxie Date: Wed, 17 Aug 2016 22:56:21 +0800 Subject: [PATCH 57/96] fix the typo --- mime.go | 2 +- parser.go | 2 +- utils/file_test.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mime.go b/mime.go index e85fcb2a..ca2878ab 100644 --- a/mime.go +++ b/mime.go @@ -339,7 +339,7 @@ var mimemaps = map[string]string{ ".pvu": "paleovu/x-pv", ".pwz": "application/vndms-powerpoint", ".py": "text/x-scriptphyton", - ".pyc": "applicaiton/x-bytecodepython", + ".pyc": "application/x-bytecodepython", ".qcp": "audio/vndqcelp", ".qd3": "x-world/x-3dmf", ".qd3d": "x-world/x-3dmf", diff --git a/parser.go b/parser.go index caa2b38b..ffcd27a4 100644 --- a/parser.go +++ b/parser.go @@ -101,7 +101,7 @@ func parserComments(comments *ast.CommentGroup, funcName, controllerName, pkgpat elements := strings.TrimLeft(t, "@router ") e1 := strings.SplitN(elements, " ", 2) if len(e1) < 1 { - return errors.New("you should has router infomation") + return errors.New("you should has router information") } key := pkgpath + ":" + controllerName cc := ControllerComments{} diff --git a/utils/file_test.go b/utils/file_test.go index 020d7e4c..86d1a700 100644 --- a/utils/file_test.go +++ b/utils/file_test.go @@ -41,7 +41,7 @@ func TestFileExists(t *testing.T) { } if FileExists(noExistedFile) { - t.Errorf("Wierd, how could this file exists: %s", noExistedFile) + t.Errorf("Weird, how could this file exists: %s", noExistedFile) } } @@ -52,7 +52,7 @@ func TestSearchFile(t *testing.T) { } t.Log(path) - path, err = SearchFile(noExistedFile, ".") + _, err = SearchFile(noExistedFile, ".") if err == nil { t.Errorf("err shouldnot be nil, got path: %s", SelfDir()) } From 7760d2476184a6bdd83f3e16d446ae9feb07fcba Mon Sep 17 00:00:00 2001 From: astaxie Date: Wed, 17 Aug 2016 23:52:34 +0800 Subject: [PATCH 58/96] fix the typo --- config/yaml/yaml.go | 2 +- router.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/yaml/yaml.go b/config/yaml/yaml.go index 64e25cb3..e3260215 100644 --- a/config/yaml/yaml.go +++ b/config/yaml/yaml.go @@ -281,7 +281,7 @@ func (c *ConfigContainer) DIY(key string) (v interface{}, err error) { func (c *ConfigContainer) getData(key string) (interface{}, error) { if len(key) == 0 { - return nil, errors.New("key is emtpy") + return nil, errors.New("key is empty") } if v, ok := c.data[key]; ok { diff --git a/router.go b/router.go index 6dc94a13..2adecbf0 100644 --- a/router.go +++ b/router.go @@ -434,7 +434,7 @@ func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter Filter // add Filter into func (p *ControllerRegister) insertFilterRouter(pos int, mr *FilterRouter) (err error) { if pos < BeforeStatic || pos > FinishRouter { - err = fmt.Errorf("can not find your filter postion") + err = fmt.Errorf("can not find your filter position") return } p.enableFilter = true From 1c54ff27c4a1fcd9a5b306ad0c7a5b0d7da4ee6e Mon Sep 17 00:00:00 2001 From: tnextday Date: Thu, 18 Aug 2016 21:24:54 +0800 Subject: [PATCH 59/96] update swagger define --- swagger/swagger.go | 1 + 1 file changed, 1 insertion(+) diff --git a/swagger/swagger.go b/swagger/swagger.go index d585e9d9..17429d3c 100644 --- a/swagger/swagger.go +++ b/swagger/swagger.go @@ -121,6 +121,7 @@ type Propertie struct { Format string `json:"format,omitempty"` ReadOnly bool `json:"readOnly,omitempty"` Properties map[string]Propertie `json:"properties,omitempty"` + Items []Propertie `json:"items,omitempty"` AdditionalProperties *Propertie `json:"additionalProperties,omitempty"` } From ffd748bf75b150987e7562ddbe49236b32f20e82 Mon Sep 17 00:00:00 2001 From: tnextday Date: Thu, 18 Aug 2016 22:26:15 +0800 Subject: [PATCH 60/96] update swagger --- swagger/swagger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swagger/swagger.go b/swagger/swagger.go index 17429d3c..1060477a 100644 --- a/swagger/swagger.go +++ b/swagger/swagger.go @@ -121,7 +121,7 @@ type Propertie struct { Format string `json:"format,omitempty"` ReadOnly bool `json:"readOnly,omitempty"` Properties map[string]Propertie `json:"properties,omitempty"` - Items []Propertie `json:"items,omitempty"` + Items *Propertie `json:"items,omitempty"` AdditionalProperties *Propertie `json:"additionalProperties,omitempty"` } From 86b3162afffda5b9b8d1c363d6712b0ded1fc1e9 Mon Sep 17 00:00:00 2001 From: astaxie Date: Fri, 19 Aug 2016 00:11:19 +0800 Subject: [PATCH 61/96] fix #1695 update docs --- context/input.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/context/input.go b/context/input.go index 1683060e..1e051784 100644 --- a/context/input.go +++ b/context/input.go @@ -337,7 +337,7 @@ func (input *BeegoInput) Cookie(key string) string { } // Session returns current session item value by a given key. -// if non-existed, return empty string. +// if non-existed, return nil. func (input *BeegoInput) Session(key interface{}) interface{} { return input.CruSession.Get(key) } From 8b525b1aa5d384ef25370b4149f4cf2db25d399b Mon Sep 17 00:00:00 2001 From: astaxie Date: Fri, 19 Aug 2016 00:31:46 +0800 Subject: [PATCH 62/96] fix #1656 --- context/input.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/context/input.go b/context/input.go index 1e051784..fd47c36f 100644 --- a/context/input.go +++ b/context/input.go @@ -344,6 +344,9 @@ func (input *BeegoInput) Session(key interface{}) interface{} { // CopyBody returns the raw request body data as bytes. func (input *BeegoInput) CopyBody(MaxMemory int64) []byte { + if input.Context.Request.Body == nil { + return []byte{} + } safe := &io.LimitedReader{R: input.Context.Request.Body, N: MaxMemory} requestbody, _ := ioutil.ReadAll(safe) input.Context.Request.Body.Close() From efbde1ee77517486eac03e814e01d724ddad18e6 Mon Sep 17 00:00:00 2001 From: astaxie Date: Fri, 19 Aug 2016 23:52:19 +0800 Subject: [PATCH 63/96] add *Item into Schema --- swagger/swagger.go | 1 + 1 file changed, 1 insertion(+) diff --git a/swagger/swagger.go b/swagger/swagger.go index 1060477a..ff04da13 100644 --- a/swagger/swagger.go +++ b/swagger/swagger.go @@ -106,6 +106,7 @@ type Schema struct { Description string `json:"description,omitempty"` Required []string `json:"required,omitempty"` Type string `json:"type,omitempty"` + Items *Propertie `json:"items,omitempty"` Properties map[string]Propertie `json:"properties,omitempty"` } From 9224cd3ef7e44e8684c47891b93b64688d4e223e Mon Sep 17 00:00:00 2001 From: tnextday Date: Sat, 20 Aug 2016 11:22:03 +0800 Subject: [PATCH 64/96] add ParameterItems in Parameter --- swagger/swagger.go | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/swagger/swagger.go b/swagger/swagger.go index 1060477a..6f9f7b93 100644 --- a/swagger/swagger.go +++ b/swagger/swagger.go @@ -89,13 +89,24 @@ type Operation struct { // Parameter Describes a single operation parameter. type Parameter struct { - In string `json:"in,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Required bool `json:"required,omitempty"` - Schema *Schema `json:"schema,omitempty"` - Type string `json:"type,omitempty"` - Format string `json:"format,omitempty"` + In string `json:"in,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Required bool `json:"required,omitempty"` + Schema *Schema `json:"schema,omitempty"` + Type string `json:"type,omitempty"` + Format string `json:"format,omitempty"` + Items *ParameterItems `json:"items,omitempty"` +} + +// A limited subset of JSON-Schema's items object. It is used by parameter definitions that are not located in "body". +// http://swagger.io/specification/#itemsObject +type ParameterItems struct { + Type string `json:"type,omitempty"` + Format string `json:"format,omitempty"` + Items []*ParameterItems `json:"items,omitempty"` //Required if type is "array". Describes the type of items in the array. + CollectionFormat string `json:"collectionFormat,omitempty"` + Default string `json:"default,omitempty"` } // Schema Object allows the definition of input and output data types. From 7253ff2f8ca66dfbd74bb5a45cb0959ee98f68f7 Mon Sep 17 00:00:00 2001 From: tnextday Date: Sat, 20 Aug 2016 15:03:41 +0800 Subject: [PATCH 65/96] update schema define --- swagger/swagger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swagger/swagger.go b/swagger/swagger.go index 0043a8e1..a16b26b4 100644 --- a/swagger/swagger.go +++ b/swagger/swagger.go @@ -117,7 +117,7 @@ type Schema struct { Description string `json:"description,omitempty"` Required []string `json:"required,omitempty"` Type string `json:"type,omitempty"` - Items *Propertie `json:"items,omitempty"` + Items *Schema `json:"items,omitempty"` Properties map[string]Propertie `json:"properties,omitempty"` } From 00e986cd3b50a464ebb74b332144d87b93950066 Mon Sep 17 00:00:00 2001 From: chenlei106 Date: Tue, 23 Aug 2016 16:16:57 +0800 Subject: [PATCH 66/96] log output format improvement move log level info ahead to filename info, better readability --- logs/log.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/logs/log.go b/logs/log.go index c43782f3..3ff72447 100644 --- a/logs/log.go +++ b/logs/log.go @@ -260,12 +260,7 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error bl.setLogger(AdapterConsole) bl.lock.Unlock() } - if logLevel == levelLoggerImpl { - // set to emergency to ensure all log will be print out correctly - logLevel = LevelEmergency - } else { - msg = levelPrefix[logLevel] + msg - } + if len(v) > 0 { msg = fmt.Sprintf(msg, v...) } @@ -279,6 +274,15 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error _, filename := path.Split(file) msg = "[" + filename + ":" + strconv.FormatInt(int64(line), 10) + "] " + msg } + + //set level info in front of filename info + if logLevel == levelLoggerImpl { + // set to emergency to ensure all log will be print out correctly + logLevel = LevelEmergency + } else { + msg = levelPrefix[logLevel] + msg + } + if bl.asynchronous { lm := logMsgPool.Get().(*logMsg) lm.level = logLevel From 227678c2ef1af94ca55fde610fe862e346f5bde7 Mon Sep 17 00:00:00 2001 From: astaxie Date: Tue, 23 Aug 2016 23:48:47 +0800 Subject: [PATCH 67/96] Set SetLogFuncCallDepth default to 4 --- logs/log.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logs/log.go b/logs/log.go index 3ff72447..ce9eb79d 100644 --- a/logs/log.go +++ b/logs/log.go @@ -536,10 +536,10 @@ func EnableFuncCallDepth(b bool) { beeLogger.enableFuncCallDepth = b } -// SetLogFuncCall set the CallDepth, default is 3 +// SetLogFuncCall set the CallDepth, default is 4 func SetLogFuncCall(b bool) { beeLogger.EnableFuncCallDepth(b) - beeLogger.SetLogFuncCallDepth(3) + beeLogger.SetLogFuncCallDepth(4) } // SetLogFuncCallDepth set log funcCallDepth From 0ad4038d9fbcf49cbe360da119352ef99e9b1c29 Mon Sep 17 00:00:00 2001 From: YakunZ Date: Wed, 24 Aug 2016 16:04:22 +0800 Subject: [PATCH 68/96] fix#2039 & test --- context/input.go | 9 ++++++--- context/input_test.go | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/context/input.go b/context/input.go index fd47c36f..5af8e6c4 100644 --- a/context/input.go +++ b/context/input.go @@ -590,12 +590,15 @@ func (input *BeegoInput) bindStruct(params *url.Values, key string, typ reflect. result := reflect.New(typ).Elem() fieldValues := make(map[string]reflect.Value) for reqKey, val := range *params { - if !strings.HasPrefix(reqKey, key+".") { + var fieldName string + if strings.HasPrefix(reqKey, key+".") { + fieldName = reqKey[len(key)+1:] + } else if strings.HasPrefix(reqKey, key+"[") && reqKey[len(reqKey)-1] == ']' { + fieldName = reqKey[len(key)+1 : len(reqKey)-1] + } else { continue } - fieldName := reqKey[len(key)+1:] - if _, ok := fieldValues[fieldName]; !ok { // Time to bind this field. Get it and make sure we can set it. fieldValue := result.FieldByName(fieldName) diff --git a/context/input_test.go b/context/input_test.go index 8887aec4..e64addba 100644 --- a/context/input_test.go +++ b/context/input_test.go @@ -75,6 +75,24 @@ func TestParse(t *testing.T) { fmt.Println(user) } +func TestParse2(t *testing.T) { + r, _ := http.NewRequest("GET", "/?user[0][Username]=Raph&user[1].Username=Leo&user[0].Password=123456&user[1][Password]=654321", nil) + beegoInput := NewInput() + beegoInput.Context = NewContext() + beegoInput.Context.Reset(httptest.NewRecorder(), r) + beegoInput.ParseFormOrMulitForm(1 << 20) + type User struct { + Username string + Password string + } + var users []User + err := beegoInput.Bind(&users, "user") + fmt.Println(users) + if err != nil || users[0].Username != "Raph" || users[0].Password != "123456" || users[1].Username != "Leo" || users[1].Password != "654321" { + t.Fatal("users info wrong") + } +} + func TestSubDomain(t *testing.T) { r, _ := http.NewRequest("GET", "http://www.example.com/?id=123&isok=true&ft=1.2&ol[0]=1&ol[1]=2&ul[]=str&ul[]=array&user.Name=astaxie", nil) beegoInput := NewInput() From f280dab8805230a7587e0c87f06f74ad7791444a Mon Sep 17 00:00:00 2001 From: astaxie Date: Mon, 29 Aug 2016 13:42:40 +0800 Subject: [PATCH 69/96] Revert "route variables should not have underline" --- tree.go | 4 ++-- tree_test.go | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tree.go b/tree.go index f93dfa64..25b78e50 100644 --- a/tree.go +++ b/tree.go @@ -467,7 +467,7 @@ func splitPath(key string) []string { // ":name:string" -> true, [:name], ([\w]+) // ":id([0-9]+)" -> true, [:id], ([0-9]+) // ":id([0-9]+)_:name" -> true, [:id :name], ([0-9]+)_(.+) -// "cms_:id_:page.html" -> true, [:id :page], cms_(.+)_(.+).html +// "cms_:id_:page.html" -> true, [:id_ :page], cms_(.+)(.+).html // "cms_:id(.+)_:page.html" -> true, [:id :page], cms_(.+)_(.+).html // "*" -> true, [:splat], "" // "*.*" -> true,[. :path :ext], "" . meaning separator @@ -487,7 +487,7 @@ func splitSegment(key string) (bool, []string, string) { var expt []rune var skipnum int params := []string{} - reg := regexp.MustCompile(`[a-zA-Z0-9]+`) + reg := regexp.MustCompile(`[a-zA-Z0-9_]+`) for i, v := range key { if skipnum > 0 { skipnum-- diff --git a/tree_test.go b/tree_test.go index a20aed84..81ff7edd 100644 --- a/tree_test.go +++ b/tree_test.go @@ -74,8 +74,6 @@ func init() { routers = append(routers, testinfo{"/v1/:v(.+)_cms/ttt_:id(.+)_:page(.+).html", "/v1/2_cms/ttt_123_1.html", map[string]string{":v": "2", ":id": "123", ":page": "1"}}) routers = append(routers, testinfo{"/api/projects/:pid/members/?:mid", "/api/projects/1/members", map[string]string{":pid": "1"}}) routers = append(routers, testinfo{"/api/projects/:pid/members/?:mid", "/api/projects/1/members/2", map[string]string{":pid": "1", ":mid": "2"}}) - routers = append(routers, testinfo{"/view/:id_:page", "/view/12_33", map[string]string{":id": "12", ":page": "33"}}) - routers = append(routers, testinfo{"/view/:id_:page.html", "/view/12_33.html", map[string]string{":id": "12", ":page": "33"}}) } func TestTreeRouters(t *testing.T) { From 3f67c62dd87d651ef95883586bca141abf795507 Mon Sep 17 00:00:00 2001 From: astaxie Date: Tue, 30 Aug 2016 00:15:02 +0800 Subject: [PATCH 70/96] revert the snakeString --- orm/orm_test.go | 12 ++++++------ orm/utils.go | 30 ++++++------------------------ 2 files changed, 12 insertions(+), 30 deletions(-) diff --git a/orm/orm_test.go b/orm/orm_test.go index 75a60cb0..bb1831f9 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -2135,13 +2135,13 @@ func TestSnake(t *testing.T) { "i": "i", "I": "i", "iD": "i_d", - "ID": "id", - "NO": "no", - "NOO": "noo", - "NOOooOOoo": "noo_oo_oo_oo", - "OrderNO": "order_no", + "ID": "i_d", + "NO": "n_o", + "NOO": "n_o_o", + "NOOooOOoo": "n_o_ooo_o_ooo", + "OrderNO": "order_n_o", "tagName": "tag_name", - "tag_Name": "tag_name", + "tag_Name": "tag__name", "tag_name": "tag_name", "_tag_name": "_tag_name", "tag_666name": "tag_666name", diff --git a/orm/utils.go b/orm/utils.go index e3cd8ad6..6e23447e 100644 --- a/orm/utils.go +++ b/orm/utils.go @@ -184,33 +184,15 @@ func ToInt64(value interface{}) (d int64) { // snake string, XxYy to xx_yy , XxYY to xx_yy func snakeString(s string) string { data := make([]byte, 0, len(s)*2) + j := false num := len(s) for i := 0; i < num; i++ { d := s[i] - if i > 0 && d != '_' && s[i-1] != '_' { - need := false - // upper as 1, lower as 0 - // XX -> 11 -> 11 - // Xx -> 10 -> 10 - // XxYyZZ -> 101011 -> 10_10_11 - isUpper := d >= 'A' && d <= 'Z' - preIsUpper := s[i-1] >= 'A' && s[i-1] <= 'Z' - if isUpper { - // like : xxYy - if !preIsUpper { - need = true - } - } else { - if preIsUpper { - // ignore "Xy" in "xxXyy" - if i-2 >= 0 && s[i-2] >= 'A' && s[i-2] <= 'Z' { - need = true - } - } - } - if need { - data = append(data, '_') - } + if i > 0 && d >= 'A' && d <= 'Z' && j { + data = append(data, '_') + } + if d != '_' { + j = true } data = append(data, d) } From fb2343567b41bc116447b3870976291dc9afadd4 Mon Sep 17 00:00:00 2001 From: astaxie Date: Tue, 30 Aug 2016 20:40:46 +0800 Subject: [PATCH 71/96] fix #2125 --- orm/db_utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orm/db_utils.go b/orm/db_utils.go index cf465d02..34ddfae9 100644 --- a/orm/db_utils.go +++ b/orm/db_utils.go @@ -145,7 +145,7 @@ outFor: if v, ok := arg.(time.Time); ok { if fi != nil && fi.fieldType == TypeDateField { arg = v.In(tz).Format(formatDate) - } else if fi.fieldType == TypeDateTimeField { + } else if fi != nil && fi.fieldType == TypeDateTimeField { arg = v.In(tz).Format(formatDateTime) } else { arg = v.In(tz).Format(formatTime) From 1e1e90027801e74db13447245a59426c0efbe435 Mon Sep 17 00:00:00 2001 From: astaxie Date: Tue, 30 Aug 2016 21:00:27 +0800 Subject: [PATCH 72/96] fix #2126 --- error.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/error.go b/error.go index ce25d281..ab626247 100644 --- a/error.go +++ b/error.go @@ -93,7 +93,11 @@ func showErr(err interface{}, ctx *context.Context, stack string) { "BeegoVersion": VERSION, "GoVersion": runtime.Version(), } - ctx.ResponseWriter.WriteHeader(500) + if ctx.Output.Status != 0 { + ctx.ResponseWriter.WriteHeader(ctx.Output.Status) + } else { + ctx.ResponseWriter.WriteHeader(500) + } t.Execute(ctx.ResponseWriter, data) } From 7df74c0a309df525ac8c25aba15056f4060bcf44 Mon Sep 17 00:00:00 2001 From: astaxie Date: Tue, 30 Aug 2016 21:24:56 +0800 Subject: [PATCH 73/96] fix #1521 --- cache/memcache/memcache.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/cache/memcache/memcache.go b/cache/memcache/memcache.go index 3f0fe411..972361f7 100644 --- a/cache/memcache/memcache.go +++ b/cache/memcache/memcache.go @@ -33,12 +33,10 @@ import ( "encoding/json" "errors" "strings" - - "github.com/bradfitz/gomemcache/memcache" - "time" "github.com/astaxie/beego/cache" + "github.com/bradfitz/gomemcache/memcache" ) // Cache Memcache adapter. @@ -60,7 +58,7 @@ func (rc *Cache) Get(key string) interface{} { } } if item, err := rc.conn.Get(key); err == nil { - return string(item.Value) + return item.Value } return nil } @@ -80,7 +78,7 @@ func (rc *Cache) GetMulti(keys []string) []interface{} { mv, err := rc.conn.GetMulti(keys) if err == nil { for _, v := range mv { - rv = append(rv, string(v.Value)) + rv = append(rv, v.Value) } return rv } @@ -90,18 +88,21 @@ func (rc *Cache) GetMulti(keys []string) []interface{} { return rv } -// Put put value to memcache. only support string. +// Put put value to memcache. func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error { if rc.conn == nil { if err := rc.connectInit(); err != nil { return err } } - v, ok := val.(string) - if !ok { - return errors.New("val must string") + item := memcache.Item{Key: key, Expiration: int32(timeout / time.Second)} + if v, ok := val.([]byte); ok { + item.Value = v + } else if str, ok := val.(string); ok { + item.Value = []byte(str) + } else { + return errors.New("val only support string and []byte") } - item := memcache.Item{Key: key, Value: []byte(v), Expiration: int32(timeout / time.Second)} return rc.conn.Set(&item) } From aa091cea42a1ce1d227e149ecb66ba46ffda4f40 Mon Sep 17 00:00:00 2001 From: astaxie Date: Tue, 30 Aug 2016 22:02:11 +0800 Subject: [PATCH 74/96] improvement the error if use &&Struct --- orm/models.go | 10 +++++----- orm/models_boot.go | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/orm/models.go b/orm/models.go index faf551be..5cfc69b1 100644 --- a/orm/models.go +++ b/orm/models.go @@ -57,11 +57,11 @@ var ( // model info collection type _modelCache struct { - sync.RWMutex - orders []string - cache map[string]*modelInfo - cacheByFN map[string]*modelInfo - done bool + sync.RWMutex // only used outsite for bootStrap + orders []string + cache map[string]*modelInfo + cacheByFN map[string]*modelInfo + done bool } // get all model info diff --git a/orm/models_boot.go b/orm/models_boot.go index c9905330..364b68e9 100644 --- a/orm/models_boot.go +++ b/orm/models_boot.go @@ -32,6 +32,9 @@ func registerModel(prefix string, model interface{}) { if val.Kind() != reflect.Ptr { panic(fmt.Errorf(" cannot use non-ptr model struct `%s`", getFullName(typ))) } + if typ.Kind() == reflect.Ptr { + panic(fmt.Errorf(" only allow ptr model struct, it looks you use two reference to the struct `%s`", typ)) + } table := getTableName(val) @@ -320,7 +323,6 @@ func BootStrap() { if modelCache.done { return } - modelCache.Lock() defer modelCache.Unlock() bootStrap() From 161c0613764134430511519a3f7fbf05215fd3d9 Mon Sep 17 00:00:00 2001 From: astaxie Date: Tue, 30 Aug 2016 23:24:30 +0800 Subject: [PATCH 75/96] fix cache/memcache test --- cache/memcache/memcache_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cache/memcache/memcache_test.go b/cache/memcache/memcache_test.go index 0c8c57f2..d9129b69 100644 --- a/cache/memcache/memcache_test.go +++ b/cache/memcache/memcache_test.go @@ -46,7 +46,7 @@ func TestMemcacheCache(t *testing.T) { t.Error("set Error", err) } - if v, err := strconv.Atoi(bm.Get("astaxie").(string)); err != nil || v != 1 { + if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 { t.Error("get err") } @@ -54,7 +54,7 @@ func TestMemcacheCache(t *testing.T) { t.Error("Incr Error", err) } - if v, err := strconv.Atoi(bm.Get("astaxie").(string)); err != nil || v != 2 { + if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 2 { t.Error("get err") } @@ -62,7 +62,7 @@ func TestMemcacheCache(t *testing.T) { t.Error("Decr Error", err) } - if v, err := strconv.Atoi(bm.Get("astaxie").(string)); err != nil || v != 1 { + if v, err := strconv.Atoi(string(bm.Get("astaxie").([]byte))); err != nil || v != 1 { t.Error("get err") } bm.Delete("astaxie") @@ -78,7 +78,7 @@ func TestMemcacheCache(t *testing.T) { t.Error("check err") } - if v := bm.Get("astaxie").(string); v != "author" { + if v := bm.Get("astaxie").([]byte); string(v) != "author" { t.Error("get err") } @@ -94,10 +94,10 @@ func TestMemcacheCache(t *testing.T) { if len(vv) != 2 { t.Error("GetMulti ERROR") } - if vv[0].(string) != "author" && vv[0].(string) != "author1" { + if string(vv[0].([]byte)) != "author" && string(vv[0].([]byte)) != "author1" { t.Error("GetMulti ERROR") } - if vv[1].(string) != "author1" && vv[1].(string) != "author" { + if string(vv[1].([]byte)) != "author1" && string(vv[1].([]byte)) != "author" { t.Error("GetMulti ERROR") } From 8c37a07adb23862114a6a1700b5f661a079b2fe0 Mon Sep 17 00:00:00 2001 From: astaxie Date: Wed, 31 Aug 2016 00:07:19 +0800 Subject: [PATCH 76/96] optimize the ORM --- orm/db_utils.go | 2 +- orm/models.go | 45 ++++++---------------- orm/models_boot.go | 14 ++++--- orm/models_info_f.go | 8 +++- orm/models_info_m.go | 90 ++++++++++++++++++++------------------------ orm/models_utils.go | 60 ++++++++++++++++++++--------- orm/orm.go | 4 +- orm/orm_raw.go | 18 +++------ orm/orm_test.go | 2 +- 9 files changed, 119 insertions(+), 124 deletions(-) diff --git a/orm/db_utils.go b/orm/db_utils.go index 34ddfae9..0279a14a 100644 --- a/orm/db_utils.go +++ b/orm/db_utils.go @@ -154,7 +154,7 @@ outFor: typ := val.Type() name := getFullName(typ) var value interface{} - if mmi, ok := modelCache.getByFN(name); ok { + if mmi, ok := modelCache.getByFullName(name); ok { if _, vu, exist := getExistPk(mmi, val); exist { value = vu } diff --git a/orm/models.go b/orm/models.go index 5cfc69b1..1d5a4dc2 100644 --- a/orm/models.go +++ b/orm/models.go @@ -29,39 +29,18 @@ const ( var ( modelCache = &_modelCache{ - cache: make(map[string]*modelInfo), - cacheByFN: make(map[string]*modelInfo), - } - supportTag = map[string]int{ - "-": 1, - "null": 1, - "index": 1, - "unique": 1, - "pk": 1, - "auto": 1, - "auto_now": 1, - "auto_now_add": 1, - "size": 2, - "column": 2, - "default": 2, - "rel": 2, - "reverse": 2, - "rel_table": 2, - "rel_through": 2, - "digits": 2, - "decimals": 2, - "on_delete": 2, - "type": 2, + cache: make(map[string]*modelInfo), + cacheByFullName: make(map[string]*modelInfo), } ) // model info collection type _modelCache struct { - sync.RWMutex // only used outsite for bootStrap - orders []string - cache map[string]*modelInfo - cacheByFN map[string]*modelInfo - done bool + sync.RWMutex // only used outsite for bootStrap + orders []string + cache map[string]*modelInfo + cacheByFullName map[string]*modelInfo + done bool } // get all model info @@ -88,9 +67,9 @@ func (mc *_modelCache) get(table string) (mi *modelInfo, ok bool) { return } -// get model info by field name -func (mc *_modelCache) getByFN(name string) (mi *modelInfo, ok bool) { - mi, ok = mc.cacheByFN[name] +// get model info by full name +func (mc *_modelCache) getByFullName(name string) (mi *modelInfo, ok bool) { + mi, ok = mc.cacheByFullName[name] return } @@ -98,7 +77,7 @@ func (mc *_modelCache) getByFN(name string) (mi *modelInfo, ok bool) { func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo { mii := mc.cache[table] mc.cache[table] = mi - mc.cacheByFN[mi.fullName] = mi + mc.cacheByFullName[mi.fullName] = mi if mii == nil { mc.orders = append(mc.orders, table) } @@ -109,7 +88,7 @@ func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo { func (mc *_modelCache) clean() { mc.orders = make([]string, 0) mc.cache = make(map[string]*modelInfo) - mc.cacheByFN = make(map[string]*modelInfo) + mc.cacheByFullName = make(map[string]*modelInfo) mc.done = false } diff --git a/orm/models_boot.go b/orm/models_boot.go index 364b68e9..7d9746d0 100644 --- a/orm/models_boot.go +++ b/orm/models_boot.go @@ -26,12 +26,14 @@ import ( // prefix means table name prefix. func registerModel(prefix string, model interface{}) { val := reflect.ValueOf(model) - ind := reflect.Indirect(val) - typ := ind.Type() + typ := reflect.Indirect(val).Type() if val.Kind() != reflect.Ptr { panic(fmt.Errorf(" cannot use non-ptr model struct `%s`", getFullName(typ))) } + // For this case: + // u := &User{} + // registerModel(&u) if typ.Kind() == reflect.Ptr { panic(fmt.Errorf(" only allow ptr model struct, it looks you use two reference to the struct `%s`", typ)) } @@ -41,9 +43,9 @@ func registerModel(prefix string, model interface{}) { if prefix != "" { table = prefix + table } - + // models's fullname is pkgpath + struct name name := getFullName(typ) - if _, ok := modelCache.getByFN(name); ok { + if _, ok := modelCache.getByFullName(name); ok { fmt.Printf(" model `%s` repeat register, must be unique\n", name) os.Exit(2) } @@ -110,7 +112,7 @@ func bootStrap() { } name := getFullName(elm) - mii, ok := modelCache.getByFN(name) + mii, ok := modelCache.getByFullName(name) if ok == false || mii.pkg != elm.PkgPath() { err = fmt.Errorf("can not found rel in field `%s`, `%s` may be miss register", fi.fullName, elm.String()) goto end @@ -123,7 +125,7 @@ func bootStrap() { msg := fmt.Sprintf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough) if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) { pn := fi.relThrough[:i] - rmi, ok := modelCache.getByFN(fi.relThrough) + rmi, ok := modelCache.getByFullName(fi.relThrough) if ok == false || pn != rmi.pkg { err = errors.New(msg + " cannot find table") goto end diff --git a/orm/models_info_f.go b/orm/models_info_f.go index be6c9aa4..33db0d4f 100644 --- a/orm/models_info_f.go +++ b/orm/models_info_f.go @@ -152,6 +152,10 @@ func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mN fi = new(fieldInfo) + // if field which CanAddr is the follow type + // A value is addressable if it is an element of a slice, + // an element of an addressable array, a field of an + // addressable struct, or the result of dereferencing a pointer. addrField = field if field.CanAddr() && field.Kind() != reflect.Ptr { addrField = field.Addr() @@ -162,7 +166,7 @@ func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mN } } - parseStructTag(sf.Tag.Get(defaultStructTagName), &attrs, &tags) + attrs, tags = parseStructTag(sf.Tag.Get(defaultStructTagName)) if _, ok := attrs["-"]; ok { return nil, errSkipField @@ -188,7 +192,7 @@ checkType: } fieldType = f.FieldType() if fieldType&IsRelField > 0 { - err = fmt.Errorf("unsupport rel type custom field") + err = fmt.Errorf("unsupport type custom field, please refer to https://github.com/astaxie/beego/blob/master/orm/models_fields.go#L24-L42") goto end } default: diff --git a/orm/models_info_m.go b/orm/models_info_m.go index bbb82444..2e0905ec 100644 --- a/orm/models_info_m.go +++ b/orm/models_info_m.go @@ -29,31 +29,25 @@ type modelInfo struct { model interface{} fields *fields manual bool - addrField reflect.Value + addrField reflect.Value //store the original struct value uniques []string isThrough bool } // new model info -func newModelInfo(val reflect.Value) (info *modelInfo) { - - info = &modelInfo{} - info.fields = newFields() - +func newModelInfo(val reflect.Value) (mi *modelInfo) { + mi = &modelInfo{} + mi.fields = newFields() ind := reflect.Indirect(val) - typ := ind.Type() - - info.addrField = val - - info.name = typ.Name() - info.fullName = getFullName(typ) - - addModelFields(info, ind, "", []int{}) - + mi.addrField = val + mi.name = ind.Type().Name() + mi.fullName = getFullName(ind.Type()) + addModelFields(mi, ind, "", []int{}) return } -func addModelFields(info *modelInfo, ind reflect.Value, mName string, index []int) { +// index: FieldByIndex returns the nested field corresponding to index +func addModelFields(mi *modelInfo, ind reflect.Value, mName string, index []int) { var ( err error fi *fieldInfo @@ -63,43 +57,39 @@ func addModelFields(info *modelInfo, ind reflect.Value, mName string, index []in for i := 0; i < ind.NumField(); i++ { field := ind.Field(i) sf = ind.Type().Field(i) + // if the field is unexported skip if sf.PkgPath != "" { continue } // add anonymous struct fields if sf.Anonymous { - addModelFields(info, field, mName+"."+sf.Name, append(index, i)) + addModelFields(mi, field, mName+"."+sf.Name, append(index, i)) continue } - fi, err = newFieldInfo(info, field, sf, mName) - - if err != nil { - if err == errSkipField { - err = nil - continue - } + fi, err = newFieldInfo(mi, field, sf, mName) + if err == errSkipField { + err = nil + continue + } else if err != nil { break } - - added := info.fields.Add(fi) - if added == false { + //record current field index + fi.fieldIndex = append(index, i) + fi.mi = mi + fi.inModel = true + if mi.fields.Add(fi) == false { err = fmt.Errorf("duplicate column name: %s", fi.column) break } - if fi.pk { - if info.fields.pk != nil { + if mi.fields.pk != nil { err = fmt.Errorf("one model must have one pk field only") break } else { - info.fields.pk = fi + mi.fields.pk = fi } } - - fi.fieldIndex = append(index, i) - fi.mi = info - fi.inModel = true } if err != nil { @@ -110,12 +100,12 @@ func addModelFields(info *modelInfo, ind reflect.Value, mName string, index []in // combine related model info to new model info. // prepare for relation models query. -func newM2MModelInfo(m1, m2 *modelInfo) (info *modelInfo) { - info = new(modelInfo) - info.fields = newFields() - info.table = m1.table + "_" + m2.table + "s" - info.name = camelString(info.table) - info.fullName = m1.pkg + "." + info.name +func newM2MModelInfo(m1, m2 *modelInfo) (mi *modelInfo) { + mi = new(modelInfo) + mi.fields = newFields() + mi.table = m1.table + "_" + m2.table + "s" + mi.name = camelString(mi.table) + mi.fullName = m1.pkg + "." + mi.name fa := new(fieldInfo) f1 := new(fieldInfo) @@ -126,7 +116,7 @@ func newM2MModelInfo(m1, m2 *modelInfo) (info *modelInfo) { fa.dbcol = true fa.name = "Id" fa.column = "id" - fa.fullName = info.fullName + "." + fa.name + fa.fullName = mi.fullName + "." + fa.name f1.dbcol = true f2.dbcol = true @@ -134,8 +124,8 @@ func newM2MModelInfo(m1, m2 *modelInfo) (info *modelInfo) { f2.fieldType = RelForeignKey f1.name = camelString(m1.table) f2.name = camelString(m2.table) - f1.fullName = info.fullName + "." + f1.name - f2.fullName = info.fullName + "." + f2.name + f1.fullName = mi.fullName + "." + f1.name + f2.fullName = mi.fullName + "." + f2.name f1.column = m1.table + "_id" f2.column = m2.table + "_id" f1.rel = true @@ -144,14 +134,14 @@ func newM2MModelInfo(m1, m2 *modelInfo) (info *modelInfo) { f2.relTable = m2.table f1.relModelInfo = m1 f2.relModelInfo = m2 - f1.mi = info - f2.mi = info + f1.mi = mi + f2.mi = mi - info.fields.Add(fa) - info.fields.Add(f1) - info.fields.Add(f2) - info.fields.pk = fa + mi.fields.Add(fa) + mi.fields.Add(f1) + mi.fields.Add(f2) + mi.fields.pk = fa - info.uniques = []string{f1.column, f2.column} + mi.uniques = []string{f1.column, f2.column} return } diff --git a/orm/models_utils.go b/orm/models_utils.go index 4c4b0f24..2a1f9d53 100644 --- a/orm/models_utils.go +++ b/orm/models_utils.go @@ -22,25 +22,47 @@ import ( "time" ) +// 1 is attr +// 2 is tag +var supportTag = map[string]int{ + "-": 1, + "null": 1, + "index": 1, + "unique": 1, + "pk": 1, + "auto": 1, + "auto_now": 1, + "auto_now_add": 1, + "size": 2, + "column": 2, + "default": 2, + "rel": 2, + "reverse": 2, + "rel_table": 2, + "rel_through": 2, + "digits": 2, + "decimals": 2, + "on_delete": 2, + "type": 2, +} + // get reflect.Type name with package path. func getFullName(typ reflect.Type) string { return typ.PkgPath() + "." + typ.Name() } -// get table name. method, or field name. auto snaked. +// getTableName get struct table name. +// If the struct implement the TableName, then get the result as tablename +// else use the struct name which will apply snakeString. func getTableName(val reflect.Value) string { - ind := reflect.Indirect(val) - fun := val.MethodByName("TableName") - if fun.IsValid() { + if fun := val.MethodByName("TableName"); fun.IsValid() { vals := fun.Call([]reflect.Value{}) - if len(vals) > 0 { - val := vals[0] - if val.Kind() == reflect.String { - return val.String() - } + // has return and the first val is string + if len(vals) > 0 && vals[0].Kind() == reflect.String { + return vals[0].String() } } - return snakeString(ind.Type().Name()) + return snakeString(reflect.Indirect(val).Type().Name()) } // get table engine, mysiam or innodb. @@ -189,21 +211,25 @@ func getFieldType(val reflect.Value) (ft int, err error) { } // parse struct tag string -func parseStructTag(data string, attrs *map[string]bool, tags *map[string]string) { - attr := make(map[string]bool) - tag := make(map[string]string) +func parseStructTag(data string) (attrs map[string]bool, tags map[string]string) { + attrs = make(map[string]bool) + tags = make(map[string]string) for _, v := range strings.Split(data, defaultStructTagDelim) { + if v == "" { + continue + } v = strings.TrimSpace(v) if t := strings.ToLower(v); supportTag[t] == 1 { - attr[t] = true + attrs[t] = true } else if i := strings.Index(v, "("); i > 0 && strings.Index(v, ")") == len(v)-1 { name := t[:i] if supportTag[name] == 2 { v = v[i+1 : len(v)-1] - tag[name] = v + tags[name] = v } + } else { + DebugLog.Println("unsupport orm tag", v) } } - *attrs = attr - *tags = tag + return } diff --git a/orm/orm.go b/orm/orm.go index 994ed7e3..390d300f 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -104,7 +104,7 @@ func (o *orm) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect panic(fmt.Errorf(" cannot use non-ptr model struct `%s`", getFullName(typ))) } name := getFullName(typ) - if mi, ok := modelCache.getByFN(name); ok { + if mi, ok := modelCache.getByFullName(name); ok { return mi, ind } panic(fmt.Errorf(" table: `%s` not found, maybe not RegisterModel", name)) @@ -427,7 +427,7 @@ func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) { } } else { name = getFullName(indirectType(reflect.TypeOf(ptrStructOrTableName))) - if mi, ok := modelCache.getByFN(name); ok { + if mi, ok := modelCache.getByFullName(name); ok { qs = newQuerySet(o, mi) } } diff --git a/orm/orm_raw.go b/orm/orm_raw.go index 3b945833..a968b1a1 100644 --- a/orm/orm_raw.go +++ b/orm/orm_raw.go @@ -286,7 +286,7 @@ func (o *rawSet) QueryRow(containers ...interface{}) error { structMode = true fn := getFullName(typ) - if mi, ok := modelCache.getByFN(fn); ok { + if mi, ok := modelCache.getByFullName(fn); ok { sMi = mi } } else { @@ -355,12 +355,9 @@ func (o *rawSet) QueryRow(containers ...interface{}) error { for i := 0; i < ind.NumField(); i++ { f := ind.Field(i) fe := ind.Type().Field(i) - - var attrs map[string]bool - var tags map[string]string - parseStructTag(fe.Tag.Get("orm"), &attrs, &tags) + _, tags := parseStructTag(fe.Tag.Get(defaultStructTagName)) var col string - if col = tags["column"]; len(col) == 0 { + if col = tags["column"]; col == "" { col = snakeString(fe.Name) } if v, ok := columnsMp[col]; ok { @@ -422,7 +419,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) { structMode = true fn := getFullName(typ) - if mi, ok := modelCache.getByFN(fn); ok { + if mi, ok := modelCache.getByFullName(fn); ok { sMi = mi } } else { @@ -499,12 +496,9 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) { for i := 0; i < ind.NumField(); i++ { f := ind.Field(i) fe := ind.Type().Field(i) - - var attrs map[string]bool - var tags map[string]string - parseStructTag(fe.Tag.Get("orm"), &attrs, &tags) + _, tags := parseStructTag(fe.Tag.Get(defaultStructTagName)) var col string - if col = tags["column"]; len(col) == 0 { + if col = tags["column"]; col == "" { col = snakeString(fe.Name) } if v, ok := columnsMp[col]; ok { diff --git a/orm/orm_test.go b/orm/orm_test.go index bb1831f9..5e288039 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -227,7 +227,7 @@ func TestModelSyntax(t *testing.T) { user := &User{} ind := reflect.ValueOf(user).Elem() fn := getFullName(ind.Type()) - mi, ok := modelCache.getByFN(fn) + mi, ok := modelCache.getByFullName(fn) throwFail(t, AssertIs(ok, true)) mi, ok = modelCache.get("user") From 56aa224a6e2969612a75c37282bc9eb8cdfdd8e4 Mon Sep 17 00:00:00 2001 From: astaxie Date: Wed, 31 Aug 2016 22:47:31 +0800 Subject: [PATCH 77/96] simplfy the code --- orm/models_info_f.go | 6 +++--- orm/models_utils.go | 25 ++++++++----------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/orm/models_info_f.go b/orm/models_info_f.go index 33db0d4f..ba25f744 100644 --- a/orm/models_info_f.go +++ b/orm/models_info_f.go @@ -143,7 +143,7 @@ func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mN var ( tag string tagValue string - initial StrTo + initial StrTo // store the default value fieldType int attrs map[string]bool tags map[string]string @@ -215,7 +215,7 @@ checkType: } break checkType default: - err = fmt.Errorf("error") + err = fmt.Errorf("rel only allow these value: fk, one, m2m") goto wrongTag } } @@ -235,7 +235,7 @@ checkType: } break checkType default: - err = fmt.Errorf("error") + err = fmt.Errorf("reverse only allow these value: one, many") goto wrongTag } } diff --git a/orm/models_utils.go b/orm/models_utils.go index 2a1f9d53..44a0e76a 100644 --- a/orm/models_utils.go +++ b/orm/models_utils.go @@ -70,11 +70,8 @@ func getTableEngine(val reflect.Value) string { fun := val.MethodByName("TableEngine") if fun.IsValid() { vals := fun.Call([]reflect.Value{}) - if len(vals) > 0 { - val := vals[0] - if val.Kind() == reflect.String { - return val.String() - } + if len(vals) > 0 && vals[0].Kind() == reflect.String { + return vals[0].String() } } return "" @@ -85,12 +82,9 @@ func getTableIndex(val reflect.Value) [][]string { fun := val.MethodByName("TableIndex") if fun.IsValid() { vals := fun.Call([]reflect.Value{}) - if len(vals) > 0 { - val := vals[0] - if val.CanInterface() { - if d, ok := val.Interface().([][]string); ok { - return d - } + if len(vals) > 0 && vals[0].CanInterface() { + if d, ok := vals[0].Interface().([][]string); ok { + return d } } } @@ -102,12 +96,9 @@ func getTableUnique(val reflect.Value) [][]string { fun := val.MethodByName("TableUnique") if fun.IsValid() { vals := fun.Call([]reflect.Value{}) - if len(vals) > 0 { - val := vals[0] - if val.CanInterface() { - if d, ok := val.Interface().([][]string); ok { - return d - } + if len(vals) > 0 && vals[0].CanInterface() { + if d, ok := vals[0].Interface().([][]string); ok { + return d } } } From 7c2e563879b2a42a66c51d5a27e07741af1636b9 Mon Sep 17 00:00:00 2001 From: Zaaksam Date: Thu, 1 Sep 2016 15:04:57 +0800 Subject: [PATCH 78/96] beego.ParseForm() improvement --- templatefunc.go | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/templatefunc.go b/templatefunc.go index 36442984..01751717 100644 --- a/templatefunc.go +++ b/templatefunc.go @@ -280,15 +280,8 @@ func AssetsCSS(src string) template.HTML { } // ParseForm will parse form values to struct via tag. -func ParseForm(form url.Values, obj interface{}) error { - objT := reflect.TypeOf(obj) - objV := reflect.ValueOf(obj) - if !isStructPtr(objT) { - return fmt.Errorf("%v must be a struct pointer", obj) - } - objT = objT.Elem() - objV = objV.Elem() - +// Support for anonymous struct. +func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) error { for i := 0; i < objT.NumField(); i++ { fieldV := objV.Field(i) if !fieldV.CanSet() { @@ -296,6 +289,14 @@ func ParseForm(form url.Values, obj interface{}) error { } fieldT := objT.Field(i) + if fieldT.Anonymous && fieldT.Type.Kind() == reflect.Struct { + err := parseFormToStruct(form, fieldT.Type, fieldV) + if err != nil { + return err + } + continue + } + tags := strings.Split(fieldT.Tag.Get("form"), ",") var tag string if len(tags) == 0 || len(tags[0]) == 0 { @@ -384,6 +385,19 @@ func ParseForm(form url.Values, obj interface{}) error { return nil } +// ParseForm will parse form values to struct via tag. +func ParseForm(form url.Values, obj interface{}) error { + objT := reflect.TypeOf(obj) + objV := reflect.ValueOf(obj) + if !isStructPtr(objT) { + return fmt.Errorf("%v must be a struct pointer", obj) + } + objT = objT.Elem() + objV = objV.Elem() + + return parseFormToStruct(form, objT, objV) +} + var sliceOfInts = reflect.TypeOf([]int(nil)) var sliceOfStrings = reflect.TypeOf([]string(nil)) From c697b9800624f820ac05325c24028be7bb1eccbd Mon Sep 17 00:00:00 2001 From: astaxie Date: Thu, 1 Sep 2016 23:28:34 +0800 Subject: [PATCH 79/96] enhancement --- orm/db_alias.go | 2 +- orm/models_boot.go | 38 +++++++++++++++----------------------- orm/models_info_f.go | 17 +++++++++-------- orm/models_info_m.go | 6 +++--- 4 files changed, 28 insertions(+), 35 deletions(-) diff --git a/orm/db_alias.go b/orm/db_alias.go index b6c833a7..c95d49c9 100644 --- a/orm/db_alias.go +++ b/orm/db_alias.go @@ -80,7 +80,7 @@ type _dbCache struct { func (ac *_dbCache) add(name string, al *alias) (added bool) { ac.mux.Lock() defer ac.mux.Unlock() - if _, ok := ac.cache[name]; ok == false { + if _, ok := ac.cache[name]; !ok { ac.cache[name] = al added = true } diff --git a/orm/models_boot.go b/orm/models_boot.go index 7d9746d0..d48d2677 100644 --- a/orm/models_boot.go +++ b/orm/models_boot.go @@ -15,7 +15,6 @@ package orm import ( - "errors" "fmt" "os" "reflect" @@ -55,34 +54,34 @@ func registerModel(prefix string, model interface{}) { os.Exit(2) } - info := newModelInfo(val) - if info.fields.pk == nil { + mi := newModelInfo(val) + if mi.fields.pk == nil { outFor: - for _, fi := range info.fields.fieldsDB { + for _, fi := range mi.fields.fieldsDB { if strings.ToLower(fi.name) == "id" { switch fi.addrValue.Elem().Kind() { case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64: fi.auto = true fi.pk = true - info.fields.pk = fi + mi.fields.pk = fi break outFor } } } - if info.fields.pk == nil { + if mi.fields.pk == nil { fmt.Printf(" `%s` need a primary key field, default use 'id' if not set\n", name) os.Exit(2) } } - info.table = table - info.pkg = typ.PkgPath() - info.model = model - info.manual = true + mi.table = table + mi.pkg = typ.PkgPath() + mi.model = model + mi.manual = true - modelCache.set(table, info) + modelCache.set(table, mi) } // boostrap models @@ -90,12 +89,10 @@ func bootStrap() { if modelCache.done { return } - var ( err error models map[string]*modelInfo ) - if dataBaseCache.getDefault() == nil { err = fmt.Errorf("must have one register DataBase alias named `default`") goto end @@ -106,14 +103,13 @@ func bootStrap() { for _, fi := range mi.fields.columns { if fi.rel || fi.reverse { elm := fi.addrValue.Type().Elem() - switch fi.fieldType { - case RelReverseMany, RelManyToMany: + if fi.fieldType == RelReverseMany || fi.fieldType == RelManyToMany { elm = elm.Elem() } - + // check the rel or reverse model already register name := getFullName(elm) mii, ok := modelCache.getByFullName(name) - if ok == false || mii.pkg != elm.PkgPath() { + if !ok || mii.pkg != elm.PkgPath() { err = fmt.Errorf("can not found rel in field `%s`, `%s` may be miss register", fi.fullName, elm.String()) goto end } @@ -122,20 +118,17 @@ func bootStrap() { switch fi.fieldType { case RelManyToMany: if fi.relThrough != "" { - msg := fmt.Sprintf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough) if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) { pn := fi.relThrough[:i] rmi, ok := modelCache.getByFullName(fi.relThrough) if ok == false || pn != rmi.pkg { - err = errors.New(msg + " cannot find table") + err = fmt.Errorf("field `%s` wrong rel_through value `%s` cannot find table", fi.fullName, fi.relThrough) goto end } - fi.relThroughModelInfo = rmi fi.relTable = rmi.table - } else { - err = errors.New(msg) + err = fmt.Errorf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough) goto end } } else { @@ -143,7 +136,6 @@ func bootStrap() { if fi.relTable != "" { i.table = fi.relTable } - if v := modelCache.set(i.table, i); v != nil { err = fmt.Errorf("the rel table name `%s` already registered, cannot be use, please change one", fi.relTable) goto end diff --git a/orm/models_info_f.go b/orm/models_info_f.go index ba25f744..4b3d3e27 100644 --- a/orm/models_info_f.go +++ b/orm/models_info_f.go @@ -104,7 +104,7 @@ type fieldInfo struct { mi *modelInfo fieldIndex []int fieldType int - dbcol bool + dbcol bool // table column fk and onetoone inModel bool name string fullName string @@ -116,13 +116,13 @@ type fieldInfo struct { null bool index bool unique bool - colDefault bool - initial StrTo + colDefault bool // whether has default tag + initial StrTo // store the default value size int toText bool autoNow bool autoNowAdd bool - rel bool + rel bool // if type equal to RelForeignKey, RelOneToOne, RelManyToMany then true reverse bool reverseField string reverseFieldInfo *fieldInfo @@ -134,7 +134,7 @@ type fieldInfo struct { relModelInfo *modelInfo digits int decimals int - isFielder bool + isFielder bool // implement Fielder interface onDelete string } @@ -265,6 +265,9 @@ checkType: } } + // check the rel and reverse type + // rel should Ptr + // reverse should slice []*struct switch fieldType { case RelForeignKey, RelOneToOne, RelReverseOne: if field.Kind() != reflect.Ptr { @@ -403,14 +406,12 @@ checkType: if fi.auto || fi.pk { if fi.auto { - switch addrField.Elem().Kind() { case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64: default: err = fmt.Errorf("auto primary key only support int, int32, int64, uint, uint32, uint64 but found `%s`", addrField.Elem().Kind()) goto end } - fi.pk = true } fi.null = false @@ -422,8 +423,8 @@ checkType: fi.index = false } + // can not set default for these type if fi.auto || fi.pk || fi.unique || fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField { - // can not set default initial.Clear() } diff --git a/orm/models_info_m.go b/orm/models_info_m.go index 2e0905ec..d6ba1dca 100644 --- a/orm/models_info_m.go +++ b/orm/models_info_m.go @@ -107,9 +107,9 @@ func newM2MModelInfo(m1, m2 *modelInfo) (mi *modelInfo) { mi.name = camelString(mi.table) mi.fullName = m1.pkg + "." + mi.name - fa := new(fieldInfo) - f1 := new(fieldInfo) - f2 := new(fieldInfo) + fa := new(fieldInfo) // pk + f1 := new(fieldInfo) // m1 table RelForeignKey + f2 := new(fieldInfo) // m2 table RelForeignKey fa.fieldType = TypeBigIntegerField fa.auto = true fa.pk = true From 11ef5929aa6f54e9cdf060572c21309f047851ac Mon Sep 17 00:00:00 2001 From: Zaaksam Date: Fri, 2 Sep 2016 16:08:04 +0800 Subject: [PATCH 80/96] update TestParseForm --- templatefunc_test.go | 46 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/templatefunc_test.go b/templatefunc_test.go index 86de37ae..a1ec1136 100644 --- a/templatefunc_test.go +++ b/templatefunc_test.go @@ -110,6 +110,17 @@ func TestHtmlunquote(t *testing.T) { } func TestParseForm(t *testing.T) { + type ExtendInfo struct { + Hobby string `form:"hobby"` + Memo string + } + + type OtherInfo struct { + Organization string `form:"organization"` + Title string `form:"title"` + ExtendInfo + } + type user struct { ID int `form:"-"` tag string `form:"tag"` @@ -119,19 +130,24 @@ func TestParseForm(t *testing.T) { Intro string `form:",textarea"` StrBool bool `form:"strbool"` Date time.Time `form:"date,2006-01-02"` + OtherInfo } u := user{} form := url.Values{ - "ID": []string{"1"}, - "-": []string{"1"}, - "tag": []string{"no"}, - "username": []string{"test"}, - "age": []string{"40"}, - "Email": []string{"test@gmail.com"}, - "Intro": []string{"I am an engineer!"}, - "strbool": []string{"yes"}, - "date": []string{"2014-11-12"}, + "ID": []string{"1"}, + "-": []string{"1"}, + "tag": []string{"no"}, + "username": []string{"test"}, + "age": []string{"40"}, + "Email": []string{"test@gmail.com"}, + "Intro": []string{"I am an engineer!"}, + "strbool": []string{"yes"}, + "date": []string{"2014-11-12"}, + "organization": []string{"beego"}, + "title": []string{"CXO"}, + "hobby": []string{"Basketball"}, + "memo": []string{"nothing"}, } if err := ParseForm(form, u); err == nil { t.Fatal("nothing will be changed") @@ -164,6 +180,18 @@ func TestParseForm(t *testing.T) { if y != 2014 || m.String() != "November" || d != 12 { t.Errorf("Date should equal `2014-11-12`, but got `%v`", u.Date.String()) } + if u.Organization != "beego" { + t.Errorf("Organization should equal `beego`, but got `%v`", u.Organization) + } + if u.Title != "CXO" { + t.Errorf("Title should equal `CXO`, but got `%v`", u.Title) + } + if u.Hobby != "Basketball" { + t.Errorf("Hobby should equal `Basketball`, but got `%v`", u.Hobby) + } + if len(u.Memo) != 0 { + t.Errorf("Memo's length should equal 0 but got %v", len(u.Memo)) + } } func TestRenderForm(t *testing.T) { From 7d6c45d4c91ca79c9221ab44c2748a34aeea3669 Mon Sep 17 00:00:00 2001 From: astaxie Date: Tue, 6 Sep 2016 23:05:41 +0800 Subject: [PATCH 81/96] add RegisterModelWithSuffix #2140 --- orm/models_boot.go | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/orm/models_boot.go b/orm/models_boot.go index d48d2677..4dbb54a9 100644 --- a/orm/models_boot.go +++ b/orm/models_boot.go @@ -22,8 +22,9 @@ import ( ) // register models. -// prefix means table name prefix. -func registerModel(prefix string, model interface{}) { +// PrefixOrSuffix means table name prefix or suffix. +// isPrefix whether the prefix is prefix or suffix +func registerModel(PrefixOrSuffix string, model interface{}, isPrefix bool) { val := reflect.ValueOf(model) typ := reflect.Indirect(val).Type() @@ -39,8 +40,12 @@ func registerModel(prefix string, model interface{}) { table := getTableName(val) - if prefix != "" { - table = prefix + table + if PrefixOrSuffix != "" { + if isPrefix { + table = PrefixOrSuffix + table + } else { + table = table + PrefixOrSuffix + } } // models's fullname is pkgpath + struct name name := getFullName(typ) @@ -213,7 +218,6 @@ func bootStrap() { } } } - if fi.reverseFieldInfoTwo == nil { err = fmt.Errorf("can not find m2m field for m2m model `%s`, ensure your m2m model defined correct", fi.relThroughModelInfo.fullName) @@ -297,17 +301,31 @@ end: // RegisterModel register models func RegisterModel(models ...interface{}) { + if modelCache.done { + panic(fmt.Errorf("RegisterModel must be run before BootStrap")) + } RegisterModelWithPrefix("", models...) } // RegisterModelWithPrefix register models with a prefix func RegisterModelWithPrefix(prefix string, models ...interface{}) { if modelCache.done { - panic(fmt.Errorf("RegisterModel must be run before BootStrap")) + panic(fmt.Errorf("RegisterModelWithPrefix must be run before BootStrap")) } for _, model := range models { - registerModel(prefix, model) + registerModel(prefix, model, true) + } +} + +// RegisterModelWithSuffix register models with a suffix +func RegisterModelWithSuffix(suffix string, models ...interface{}) { + if modelCache.done { + panic(fmt.Errorf("RegisterModelWithSuffix must be run before BootStrap")) + } + + for _, model := range models { + registerModel(suffix, model, false) } } From 30661472c8acd31371fd4364a5907d166d4e3f77 Mon Sep 17 00:00:00 2001 From: Phil Date: Wed, 7 Sep 2016 13:33:11 +0800 Subject: [PATCH 82/96] Fix the typo --- logs/log.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logs/log.go b/logs/log.go index ce9eb79d..8aa8ff3a 100644 --- a/logs/log.go +++ b/logs/log.go @@ -66,7 +66,7 @@ const ( AdapterConsole = "console" AdapterFile = "file" AdapterMultiFile = "multifile" - AdapterMail = "stmp" + AdapterMail = "smtp" AdapterConn = "conn" AdapterEs = "es" ) From fcd8a2024ed9153cea56580cb56bd3a60a779a6b Mon Sep 17 00:00:00 2001 From: Phil Date: Thu, 8 Sep 2016 17:21:11 +0800 Subject: [PATCH 83/96] Add jianliao and slack log adapter --- logs/jianliao.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ logs/log.go | 2 ++ logs/slack.go | 66 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 logs/jianliao.go create mode 100644 logs/slack.go diff --git a/logs/jianliao.go b/logs/jianliao.go new file mode 100644 index 00000000..3755118d --- /dev/null +++ b/logs/jianliao.go @@ -0,0 +1,78 @@ +package logs + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" +) + +// JLWriter implements beego LoggerInterface and is used to send jiaoliao webhook +type JLWriter struct { + AuthorName string `json:"authorname"` + Title string `json:"title"` + WebhookURL string `json:"webhookurl"` + RedirectURL string `json:"redirecturl,omitempty"` + ImageURL string `json:"imageurl,omitempty"` + Level int `json:"level"` +} + +// newJLWriter create jiaoliao writer. +func newJLWriter() Logger { + return &JLWriter{Level: LevelTrace} +} + +// Init JLWriter with json config string +func (s *JLWriter) Init(jsonconfig string) error { + err := json.Unmarshal([]byte(jsonconfig), s) + if err != nil { + return err + } + return nil +} + +// WriteMsg write message in smtp writer. +// it will send an email with subject and only this message. +func (s *JLWriter) WriteMsg(when time.Time, msg string, level int) error { + if level > s.Level { + return nil + } + + text := fmt.Sprintf("%s %s", when.Format("2006-01-02 15:04:05"), msg) + + form := url.Values{} + form.Add("authorName", s.AuthorName) + form.Add("title", s.Title) + form.Add("text", text) + if s.RedirectURL != "" { + form.Add("redirectUrl", s.RedirectURL) + } + if s.ImageURL != "" { + form.Add("imageUrl", s.ImageURL) + } + + resp, err := http.PostForm(s.WebhookURL, form) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode) + } + resp.Body.Close() + return nil +} + +// Flush implementing method. empty. +func (s *JLWriter) Flush() { + return +} + +// Destroy implementing method. empty. +func (s *JLWriter) Destroy() { + return +} + +func init() { + Register(AdapterJianLiao, newJLWriter) +} diff --git a/logs/log.go b/logs/log.go index 8aa8ff3a..3d512d2e 100644 --- a/logs/log.go +++ b/logs/log.go @@ -69,6 +69,8 @@ const ( AdapterMail = "smtp" AdapterConn = "conn" AdapterEs = "es" + AdapterJianLiao = "jianliao" + AdapterSlack = "slack" ) // Legacy log level constants to ensure backwards compatibility. diff --git a/logs/slack.go b/logs/slack.go new file mode 100644 index 00000000..eddedd5d --- /dev/null +++ b/logs/slack.go @@ -0,0 +1,66 @@ +package logs + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" +) + +// SLACKWriter implements beego LoggerInterface and is used to send jiaoliao webhook +type SLACKWriter struct { + WebhookURL string `json:"webhookurl"` + Level int `json:"level"` +} + +// newSLACKWriter create jiaoliao writer. +func newSLACKWriter() Logger { + return &SLACKWriter{Level: LevelTrace} +} + +// Init SLACKWriter with json config string +func (s *SLACKWriter) Init(jsonconfig string) error { + err := json.Unmarshal([]byte(jsonconfig), s) + if err != nil { + return err + } + return nil +} + +// WriteMsg write message in smtp writer. +// it will send an email with subject and only this message. +func (s *SLACKWriter) WriteMsg(when time.Time, msg string, level int) error { + if level > s.Level { + return nil + } + + text := fmt.Sprintf("{\"text\": \"%s %s\"}", when.Format("2006-01-02 15:04:05"), msg) + + form := url.Values{} + form.Add("payload", text) + + resp, err := http.PostForm(s.WebhookURL, form) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("Post webhook failed %s %d", resp.Status, resp.StatusCode) + } + resp.Body.Close() + return nil +} + +// Flush implementing method. empty. +func (s *SLACKWriter) Flush() { + return +} + +// Destroy implementing method. empty. +func (s *SLACKWriter) Destroy() { + return +} + +func init() { + Register(AdapterSlack, newSLACKWriter) +} From 0ef357ebd7f592cdebe50c89853204f9522ccd2b Mon Sep 17 00:00:00 2001 From: astaxie Date: Sun, 11 Sep 2016 21:02:03 +0800 Subject: [PATCH 84/96] session:output error --- session/sess_file.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/session/sess_file.go b/session/sess_file.go index 91acfcd4..132f5a00 100644 --- a/session/sess_file.go +++ b/session/sess_file.go @@ -88,10 +88,9 @@ func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) { var f *os.File if err == nil { f, err = os.OpenFile(path.Join(filepder.savePath, string(fs.sid[0]), string(fs.sid[1]), fs.sid), os.O_RDWR, 0777) - SLogger.Println(err) } else if os.IsNotExist(err) { f, err = os.Create(path.Join(filepder.savePath, string(fs.sid[0]), string(fs.sid[1]), fs.sid)) - SLogger.Println(err) + } else { return } From a32241e7d31bec7dc6fd1f0b69397e842892007f Mon Sep 17 00:00:00 2001 From: astaxie Date: Sun, 11 Sep 2016 21:27:27 +0800 Subject: [PATCH 85/96] fix #2142 --- staticfile.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/staticfile.go b/staticfile.go index 7c8b4517..b7be24f3 100644 --- a/staticfile.go +++ b/staticfile.go @@ -196,9 +196,11 @@ func lookupFile(ctx *context.Context) (bool, string, os.FileInfo, error) { if !fi.IsDir() { return false, fp, fi, err } - ifp := filepath.Join(fp, "index.html") - if ifi, _ := os.Stat(ifp); ifi != nil && ifi.Mode().IsRegular() { - return false, ifp, ifi, err + if requestURL := ctx.Input.URL(); requestURL[len(requestURL)-1] == '/' { + ifp := filepath.Join(fp, "index.html") + if ifi, _ := os.Stat(ifp); ifi != nil && ifi.Mode().IsRegular() { + return false, ifp, ifi, err + } } return !BConfig.WebConfig.DirectoryIndex, fp, fi, err } From dd0f05b1f1b3e36d4d7ee17f695082d4c38f6247 Mon Sep 17 00:00:00 2001 From: astaxie Date: Sun, 11 Sep 2016 22:00:14 +0800 Subject: [PATCH 86/96] fix the method color --- router.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/router.go b/router.go index 2adecbf0..248c2a48 100644 --- a/router.go +++ b/router.go @@ -838,16 +838,16 @@ Admin: if findRouter { if routerInfo != nil { - devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-3s r:%s", statusColor, statusCode, - resetColor, timeDur.String(), "match", methodColor, resetColor, r.Method, r.URL.Path, + devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", statusColor, statusCode, + resetColor, timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path, routerInfo.pattern) } else { - devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-3s", statusColor, statusCode, resetColor, - timeDur.String(), "match", methodColor, resetColor, r.Method, r.URL.Path) + devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", statusColor, statusCode, resetColor, + timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path) } } else { - devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-3s", statusColor, statusCode, resetColor, - timeDur.String(), "nomatch", methodColor, resetColor, r.Method, r.URL.Path) + devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", statusColor, statusCode, resetColor, + timeDur.String(), "nomatch", methodColor, r.Method, resetColor, r.URL.Path) } if iswin { logs.W32Debug(devInfo) From 5b21c7cd7119104eb040340a2e987e40ab828ca4 Mon Sep 17 00:00:00 2001 From: astaxie Date: Mon, 12 Sep 2016 21:13:21 +0800 Subject: [PATCH 87/96] fix #1802 --- orm/db.go | 32 +++++++++++++++++++++++++------- orm/orm.go | 5 +++-- orm/orm_test.go | 4 ++++ orm/types.go | 4 ++-- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/orm/db.go b/orm/db.go index d2f8a5b2..1ed83145 100644 --- a/orm/db.go +++ b/orm/db.go @@ -634,18 +634,36 @@ func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time. // execute delete sql dbQuerier with given struct reflect.Value. // delete index is pk. -func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) { - pkName, pkValue, ok := getExistPk(mi, ind) - if ok == false { - return 0, ErrMissPK +func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) { + var whereCols []string + var args []interface{} + // if specify cols length > 0, then use it for where condition. + if len(cols) > 0 { + var err error + whereCols = make([]string, 0, len(cols)) + args, _, err = d.collectValues(mi, ind, cols, false, false, &whereCols, tz) + if err != nil { + return 0, err + } + } else { + // default use pk value as where condtion. + pkColumn, pkValue, ok := getExistPk(mi, ind) + if ok == false { + return 0, ErrMissPK + } + whereCols = []string{pkColumn} + args = append(args, pkValue) } Q := d.ins.TableQuote() - query := fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s = ?", Q, mi.table, Q, Q, pkName, Q) + sep := fmt.Sprintf("%s = ? AND %s", Q, Q) + wheres := strings.Join(whereCols, sep) + + query := fmt.Sprintf("DELETE FROM %s%s%s WHERE %s%s%s = ?", Q, mi.table, Q, Q, wheres, Q) d.ins.ReplaceMarks(&query) - res, err := q.Exec(query, pkValue) + res, err := q.Exec(query, args...) if err == nil { num, err := res.RowsAffected() if err != nil { @@ -659,7 +677,7 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time. ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(0) } } - err := d.deleteRels(q, mi, []interface{}{pkValue}, tz) + err := d.deleteRels(q, mi, args, tz) if err != nil { return num, err } diff --git a/orm/orm.go b/orm/orm.go index 390d300f..f416fc08 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -234,9 +234,10 @@ func (o *orm) Update(md interface{}, cols ...string) (int64, error) { } // delete model in database -func (o *orm) Delete(md interface{}) (int64, error) { +// cols shows the delete conditions values read from. deafult is pk +func (o *orm) Delete(md interface{}, cols ...string) (int64, error) { mi, ind := o.getMiInd(md, true) - num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ) + num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ,cols) if err != nil { return num, err } diff --git a/orm/orm_test.go b/orm/orm_test.go index 5e288039..fbf4768d 100644 --- a/orm/orm_test.go +++ b/orm/orm_test.go @@ -577,6 +577,10 @@ func TestCRUD(t *testing.T) { err = dORM.Read(&ub) throwFail(t, err) throwFail(t, AssertIs(ub.Name, "name")) + + num, err = dORM.Delete(&ub, "name") + throwFail(t, err) + throwFail(t, AssertIs(num, 1)) } func TestInsertTestData(t *testing.T) { diff --git a/orm/types.go b/orm/types.go index 8c17271d..60a0f1df 100644 --- a/orm/types.go +++ b/orm/types.go @@ -71,7 +71,7 @@ type Ormer interface { // num, err = Ormer.Update(&user, "Langs", "Extra") Update(md interface{}, cols ...string) (int64, error) // delete model in database - Delete(md interface{}) (int64, error) + Delete(md interface{}, cols ...string) (int64, error) // load related models to md model. // args are limit, offset int and order string. // @@ -401,7 +401,7 @@ type dbBaser interface { InsertValue(dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error) InsertStmt(stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error) Update(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error) - Delete(dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error) + Delete(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (int64, error) ReadBatch(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, *time.Location, []string) (int64, error) SupportUpdateJoin() bool UpdateBatch(dbQuerier, *querySet, *modelInfo, *Condition, Params, *time.Location) (int64, error) From 11247d41a7eedcb63f7f5bd7276001360fb72c3e Mon Sep 17 00:00:00 2001 From: Wang Yujian Date: Mon, 12 Sep 2016 20:07:30 +0000 Subject: [PATCH 88/96] Add support "SELECT FOR UPDATE" to orm. Resolve issue #2157 --- orm/db.go | 9 +++++++-- orm/orm.go | 14 ++++++++++++-- orm/qb.go | 1 + orm/qb_mysql.go | 6 ++++++ orm/qb_tidb.go | 6 ++++++ orm/types.go | 5 ++++- 6 files changed, 36 insertions(+), 5 deletions(-) diff --git a/orm/db.go b/orm/db.go index d2f8a5b2..52ce9cc3 100644 --- a/orm/db.go +++ b/orm/db.go @@ -310,7 +310,7 @@ func (d *dbBase) InsertStmt(stmt stmtQuerier, mi *modelInfo, ind reflect.Value, } // query sql ,read records and persist in dbBaser. -func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) error { +func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string, isForUpdate bool) error { var whereCols []string var args []interface{} @@ -341,7 +341,12 @@ func (d *dbBase) Read(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Lo sep = fmt.Sprintf("%s = ? AND %s", Q, Q) wheres := strings.Join(whereCols, sep) - query := fmt.Sprintf("SELECT %s%s%s FROM %s%s%s WHERE %s%s%s = ?", Q, sels, Q, Q, mi.table, Q, Q, wheres, Q) + forUpdate := "" + if isForUpdate { + forUpdate = "FOR UPDATE" + } + + query := fmt.Sprintf("SELECT %s%s%s FROM %s%s%s WHERE %s%s%s = ? %s", Q, sels, Q, Q, mi.table, Q, Q, wheres, Q, forUpdate) refs := make([]interface{}, colsNum) for i := range refs { diff --git a/orm/orm.go b/orm/orm.go index 390d300f..42931be9 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -122,7 +122,17 @@ func (o *orm) getFieldInfo(mi *modelInfo, name string) *fieldInfo { // read data to model func (o *orm) Read(md interface{}, cols ...string) error { mi, ind := o.getMiInd(md, true) - err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols) + err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false) + if err != nil { + return err + } + return nil +} + +// read data to model, like Read(), but use "SELECT FOR UPDATE" form +func (o *orm) ReadForUpdate(md interface{}, cols ...string) error { + mi, ind := o.getMiInd(md, true) + err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, true) if err != nil { return err } @@ -133,7 +143,7 @@ func (o *orm) Read(md interface{}, cols ...string) error { func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) { cols = append([]string{col1}, cols...) mi, ind := o.getMiInd(md, true) - err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols) + err := o.alias.DbBaser.Read(o.db, mi, ind, o.alias.TZ, cols, false) if err == ErrNoRows { // Create id, err := o.Insert(md) diff --git a/orm/qb.go b/orm/qb.go index 9f778916..e0655a17 100644 --- a/orm/qb.go +++ b/orm/qb.go @@ -19,6 +19,7 @@ import "errors" // QueryBuilder is the Query builder interface type QueryBuilder interface { Select(fields ...string) QueryBuilder + ForUpdate() QueryBuilder From(tables ...string) QueryBuilder InnerJoin(table string) QueryBuilder LeftJoin(table string) QueryBuilder diff --git a/orm/qb_mysql.go b/orm/qb_mysql.go index 886bc50e..23bdc9ee 100644 --- a/orm/qb_mysql.go +++ b/orm/qb_mysql.go @@ -34,6 +34,12 @@ func (qb *MySQLQueryBuilder) Select(fields ...string) QueryBuilder { return qb } +// ForUpdate add the FOR UPDATE clause +func (qb *MySQLQueryBuilder) ForUpdate() QueryBuilder { + qb.Tokens = append(qb.Tokens, "FOR UPDATE") + return qb +} + // From join the tables func (qb *MySQLQueryBuilder) From(tables ...string) QueryBuilder { qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, CommaSpace)) diff --git a/orm/qb_tidb.go b/orm/qb_tidb.go index c504049e..87b3ae84 100644 --- a/orm/qb_tidb.go +++ b/orm/qb_tidb.go @@ -31,6 +31,12 @@ func (qb *TiDBQueryBuilder) Select(fields ...string) QueryBuilder { return qb } +// ForUpdate add the FOR UPDATE clause +func (qb *TiDBQueryBuilder) ForUpdate() QueryBuilder { + qb.Tokens = append(qb.Tokens, "FOR UPDATE") + return qb +} + // From join the tables func (qb *TiDBQueryBuilder) From(tables ...string) QueryBuilder { qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, CommaSpace)) diff --git a/orm/types.go b/orm/types.go index 8c17271d..a36e0637 100644 --- a/orm/types.go +++ b/orm/types.go @@ -45,6 +45,9 @@ type Ormer interface { // u = &User{UserName: "astaxie", Password: "pass"} // err = Ormer.Read(u, "UserName") Read(md interface{}, cols ...string) error + // Like Read(), but with "FOR UPDATE" clause, useful in transaction. + // Some databases are not support this feature. + ReadForUpdate(md interface{}, cols ...string) error // Try to read a row from the database, or insert one if it doesn't exist ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) // insert model data to database @@ -394,7 +397,7 @@ type txEnder interface { // base database struct type dbBaser interface { - Read(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) error + Read(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string, bool) error Insert(dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error) InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *alias, ...string) (int64, error) InsertMulti(dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error) From 58ffc6f5f852b7e136613d0fbb3f25ca1452d9f7 Mon Sep 17 00:00:00 2001 From: astaxie Date: Tue, 13 Sep 2016 22:43:40 +0800 Subject: [PATCH 89/96] fix #1877 --- adminui.go | 13 +++++++------ toolbox/statistics.go | 4 ++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/adminui.go b/adminui.go index 7bb32b34..cdcdef33 100644 --- a/adminui.go +++ b/adminui.go @@ -78,13 +78,14 @@ var qpsTpl = `{{define "content"}} {{range $i, $elem := .Content.Data}} - {{range $elem}} - - {{.}} - - {{end}} + {{index $elem 0}} + {{index $elem 1}} + {{index $elem 2}} + {{index $elem 4}} + {{index $elem 6}} + {{index $elem 8}} + {{index $elem 10}} - {{end}} diff --git a/toolbox/statistics.go b/toolbox/statistics.go index 32eb7e23..69b88772 100644 --- a/toolbox/statistics.go +++ b/toolbox/statistics.go @@ -99,9 +99,13 @@ func (m *URLMap) GetMap() map[string]interface{} { fmt.Sprintf("% -50s", k), fmt.Sprintf("% -10s", kk), fmt.Sprintf("% -16d", vv.RequestNum), + fmt.Sprintf("%d", vv.TotalTime), fmt.Sprintf("% -16s", toS(vv.TotalTime)), + fmt.Sprintf("%d", vv.MaxTime), fmt.Sprintf("% -16s", toS(vv.MaxTime)), + fmt.Sprintf("%d", vv.MinTime), fmt.Sprintf("% -16s", toS(vv.MinTime)), + fmt.Sprintf("%d", time.Duration(int64(vv.TotalTime)/vv.RequestNum)), fmt.Sprintf("% -16s", toS(time.Duration(int64(vv.TotalTime)/vv.RequestNum))), } resultLists = append(resultLists, result) From da0e6e790df1530bb8902e048a1442b2a359e1be Mon Sep 17 00:00:00 2001 From: Sergey Lanzman Date: Wed, 14 Sep 2016 23:36:38 +0300 Subject: [PATCH 90/96] Update swagger.go --- swagger/swagger.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/swagger/swagger.go b/swagger/swagger.go index a16b26b4..4c542b73 100644 --- a/swagger/swagger.go +++ b/swagger/swagger.go @@ -22,19 +22,19 @@ package swagger // Swagger list the resource type Swagger struct { - SwaggerVersion string `json:"swagger,omitempty"` - Infos Information `json:"info"` - Host string `json:"host,omitempty"` - BasePath string `json:"basePath,omitempty"` - Schemes []string `json:"schemes,omitempty"` - Consumes []string `json:"consumes,omitempty"` - Produces []string `json:"produces,omitempty"` - Paths map[string]*Item `json:"paths"` - Definitions map[string]Schema `json:"definitions,omitempty"` - SecurityDefinitions map[string]Scurity `json:"securityDefinitions,omitempty"` - Security map[string][]string `json:"security,omitempty"` - Tags []Tag `json:"tags,omitempty"` - ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` + SwaggerVersion string `json:"swagger,omitempty"` + Infos Information `json:"info"` + Host string `json:"host,omitempty"` + BasePath string `json:"basePath,omitempty"` + Schemes []string `json:"schemes,omitempty"` + Consumes []string `json:"consumes,omitempty"` + Produces []string `json:"produces,omitempty"` + Paths map[string]*Item `json:"paths"` + Definitions map[string]Schema `json:"definitions,omitempty"` + SecurityDefinitions map[string]Security `json:"securityDefinitions,omitempty"` + Security map[string][]string `json:"security,omitempty"` + Tags []Tag `json:"tags,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` } // Information Provides metadata about the API. The metadata can be used by the clients if needed. @@ -44,8 +44,8 @@ type Information struct { Version string `json:"version,omitempty"` TermsOfService string `json:"termsOfService,omitempty"` - Contact Contact `json:"contact,omitempty"` - License License `json:"license,omitempty"` + Contact Contact `json:"contact,omitempty"` + License *License `json:"license,omitempty"` } // Contact information for the exposed API. @@ -144,8 +144,8 @@ type Response struct { Ref string `json:"$ref,omitempty"` } -// Scurity Allows the definition of a security scheme that can be used by the operations -type Scurity struct { +// Security Allows the definition of a security scheme that can be used by the operations +type Security struct { Type string `json:"type,omitempty"` // Valid values are "basic", "apiKey" or "oauth2". Description string `json:"description,omitempty"` Name string `json:"name,omitempty"` From e53c1471290eaf5b314c11b0a291d5f50a494235 Mon Sep 17 00:00:00 2001 From: Sergey Lanzman Date: Thu, 15 Sep 2016 00:15:02 +0300 Subject: [PATCH 91/96] improve Swagger 1. Add yaml 2. Fix typo scurity => security 3. Make license optional --- swagger/swagger.go | 182 ++++++++++++++++++++++----------------------- 1 file changed, 91 insertions(+), 91 deletions(-) diff --git a/swagger/swagger.go b/swagger/swagger.go index 4c542b73..409e264e 100644 --- a/swagger/swagger.go +++ b/swagger/swagger.go @@ -22,149 +22,149 @@ package swagger // Swagger list the resource type Swagger struct { - SwaggerVersion string `json:"swagger,omitempty"` - Infos Information `json:"info"` - Host string `json:"host,omitempty"` - BasePath string `json:"basePath,omitempty"` - Schemes []string `json:"schemes,omitempty"` - Consumes []string `json:"consumes,omitempty"` - Produces []string `json:"produces,omitempty"` - Paths map[string]*Item `json:"paths"` - Definitions map[string]Schema `json:"definitions,omitempty"` - SecurityDefinitions map[string]Security `json:"securityDefinitions,omitempty"` - Security map[string][]string `json:"security,omitempty"` - Tags []Tag `json:"tags,omitempty"` - ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` + SwaggerVersion string `json:"swagger,omitempty" yaml:"swagger,omitempty"` + Infos Information `json:"info" yaml:"info"` + Host string `json:"host,omitempty" yaml:"host,omitempty"` + BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"` + Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"` + Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"` + Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"` + Paths map[string]*Item `json:"paths" yaml:"paths"` + Definitions map[string]Schema `json:"definitions,omitempty" yaml:"definitions,omitempty"` + SecurityDefinitions map[string]Security `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"` + Security map[string][]string `json:"security,omitempty" yaml:"security,omitempty"` + Tags []Tag `json:"tags,omitempty" yaml:"tags,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` } // Information Provides metadata about the API. The metadata can be used by the clients if needed. type Information struct { - Title string `json:"title,omitempty"` - Description string `json:"description,omitempty"` - Version string `json:"version,omitempty"` - TermsOfService string `json:"termsOfService,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Version string `json:"version,omitempty" yaml:"version,omitempty"` + TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"` - Contact Contact `json:"contact,omitempty"` - License *License `json:"license,omitempty"` + Contact Contact `json:"contact,omitempty" yaml:"contact,omitempty"` + License *License `json:"license,omitempty" yaml:"license,omitempty"` } // Contact information for the exposed API. type Contact struct { - Name string `json:"name,omitempty"` - URL string `json:"url,omitempty"` - EMail string `json:"email,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + URL string `json:"url,omitempty" yaml:"url,omitempty"` + EMail string `json:"email,omitempty" yaml:"email,omitempty"` } // License information for the exposed API. type License struct { - Name string `json:"name,omitempty"` - URL string `json:"url,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + URL string `json:"url,omitempty" yaml:"url,omitempty"` } // Item Describes the operations available on a single path. type Item struct { - Ref string `json:"$ref,omitempty"` - Get *Operation `json:"get,omitempty"` - Put *Operation `json:"put,omitempty"` - Post *Operation `json:"post,omitempty"` - Delete *Operation `json:"delete,omitempty"` - Options *Operation `json:"options,omitempty"` - Head *Operation `json:"head,omitempty"` - Patch *Operation `json:"patch,omitempty"` + Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` + Get *Operation `json:"get,omitempty" yaml:"get,omitempty"` + Put *Operation `json:"put,omitempty" yaml:"put,omitempty"` + Post *Operation `json:"post,omitempty" yaml:"post,omitempty"` + Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"` + Options *Operation `json:"options,omitempty" yaml:"options,omitempty"` + Head *Operation `json:"head,omitempty" yaml:"head,omitempty"` + Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"` } // Operation Describes a single API operation on a path. type Operation struct { - Tags []string `json:"tags,omitempty"` - Summary string `json:"summary,omitempty"` - Description string `json:"description,omitempty"` - OperationID string `json:"operationId,omitempty"` - Consumes []string `json:"consumes,omitempty"` - Produces []string `json:"produces,omitempty"` - Schemes []string `json:"schemes,omitempty"` - Parameters []Parameter `json:"parameters,omitempty"` - Responses map[string]Response `json:"responses,omitempty"` - Deprecated bool `json:"deprecated,omitempty"` + Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"` + Summary string `json:"summary,omitempty" yaml:"summary,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"` + Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"` + Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"` + Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"` + Parameters []Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"` + Responses map[string]Response `json:"responses,omitempty" yaml:"responses,omitempty"` + Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"` } // Parameter Describes a single operation parameter. type Parameter struct { - In string `json:"in,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - Required bool `json:"required,omitempty"` - Schema *Schema `json:"schema,omitempty"` - Type string `json:"type,omitempty"` - Format string `json:"format,omitempty"` - Items *ParameterItems `json:"items,omitempty"` + In string `json:"in,omitempty" yaml:"in,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Required bool `json:"required,omitempty" yaml:"required,omitempty"` + Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"` + Type string `json:"type,omitempty" yaml:"type,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + Items *ParameterItems `json:"items,omitempty" yaml:"items,omitempty"` } // A limited subset of JSON-Schema's items object. It is used by parameter definitions that are not located in "body". // http://swagger.io/specification/#itemsObject type ParameterItems struct { - Type string `json:"type,omitempty"` - Format string `json:"format,omitempty"` - Items []*ParameterItems `json:"items,omitempty"` //Required if type is "array". Describes the type of items in the array. - CollectionFormat string `json:"collectionFormat,omitempty"` - Default string `json:"default,omitempty"` + Type string `json:"type,omitempty" yaml:"type,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + Items []*ParameterItems `json:"items,omitempty" yaml:"items,omitempty"` //Required if type is "array". Describes the type of items in the array. + CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"` + Default string `json:"default,omitempty" yaml:"default,omitempty"` } // Schema Object allows the definition of input and output data types. type Schema struct { - Ref string `json:"$ref,omitempty"` - Title string `json:"title,omitempty"` - Format string `json:"format,omitempty"` - Description string `json:"description,omitempty"` - Required []string `json:"required,omitempty"` - Type string `json:"type,omitempty"` - Items *Schema `json:"items,omitempty"` - Properties map[string]Propertie `json:"properties,omitempty"` + Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Required []string `json:"required,omitempty" yaml:"required,omitempty"` + Type string `json:"type,omitempty" yaml:"type,omitempty"` + Items *Schema `json:"items,omitempty" yaml:"items,omitempty"` + Properties map[string]Propertie `json:"properties,omitempty" yaml:"properties,omitempty"` } // Propertie are taken from the JSON Schema definition but their definitions were adjusted to the Swagger Specification type Propertie struct { - Ref string `json:"$ref,omitempty"` - Title string `json:"title,omitempty"` - Description string `json:"description,omitempty"` - Default string `json:"default,omitempty"` - Type string `json:"type,omitempty"` - Example string `json:"example,omitempty"` - Required []string `json:"required,omitempty"` - Format string `json:"format,omitempty"` - ReadOnly bool `json:"readOnly,omitempty"` - Properties map[string]Propertie `json:"properties,omitempty"` - Items *Propertie `json:"items,omitempty"` - AdditionalProperties *Propertie `json:"additionalProperties,omitempty"` + Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` + Title string `json:"title,omitempty" yaml:"title,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Default string `json:"default,omitempty" yaml:"default,omitempty"` + Type string `json:"type,omitempty" yaml:"type,omitempty"` + Example string `json:"example,omitempty" yaml:"example,omitempty"` + Required []string `json:"required,omitempty" yaml:"required,omitempty"` + Format string `json:"format,omitempty" yaml:"format,omitempty"` + ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` + Properties map[string]Propertie `json:"properties,omitempty" yaml:"properties,omitempty"` + Items *Propertie `json:"items,omitempty" yaml:"items,omitempty"` + AdditionalProperties *Propertie `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"` } // Response as they are returned from executing this operation. type Response struct { - Description string `json:"description,omitempty"` - Schema *Schema `json:"schema,omitempty"` - Ref string `json:"$ref,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"` + Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"` } // Security Allows the definition of a security scheme that can be used by the operations type Security struct { - Type string `json:"type,omitempty"` // Valid values are "basic", "apiKey" or "oauth2". - Description string `json:"description,omitempty"` - Name string `json:"name,omitempty"` - In string `json:"in,omitempty"` // Valid values are "query" or "header". - Flow string `json:"flow,omitempty"` // Valid values are "implicit", "password", "application" or "accessCode". - AuthorizationURL string `json:"authorizationUrl,omitempty"` - TokenURL string `json:"tokenUrl,omitempty"` - Scopes map[string]string `json:"scopes,omitempty"` // The available scopes for the OAuth2 security scheme. + Type string `json:"type,omitempty" yaml:"type,omitempty"` // Valid values are "basic", "apiKey" or "oauth2". + Description string `json:"description,omitempty" yaml:"description,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + In string `json:"in,omitempty" yaml:"in,omitempty"` // Valid values are "query" or "header". + Flow string `json:"flow,omitempty" yaml:"flow,omitempty"` // Valid values are "implicit", "password", "application" or "accessCode". + AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"` + TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"` + Scopes map[string]string `json:"scopes,omitempty" yaml:"scopes,omitempty"` // The available scopes for the OAuth2 security scheme. } // Tag Allows adding meta data to a single tag that is used by the Operation Object type Tag struct { - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"` } // ExternalDocs include Additional external documentation type ExternalDocs struct { - Description string `json:"description,omitempty"` - URL string `json:"url,omitempty"` + Description string `json:"description,omitempty" yaml:"description,omitempty"` + URL string `json:"url,omitempty" yaml:"url,omitempty"` } From a58115fed269b1502f65bb14182abecdad4bae56 Mon Sep 17 00:00:00 2001 From: astaxie Date: Thu, 15 Sep 2016 10:53:40 +0800 Subject: [PATCH 92/96] orm log delete repetition time --- orm/orm.go | 4 ++-- orm/orm_log.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/orm/orm.go b/orm/orm.go index 9a81b620..538916e4 100644 --- a/orm/orm.go +++ b/orm/orm.go @@ -68,7 +68,7 @@ const ( // Define common vars var ( Debug = false - DebugLog = NewLog(os.Stderr) + DebugLog = NewLog(os.Stdout) DefaultRowsLimit = 1000 DefaultRelsDepth = 2 DefaultTimeLoc = time.Local @@ -247,7 +247,7 @@ func (o *orm) Update(md interface{}, cols ...string) (int64, error) { // cols shows the delete conditions values read from. deafult is pk func (o *orm) Delete(md interface{}, cols ...string) (int64, error) { mi, ind := o.getMiInd(md, true) - num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ,cols) + num, err := o.alias.DbBaser.Delete(o.db, mi, ind, o.alias.TZ, cols) if err != nil { return num, err } diff --git a/orm/orm_log.go b/orm/orm_log.go index 54723273..26c73f9e 100644 --- a/orm/orm_log.go +++ b/orm/orm_log.go @@ -42,7 +42,7 @@ func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error if err != nil { flag = "FAIL" } - con := fmt.Sprintf(" - %s - [Queries/%s] - [%s / %11s / %7.1fms] - [%s]", t.Format(formatDateTime), alias.Name, flag, operaton, elsp, query) + con := fmt.Sprintf(" -[Queries/%s] - [%s / %11s / %7.1fms] - [%s]", alias.Name, flag, operaton, elsp, query) cons := make([]string, 0, len(args)) for _, arg := range args { cons = append(cons, fmt.Sprintf("%v", arg)) From 2b7dd85b925e7031b3fea162dfa50c0882e71f42 Mon Sep 17 00:00:00 2001 From: astaxie Date: Thu, 15 Sep 2016 10:58:46 +0800 Subject: [PATCH 93/96] access log add client request ip --- router.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/router.go b/router.go index 248c2a48..f528509d 100644 --- a/router.go +++ b/router.go @@ -838,15 +838,15 @@ Admin: if findRouter { if routerInfo != nil { - devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", statusColor, statusCode, + devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", context.Input.IP(), statusColor, statusCode, resetColor, timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path, routerInfo.pattern) } else { - devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", statusColor, statusCode, resetColor, + devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor, timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path) } } else { - devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", statusColor, statusCode, resetColor, + devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor, timeDur.String(), "nomatch", methodColor, r.Method, resetColor, r.URL.Path) } if iswin { From c16507607c69839f7ccba309a8f559aed98e017e Mon Sep 17 00:00:00 2001 From: astaxie Date: Thu, 15 Sep 2016 11:11:34 +0800 Subject: [PATCH 94/96] fix #2161 --- utils/captcha/image.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/captcha/image.go b/utils/captcha/image.go index 1057192a..0ceb8e42 100644 --- a/utils/captcha/image.go +++ b/utils/captcha/image.go @@ -359,6 +359,9 @@ func (m *Image) calculateSizes(width, height, ncount int) { } // Calculate dot size. m.dotSize = int(nh / fh) + if m.dotSize < 1 { + m.dotSize = 1 + } // Save everything, making the actual width smaller by 1 dot to account // for spacing between digits. m.numWidth = int(nw) - m.dotSize From 421bf97b84feff22d52644bc8668c8021cf56dec Mon Sep 17 00:00:00 2001 From: astaxie Date: Thu, 15 Sep 2016 12:16:24 +0800 Subject: [PATCH 95/96] Support custome recover func fix #2004 --- config.go | 35 +++++++++++++++++++++++++++++++++++ router.go | 35 +++-------------------------------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/config.go b/config.go index effc5e12..a4f40611 100644 --- a/config.go +++ b/config.go @@ -19,9 +19,11 @@ import ( "os" "path/filepath" "reflect" + "runtime" "strings" "github.com/astaxie/beego/config" + "github.com/astaxie/beego/context" "github.com/astaxie/beego/logs" "github.com/astaxie/beego/session" "github.com/astaxie/beego/utils" @@ -34,6 +36,7 @@ type Config struct { RouterCaseSensitive bool ServerName string RecoverPanic bool + RecoverFunc func(*context.Context) CopyRequestBody bool EnableGzip bool MaxMemory int64 @@ -142,6 +145,37 @@ func init() { } } +func recoverPanic(ctx *context.Context) { + if err := recover(); err != nil { + if err == ErrAbort { + return + } + if !BConfig.RecoverPanic { + panic(err) + } + if BConfig.EnableErrorsShow { + if _, ok := ErrorMaps[fmt.Sprint(err)]; ok { + exception(fmt.Sprint(err), ctx) + return + } + } + var stack string + logs.Critical("the request url is ", ctx.Input.URL()) + logs.Critical("Handler crashed with error", err) + for i := 1; ; i++ { + _, file, line, ok := runtime.Caller(i) + if !ok { + break + } + logs.Critical(fmt.Sprintf("%s:%d", file, line)) + stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line)) + } + if BConfig.RunMode == DEV { + showErr(err, ctx, stack) + } + } +} + func newBConfig() *Config { return &Config{ AppName: "beego", @@ -149,6 +183,7 @@ func newBConfig() *Config { RouterCaseSensitive: true, ServerName: "beegoServer:" + VERSION, RecoverPanic: true, + RecoverFunc: recoverPanic, CopyRequestBody: false, EnableGzip: false, MaxMemory: 1 << 26, //64MB diff --git a/router.go b/router.go index f528509d..835b10b2 100644 --- a/router.go +++ b/router.go @@ -626,7 +626,9 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) context.Reset(rw, r) defer p.pool.Put(context) - defer p.recoverPanic(context) + if BConfig.RecoverFunc != nil { + defer BConfig.RecoverFunc(context) + } context.Output.EnableGzip = BConfig.EnableGzip @@ -878,37 +880,6 @@ func (p *ControllerRegister) FindRouter(context *beecontext.Context) (routerInfo return } -func (p *ControllerRegister) recoverPanic(context *beecontext.Context) { - if err := recover(); err != nil { - if err == ErrAbort { - return - } - if !BConfig.RecoverPanic { - panic(err) - } - if BConfig.EnableErrorsShow { - if _, ok := ErrorMaps[fmt.Sprint(err)]; ok { - exception(fmt.Sprint(err), context) - return - } - } - var stack string - logs.Critical("the request url is ", context.Input.URL()) - logs.Critical("Handler crashed with error", err) - for i := 1; ; i++ { - _, file, line, ok := runtime.Caller(i) - if !ok { - break - } - logs.Critical(fmt.Sprintf("%s:%d", file, line)) - stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d", file, line)) - } - if BConfig.RunMode == DEV { - showErr(err, context, stack) - } - } -} - func toURL(params map[string]string) string { if len(params) == 0 { return "" From 868e14b8ba1b1a8664596021e5329929bb735856 Mon Sep 17 00:00:00 2001 From: astaxie Date: Thu, 15 Sep 2016 20:04:45 +0800 Subject: [PATCH 96/96] fix #2017 --- context/input.go | 14 ++++++++------ router.go | 17 +++++++++++++---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/context/input.go b/context/input.go index 5af8e6c4..1e6eaf71 100644 --- a/context/input.go +++ b/context/input.go @@ -40,12 +40,14 @@ var ( // BeegoInput operates the http request header, data, cookie and body. // it also contains router params and current session. type BeegoInput struct { - Context *Context - CruSession session.Store - pnames []string - pvalues []string - data map[interface{}]interface{} // store some values in this context when calling context in filter or controller. - RequestBody []byte + Context *Context + CruSession session.Store + pnames []string + pvalues []string + data map[interface{}]interface{} // store some values in this context when calling context in filter or controller. + RequestBody []byte + RunMethod string + RunController reflect.Type } // NewInput return BeegoInput generated by Context. diff --git a/router.go b/router.go index 835b10b2..97b0edba 100644 --- a/router.go +++ b/router.go @@ -685,8 +685,16 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) if len(p.filters[BeforeRouter]) > 0 && p.execFilter(context, urlPath, BeforeRouter) { goto Admin } + // User can define RunController and RunMethod in filter + if context.Input.RunController != nil && context.Input.RunMethod != "" { + findRouter = true + isRunnable = true + runMethod = context.Input.RunMethod + runRouter = context.Input.RunController + } else { + routerInfo, findRouter = p.FindRouter(context) + } - routerInfo, findRouter = p.FindRouter(context) //if no matches to url, throw a not found exception if !findRouter { exception("404", context) @@ -698,15 +706,16 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) } } - //store router pattern into context - context.Input.SetData("RouterPattern", routerInfo.pattern) - //execute middleware filters if len(p.filters[BeforeExec]) > 0 && p.execFilter(context, urlPath, BeforeExec) { goto Admin } if routerInfo != nil { + if BConfig.RunMode == DEV { + //store router pattern into context + context.Input.SetData("RouterPattern", routerInfo.pattern) + } if routerInfo.routerType == routerTypeRESTFul { if _, ok := routerInfo.methods[r.Method]; ok { isRunnable = true