mirror of
https://github.com/astaxie/beego.git
synced 2025-07-11 18:21:02 +00:00
Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
2d87d4feaf | |||
15a45ccc7b | |||
e0c59fcf0b | |||
083f697c59 | |||
a5a6546b91 | |||
9cafbf6a21 | |||
faba0d7273 | |||
c3116d3601 | |||
868e14b8ba | |||
421bf97b84 | |||
c16507607c | |||
2b7dd85b92 | |||
a58115fed2 | |||
f9b5b0f551 | |||
e53c147129 | |||
da0e6e790d | |||
58ffc6f5f8 | |||
d5fb74aa94 | |||
11247d41a7 | |||
5b21c7cd71 | |||
dd0f05b1f1 | |||
a32241e7d3 | |||
0ef357ebd7 | |||
32dd976620 | |||
fcd8a2024e | |||
30661472c8 | |||
6ced26660e | |||
7d6c45d4c9 | |||
98740fddac | |||
6d3042f5e5 | |||
8b9d6eee1a | |||
11ef5929aa | |||
c697b98006 | |||
7c2e563879 | |||
56aa224a6e | |||
8c37a07adb | |||
161c061376 | |||
aa091cea42 | |||
7df74c0a30 | |||
1e1e900278 | |||
fb2343567b |
13
adminui.go
13
adminui.go
@ -78,13 +78,14 @@ var qpsTpl = `{{define "content"}}
|
|||||||
{{range $i, $elem := .Content.Data}}
|
{{range $i, $elem := .Content.Data}}
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
{{range $elem}}
|
<td>{{index $elem 0}}</td>
|
||||||
<td>
|
<td>{{index $elem 1}}</td>
|
||||||
{{.}}
|
<td>{{index $elem 2}}</td>
|
||||||
</td>
|
<td data-order="{{index $elem 3}}">{{index $elem 4}}</td>
|
||||||
{{end}}
|
<td data-order="{{index $elem 5}}">{{index $elem 6}}</td>
|
||||||
|
<td data-order="{{index $elem 7}}">{{index $elem 8}}</td>
|
||||||
|
<td data-order="{{index $elem 9}}">{{index $elem 10}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{{end}}
|
{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
||||||
|
9
beego.go
9
beego.go
@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
// VERSION represent beego web framework version.
|
// VERSION represent beego web framework version.
|
||||||
VERSION = "1.7.0"
|
VERSION = "1.7.1"
|
||||||
|
|
||||||
// DEV is for develop
|
// DEV is for develop
|
||||||
DEV = "dev"
|
DEV = "dev"
|
||||||
@ -85,8 +85,13 @@ func initBeforeHTTPRun() {
|
|||||||
|
|
||||||
// TestBeegoInit is for test package init
|
// TestBeegoInit is for test package init
|
||||||
func TestBeegoInit(ap string) {
|
func TestBeegoInit(ap string) {
|
||||||
appConfigPath = filepath.Join(ap, "conf", "app.conf")
|
path := filepath.Join(ap, "conf", "app.conf")
|
||||||
os.Chdir(ap)
|
os.Chdir(ap)
|
||||||
|
InitBeegoBeforeTest(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitBeegoBeforeTest is for test package init
|
||||||
|
func InitBeegoBeforeTest(appConfigPath string) {
|
||||||
if err := LoadAppConfig(appConfigProvider, appConfigPath); err != nil {
|
if err := LoadAppConfig(appConfigProvider, appConfigPath); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
21
cache/memcache/memcache.go
vendored
21
cache/memcache/memcache.go
vendored
@ -33,12 +33,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/bradfitz/gomemcache/memcache"
|
|
||||||
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/cache"
|
"github.com/astaxie/beego/cache"
|
||||||
|
"github.com/bradfitz/gomemcache/memcache"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cache Memcache adapter.
|
// Cache Memcache adapter.
|
||||||
@ -60,7 +58,7 @@ func (rc *Cache) Get(key string) interface{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if item, err := rc.conn.Get(key); err == nil {
|
if item, err := rc.conn.Get(key); err == nil {
|
||||||
return string(item.Value)
|
return item.Value
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -80,7 +78,7 @@ func (rc *Cache) GetMulti(keys []string) []interface{} {
|
|||||||
mv, err := rc.conn.GetMulti(keys)
|
mv, err := rc.conn.GetMulti(keys)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, v := range mv {
|
for _, v := range mv {
|
||||||
rv = append(rv, string(v.Value))
|
rv = append(rv, v.Value)
|
||||||
}
|
}
|
||||||
return rv
|
return rv
|
||||||
}
|
}
|
||||||
@ -90,18 +88,21 @@ func (rc *Cache) GetMulti(keys []string) []interface{} {
|
|||||||
return rv
|
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 {
|
func (rc *Cache) Put(key string, val interface{}, timeout time.Duration) error {
|
||||||
if rc.conn == nil {
|
if rc.conn == nil {
|
||||||
if err := rc.connectInit(); err != nil {
|
if err := rc.connectInit(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
v, ok := val.(string)
|
item := memcache.Item{Key: key, Expiration: int32(timeout / time.Second)}
|
||||||
if !ok {
|
if v, ok := val.([]byte); ok {
|
||||||
return errors.New("val must string")
|
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)
|
return rc.conn.Set(&item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
12
cache/memcache/memcache_test.go
vendored
12
cache/memcache/memcache_test.go
vendored
@ -46,7 +46,7 @@ func TestMemcacheCache(t *testing.T) {
|
|||||||
t.Error("set Error", err)
|
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")
|
t.Error("get err")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ func TestMemcacheCache(t *testing.T) {
|
|||||||
t.Error("Incr Error", err)
|
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")
|
t.Error("get err")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +62,7 @@ func TestMemcacheCache(t *testing.T) {
|
|||||||
t.Error("Decr Error", err)
|
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")
|
t.Error("get err")
|
||||||
}
|
}
|
||||||
bm.Delete("astaxie")
|
bm.Delete("astaxie")
|
||||||
@ -78,7 +78,7 @@ func TestMemcacheCache(t *testing.T) {
|
|||||||
t.Error("check err")
|
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")
|
t.Error("get err")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,10 +94,10 @@ func TestMemcacheCache(t *testing.T) {
|
|||||||
if len(vv) != 2 {
|
if len(vv) != 2 {
|
||||||
t.Error("GetMulti ERROR")
|
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")
|
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")
|
t.Error("GetMulti ERROR")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
35
config.go
35
config.go
@ -19,9 +19,11 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/astaxie/beego/config"
|
"github.com/astaxie/beego/config"
|
||||||
|
"github.com/astaxie/beego/context"
|
||||||
"github.com/astaxie/beego/logs"
|
"github.com/astaxie/beego/logs"
|
||||||
"github.com/astaxie/beego/session"
|
"github.com/astaxie/beego/session"
|
||||||
"github.com/astaxie/beego/utils"
|
"github.com/astaxie/beego/utils"
|
||||||
@ -34,6 +36,7 @@ type Config struct {
|
|||||||
RouterCaseSensitive bool
|
RouterCaseSensitive bool
|
||||||
ServerName string
|
ServerName string
|
||||||
RecoverPanic bool
|
RecoverPanic bool
|
||||||
|
RecoverFunc func(*context.Context)
|
||||||
CopyRequestBody bool
|
CopyRequestBody bool
|
||||||
EnableGzip bool
|
EnableGzip bool
|
||||||
MaxMemory int64
|
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 {
|
func newBConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
AppName: "beego",
|
AppName: "beego",
|
||||||
@ -149,6 +183,7 @@ func newBConfig() *Config {
|
|||||||
RouterCaseSensitive: true,
|
RouterCaseSensitive: true,
|
||||||
ServerName: "beegoServer:" + VERSION,
|
ServerName: "beegoServer:" + VERSION,
|
||||||
RecoverPanic: true,
|
RecoverPanic: true,
|
||||||
|
RecoverFunc: recoverPanic,
|
||||||
CopyRequestBody: false,
|
CopyRequestBody: false,
|
||||||
EnableGzip: false,
|
EnableGzip: false,
|
||||||
MaxMemory: 1 << 26, //64MB
|
MaxMemory: 1 << 26, //64MB
|
||||||
|
@ -40,12 +40,14 @@ var (
|
|||||||
// BeegoInput operates the http request header, data, cookie and body.
|
// BeegoInput operates the http request header, data, cookie and body.
|
||||||
// it also contains router params and current session.
|
// it also contains router params and current session.
|
||||||
type BeegoInput struct {
|
type BeegoInput struct {
|
||||||
Context *Context
|
Context *Context
|
||||||
CruSession session.Store
|
CruSession session.Store
|
||||||
pnames []string
|
pnames []string
|
||||||
pvalues []string
|
pvalues []string
|
||||||
data map[interface{}]interface{} // store some values in this context when calling context in filter or controller.
|
data map[interface{}]interface{} // store some values in this context when calling context in filter or controller.
|
||||||
RequestBody []byte
|
RequestBody []byte
|
||||||
|
RunMethod string
|
||||||
|
RunController reflect.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewInput return BeegoInput generated by Context.
|
// NewInput return BeegoInput generated by Context.
|
||||||
|
6
error.go
6
error.go
@ -93,7 +93,11 @@ func showErr(err interface{}, ctx *context.Context, stack string) {
|
|||||||
"BeegoVersion": VERSION,
|
"BeegoVersion": VERSION,
|
||||||
"GoVersion": runtime.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)
|
t.Execute(ctx.ResponseWriter, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
78
logs/jianliao.go
Normal file
78
logs/jianliao.go
Normal file
@ -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)
|
||||||
|
}
|
@ -66,9 +66,11 @@ const (
|
|||||||
AdapterConsole = "console"
|
AdapterConsole = "console"
|
||||||
AdapterFile = "file"
|
AdapterFile = "file"
|
||||||
AdapterMultiFile = "multifile"
|
AdapterMultiFile = "multifile"
|
||||||
AdapterMail = "stmp"
|
AdapterMail = "smtp"
|
||||||
AdapterConn = "conn"
|
AdapterConn = "conn"
|
||||||
AdapterEs = "es"
|
AdapterEs = "es"
|
||||||
|
AdapterJianLiao = "jianliao"
|
||||||
|
AdapterSlack = "slack"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Legacy log level constants to ensure backwards compatibility.
|
// Legacy log level constants to ensure backwards compatibility.
|
||||||
|
66
logs/slack.go
Normal file
66
logs/slack.go
Normal file
@ -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)
|
||||||
|
}
|
41
orm/db.go
41
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.
|
// 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 whereCols []string
|
||||||
var args []interface{}
|
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)
|
sep = fmt.Sprintf("%s = ? AND %s", Q, Q)
|
||||||
wheres := strings.Join(whereCols, sep)
|
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)
|
refs := make([]interface{}, colsNum)
|
||||||
for i := range refs {
|
for i := range refs {
|
||||||
@ -634,18 +639,36 @@ func (d *dbBase) Update(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
|
|||||||
|
|
||||||
// execute delete sql dbQuerier with given struct reflect.Value.
|
// execute delete sql dbQuerier with given struct reflect.Value.
|
||||||
// delete index is pk.
|
// delete index is pk.
|
||||||
func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location) (int64, error) {
|
func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.Location, cols []string) (int64, error) {
|
||||||
pkName, pkValue, ok := getExistPk(mi, ind)
|
var whereCols []string
|
||||||
if ok == false {
|
var args []interface{}
|
||||||
return 0, ErrMissPK
|
// 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()
|
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)
|
d.ins.ReplaceMarks(&query)
|
||||||
res, err := q.Exec(query, pkValue)
|
res, err := q.Exec(query, args...)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
num, err := res.RowsAffected()
|
num, err := res.RowsAffected()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -659,7 +682,7 @@ func (d *dbBase) Delete(q dbQuerier, mi *modelInfo, ind reflect.Value, tz *time.
|
|||||||
ind.FieldByIndex(mi.fields.pk.fieldIndex).SetInt(0)
|
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 {
|
if err != nil {
|
||||||
return num, err
|
return num, err
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ type _dbCache struct {
|
|||||||
func (ac *_dbCache) add(name string, al *alias) (added bool) {
|
func (ac *_dbCache) add(name string, al *alias) (added bool) {
|
||||||
ac.mux.Lock()
|
ac.mux.Lock()
|
||||||
defer ac.mux.Unlock()
|
defer ac.mux.Unlock()
|
||||||
if _, ok := ac.cache[name]; ok == false {
|
if _, ok := ac.cache[name]; !ok {
|
||||||
ac.cache[name] = al
|
ac.cache[name] = al
|
||||||
added = true
|
added = true
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,7 @@ outFor:
|
|||||||
if v, ok := arg.(time.Time); ok {
|
if v, ok := arg.(time.Time); ok {
|
||||||
if fi != nil && fi.fieldType == TypeDateField {
|
if fi != nil && fi.fieldType == TypeDateField {
|
||||||
arg = v.In(tz).Format(formatDate)
|
arg = v.In(tz).Format(formatDate)
|
||||||
} else if fi.fieldType == TypeDateTimeField {
|
} else if fi != nil && fi.fieldType == TypeDateTimeField {
|
||||||
arg = v.In(tz).Format(formatDateTime)
|
arg = v.In(tz).Format(formatDateTime)
|
||||||
} else {
|
} else {
|
||||||
arg = v.In(tz).Format(formatTime)
|
arg = v.In(tz).Format(formatTime)
|
||||||
@ -154,7 +154,7 @@ outFor:
|
|||||||
typ := val.Type()
|
typ := val.Type()
|
||||||
name := getFullName(typ)
|
name := getFullName(typ)
|
||||||
var value interface{}
|
var value interface{}
|
||||||
if mmi, ok := modelCache.getByFN(name); ok {
|
if mmi, ok := modelCache.getByFullName(name); ok {
|
||||||
if _, vu, exist := getExistPk(mmi, val); exist {
|
if _, vu, exist := getExistPk(mmi, val); exist {
|
||||||
value = vu
|
value = vu
|
||||||
}
|
}
|
||||||
|
@ -29,39 +29,18 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
modelCache = &_modelCache{
|
modelCache = &_modelCache{
|
||||||
cache: make(map[string]*modelInfo),
|
cache: make(map[string]*modelInfo),
|
||||||
cacheByFN: make(map[string]*modelInfo),
|
cacheByFullName: 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,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// model info collection
|
// model info collection
|
||||||
type _modelCache struct {
|
type _modelCache struct {
|
||||||
sync.RWMutex
|
sync.RWMutex // only used outsite for bootStrap
|
||||||
orders []string
|
orders []string
|
||||||
cache map[string]*modelInfo
|
cache map[string]*modelInfo
|
||||||
cacheByFN map[string]*modelInfo
|
cacheByFullName map[string]*modelInfo
|
||||||
done bool
|
done bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// get all model info
|
// get all model info
|
||||||
@ -88,9 +67,9 @@ func (mc *_modelCache) get(table string) (mi *modelInfo, ok bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get model info by field name
|
// get model info by full name
|
||||||
func (mc *_modelCache) getByFN(name string) (mi *modelInfo, ok bool) {
|
func (mc *_modelCache) getByFullName(name string) (mi *modelInfo, ok bool) {
|
||||||
mi, ok = mc.cacheByFN[name]
|
mi, ok = mc.cacheByFullName[name]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,7 +77,7 @@ func (mc *_modelCache) getByFN(name string) (mi *modelInfo, ok bool) {
|
|||||||
func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo {
|
func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo {
|
||||||
mii := mc.cache[table]
|
mii := mc.cache[table]
|
||||||
mc.cache[table] = mi
|
mc.cache[table] = mi
|
||||||
mc.cacheByFN[mi.fullName] = mi
|
mc.cacheByFullName[mi.fullName] = mi
|
||||||
if mii == nil {
|
if mii == nil {
|
||||||
mc.orders = append(mc.orders, table)
|
mc.orders = append(mc.orders, table)
|
||||||
}
|
}
|
||||||
@ -109,7 +88,7 @@ func (mc *_modelCache) set(table string, mi *modelInfo) *modelInfo {
|
|||||||
func (mc *_modelCache) clean() {
|
func (mc *_modelCache) clean() {
|
||||||
mc.orders = make([]string, 0)
|
mc.orders = make([]string, 0)
|
||||||
mc.cache = make(map[string]*modelInfo)
|
mc.cache = make(map[string]*modelInfo)
|
||||||
mc.cacheByFN = make(map[string]*modelInfo)
|
mc.cacheByFullName = make(map[string]*modelInfo)
|
||||||
mc.done = false
|
mc.done = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
package orm
|
package orm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -23,24 +22,34 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// register models.
|
// register models.
|
||||||
// prefix means table name prefix.
|
// PrefixOrSuffix means table name prefix or suffix.
|
||||||
func registerModel(prefix string, model interface{}) {
|
// isPrefix whether the prefix is prefix or suffix
|
||||||
|
func registerModel(PrefixOrSuffix string, model interface{}, isPrefix bool) {
|
||||||
val := reflect.ValueOf(model)
|
val := reflect.ValueOf(model)
|
||||||
ind := reflect.Indirect(val)
|
typ := reflect.Indirect(val).Type()
|
||||||
typ := ind.Type()
|
|
||||||
|
|
||||||
if val.Kind() != reflect.Ptr {
|
if val.Kind() != reflect.Ptr {
|
||||||
panic(fmt.Errorf("<orm.RegisterModel> cannot use non-ptr model struct `%s`", getFullName(typ)))
|
panic(fmt.Errorf("<orm.RegisterModel> cannot use non-ptr model struct `%s`", getFullName(typ)))
|
||||||
}
|
}
|
||||||
|
// For this case:
|
||||||
|
// u := &User{}
|
||||||
|
// registerModel(&u)
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
panic(fmt.Errorf("<orm.RegisterModel> only allow ptr model struct, it looks you use two reference to the struct `%s`", typ))
|
||||||
|
}
|
||||||
|
|
||||||
table := getTableName(val)
|
table := getTableName(val)
|
||||||
|
|
||||||
if prefix != "" {
|
if PrefixOrSuffix != "" {
|
||||||
table = prefix + table
|
if isPrefix {
|
||||||
|
table = PrefixOrSuffix + table
|
||||||
|
} else {
|
||||||
|
table = table + PrefixOrSuffix
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// models's fullname is pkgpath + struct name
|
||||||
name := getFullName(typ)
|
name := getFullName(typ)
|
||||||
if _, ok := modelCache.getByFN(name); ok {
|
if _, ok := modelCache.getByFullName(name); ok {
|
||||||
fmt.Printf("<orm.RegisterModel> model `%s` repeat register, must be unique\n", name)
|
fmt.Printf("<orm.RegisterModel> model `%s` repeat register, must be unique\n", name)
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
@ -50,34 +59,34 @@ func registerModel(prefix string, model interface{}) {
|
|||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
info := newModelInfo(val)
|
mi := newModelInfo(val)
|
||||||
if info.fields.pk == nil {
|
if mi.fields.pk == nil {
|
||||||
outFor:
|
outFor:
|
||||||
for _, fi := range info.fields.fieldsDB {
|
for _, fi := range mi.fields.fieldsDB {
|
||||||
if strings.ToLower(fi.name) == "id" {
|
if strings.ToLower(fi.name) == "id" {
|
||||||
switch fi.addrValue.Elem().Kind() {
|
switch fi.addrValue.Elem().Kind() {
|
||||||
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||||
fi.auto = true
|
fi.auto = true
|
||||||
fi.pk = true
|
fi.pk = true
|
||||||
info.fields.pk = fi
|
mi.fields.pk = fi
|
||||||
break outFor
|
break outFor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.fields.pk == nil {
|
if mi.fields.pk == nil {
|
||||||
fmt.Printf("<orm.RegisterModel> `%s` need a primary key field, default use 'id' if not set\n", name)
|
fmt.Printf("<orm.RegisterModel> `%s` need a primary key field, default use 'id' if not set\n", name)
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info.table = table
|
mi.table = table
|
||||||
info.pkg = typ.PkgPath()
|
mi.pkg = typ.PkgPath()
|
||||||
info.model = model
|
mi.model = model
|
||||||
info.manual = true
|
mi.manual = true
|
||||||
|
|
||||||
modelCache.set(table, info)
|
modelCache.set(table, mi)
|
||||||
}
|
}
|
||||||
|
|
||||||
// boostrap models
|
// boostrap models
|
||||||
@ -85,30 +94,29 @@ func bootStrap() {
|
|||||||
if modelCache.done {
|
if modelCache.done {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
models map[string]*modelInfo
|
models map[string]*modelInfo
|
||||||
)
|
)
|
||||||
|
|
||||||
if dataBaseCache.getDefault() == nil {
|
if dataBaseCache.getDefault() == nil {
|
||||||
err = fmt.Errorf("must have one register DataBase alias named `default`")
|
err = fmt.Errorf("must have one register DataBase alias named `default`")
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// set rel and reverse model
|
||||||
|
// RelManyToMany set the relTable
|
||||||
models = modelCache.all()
|
models = modelCache.all()
|
||||||
for _, mi := range models {
|
for _, mi := range models {
|
||||||
for _, fi := range mi.fields.columns {
|
for _, fi := range mi.fields.columns {
|
||||||
if fi.rel || fi.reverse {
|
if fi.rel || fi.reverse {
|
||||||
elm := fi.addrValue.Type().Elem()
|
elm := fi.addrValue.Type().Elem()
|
||||||
switch fi.fieldType {
|
if fi.fieldType == RelReverseMany || fi.fieldType == RelManyToMany {
|
||||||
case RelReverseMany, RelManyToMany:
|
|
||||||
elm = elm.Elem()
|
elm = elm.Elem()
|
||||||
}
|
}
|
||||||
|
// check the rel or reverse model already register
|
||||||
name := getFullName(elm)
|
name := getFullName(elm)
|
||||||
mii, ok := modelCache.getByFN(name)
|
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())
|
err = fmt.Errorf("can not found rel in field `%s`, `%s` may be miss register", fi.fullName, elm.String())
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
@ -117,20 +125,17 @@ func bootStrap() {
|
|||||||
switch fi.fieldType {
|
switch fi.fieldType {
|
||||||
case RelManyToMany:
|
case RelManyToMany:
|
||||||
if fi.relThrough != "" {
|
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) {
|
if i := strings.LastIndex(fi.relThrough, "."); i != -1 && len(fi.relThrough) > (i+1) {
|
||||||
pn := fi.relThrough[:i]
|
pn := fi.relThrough[:i]
|
||||||
rmi, ok := modelCache.getByFN(fi.relThrough)
|
rmi, ok := modelCache.getByFullName(fi.relThrough)
|
||||||
if ok == false || pn != rmi.pkg {
|
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
|
goto end
|
||||||
}
|
}
|
||||||
|
|
||||||
fi.relThroughModelInfo = rmi
|
fi.relThroughModelInfo = rmi
|
||||||
fi.relTable = rmi.table
|
fi.relTable = rmi.table
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
err = errors.New(msg)
|
err = fmt.Errorf("field `%s` wrong rel_through value `%s`", fi.fullName, fi.relThrough)
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -138,7 +143,6 @@ func bootStrap() {
|
|||||||
if fi.relTable != "" {
|
if fi.relTable != "" {
|
||||||
i.table = fi.relTable
|
i.table = fi.relTable
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := modelCache.set(i.table, i); v != nil {
|
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)
|
err = fmt.Errorf("the rel table name `%s` already registered, cannot be use, please change one", fi.relTable)
|
||||||
goto end
|
goto end
|
||||||
@ -153,6 +157,8 @@ func bootStrap() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the rel filed while the relModelInfo also has filed point to current model
|
||||||
|
// if not exist, add a new field to the relModelInfo
|
||||||
models = modelCache.all()
|
models = modelCache.all()
|
||||||
for _, mi := range models {
|
for _, mi := range models {
|
||||||
for _, fi := range mi.fields.fieldsRel {
|
for _, fi := range mi.fields.fieldsRel {
|
||||||
@ -165,7 +171,6 @@ func bootStrap() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if inModel == false {
|
if inModel == false {
|
||||||
rmi := fi.relModelInfo
|
rmi := fi.relModelInfo
|
||||||
ffi := new(fieldInfo)
|
ffi := new(fieldInfo)
|
||||||
@ -216,7 +221,6 @@ func bootStrap() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.reverseFieldInfoTwo == nil {
|
if fi.reverseFieldInfoTwo == nil {
|
||||||
err = fmt.Errorf("can not find m2m field for m2m model `%s`, ensure your m2m model defined correct",
|
err = fmt.Errorf("can not find m2m field for m2m model `%s`, ensure your m2m model defined correct",
|
||||||
fi.relThroughModelInfo.fullName)
|
fi.relThroughModelInfo.fullName)
|
||||||
@ -300,17 +304,31 @@ end:
|
|||||||
|
|
||||||
// RegisterModel register models
|
// RegisterModel register models
|
||||||
func RegisterModel(models ...interface{}) {
|
func RegisterModel(models ...interface{}) {
|
||||||
|
if modelCache.done {
|
||||||
|
panic(fmt.Errorf("RegisterModel must be run before BootStrap"))
|
||||||
|
}
|
||||||
RegisterModelWithPrefix("", models...)
|
RegisterModelWithPrefix("", models...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterModelWithPrefix register models with a prefix
|
// RegisterModelWithPrefix register models with a prefix
|
||||||
func RegisterModelWithPrefix(prefix string, models ...interface{}) {
|
func RegisterModelWithPrefix(prefix string, models ...interface{}) {
|
||||||
if modelCache.done {
|
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 {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -320,7 +338,6 @@ func BootStrap() {
|
|||||||
if modelCache.done {
|
if modelCache.done {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
modelCache.Lock()
|
modelCache.Lock()
|
||||||
defer modelCache.Unlock()
|
defer modelCache.Unlock()
|
||||||
bootStrap()
|
bootStrap()
|
||||||
|
@ -104,7 +104,7 @@ type fieldInfo struct {
|
|||||||
mi *modelInfo
|
mi *modelInfo
|
||||||
fieldIndex []int
|
fieldIndex []int
|
||||||
fieldType int
|
fieldType int
|
||||||
dbcol bool
|
dbcol bool // table column fk and onetoone
|
||||||
inModel bool
|
inModel bool
|
||||||
name string
|
name string
|
||||||
fullName string
|
fullName string
|
||||||
@ -116,13 +116,13 @@ type fieldInfo struct {
|
|||||||
null bool
|
null bool
|
||||||
index bool
|
index bool
|
||||||
unique bool
|
unique bool
|
||||||
colDefault bool
|
colDefault bool // whether has default tag
|
||||||
initial StrTo
|
initial StrTo // store the default value
|
||||||
size int
|
size int
|
||||||
toText bool
|
toText bool
|
||||||
autoNow bool
|
autoNow bool
|
||||||
autoNowAdd bool
|
autoNowAdd bool
|
||||||
rel bool
|
rel bool // if type equal to RelForeignKey, RelOneToOne, RelManyToMany then true
|
||||||
reverse bool
|
reverse bool
|
||||||
reverseField string
|
reverseField string
|
||||||
reverseFieldInfo *fieldInfo
|
reverseFieldInfo *fieldInfo
|
||||||
@ -134,7 +134,7 @@ type fieldInfo struct {
|
|||||||
relModelInfo *modelInfo
|
relModelInfo *modelInfo
|
||||||
digits int
|
digits int
|
||||||
decimals int
|
decimals int
|
||||||
isFielder bool
|
isFielder bool // implement Fielder interface
|
||||||
onDelete string
|
onDelete string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +143,7 @@ func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mN
|
|||||||
var (
|
var (
|
||||||
tag string
|
tag string
|
||||||
tagValue string
|
tagValue string
|
||||||
initial StrTo
|
initial StrTo // store the default value
|
||||||
fieldType int
|
fieldType int
|
||||||
attrs map[string]bool
|
attrs map[string]bool
|
||||||
tags map[string]string
|
tags map[string]string
|
||||||
@ -152,6 +152,10 @@ func newFieldInfo(mi *modelInfo, field reflect.Value, sf reflect.StructField, mN
|
|||||||
|
|
||||||
fi = new(fieldInfo)
|
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
|
addrField = field
|
||||||
if field.CanAddr() && field.Kind() != reflect.Ptr {
|
if field.CanAddr() && field.Kind() != reflect.Ptr {
|
||||||
addrField = field.Addr()
|
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 {
|
if _, ok := attrs["-"]; ok {
|
||||||
return nil, errSkipField
|
return nil, errSkipField
|
||||||
@ -188,7 +192,7 @@ checkType:
|
|||||||
}
|
}
|
||||||
fieldType = f.FieldType()
|
fieldType = f.FieldType()
|
||||||
if fieldType&IsRelField > 0 {
|
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
|
goto end
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -211,7 +215,7 @@ checkType:
|
|||||||
}
|
}
|
||||||
break checkType
|
break checkType
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("error")
|
err = fmt.Errorf("rel only allow these value: fk, one, m2m")
|
||||||
goto wrongTag
|
goto wrongTag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,7 +235,7 @@ checkType:
|
|||||||
}
|
}
|
||||||
break checkType
|
break checkType
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("error")
|
err = fmt.Errorf("reverse only allow these value: one, many")
|
||||||
goto wrongTag
|
goto wrongTag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -261,6 +265,9 @@ checkType:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check the rel and reverse type
|
||||||
|
// rel should Ptr
|
||||||
|
// reverse should slice []*struct
|
||||||
switch fieldType {
|
switch fieldType {
|
||||||
case RelForeignKey, RelOneToOne, RelReverseOne:
|
case RelForeignKey, RelOneToOne, RelReverseOne:
|
||||||
if field.Kind() != reflect.Ptr {
|
if field.Kind() != reflect.Ptr {
|
||||||
@ -399,14 +406,12 @@ checkType:
|
|||||||
|
|
||||||
if fi.auto || fi.pk {
|
if fi.auto || fi.pk {
|
||||||
if fi.auto {
|
if fi.auto {
|
||||||
|
|
||||||
switch addrField.Elem().Kind() {
|
switch addrField.Elem().Kind() {
|
||||||
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint32, reflect.Uint64:
|
||||||
default:
|
default:
|
||||||
err = fmt.Errorf("auto primary key only support int, int32, int64, uint, uint32, uint64 but found `%s`", addrField.Elem().Kind())
|
err = fmt.Errorf("auto primary key only support int, int32, int64, uint, uint32, uint64 but found `%s`", addrField.Elem().Kind())
|
||||||
goto end
|
goto end
|
||||||
}
|
}
|
||||||
|
|
||||||
fi.pk = true
|
fi.pk = true
|
||||||
}
|
}
|
||||||
fi.null = false
|
fi.null = false
|
||||||
@ -418,8 +423,8 @@ checkType:
|
|||||||
fi.index = false
|
fi.index = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// can not set default for these type
|
||||||
if fi.auto || fi.pk || fi.unique || fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField {
|
if fi.auto || fi.pk || fi.unique || fieldType == TypeTimeField || fieldType == TypeDateField || fieldType == TypeDateTimeField {
|
||||||
// can not set default
|
|
||||||
initial.Clear()
|
initial.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,31 +29,25 @@ type modelInfo struct {
|
|||||||
model interface{}
|
model interface{}
|
||||||
fields *fields
|
fields *fields
|
||||||
manual bool
|
manual bool
|
||||||
addrField reflect.Value
|
addrField reflect.Value //store the original struct value
|
||||||
uniques []string
|
uniques []string
|
||||||
isThrough bool
|
isThrough bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// new model info
|
// new model info
|
||||||
func newModelInfo(val reflect.Value) (info *modelInfo) {
|
func newModelInfo(val reflect.Value) (mi *modelInfo) {
|
||||||
|
mi = &modelInfo{}
|
||||||
info = &modelInfo{}
|
mi.fields = newFields()
|
||||||
info.fields = newFields()
|
|
||||||
|
|
||||||
ind := reflect.Indirect(val)
|
ind := reflect.Indirect(val)
|
||||||
typ := ind.Type()
|
mi.addrField = val
|
||||||
|
mi.name = ind.Type().Name()
|
||||||
info.addrField = val
|
mi.fullName = getFullName(ind.Type())
|
||||||
|
addModelFields(mi, ind, "", []int{})
|
||||||
info.name = typ.Name()
|
|
||||||
info.fullName = getFullName(typ)
|
|
||||||
|
|
||||||
addModelFields(info, ind, "", []int{})
|
|
||||||
|
|
||||||
return
|
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 (
|
var (
|
||||||
err error
|
err error
|
||||||
fi *fieldInfo
|
fi *fieldInfo
|
||||||
@ -63,43 +57,39 @@ func addModelFields(info *modelInfo, ind reflect.Value, mName string, index []in
|
|||||||
for i := 0; i < ind.NumField(); i++ {
|
for i := 0; i < ind.NumField(); i++ {
|
||||||
field := ind.Field(i)
|
field := ind.Field(i)
|
||||||
sf = ind.Type().Field(i)
|
sf = ind.Type().Field(i)
|
||||||
|
// if the field is unexported skip
|
||||||
if sf.PkgPath != "" {
|
if sf.PkgPath != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// add anonymous struct fields
|
// add anonymous struct fields
|
||||||
if sf.Anonymous {
|
if sf.Anonymous {
|
||||||
addModelFields(info, field, mName+"."+sf.Name, append(index, i))
|
addModelFields(mi, field, mName+"."+sf.Name, append(index, i))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, err = newFieldInfo(info, field, sf, mName)
|
fi, err = newFieldInfo(mi, field, sf, mName)
|
||||||
|
if err == errSkipField {
|
||||||
if err != nil {
|
err = nil
|
||||||
if err == errSkipField {
|
continue
|
||||||
err = nil
|
} else if err != nil {
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
//record current field index
|
||||||
added := info.fields.Add(fi)
|
fi.fieldIndex = append(index, i)
|
||||||
if added == false {
|
fi.mi = mi
|
||||||
|
fi.inModel = true
|
||||||
|
if mi.fields.Add(fi) == false {
|
||||||
err = fmt.Errorf("duplicate column name: %s", fi.column)
|
err = fmt.Errorf("duplicate column name: %s", fi.column)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if fi.pk {
|
if fi.pk {
|
||||||
if info.fields.pk != nil {
|
if mi.fields.pk != nil {
|
||||||
err = fmt.Errorf("one model must have one pk field only")
|
err = fmt.Errorf("one model must have one pk field only")
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
info.fields.pk = fi
|
mi.fields.pk = fi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fi.fieldIndex = append(index, i)
|
|
||||||
fi.mi = info
|
|
||||||
fi.inModel = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -110,23 +100,23 @@ func addModelFields(info *modelInfo, ind reflect.Value, mName string, index []in
|
|||||||
|
|
||||||
// combine related model info to new model info.
|
// combine related model info to new model info.
|
||||||
// prepare for relation models query.
|
// prepare for relation models query.
|
||||||
func newM2MModelInfo(m1, m2 *modelInfo) (info *modelInfo) {
|
func newM2MModelInfo(m1, m2 *modelInfo) (mi *modelInfo) {
|
||||||
info = new(modelInfo)
|
mi = new(modelInfo)
|
||||||
info.fields = newFields()
|
mi.fields = newFields()
|
||||||
info.table = m1.table + "_" + m2.table + "s"
|
mi.table = m1.table + "_" + m2.table + "s"
|
||||||
info.name = camelString(info.table)
|
mi.name = camelString(mi.table)
|
||||||
info.fullName = m1.pkg + "." + info.name
|
mi.fullName = m1.pkg + "." + mi.name
|
||||||
|
|
||||||
fa := new(fieldInfo)
|
fa := new(fieldInfo) // pk
|
||||||
f1 := new(fieldInfo)
|
f1 := new(fieldInfo) // m1 table RelForeignKey
|
||||||
f2 := new(fieldInfo)
|
f2 := new(fieldInfo) // m2 table RelForeignKey
|
||||||
fa.fieldType = TypeBigIntegerField
|
fa.fieldType = TypeBigIntegerField
|
||||||
fa.auto = true
|
fa.auto = true
|
||||||
fa.pk = true
|
fa.pk = true
|
||||||
fa.dbcol = true
|
fa.dbcol = true
|
||||||
fa.name = "Id"
|
fa.name = "Id"
|
||||||
fa.column = "id"
|
fa.column = "id"
|
||||||
fa.fullName = info.fullName + "." + fa.name
|
fa.fullName = mi.fullName + "." + fa.name
|
||||||
|
|
||||||
f1.dbcol = true
|
f1.dbcol = true
|
||||||
f2.dbcol = true
|
f2.dbcol = true
|
||||||
@ -134,8 +124,8 @@ func newM2MModelInfo(m1, m2 *modelInfo) (info *modelInfo) {
|
|||||||
f2.fieldType = RelForeignKey
|
f2.fieldType = RelForeignKey
|
||||||
f1.name = camelString(m1.table)
|
f1.name = camelString(m1.table)
|
||||||
f2.name = camelString(m2.table)
|
f2.name = camelString(m2.table)
|
||||||
f1.fullName = info.fullName + "." + f1.name
|
f1.fullName = mi.fullName + "." + f1.name
|
||||||
f2.fullName = info.fullName + "." + f2.name
|
f2.fullName = mi.fullName + "." + f2.name
|
||||||
f1.column = m1.table + "_id"
|
f1.column = m1.table + "_id"
|
||||||
f2.column = m2.table + "_id"
|
f2.column = m2.table + "_id"
|
||||||
f1.rel = true
|
f1.rel = true
|
||||||
@ -144,14 +134,14 @@ func newM2MModelInfo(m1, m2 *modelInfo) (info *modelInfo) {
|
|||||||
f2.relTable = m2.table
|
f2.relTable = m2.table
|
||||||
f1.relModelInfo = m1
|
f1.relModelInfo = m1
|
||||||
f2.relModelInfo = m2
|
f2.relModelInfo = m2
|
||||||
f1.mi = info
|
f1.mi = mi
|
||||||
f2.mi = info
|
f2.mi = mi
|
||||||
|
|
||||||
info.fields.Add(fa)
|
mi.fields.Add(fa)
|
||||||
info.fields.Add(f1)
|
mi.fields.Add(f1)
|
||||||
info.fields.Add(f2)
|
mi.fields.Add(f2)
|
||||||
info.fields.pk = fa
|
mi.fields.pk = fa
|
||||||
|
|
||||||
info.uniques = []string{f1.column, f2.column}
|
mi.uniques = []string{f1.column, f2.column}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -22,25 +22,47 @@ import (
|
|||||||
"time"
|
"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.
|
// get reflect.Type name with package path.
|
||||||
func getFullName(typ reflect.Type) string {
|
func getFullName(typ reflect.Type) string {
|
||||||
return typ.PkgPath() + "." + typ.Name()
|
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 {
|
func getTableName(val reflect.Value) string {
|
||||||
ind := reflect.Indirect(val)
|
if fun := val.MethodByName("TableName"); fun.IsValid() {
|
||||||
fun := val.MethodByName("TableName")
|
|
||||||
if fun.IsValid() {
|
|
||||||
vals := fun.Call([]reflect.Value{})
|
vals := fun.Call([]reflect.Value{})
|
||||||
if len(vals) > 0 {
|
// has return and the first val is string
|
||||||
val := vals[0]
|
if len(vals) > 0 && vals[0].Kind() == reflect.String {
|
||||||
if val.Kind() == reflect.String {
|
return vals[0].String()
|
||||||
return val.String()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return snakeString(ind.Type().Name())
|
return snakeString(reflect.Indirect(val).Type().Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
// get table engine, mysiam or innodb.
|
// get table engine, mysiam or innodb.
|
||||||
@ -48,11 +70,8 @@ func getTableEngine(val reflect.Value) string {
|
|||||||
fun := val.MethodByName("TableEngine")
|
fun := val.MethodByName("TableEngine")
|
||||||
if fun.IsValid() {
|
if fun.IsValid() {
|
||||||
vals := fun.Call([]reflect.Value{})
|
vals := fun.Call([]reflect.Value{})
|
||||||
if len(vals) > 0 {
|
if len(vals) > 0 && vals[0].Kind() == reflect.String {
|
||||||
val := vals[0]
|
return vals[0].String()
|
||||||
if val.Kind() == reflect.String {
|
|
||||||
return val.String()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
@ -63,12 +82,9 @@ func getTableIndex(val reflect.Value) [][]string {
|
|||||||
fun := val.MethodByName("TableIndex")
|
fun := val.MethodByName("TableIndex")
|
||||||
if fun.IsValid() {
|
if fun.IsValid() {
|
||||||
vals := fun.Call([]reflect.Value{})
|
vals := fun.Call([]reflect.Value{})
|
||||||
if len(vals) > 0 {
|
if len(vals) > 0 && vals[0].CanInterface() {
|
||||||
val := vals[0]
|
if d, ok := vals[0].Interface().([][]string); ok {
|
||||||
if val.CanInterface() {
|
return d
|
||||||
if d, ok := val.Interface().([][]string); ok {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -80,12 +96,9 @@ func getTableUnique(val reflect.Value) [][]string {
|
|||||||
fun := val.MethodByName("TableUnique")
|
fun := val.MethodByName("TableUnique")
|
||||||
if fun.IsValid() {
|
if fun.IsValid() {
|
||||||
vals := fun.Call([]reflect.Value{})
|
vals := fun.Call([]reflect.Value{})
|
||||||
if len(vals) > 0 {
|
if len(vals) > 0 && vals[0].CanInterface() {
|
||||||
val := vals[0]
|
if d, ok := vals[0].Interface().([][]string); ok {
|
||||||
if val.CanInterface() {
|
return d
|
||||||
if d, ok := val.Interface().([][]string); ok {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,21 +202,25 @@ func getFieldType(val reflect.Value) (ft int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parse struct tag string
|
// parse struct tag string
|
||||||
func parseStructTag(data string, attrs *map[string]bool, tags *map[string]string) {
|
func parseStructTag(data string) (attrs map[string]bool, tags map[string]string) {
|
||||||
attr := make(map[string]bool)
|
attrs = make(map[string]bool)
|
||||||
tag := make(map[string]string)
|
tags = make(map[string]string)
|
||||||
for _, v := range strings.Split(data, defaultStructTagDelim) {
|
for _, v := range strings.Split(data, defaultStructTagDelim) {
|
||||||
|
if v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
v = strings.TrimSpace(v)
|
v = strings.TrimSpace(v)
|
||||||
if t := strings.ToLower(v); supportTag[t] == 1 {
|
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 {
|
} else if i := strings.Index(v, "("); i > 0 && strings.Index(v, ")") == len(v)-1 {
|
||||||
name := t[:i]
|
name := t[:i]
|
||||||
if supportTag[name] == 2 {
|
if supportTag[name] == 2 {
|
||||||
v = v[i+1 : len(v)-1]
|
v = v[i+1 : len(v)-1]
|
||||||
tag[name] = v
|
tags[name] = v
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
DebugLog.Println("unsupport orm tag", v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*attrs = attr
|
return
|
||||||
*tags = tag
|
|
||||||
}
|
}
|
||||||
|
25
orm/orm.go
25
orm/orm.go
@ -68,7 +68,7 @@ const (
|
|||||||
// Define common vars
|
// Define common vars
|
||||||
var (
|
var (
|
||||||
Debug = false
|
Debug = false
|
||||||
DebugLog = NewLog(os.Stderr)
|
DebugLog = NewLog(os.Stdout)
|
||||||
DefaultRowsLimit = 1000
|
DefaultRowsLimit = 1000
|
||||||
DefaultRelsDepth = 2
|
DefaultRelsDepth = 2
|
||||||
DefaultTimeLoc = time.Local
|
DefaultTimeLoc = time.Local
|
||||||
@ -104,7 +104,7 @@ func (o *orm) getMiInd(md interface{}, needPtr bool) (mi *modelInfo, ind reflect
|
|||||||
panic(fmt.Errorf("<Ormer> cannot use non-ptr model struct `%s`", getFullName(typ)))
|
panic(fmt.Errorf("<Ormer> cannot use non-ptr model struct `%s`", getFullName(typ)))
|
||||||
}
|
}
|
||||||
name := getFullName(typ)
|
name := getFullName(typ)
|
||||||
if mi, ok := modelCache.getByFN(name); ok {
|
if mi, ok := modelCache.getByFullName(name); ok {
|
||||||
return mi, ind
|
return mi, ind
|
||||||
}
|
}
|
||||||
panic(fmt.Errorf("<Ormer> table: `%s` not found, maybe not RegisterModel", name))
|
panic(fmt.Errorf("<Ormer> table: `%s` not found, maybe not RegisterModel", name))
|
||||||
@ -122,7 +122,17 @@ func (o *orm) getFieldInfo(mi *modelInfo, name string) *fieldInfo {
|
|||||||
// read data to model
|
// read data to model
|
||||||
func (o *orm) Read(md interface{}, cols ...string) error {
|
func (o *orm) Read(md interface{}, cols ...string) error {
|
||||||
mi, ind := o.getMiInd(md, true)
|
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 {
|
if err != nil {
|
||||||
return err
|
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) {
|
func (o *orm) ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error) {
|
||||||
cols = append([]string{col1}, cols...)
|
cols = append([]string{col1}, cols...)
|
||||||
mi, ind := o.getMiInd(md, true)
|
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 {
|
if err == ErrNoRows {
|
||||||
// Create
|
// Create
|
||||||
id, err := o.Insert(md)
|
id, err := o.Insert(md)
|
||||||
@ -234,9 +244,10 @@ func (o *orm) Update(md interface{}, cols ...string) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// delete model in database
|
// 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)
|
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 {
|
if err != nil {
|
||||||
return num, err
|
return num, err
|
||||||
}
|
}
|
||||||
@ -427,7 +438,7 @@ func (o *orm) QueryTable(ptrStructOrTableName interface{}) (qs QuerySeter) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
name = getFullName(indirectType(reflect.TypeOf(ptrStructOrTableName)))
|
name = getFullName(indirectType(reflect.TypeOf(ptrStructOrTableName)))
|
||||||
if mi, ok := modelCache.getByFN(name); ok {
|
if mi, ok := modelCache.getByFullName(name); ok {
|
||||||
qs = newQuerySet(o, mi)
|
qs = newQuerySet(o, mi)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ func debugLogQueies(alias *alias, operaton, query string, t time.Time, err error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
flag = "FAIL"
|
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))
|
cons := make([]string, 0, len(args))
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
cons = append(cons, fmt.Sprintf("%v", arg))
|
cons = append(cons, fmt.Sprintf("%v", arg))
|
||||||
|
@ -286,7 +286,7 @@ func (o *rawSet) QueryRow(containers ...interface{}) error {
|
|||||||
|
|
||||||
structMode = true
|
structMode = true
|
||||||
fn := getFullName(typ)
|
fn := getFullName(typ)
|
||||||
if mi, ok := modelCache.getByFN(fn); ok {
|
if mi, ok := modelCache.getByFullName(fn); ok {
|
||||||
sMi = mi
|
sMi = mi
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -355,12 +355,9 @@ func (o *rawSet) QueryRow(containers ...interface{}) error {
|
|||||||
for i := 0; i < ind.NumField(); i++ {
|
for i := 0; i < ind.NumField(); i++ {
|
||||||
f := ind.Field(i)
|
f := ind.Field(i)
|
||||||
fe := ind.Type().Field(i)
|
fe := ind.Type().Field(i)
|
||||||
|
_, tags := parseStructTag(fe.Tag.Get(defaultStructTagName))
|
||||||
var attrs map[string]bool
|
|
||||||
var tags map[string]string
|
|
||||||
parseStructTag(fe.Tag.Get("orm"), &attrs, &tags)
|
|
||||||
var col string
|
var col string
|
||||||
if col = tags["column"]; len(col) == 0 {
|
if col = tags["column"]; col == "" {
|
||||||
col = snakeString(fe.Name)
|
col = snakeString(fe.Name)
|
||||||
}
|
}
|
||||||
if v, ok := columnsMp[col]; ok {
|
if v, ok := columnsMp[col]; ok {
|
||||||
@ -422,7 +419,7 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) {
|
|||||||
|
|
||||||
structMode = true
|
structMode = true
|
||||||
fn := getFullName(typ)
|
fn := getFullName(typ)
|
||||||
if mi, ok := modelCache.getByFN(fn); ok {
|
if mi, ok := modelCache.getByFullName(fn); ok {
|
||||||
sMi = mi
|
sMi = mi
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -499,12 +496,9 @@ func (o *rawSet) QueryRows(containers ...interface{}) (int64, error) {
|
|||||||
for i := 0; i < ind.NumField(); i++ {
|
for i := 0; i < ind.NumField(); i++ {
|
||||||
f := ind.Field(i)
|
f := ind.Field(i)
|
||||||
fe := ind.Type().Field(i)
|
fe := ind.Type().Field(i)
|
||||||
|
_, tags := parseStructTag(fe.Tag.Get(defaultStructTagName))
|
||||||
var attrs map[string]bool
|
|
||||||
var tags map[string]string
|
|
||||||
parseStructTag(fe.Tag.Get("orm"), &attrs, &tags)
|
|
||||||
var col string
|
var col string
|
||||||
if col = tags["column"]; len(col) == 0 {
|
if col = tags["column"]; col == "" {
|
||||||
col = snakeString(fe.Name)
|
col = snakeString(fe.Name)
|
||||||
}
|
}
|
||||||
if v, ok := columnsMp[col]; ok {
|
if v, ok := columnsMp[col]; ok {
|
||||||
|
@ -227,7 +227,7 @@ func TestModelSyntax(t *testing.T) {
|
|||||||
user := &User{}
|
user := &User{}
|
||||||
ind := reflect.ValueOf(user).Elem()
|
ind := reflect.ValueOf(user).Elem()
|
||||||
fn := getFullName(ind.Type())
|
fn := getFullName(ind.Type())
|
||||||
mi, ok := modelCache.getByFN(fn)
|
mi, ok := modelCache.getByFullName(fn)
|
||||||
throwFail(t, AssertIs(ok, true))
|
throwFail(t, AssertIs(ok, true))
|
||||||
|
|
||||||
mi, ok = modelCache.get("user")
|
mi, ok = modelCache.get("user")
|
||||||
@ -577,6 +577,10 @@ func TestCRUD(t *testing.T) {
|
|||||||
err = dORM.Read(&ub)
|
err = dORM.Read(&ub)
|
||||||
throwFail(t, err)
|
throwFail(t, err)
|
||||||
throwFail(t, AssertIs(ub.Name, "name"))
|
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) {
|
func TestInsertTestData(t *testing.T) {
|
||||||
|
@ -19,6 +19,7 @@ import "errors"
|
|||||||
// QueryBuilder is the Query builder interface
|
// QueryBuilder is the Query builder interface
|
||||||
type QueryBuilder interface {
|
type QueryBuilder interface {
|
||||||
Select(fields ...string) QueryBuilder
|
Select(fields ...string) QueryBuilder
|
||||||
|
ForUpdate() QueryBuilder
|
||||||
From(tables ...string) QueryBuilder
|
From(tables ...string) QueryBuilder
|
||||||
InnerJoin(table string) QueryBuilder
|
InnerJoin(table string) QueryBuilder
|
||||||
LeftJoin(table string) QueryBuilder
|
LeftJoin(table string) QueryBuilder
|
||||||
|
@ -34,6 +34,12 @@ func (qb *MySQLQueryBuilder) Select(fields ...string) QueryBuilder {
|
|||||||
return qb
|
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
|
// From join the tables
|
||||||
func (qb *MySQLQueryBuilder) From(tables ...string) QueryBuilder {
|
func (qb *MySQLQueryBuilder) From(tables ...string) QueryBuilder {
|
||||||
qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, CommaSpace))
|
qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, CommaSpace))
|
||||||
|
@ -31,6 +31,12 @@ func (qb *TiDBQueryBuilder) Select(fields ...string) QueryBuilder {
|
|||||||
return qb
|
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
|
// From join the tables
|
||||||
func (qb *TiDBQueryBuilder) From(tables ...string) QueryBuilder {
|
func (qb *TiDBQueryBuilder) From(tables ...string) QueryBuilder {
|
||||||
qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, CommaSpace))
|
qb.Tokens = append(qb.Tokens, "FROM", strings.Join(tables, CommaSpace))
|
||||||
|
@ -45,6 +45,9 @@ type Ormer interface {
|
|||||||
// u = &User{UserName: "astaxie", Password: "pass"}
|
// u = &User{UserName: "astaxie", Password: "pass"}
|
||||||
// err = Ormer.Read(u, "UserName")
|
// err = Ormer.Read(u, "UserName")
|
||||||
Read(md interface{}, cols ...string) error
|
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
|
// 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)
|
ReadOrCreate(md interface{}, col1 string, cols ...string) (bool, int64, error)
|
||||||
// insert model data to database
|
// insert model data to database
|
||||||
@ -71,7 +74,7 @@ type Ormer interface {
|
|||||||
// num, err = Ormer.Update(&user, "Langs", "Extra")
|
// num, err = Ormer.Update(&user, "Langs", "Extra")
|
||||||
Update(md interface{}, cols ...string) (int64, error)
|
Update(md interface{}, cols ...string) (int64, error)
|
||||||
// delete model in database
|
// delete model in database
|
||||||
Delete(md interface{}) (int64, error)
|
Delete(md interface{}, cols ...string) (int64, error)
|
||||||
// load related models to md model.
|
// load related models to md model.
|
||||||
// args are limit, offset int and order string.
|
// args are limit, offset int and order string.
|
||||||
//
|
//
|
||||||
@ -394,14 +397,14 @@ type txEnder interface {
|
|||||||
|
|
||||||
// base database struct
|
// base database struct
|
||||||
type dbBaser interface {
|
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)
|
Insert(dbQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
|
||||||
InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *alias, ...string) (int64, error)
|
InsertOrUpdate(dbQuerier, *modelInfo, reflect.Value, *alias, ...string) (int64, error)
|
||||||
InsertMulti(dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error)
|
InsertMulti(dbQuerier, *modelInfo, reflect.Value, int, *time.Location) (int64, error)
|
||||||
InsertValue(dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error)
|
InsertValue(dbQuerier, *modelInfo, bool, []string, []interface{}) (int64, error)
|
||||||
InsertStmt(stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
|
InsertStmt(stmtQuerier, *modelInfo, reflect.Value, *time.Location) (int64, error)
|
||||||
Update(dbQuerier, *modelInfo, reflect.Value, *time.Location, []string) (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)
|
ReadBatch(dbQuerier, *querySet, *modelInfo, *Condition, interface{}, *time.Location, []string) (int64, error)
|
||||||
SupportUpdateJoin() bool
|
SupportUpdateJoin() bool
|
||||||
UpdateBatch(dbQuerier, *querySet, *modelInfo, *Condition, Params, *time.Location) (int64, error)
|
UpdateBatch(dbQuerier, *querySet, *modelInfo, *Condition, Params, *time.Location) (int64, error)
|
||||||
|
@ -49,7 +49,7 @@ var (
|
|||||||
genInfoList map[string][]ControllerComments
|
genInfoList map[string][]ControllerComments
|
||||||
)
|
)
|
||||||
|
|
||||||
const coomentPrefix = "commentsRouter_"
|
const commentPrefix = "commentsRouter_"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
pkgLastupdate = make(map[string]int64)
|
pkgLastupdate = make(map[string]int64)
|
||||||
@ -58,7 +58,7 @@ func init() {
|
|||||||
func parserPkg(pkgRealpath, pkgpath string) error {
|
func parserPkg(pkgRealpath, pkgpath string) error {
|
||||||
rep := strings.NewReplacer("\\", "_", "/", "_", ".", "_")
|
rep := strings.NewReplacer("\\", "_", "/", "_", ".", "_")
|
||||||
commentFilename, _ = filepath.Rel(AppPath, pkgRealpath)
|
commentFilename, _ = filepath.Rel(AppPath, pkgRealpath)
|
||||||
commentFilename = coomentPrefix + rep.Replace(commentFilename) + ".go"
|
commentFilename = commentPrefix + rep.Replace(commentFilename) + ".go"
|
||||||
if !compareFile(pkgRealpath) {
|
if !compareFile(pkgRealpath) {
|
||||||
logs.Info(pkgRealpath + " no changed")
|
logs.Info(pkgRealpath + " no changed")
|
||||||
return nil
|
return nil
|
||||||
|
@ -119,7 +119,7 @@ func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ctx.Input.Query("signature") !=
|
if ctx.Input.Query("signature") !=
|
||||||
Signature(appsecret, ctx.Input.Method(), ctx.Request.Form, ctx.Input.URI()) {
|
Signature(appsecret, ctx.Input.Method(), ctx.Request.Form, ctx.Input.URL()) {
|
||||||
ctx.ResponseWriter.WriteHeader(403)
|
ctx.ResponseWriter.WriteHeader(403)
|
||||||
ctx.WriteString("auth failed")
|
ctx.WriteString("auth failed")
|
||||||
}
|
}
|
||||||
@ -127,7 +127,7 @@ func APISecretAuth(f AppIDToAppSecret, timeout int) beego.FilterFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Signature used to generate signature with the appsecret/method/params/RequestURI
|
// Signature used to generate signature with the appsecret/method/params/RequestURI
|
||||||
func Signature(appsecret, method string, params url.Values, RequestURI string) (result string) {
|
func Signature(appsecret, method string, params url.Values, RequestURL string) (result string) {
|
||||||
var query string
|
var query string
|
||||||
pa := make(map[string]string)
|
pa := make(map[string]string)
|
||||||
for k, v := range params {
|
for k, v := range params {
|
||||||
@ -143,7 +143,7 @@ func Signature(appsecret, method string, params url.Values, RequestURI string) (
|
|||||||
query = fmt.Sprintf("%v%v%v", query, vs.Keys[i], vs.Vals[i])
|
query = fmt.Sprintf("%v%v%v", query, vs.Keys[i], vs.Vals[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stringToSign := fmt.Sprintf("%v\n%v\n%v\n", method, query, RequestURI)
|
stringToSign := fmt.Sprintf("%v\n%v\n%v\n", method, query, RequestURL)
|
||||||
|
|
||||||
sha256 := sha256.New
|
sha256 := sha256.New
|
||||||
hash := hmac.New(sha256, []byte(appsecret))
|
hash := hmac.New(sha256, []byte(appsecret))
|
||||||
|
64
router.go
64
router.go
@ -626,7 +626,9 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
|||||||
context.Reset(rw, r)
|
context.Reset(rw, r)
|
||||||
|
|
||||||
defer p.pool.Put(context)
|
defer p.pool.Put(context)
|
||||||
defer p.recoverPanic(context)
|
if BConfig.RecoverFunc != nil {
|
||||||
|
defer BConfig.RecoverFunc(context)
|
||||||
|
}
|
||||||
|
|
||||||
context.Output.EnableGzip = BConfig.EnableGzip
|
context.Output.EnableGzip = BConfig.EnableGzip
|
||||||
|
|
||||||
@ -683,8 +685,16 @@ func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)
|
|||||||
if len(p.filters[BeforeRouter]) > 0 && p.execFilter(context, urlPath, BeforeRouter) {
|
if len(p.filters[BeforeRouter]) > 0 && p.execFilter(context, urlPath, BeforeRouter) {
|
||||||
goto Admin
|
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 no matches to url, throw a not found exception
|
||||||
if !findRouter {
|
if !findRouter {
|
||||||
exception("404", context)
|
exception("404", context)
|
||||||
@ -696,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
|
//execute middleware filters
|
||||||
if len(p.filters[BeforeExec]) > 0 && p.execFilter(context, urlPath, BeforeExec) {
|
if len(p.filters[BeforeExec]) > 0 && p.execFilter(context, urlPath, BeforeExec) {
|
||||||
goto Admin
|
goto Admin
|
||||||
}
|
}
|
||||||
|
|
||||||
if routerInfo != nil {
|
if routerInfo != nil {
|
||||||
|
if BConfig.RunMode == DEV {
|
||||||
|
//store router pattern into context
|
||||||
|
context.Input.SetData("RouterPattern", routerInfo.pattern)
|
||||||
|
}
|
||||||
if routerInfo.routerType == routerTypeRESTFul {
|
if routerInfo.routerType == routerTypeRESTFul {
|
||||||
if _, ok := routerInfo.methods[r.Method]; ok {
|
if _, ok := routerInfo.methods[r.Method]; ok {
|
||||||
isRunnable = true
|
isRunnable = true
|
||||||
@ -838,16 +849,16 @@ Admin:
|
|||||||
|
|
||||||
if findRouter {
|
if findRouter {
|
||||||
if routerInfo != nil {
|
if routerInfo != nil {
|
||||||
devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-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, resetColor, r.Method, r.URL.Path,
|
resetColor, timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path,
|
||||||
routerInfo.pattern)
|
routerInfo.pattern)
|
||||||
} else {
|
} else {
|
||||||
devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-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, resetColor, r.Method, r.URL.Path)
|
timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
devInfo = fmt.Sprintf("|%s %3d %s|%13s|%8s|%s %s %-7s %-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, resetColor, r.Method, r.URL.Path)
|
timeDur.String(), "nomatch", methodColor, r.Method, resetColor, r.URL.Path)
|
||||||
}
|
}
|
||||||
if iswin {
|
if iswin {
|
||||||
logs.W32Debug(devInfo)
|
logs.W32Debug(devInfo)
|
||||||
@ -878,37 +889,6 @@ func (p *ControllerRegister) FindRouter(context *beecontext.Context) (routerInfo
|
|||||||
return
|
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 {
|
func toURL(params map[string]string) string {
|
||||||
if len(params) == 0 {
|
if len(params) == 0 {
|
||||||
return ""
|
return ""
|
||||||
|
@ -88,10 +88,9 @@ func (fs *FileSessionStore) SessionRelease(w http.ResponseWriter) {
|
|||||||
var f *os.File
|
var f *os.File
|
||||||
if err == nil {
|
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)
|
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) {
|
} else if os.IsNotExist(err) {
|
||||||
f, err = os.Create(path.Join(filepder.savePath, string(fs.sid[0]), string(fs.sid[1]), fs.sid))
|
f, err = os.Create(path.Join(filepder.savePath, string(fs.sid[0]), string(fs.sid[1]), fs.sid))
|
||||||
SLogger.Println(err)
|
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -196,9 +196,11 @@ func lookupFile(ctx *context.Context) (bool, string, os.FileInfo, error) {
|
|||||||
if !fi.IsDir() {
|
if !fi.IsDir() {
|
||||||
return false, fp, fi, err
|
return false, fp, fi, err
|
||||||
}
|
}
|
||||||
ifp := filepath.Join(fp, "index.html")
|
if requestURL := ctx.Input.URL(); requestURL[len(requestURL)-1] == '/' {
|
||||||
if ifi, _ := os.Stat(ifp); ifi != nil && ifi.Mode().IsRegular() {
|
ifp := filepath.Join(fp, "index.html")
|
||||||
return false, ifp, ifi, err
|
if ifi, _ := os.Stat(ifp); ifi != nil && ifi.Mode().IsRegular() {
|
||||||
|
return false, ifp, ifi, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return !BConfig.WebConfig.DirectoryIndex, fp, fi, err
|
return !BConfig.WebConfig.DirectoryIndex, fp, fi, err
|
||||||
}
|
}
|
||||||
|
@ -22,149 +22,149 @@ package swagger
|
|||||||
|
|
||||||
// Swagger list the resource
|
// Swagger list the resource
|
||||||
type Swagger struct {
|
type Swagger struct {
|
||||||
SwaggerVersion string `json:"swagger,omitempty"`
|
SwaggerVersion string `json:"swagger,omitempty" yaml:"swagger,omitempty"`
|
||||||
Infos Information `json:"info"`
|
Infos Information `json:"info" yaml:"info"`
|
||||||
Host string `json:"host,omitempty"`
|
Host string `json:"host,omitempty" yaml:"host,omitempty"`
|
||||||
BasePath string `json:"basePath,omitempty"`
|
BasePath string `json:"basePath,omitempty" yaml:"basePath,omitempty"`
|
||||||
Schemes []string `json:"schemes,omitempty"`
|
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
|
||||||
Consumes []string `json:"consumes,omitempty"`
|
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
|
||||||
Produces []string `json:"produces,omitempty"`
|
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
|
||||||
Paths map[string]*Item `json:"paths"`
|
Paths map[string]*Item `json:"paths" yaml:"paths"`
|
||||||
Definitions map[string]Schema `json:"definitions,omitempty"`
|
Definitions map[string]Schema `json:"definitions,omitempty" yaml:"definitions,omitempty"`
|
||||||
SecurityDefinitions map[string]Scurity `json:"securityDefinitions,omitempty"`
|
SecurityDefinitions map[string]Security `json:"securityDefinitions,omitempty" yaml:"securityDefinitions,omitempty"`
|
||||||
Security map[string][]string `json:"security,omitempty"`
|
Security map[string][]string `json:"security,omitempty" yaml:"security,omitempty"`
|
||||||
Tags []Tag `json:"tags,omitempty"`
|
Tags []Tag `json:"tags,omitempty" yaml:"tags,omitempty"`
|
||||||
ExternalDocs *ExternalDocs `json:"externalDocs,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.
|
// Information Provides metadata about the API. The metadata can be used by the clients if needed.
|
||||||
type Information struct {
|
type Information struct {
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty" yaml:"title,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty" yaml:"version,omitempty"`
|
||||||
TermsOfService string `json:"termsOfService,omitempty"`
|
TermsOfService string `json:"termsOfService,omitempty" yaml:"termsOfService,omitempty"`
|
||||||
|
|
||||||
Contact Contact `json:"contact,omitempty"`
|
Contact Contact `json:"contact,omitempty" yaml:"contact,omitempty"`
|
||||||
License License `json:"license,omitempty"`
|
License *License `json:"license,omitempty" yaml:"license,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contact information for the exposed API.
|
// Contact information for the exposed API.
|
||||||
type Contact struct {
|
type Contact struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty" yaml:"url,omitempty"`
|
||||||
EMail string `json:"email,omitempty"`
|
EMail string `json:"email,omitempty" yaml:"email,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// License information for the exposed API.
|
// License information for the exposed API.
|
||||||
type License struct {
|
type License struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty" yaml:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Item Describes the operations available on a single path.
|
// Item Describes the operations available on a single path.
|
||||||
type Item struct {
|
type Item struct {
|
||||||
Ref string `json:"$ref,omitempty"`
|
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
|
||||||
Get *Operation `json:"get,omitempty"`
|
Get *Operation `json:"get,omitempty" yaml:"get,omitempty"`
|
||||||
Put *Operation `json:"put,omitempty"`
|
Put *Operation `json:"put,omitempty" yaml:"put,omitempty"`
|
||||||
Post *Operation `json:"post,omitempty"`
|
Post *Operation `json:"post,omitempty" yaml:"post,omitempty"`
|
||||||
Delete *Operation `json:"delete,omitempty"`
|
Delete *Operation `json:"delete,omitempty" yaml:"delete,omitempty"`
|
||||||
Options *Operation `json:"options,omitempty"`
|
Options *Operation `json:"options,omitempty" yaml:"options,omitempty"`
|
||||||
Head *Operation `json:"head,omitempty"`
|
Head *Operation `json:"head,omitempty" yaml:"head,omitempty"`
|
||||||
Patch *Operation `json:"patch,omitempty"`
|
Patch *Operation `json:"patch,omitempty" yaml:"patch,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operation Describes a single API operation on a path.
|
// Operation Describes a single API operation on a path.
|
||||||
type Operation struct {
|
type Operation struct {
|
||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags,omitempty" yaml:"tags,omitempty"`
|
||||||
Summary string `json:"summary,omitempty"`
|
Summary string `json:"summary,omitempty" yaml:"summary,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||||
OperationID string `json:"operationId,omitempty"`
|
OperationID string `json:"operationId,omitempty" yaml:"operationId,omitempty"`
|
||||||
Consumes []string `json:"consumes,omitempty"`
|
Consumes []string `json:"consumes,omitempty" yaml:"consumes,omitempty"`
|
||||||
Produces []string `json:"produces,omitempty"`
|
Produces []string `json:"produces,omitempty" yaml:"produces,omitempty"`
|
||||||
Schemes []string `json:"schemes,omitempty"`
|
Schemes []string `json:"schemes,omitempty" yaml:"schemes,omitempty"`
|
||||||
Parameters []Parameter `json:"parameters,omitempty"`
|
Parameters []Parameter `json:"parameters,omitempty" yaml:"parameters,omitempty"`
|
||||||
Responses map[string]Response `json:"responses,omitempty"`
|
Responses map[string]Response `json:"responses,omitempty" yaml:"responses,omitempty"`
|
||||||
Deprecated bool `json:"deprecated,omitempty"`
|
Deprecated bool `json:"deprecated,omitempty" yaml:"deprecated,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parameter Describes a single operation parameter.
|
// Parameter Describes a single operation parameter.
|
||||||
type Parameter struct {
|
type Parameter struct {
|
||||||
In string `json:"in,omitempty"`
|
In string `json:"in,omitempty" yaml:"in,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||||
Required bool `json:"required,omitempty"`
|
Required bool `json:"required,omitempty" yaml:"required,omitempty"`
|
||||||
Schema *Schema `json:"schema,omitempty"`
|
Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||||
Format string `json:"format,omitempty"`
|
Format string `json:"format,omitempty" yaml:"format,omitempty"`
|
||||||
Items *ParameterItems `json:"items,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".
|
// 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
|
// http://swagger.io/specification/#itemsObject
|
||||||
type ParameterItems struct {
|
type ParameterItems struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||||
Format string `json:"format,omitempty"`
|
Format string `json:"format,omitempty" yaml:"format,omitempty"`
|
||||||
Items []*ParameterItems `json:"items,omitempty"` //Required if type is "array". Describes the type of items in the array.
|
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"`
|
CollectionFormat string `json:"collectionFormat,omitempty" yaml:"collectionFormat,omitempty"`
|
||||||
Default string `json:"default,omitempty"`
|
Default string `json:"default,omitempty" yaml:"default,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Schema Object allows the definition of input and output data types.
|
// Schema Object allows the definition of input and output data types.
|
||||||
type Schema struct {
|
type Schema struct {
|
||||||
Ref string `json:"$ref,omitempty"`
|
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty" yaml:"title,omitempty"`
|
||||||
Format string `json:"format,omitempty"`
|
Format string `json:"format,omitempty" yaml:"format,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||||
Required []string `json:"required,omitempty"`
|
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||||
Items *Schema `json:"items,omitempty"`
|
Items *Schema `json:"items,omitempty" yaml:"items,omitempty"`
|
||||||
Properties map[string]Propertie `json:"properties,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
|
// Propertie are taken from the JSON Schema definition but their definitions were adjusted to the Swagger Specification
|
||||||
type Propertie struct {
|
type Propertie struct {
|
||||||
Ref string `json:"$ref,omitempty"`
|
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty" yaml:"title,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||||
Default string `json:"default,omitempty"`
|
Default string `json:"default,omitempty" yaml:"default,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty" yaml:"type,omitempty"`
|
||||||
Example string `json:"example,omitempty"`
|
Example string `json:"example,omitempty" yaml:"example,omitempty"`
|
||||||
Required []string `json:"required,omitempty"`
|
Required []string `json:"required,omitempty" yaml:"required,omitempty"`
|
||||||
Format string `json:"format,omitempty"`
|
Format string `json:"format,omitempty" yaml:"format,omitempty"`
|
||||||
ReadOnly bool `json:"readOnly,omitempty"`
|
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`
|
||||||
Properties map[string]Propertie `json:"properties,omitempty"`
|
Properties map[string]Propertie `json:"properties,omitempty" yaml:"properties,omitempty"`
|
||||||
Items *Propertie `json:"items,omitempty"`
|
Items *Propertie `json:"items,omitempty" yaml:"items,omitempty"`
|
||||||
AdditionalProperties *Propertie `json:"additionalProperties,omitempty"`
|
AdditionalProperties *Propertie `json:"additionalProperties,omitempty" yaml:"additionalProperties,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response as they are returned from executing this operation.
|
// Response as they are returned from executing this operation.
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||||
Schema *Schema `json:"schema,omitempty"`
|
Schema *Schema `json:"schema,omitempty" yaml:"schema,omitempty"`
|
||||||
Ref string `json:"$ref,omitempty"`
|
Ref string `json:"$ref,omitempty" yaml:"$ref,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scurity Allows the definition of a security scheme that can be used by the operations
|
// Security Allows the definition of a security scheme that can be used by the operations
|
||||||
type Scurity struct {
|
type Security struct {
|
||||||
Type string `json:"type,omitempty"` // Valid values are "basic", "apiKey" or "oauth2".
|
Type string `json:"type,omitempty" yaml:"type,omitempty"` // Valid values are "basic", "apiKey" or "oauth2".
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
In string `json:"in,omitempty"` // Valid values are "query" or "header".
|
In string `json:"in,omitempty" yaml:"in,omitempty"` // Valid values are "query" or "header".
|
||||||
Flow string `json:"flow,omitempty"` // Valid values are "implicit", "password", "application" or "accessCode".
|
Flow string `json:"flow,omitempty" yaml:"flow,omitempty"` // Valid values are "implicit", "password", "application" or "accessCode".
|
||||||
AuthorizationURL string `json:"authorizationUrl,omitempty"`
|
AuthorizationURL string `json:"authorizationUrl,omitempty" yaml:"authorizationUrl,omitempty"`
|
||||||
TokenURL string `json:"tokenUrl,omitempty"`
|
TokenURL string `json:"tokenUrl,omitempty" yaml:"tokenUrl,omitempty"`
|
||||||
Scopes map[string]string `json:"scopes,omitempty"` // The available scopes for the OAuth2 security scheme.
|
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
|
// Tag Allows adding meta data to a single tag that is used by the Operation Object
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||||
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty"`
|
ExternalDocs *ExternalDocs `json:"externalDocs,omitempty" yaml:"externalDocs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExternalDocs include Additional external documentation
|
// ExternalDocs include Additional external documentation
|
||||||
type ExternalDocs struct {
|
type ExternalDocs struct {
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty" yaml:"description,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty" yaml:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -280,15 +280,8 @@ func AssetsCSS(src string) template.HTML {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ParseForm will parse form values to struct via tag.
|
// ParseForm will parse form values to struct via tag.
|
||||||
func ParseForm(form url.Values, obj interface{}) error {
|
// Support for anonymous struct.
|
||||||
objT := reflect.TypeOf(obj)
|
func parseFormToStruct(form url.Values, objT reflect.Type, objV reflect.Value) error {
|
||||||
objV := reflect.ValueOf(obj)
|
|
||||||
if !isStructPtr(objT) {
|
|
||||||
return fmt.Errorf("%v must be a struct pointer", obj)
|
|
||||||
}
|
|
||||||
objT = objT.Elem()
|
|
||||||
objV = objV.Elem()
|
|
||||||
|
|
||||||
for i := 0; i < objT.NumField(); i++ {
|
for i := 0; i < objT.NumField(); i++ {
|
||||||
fieldV := objV.Field(i)
|
fieldV := objV.Field(i)
|
||||||
if !fieldV.CanSet() {
|
if !fieldV.CanSet() {
|
||||||
@ -296,6 +289,14 @@ func ParseForm(form url.Values, obj interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fieldT := objT.Field(i)
|
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"), ",")
|
tags := strings.Split(fieldT.Tag.Get("form"), ",")
|
||||||
var tag string
|
var tag string
|
||||||
if len(tags) == 0 || len(tags[0]) == 0 {
|
if len(tags) == 0 || len(tags[0]) == 0 {
|
||||||
@ -384,6 +385,19 @@ func ParseForm(form url.Values, obj interface{}) error {
|
|||||||
return nil
|
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 sliceOfInts = reflect.TypeOf([]int(nil))
|
||||||
var sliceOfStrings = reflect.TypeOf([]string(nil))
|
var sliceOfStrings = reflect.TypeOf([]string(nil))
|
||||||
|
|
||||||
|
@ -110,6 +110,17 @@ func TestHtmlunquote(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestParseForm(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 {
|
type user struct {
|
||||||
ID int `form:"-"`
|
ID int `form:"-"`
|
||||||
tag string `form:"tag"`
|
tag string `form:"tag"`
|
||||||
@ -119,19 +130,24 @@ func TestParseForm(t *testing.T) {
|
|||||||
Intro string `form:",textarea"`
|
Intro string `form:",textarea"`
|
||||||
StrBool bool `form:"strbool"`
|
StrBool bool `form:"strbool"`
|
||||||
Date time.Time `form:"date,2006-01-02"`
|
Date time.Time `form:"date,2006-01-02"`
|
||||||
|
OtherInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
u := user{}
|
u := user{}
|
||||||
form := url.Values{
|
form := url.Values{
|
||||||
"ID": []string{"1"},
|
"ID": []string{"1"},
|
||||||
"-": []string{"1"},
|
"-": []string{"1"},
|
||||||
"tag": []string{"no"},
|
"tag": []string{"no"},
|
||||||
"username": []string{"test"},
|
"username": []string{"test"},
|
||||||
"age": []string{"40"},
|
"age": []string{"40"},
|
||||||
"Email": []string{"test@gmail.com"},
|
"Email": []string{"test@gmail.com"},
|
||||||
"Intro": []string{"I am an engineer!"},
|
"Intro": []string{"I am an engineer!"},
|
||||||
"strbool": []string{"yes"},
|
"strbool": []string{"yes"},
|
||||||
"date": []string{"2014-11-12"},
|
"date": []string{"2014-11-12"},
|
||||||
|
"organization": []string{"beego"},
|
||||||
|
"title": []string{"CXO"},
|
||||||
|
"hobby": []string{"Basketball"},
|
||||||
|
"memo": []string{"nothing"},
|
||||||
}
|
}
|
||||||
if err := ParseForm(form, u); err == nil {
|
if err := ParseForm(form, u); err == nil {
|
||||||
t.Fatal("nothing will be changed")
|
t.Fatal("nothing will be changed")
|
||||||
@ -164,6 +180,18 @@ func TestParseForm(t *testing.T) {
|
|||||||
if y != 2014 || m.String() != "November" || d != 12 {
|
if y != 2014 || m.String() != "November" || d != 12 {
|
||||||
t.Errorf("Date should equal `2014-11-12`, but got `%v`", u.Date.String())
|
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) {
|
func TestRenderForm(t *testing.T) {
|
||||||
|
@ -99,9 +99,13 @@ func (m *URLMap) GetMap() map[string]interface{} {
|
|||||||
fmt.Sprintf("% -50s", k),
|
fmt.Sprintf("% -50s", k),
|
||||||
fmt.Sprintf("% -10s", kk),
|
fmt.Sprintf("% -10s", kk),
|
||||||
fmt.Sprintf("% -16d", vv.RequestNum),
|
fmt.Sprintf("% -16d", vv.RequestNum),
|
||||||
|
fmt.Sprintf("%d", vv.TotalTime),
|
||||||
fmt.Sprintf("% -16s", toS(vv.TotalTime)),
|
fmt.Sprintf("% -16s", toS(vv.TotalTime)),
|
||||||
|
fmt.Sprintf("%d", vv.MaxTime),
|
||||||
fmt.Sprintf("% -16s", toS(vv.MaxTime)),
|
fmt.Sprintf("% -16s", toS(vv.MaxTime)),
|
||||||
|
fmt.Sprintf("%d", vv.MinTime),
|
||||||
fmt.Sprintf("% -16s", toS(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))),
|
fmt.Sprintf("% -16s", toS(time.Duration(int64(vv.TotalTime)/vv.RequestNum))),
|
||||||
}
|
}
|
||||||
resultLists = append(resultLists, result)
|
resultLists = append(resultLists, result)
|
||||||
|
@ -359,6 +359,9 @@ func (m *Image) calculateSizes(width, height, ncount int) {
|
|||||||
}
|
}
|
||||||
// Calculate dot size.
|
// Calculate dot size.
|
||||||
m.dotSize = int(nh / fh)
|
m.dotSize = int(nh / fh)
|
||||||
|
if m.dotSize < 1 {
|
||||||
|
m.dotSize = 1
|
||||||
|
}
|
||||||
// Save everything, making the actual width smaller by 1 dot to account
|
// Save everything, making the actual width smaller by 1 dot to account
|
||||||
// for spacing between digits.
|
// for spacing between digits.
|
||||||
m.numWidth = int(nw) - m.dotSize
|
m.numWidth = int(nw) - m.dotSize
|
||||||
|
Reference in New Issue
Block a user