1
0
mirror of https://github.com/astaxie/beego.git synced 2025-07-11 18:21:02 +00:00

41 Commits

Author SHA1 Message Date
2d87d4feaf Merge branch 'master' into develop 2016-09-22 23:18:45 +08:00
15a45ccc7b change to 1.7.1 2016-09-22 23:18:22 +08:00
e0c59fcf0b add more comments 2016-09-22 23:17:41 +08:00
083f697c59 Merge pull request #2173 from axyu/develop
fix a spelling mistake
2016-09-21 19:45:46 +08:00
a5a6546b91 fix a spelling mistake 2016-09-21 19:33:12 +08:00
9cafbf6a21 Merge pull request #2170 from ysqi/develop
Support load app config before test Beego
2016-09-19 20:55:20 +08:00
faba0d7273 Support load app config before test Beego 2016-09-19 18:38:56 +08:00
c3116d3601 Merge branch 'astaxie/develop' into develop 2016-09-19 18:16:47 +08:00
868e14b8ba fix #2017 2016-09-15 20:04:45 +08:00
421bf97b84 Support custome recover func fix #2004 2016-09-15 12:16:24 +08:00
c16507607c fix #2161 2016-09-15 11:11:34 +08:00
2b7dd85b92 access log add client request ip 2016-09-15 10:58:46 +08:00
a58115fed2 orm log delete repetition time 2016-09-15 10:54:21 +08:00
f9b5b0f551 Merge pull request #2162 from sergeylanzman/develop
improve Swagger
2016-09-15 08:56:27 +08:00
e53c147129 improve Swagger
1. Add yaml
2. Fix typo scurity => security
3. Make license optional
2016-09-15 00:15:02 +03:00
da0e6e790d Update swagger.go 2016-09-14 23:36:38 +03:00
58ffc6f5f8 fix #1877 2016-09-13 22:43:40 +08:00
d5fb74aa94 Merge pull request #2158 from simpleelegant/develop
Add support "SELECT FOR UPDATE" to orm. Resolve issue #2157
2016-09-12 21:48:14 +08:00
11247d41a7 Add support "SELECT FOR UPDATE" to orm. Resolve issue #2157 2016-09-12 20:07:30 +00:00
5b21c7cd71 fix #1802 2016-09-12 21:13:21 +08:00
dd0f05b1f1 fix the method color 2016-09-11 22:00:14 +08:00
a32241e7d3 fix #2142 2016-09-11 21:27:27 +08:00
0ef357ebd7 session:output error 2016-09-11 21:02:11 +08:00
32dd976620 Merge pull request #2146 from philchia/develop
Fix the typo
2016-09-08 17:42:10 +08:00
fcd8a2024e Add jianliao and slack log adapter 2016-09-08 17:21:11 +08:00
30661472c8 Fix the typo 2016-09-07 13:33:11 +08:00
6ced26660e Merge branch 'develop' of https://github.com/astaxie/beego into develop 2016-09-06 23:05:54 +08:00
7d6c45d4c9 add RegisterModelWithSuffix #2140 2016-09-06 23:05:41 +08:00
98740fddac Merge pull request #2138 from kbynd/patch-1
RequestURI captures the signature field as well.
2016-09-04 16:52:59 +08:00
6d3042f5e5 RequestURI captures the signature field as well.
This in turn results is failure of signature based validation. So what is need is only "/api/resource/action". which is given by ctx.Input.URL()
2016-09-04 11:36:17 +05:30
8b9d6eee1a Merge pull request #2132 from Zaaksam/master
beego.ParseForm() improvement
2016-09-02 16:23:14 +08:00
11ef5929aa update TestParseForm 2016-09-02 16:08:04 +08:00
c697b98006 enhancement 2016-09-01 23:28:34 +08:00
7c2e563879 beego.ParseForm() improvement 2016-09-01 15:04:57 +08:00
56aa224a6e simplfy the code 2016-08-31 22:47:31 +08:00
8c37a07adb optimize the ORM 2016-08-31 00:07:19 +08:00
161c061376 fix cache/memcache test 2016-08-30 23:24:30 +08:00
aa091cea42 improvement the error if use &&Struct 2016-08-30 22:02:11 +08:00
7df74c0a30 fix #1521 2016-08-30 21:24:56 +08:00
1e1e900278 fix #2126 2016-08-30 21:00:27 +08:00
fb2343567b fix #2125 2016-08-30 20:40:46 +08:00
36 changed files with 682 additions and 402 deletions

View File

@ -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>

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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")
} }

View File

@ -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

View File

@ -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.

View File

@ -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
View 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)
}

View File

@ -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
View 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)
}

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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()

View File

@ -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()
} }

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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)
} }
} }

View File

@ -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))

View File

@ -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 {

View File

@ -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) {

View File

@ -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

View File

@ -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))

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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))

View File

@ -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 ""

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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"`
} }

View File

@ -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))

View File

@ -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) {

View File

@ -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)

View File

@ -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