1
0
mirror of https://github.com/astaxie/beego.git synced 2024-06-28 06:14:13 +00:00

Merge branch 'develop' into fix_response_http_code

This commit is contained in:
astaxie 2018-07-20 15:18:30 +08:00 committed by GitHub
commit af73a2d515
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 710 additions and 222 deletions

View File

@ -1,9 +1,8 @@
language: go language: go
go: go:
- 1.7.5 - "1.9.2"
- 1.8.5 - "1.10.2"
- 1.9.2
services: services:
- redis-server - redis-server
- mysql - mysql
@ -22,7 +21,7 @@ install:
- go get github.com/go-sql-driver/mysql - go get github.com/go-sql-driver/mysql
- go get github.com/mattn/go-sqlite3 - go get github.com/mattn/go-sqlite3
- go get github.com/bradfitz/gomemcache/memcache - go get github.com/bradfitz/gomemcache/memcache
- go get github.com/garyburd/redigo/redis - go get github.com/gomodule/redigo/redis
- go get github.com/beego/x2j - go get github.com/beego/x2j
- go get github.com/couchbase/go-couchbase - go get github.com/couchbase/go-couchbase
- go get github.com/beego/goyaml2 - go get github.com/beego/goyaml2
@ -38,6 +37,7 @@ install:
- go get -u github.com/mdempsky/unconvert - go get -u github.com/mdempsky/unconvert
- go get -u github.com/gordonklaus/ineffassign - go get -u github.com/gordonklaus/ineffassign
- go get -u github.com/golang/lint/golint - go get -u github.com/golang/lint/golint
- go get -u github.com/go-redis/redis
before_script: before_script:
- psql --version - psql --version
- sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi" - sh -c "if [ '$ORM_DRIVER' = 'postgres' ]; then psql -c 'create database orm_test;' -U postgres; fi"

6
app.go
View File

@ -24,8 +24,8 @@ import (
"net/http/fcgi" "net/http/fcgi"
"os" "os"
"path" "path"
"time"
"strings" "strings"
"time"
"github.com/astaxie/beego/grace" "github.com/astaxie/beego/grace"
"github.com/astaxie/beego/logs" "github.com/astaxie/beego/logs"
@ -101,7 +101,7 @@ func (app *App) Run(mws ...MiddleWare) {
} }
app.Server.Handler = app.Handlers app.Server.Handler = app.Handlers
for i:=len(mws)-1;i>=0;i-- { for i := len(mws) - 1; i >= 0; i-- {
if mws[i] == nil { if mws[i] == nil {
continue continue
} }
@ -167,7 +167,7 @@ func (app *App) Run(mws ...MiddleWare) {
if BConfig.Listen.HTTPSPort != 0 { if BConfig.Listen.HTTPSPort != 0 {
app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort) app.Server.Addr = fmt.Sprintf("%s:%d", BConfig.Listen.HTTPSAddr, BConfig.Listen.HTTPSPort)
} else if BConfig.Listen.EnableHTTP { } else if BConfig.Listen.EnableHTTP {
BeeLogger.Info("Start https server error, confict with http.Please reset https port") BeeLogger.Info("Start https server error, conflict with http.Please reset https port")
return return
} }
logs.Info("https server Running on https://%s", app.Server.Addr) logs.Info("https server Running on https://%s", app.Server.Addr)

2
cache/README.md vendored
View File

@ -52,7 +52,7 @@ Configure like this:
## Redis adapter ## Redis adapter
Redis adapter use the [redigo](http://github.com/garyburd/redigo) client. Redis adapter use the [redigo](http://github.com/gomodule/redigo) client.
Configure like this: Configure like this:

13
cache/redis/redis.go vendored
View File

@ -14,9 +14,9 @@
// Package redis for cache provider // Package redis for cache provider
// //
// depend on github.com/garyburd/redigo/redis // depend on github.com/gomodule/redigo/redis
// //
// go install github.com/garyburd/redigo/redis // go install github.com/gomodule/redigo/redis
// //
// Usage: // Usage:
// import( // import(
@ -36,7 +36,7 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/garyburd/redigo/redis" "github.com/gomodule/redigo/redis"
"github.com/astaxie/beego/cache" "github.com/astaxie/beego/cache"
) )
@ -53,6 +53,7 @@ type Cache struct {
dbNum int dbNum int
key string key string
password string password string
maxIdle int
} }
// NewRedisCache create new redis cache with default collection name. // NewRedisCache create new redis cache with default collection name.
@ -169,10 +170,14 @@ func (rc *Cache) StartAndGC(config string) error {
if _, ok := cf["password"]; !ok { if _, ok := cf["password"]; !ok {
cf["password"] = "" cf["password"] = ""
} }
if _, ok := cf["maxIdle"]; !ok {
cf["maxIdle"] = "3"
}
rc.key = cf["key"] rc.key = cf["key"]
rc.conninfo = cf["conn"] rc.conninfo = cf["conn"]
rc.dbNum, _ = strconv.Atoi(cf["dbNum"]) rc.dbNum, _ = strconv.Atoi(cf["dbNum"])
rc.password = cf["password"] rc.password = cf["password"]
rc.maxIdle, _ = strconv.Atoi(cf["maxIdle"])
rc.connectInit() rc.connectInit()
@ -206,7 +211,7 @@ func (rc *Cache) connectInit() {
} }
// initialize a new pool // initialize a new pool
rc.p = &redis.Pool{ rc.p = &redis.Pool{
MaxIdle: 3, MaxIdle: rc.maxIdle,
IdleTimeout: 180 * time.Second, IdleTimeout: 180 * time.Second,
Dial: dialFunc, Dial: dialFunc,
} }

View File

@ -19,7 +19,7 @@ import (
"time" "time"
"github.com/astaxie/beego/cache" "github.com/astaxie/beego/cache"
"github.com/garyburd/redigo/redis" "github.com/gomodule/redigo/redis"
) )
func TestRedisCache(t *testing.T) { func TestRedisCache(t *testing.T) {

View File

@ -98,9 +98,9 @@ type SessionConfig struct {
SessionAutoSetCookie bool SessionAutoSetCookie bool
SessionDomain string SessionDomain string
SessionDisableHTTPOnly bool // used to allow for cross domain cookies/javascript cookies. SessionDisableHTTPOnly bool // used to allow for cross domain cookies/javascript cookies.
SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers SessionEnableSidInHTTPHeader bool // enable store/get the sessionId into/from http headers
SessionNameInHTTPHeader string SessionNameInHTTPHeader string
SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params SessionEnableSidInURLQuery bool // enable get the sessionId from Url Query params
} }
// LogConfig holds Log related config // LogConfig holds Log related config
@ -138,8 +138,8 @@ func init() {
panic(err) panic(err)
} }
var filename = "app.conf" var filename = "app.conf"
if os.Getenv("BEEGO_MODE") != "" { if os.Getenv("BEEGO_RUNMODE") != "" {
filename = os.Getenv("BEEGO_MODE") + ".app.conf" filename = os.Getenv("BEEGO_RUNMODE") + ".app.conf"
} }
appConfigPath = filepath.Join(workPath, "conf", filename) appConfigPath = filepath.Join(workPath, "conf", filename)
if !utils.FileExists(appConfigPath) { if !utils.FileExists(appConfigPath) {
@ -193,7 +193,7 @@ func recoverPanic(ctx *context.Context) {
func newBConfig() *Config { func newBConfig() *Config {
return &Config{ return &Config{
AppName: "beego", AppName: "beego",
RunMode: DEV, RunMode: PROD,
RouterCaseSensitive: true, RouterCaseSensitive: true,
ServerName: "beegoServer:" + VERSION, ServerName: "beegoServer:" + VERSION,
RecoverPanic: true, RecoverPanic: true,
@ -245,9 +245,9 @@ func newBConfig() *Config {
SessionCookieLifeTime: 0, //set cookie default is the browser life SessionCookieLifeTime: 0, //set cookie default is the browser life
SessionAutoSetCookie: true, SessionAutoSetCookie: true,
SessionDomain: "", SessionDomain: "",
SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers SessionEnableSidInHTTPHeader: false, // enable store/get the sessionId into/from http headers
SessionNameInHTTPHeader: "Beegosessionid", SessionNameInHTTPHeader: "Beegosessionid",
SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params SessionEnableSidInURLQuery: false, // enable get the sessionId from Url Query params
}, },
}, },
Log: LogConfig{ Log: LogConfig{

View File

@ -150,12 +150,12 @@ func ExpandValueEnv(value string) (realValue string) {
} }
key := "" key := ""
defalutV := "" defaultV := ""
// value start with "${" // value start with "${"
for i := 2; i < vLen; i++ { for i := 2; i < vLen; i++ {
if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') { if value[i] == '|' && (i+1 < vLen && value[i+1] == '|') {
key = value[2:i] key = value[2:i]
defalutV = value[i+2 : vLen-1] // other string is default value. defaultV = value[i+2 : vLen-1] // other string is default value.
break break
} else if value[i] == '}' { } else if value[i] == '}' {
key = value[2:i] key = value[2:i]
@ -165,7 +165,7 @@ func ExpandValueEnv(value string) (realValue string) {
realValue = os.Getenv(key) realValue = os.Getenv(key)
if realValue == "" { if realValue == "" {
realValue = defalutV realValue = defaultV
} }
return return

View File

@ -126,7 +126,7 @@ func (c *fakeConfigContainer) SaveConfigFile(filename string) error {
var _ Configer = new(fakeConfigContainer) var _ Configer = new(fakeConfigContainer)
// NewFakeConfig return a fake Congiger // NewFakeConfig return a fake Configer
func NewFakeConfig() Configer { func NewFakeConfig() Configer {
return &fakeConfigContainer{ return &fakeConfigContainer{
data: make(map[string]string), data: make(map[string]string),

View File

@ -215,7 +215,7 @@ func (c *IniConfigContainer) Bool(key string) (bool, error) {
} }
// DefaultBool returns the boolean value for a given key. // DefaultBool returns the boolean value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool { func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
v, err := c.Bool(key) v, err := c.Bool(key)
if err != nil { if err != nil {
@ -230,7 +230,7 @@ func (c *IniConfigContainer) Int(key string) (int, error) {
} }
// DefaultInt returns the integer value for a given key. // DefaultInt returns the integer value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int { func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
v, err := c.Int(key) v, err := c.Int(key)
if err != nil { if err != nil {
@ -245,7 +245,7 @@ func (c *IniConfigContainer) Int64(key string) (int64, error) {
} }
// DefaultInt64 returns the int64 value for a given key. // DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 { func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
v, err := c.Int64(key) v, err := c.Int64(key)
if err != nil { if err != nil {
@ -260,7 +260,7 @@ func (c *IniConfigContainer) Float(key string) (float64, error) {
} }
// DefaultFloat returns the float64 value for a given key. // DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 { func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
v, err := c.Float(key) v, err := c.Float(key)
if err != nil { if err != nil {
@ -275,7 +275,7 @@ func (c *IniConfigContainer) String(key string) string {
} }
// DefaultString returns the string value for a given key. // DefaultString returns the string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *IniConfigContainer) DefaultString(key string, defaultval string) string { func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
v := c.String(key) v := c.String(key)
if v == "" { if v == "" {
@ -295,7 +295,7 @@ func (c *IniConfigContainer) Strings(key string) []string {
} }
// DefaultStrings returns the []string value for a given key. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key) v := c.Strings(key)
if v == nil { if v == nil {
@ -314,7 +314,7 @@ func (c *IniConfigContainer) GetSection(section string) (map[string]string, erro
// SaveConfigFile save the config into file. // SaveConfigFile save the config into file.
// //
// BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Funcation. // BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Function.
func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) { func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
// Write configuration file by filename. // Write configuration file by filename.
f, err := os.Create(filename) f, err := os.Create(filename)

View File

@ -101,7 +101,7 @@ func (c *JSONConfigContainer) Int(key string) (int, error) {
} }
// DefaultInt returns the integer value for a given key. // DefaultInt returns the integer value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int { func (c *JSONConfigContainer) DefaultInt(key string, defaultval int) int {
if v, err := c.Int(key); err == nil { if v, err := c.Int(key); err == nil {
return v return v
@ -122,7 +122,7 @@ func (c *JSONConfigContainer) Int64(key string) (int64, error) {
} }
// DefaultInt64 returns the int64 value for a given key. // DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 { func (c *JSONConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
if v, err := c.Int64(key); err == nil { if v, err := c.Int64(key); err == nil {
return v return v
@ -143,7 +143,7 @@ func (c *JSONConfigContainer) Float(key string) (float64, error) {
} }
// DefaultFloat returns the float64 value for a given key. // DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 { func (c *JSONConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
if v, err := c.Float(key); err == nil { if v, err := c.Float(key); err == nil {
return v return v
@ -163,7 +163,7 @@ func (c *JSONConfigContainer) String(key string) string {
} }
// DefaultString returns the string value for a given key. // DefaultString returns the string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string { func (c *JSONConfigContainer) DefaultString(key string, defaultval string) string {
// TODO FIXME should not use "" to replace non existence // TODO FIXME should not use "" to replace non existence
if v := c.String(key); v != "" { if v := c.String(key); v != "" {
@ -182,7 +182,7 @@ func (c *JSONConfigContainer) Strings(key string) []string {
} }
// DefaultStrings returns the []string value for a given key. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *JSONConfigContainer) DefaultStrings(key string, defaultval []string) []string {
if v := c.Strings(key); v != nil { if v := c.Strings(key); v != nil {
return v return v

View File

@ -216,7 +216,7 @@ func TestJson(t *testing.T) {
t.Error("unknown keys should return an error when expecting a Bool") t.Error("unknown keys should return an error when expecting a Bool")
} }
if !jsonconf.DefaultBool("unknow", true) { if !jsonconf.DefaultBool("unknown", true) {
t.Error("unknown keys with default value wrong") t.Error("unknown keys with default value wrong")
} }
} }

View File

@ -102,7 +102,7 @@ func (c *ConfigContainer) Int(key string) (int, error) {
} }
// DefaultInt returns the integer value for a given key. // DefaultInt returns the integer value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int { func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
v, err := c.Int(key) v, err := c.Int(key)
if err != nil { if err != nil {
@ -117,7 +117,7 @@ func (c *ConfigContainer) Int64(key string) (int64, error) {
} }
// DefaultInt64 returns the int64 value for a given key. // DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 { func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
v, err := c.Int64(key) v, err := c.Int64(key)
if err != nil { if err != nil {
@ -133,7 +133,7 @@ func (c *ConfigContainer) Float(key string) (float64, error) {
} }
// DefaultFloat returns the float64 value for a given key. // DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 { func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
v, err := c.Float(key) v, err := c.Float(key)
if err != nil { if err != nil {
@ -151,7 +151,7 @@ func (c *ConfigContainer) String(key string) string {
} }
// DefaultString returns the string value for a given key. // DefaultString returns the string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultString(key string, defaultval string) string { func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
v := c.String(key) v := c.String(key)
if v == "" { if v == "" {
@ -170,7 +170,7 @@ func (c *ConfigContainer) Strings(key string) []string {
} }
// DefaultStrings returns the []string value for a given key. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key) v := c.Strings(key)
if v == nil { if v == nil {

View File

@ -154,7 +154,7 @@ func (c *ConfigContainer) Int(key string) (int, error) {
} }
// DefaultInt returns the integer value for a given key. // DefaultInt returns the integer value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultInt(key string, defaultval int) int { func (c *ConfigContainer) DefaultInt(key string, defaultval int) int {
v, err := c.Int(key) v, err := c.Int(key)
if err != nil { if err != nil {
@ -174,7 +174,7 @@ func (c *ConfigContainer) Int64(key string) (int64, error) {
} }
// DefaultInt64 returns the int64 value for a given key. // DefaultInt64 returns the int64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 { func (c *ConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
v, err := c.Int64(key) v, err := c.Int64(key)
if err != nil { if err != nil {
@ -198,7 +198,7 @@ func (c *ConfigContainer) Float(key string) (float64, error) {
} }
// DefaultFloat returns the float64 value for a given key. // DefaultFloat returns the float64 value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 { func (c *ConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
v, err := c.Float(key) v, err := c.Float(key)
if err != nil { if err != nil {
@ -218,7 +218,7 @@ func (c *ConfigContainer) String(key string) string {
} }
// DefaultString returns the string value for a given key. // DefaultString returns the string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultString(key string, defaultval string) string { func (c *ConfigContainer) DefaultString(key string, defaultval string) string {
v := c.String(key) v := c.String(key)
if v == "" { if v == "" {
@ -237,7 +237,7 @@ func (c *ConfigContainer) Strings(key string) []string {
} }
// DefaultStrings returns the []string value for a given key. // DefaultStrings returns the []string value for a given key.
// if err != nil return defaltval // if err != nil return defaultval
func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string { func (c *ConfigContainer) DefaultStrings(key string, defaultval []string) []string {
v := c.Strings(key) v := c.Strings(key)
if v == nil { if v == nil {
@ -285,9 +285,22 @@ func (c *ConfigContainer) getData(key string) (interface{}, error) {
if len(key) == 0 { if len(key) == 0 {
return nil, errors.New("key is empty") return nil, errors.New("key is empty")
} }
keys := strings.Split(key, ".")
tmpData := c.data
for _, k := range keys {
if v, ok := tmpData[k]; ok {
switch v.(type) {
case map[string]interface{}:
{
tmpData = v.(map[string]interface{})
}
default:
{
return v, nil
}
if v, ok := c.data[key]; ok { }
return v, nil }
} }
return nil, fmt.Errorf("not exist key %q", key) return nil, fmt.Errorf("not exist key %q", key)
} }

View File

@ -182,8 +182,8 @@ func errorRenderer(err error) Renderer {
} }
// JSON writes json to response body. // JSON writes json to response body.
// if coding is true, it converts utf-8 to \u0000 type. // if encoding is true, it converts utf-8 to \u0000 type.
func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) error { func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, encoding bool) error {
output.Header("Content-Type", "application/json; charset=utf-8") output.Header("Content-Type", "application/json; charset=utf-8")
var content []byte var content []byte
var err error var err error
@ -196,7 +196,7 @@ func (output *BeegoOutput) JSON(data interface{}, hasIndent bool, coding bool) e
http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError) http.Error(output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
return err return err
} }
if coding { if encoding {
content = []byte(stringsToJSON(string(content))) content = []byte(stringsToJSON(string(content)))
} }
return output.Body(content) return output.Body(content)
@ -260,7 +260,7 @@ func (output *BeegoOutput) Download(file string, filename ...string) {
} else { } else {
fName = filepath.Base(file) fName = filepath.Base(file)
} }
output.Header("Content-Disposition", "attachment; filename="+url.QueryEscape(fName)) output.Header("Content-Disposition", "attachment; filename="+url.PathEscape(fName))
output.Header("Content-Description", "File Transfer") output.Header("Content-Description", "File Transfer")
output.Header("Content-Type", "application/octet-stream") output.Header("Content-Type", "application/octet-stream")
output.Header("Content-Transfer-Encoding", "binary") output.Header("Content-Transfer-Encoding", "binary")
@ -325,13 +325,13 @@ func (output *BeegoOutput) IsForbidden() bool {
} }
// IsNotFound returns boolean of this request is not found. // IsNotFound returns boolean of this request is not found.
// HTTP 404 means forbidden. // HTTP 404 means not found.
func (output *BeegoOutput) IsNotFound() bool { func (output *BeegoOutput) IsNotFound() bool {
return output.Status == 404 return output.Status == 404
} }
// IsClientError returns boolean of this request client sends error data. // IsClientError returns boolean of this request client sends error data.
// HTTP 4xx means forbidden. // HTTP 4xx means client error.
func (output *BeegoOutput) IsClientError() bool { func (output *BeegoOutput) IsClientError() bool {
return output.Status >= 400 && output.Status < 500 return output.Status >= 400 && output.Status < 500
} }
@ -350,6 +350,11 @@ func stringsToJSON(str string) string {
jsons.WriteRune(r) jsons.WriteRune(r)
} else { } else {
jsons.WriteString("\\u") jsons.WriteString("\\u")
if rint < 0x100 {
jsons.WriteString("00")
} else if rint < 0x1000 {
jsons.WriteString("0")
}
jsons.WriteString(strconv.FormatInt(int64(rint), 16)) jsons.WriteString(strconv.FormatInt(int64(rint), 16))
} }
} }

View File

@ -275,6 +275,19 @@ func (c *Controller) Redirect(url string, code int) {
c.Ctx.Redirect(code, url) c.Ctx.Redirect(code, url)
} }
// Set the data depending on the accepted
func (c *Controller) SetData(data interface{}) {
accept := c.Ctx.Input.Header("Accept")
switch accept {
case applicationJSON:
c.Data["json"] = data
case applicationXML, textXML:
c.Data["xml"] = data
default:
c.Data["json"] = data
}
}
// Abort stops controller handler and show the error data if code is defined in ErrorMap or code string. // Abort stops controller handler and show the error data if code is defined in ErrorMap or code string.
func (c *Controller) Abort(code string) { func (c *Controller) Abort(code string) {
status, err := strconv.Atoi(code) status, err := strconv.Atoi(code)

View File

@ -317,6 +317,7 @@ func (b *BeegoHTTPRequest) Body(data interface{}) *BeegoHTTPRequest {
} }
return b return b
} }
// XMLBody adds request raw body encoding by XML. // XMLBody adds request raw body encoding by XML.
func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) { func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
if b.req.Body == nil && obj != nil { if b.req.Body == nil && obj != nil {
@ -330,6 +331,7 @@ func (b *BeegoHTTPRequest) XMLBody(obj interface{}) (*BeegoHTTPRequest, error) {
} }
return b, nil return b, nil
} }
// JSONBody adds request raw body encoding by JSON. // JSONBody adds request raw body encoding by JSON.
func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) { func (b *BeegoHTTPRequest) JSONBody(obj interface{}) (*BeegoHTTPRequest, error) {
if b.req.Body == nil && obj != nil { if b.req.Body == nil && obj != nil {
@ -444,7 +446,7 @@ func (b *BeegoHTTPRequest) DoRequest() (resp *http.Response, err error) {
TLSClientConfig: b.setting.TLSClientConfig, TLSClientConfig: b.setting.TLSClientConfig,
Proxy: b.setting.Proxy, Proxy: b.setting.Proxy,
Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout), Dial: TimeoutDialer(b.setting.ConnectTimeout, b.setting.ReadWriteTimeout),
MaxIdleConnsPerHost: -1, MaxIdleConnsPerHost: 100,
} }
} else { } else {
// if b.transport is *http.Transport then set the settings. // if b.transport is *http.Transport then set the settings.

View File

@ -16,48 +16,57 @@ As of now this logs support console, file,smtp and conn.
First you must import it First you must import it
import ( ```golang
"github.com/astaxie/beego/logs" import (
) "github.com/astaxie/beego/logs"
)
```
Then init a Log (example with console adapter) Then init a Log (example with console adapter)
log := NewLogger(10000) ```golang
log.SetLogger("console", "") log := logs.NewLogger(10000)
log.SetLogger("console", "")
```
> the first params stand for how many channel > the first params stand for how many channel
Use it like this: Use it like this:
log.Trace("trace")
log.Info("info")
log.Warn("warning")
log.Debug("debug")
log.Critical("critical")
```golang
log.Trace("trace")
log.Info("info")
log.Warn("warning")
log.Debug("debug")
log.Critical("critical")
```
## File adapter ## File adapter
Configure file adapter like this: Configure file adapter like this:
log := NewLogger(10000) ```golang
log.SetLogger("file", `{"filename":"test.log"}`) log := NewLogger(10000)
log.SetLogger("file", `{"filename":"test.log"}`)
```
## Conn adapter ## Conn adapter
Configure like this: Configure like this:
log := NewLogger(1000) ```golang
log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`) log := NewLogger(1000)
log.Info("info") log.SetLogger("conn", `{"net":"tcp","addr":":7020"}`)
log.Info("info")
```
## Smtp adapter ## Smtp adapter
Configure like this: Configure like this:
log := NewLogger(10000) ```golang
log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`) log := NewLogger(10000)
log.Critical("sendmail critical") log.SetLogger("smtp", `{"username":"beegotest@gmail.com","password":"xxxxxxxx","host":"smtp.gmail.com:587","sendTos":["xiemengjun@gmail.com"]}`)
time.Sleep(time.Second * 30) log.Critical("sendmail critical")
time.Sleep(time.Second * 30)
```

View File

@ -17,12 +17,12 @@ package logs
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"time"
"fmt" "fmt"
"time"
) )
const ( const (
apacheFormatPattern = "%s - - [%s] \"%s %d %d\" %f %s %s\n" apacheFormatPattern = "%s - - [%s] \"%s %d %d\" %f %s %s"
apacheFormat = "APACHE_FORMAT" apacheFormat = "APACHE_FORMAT"
jsonFormat = "JSON_FORMAT" jsonFormat = "JSON_FORMAT"
) )
@ -53,10 +53,9 @@ func (r *AccessLogRecord) json() ([]byte, error) {
} }
func disableEscapeHTML(i interface{}) { func disableEscapeHTML(i interface{}) {
e, ok := i.(interface { if e, ok := i.(interface {
SetEscapeHTML(bool) SetEscapeHTML(bool)
}); }); ok {
if ok {
e.SetEscapeHTML(false) e.SetEscapeHTML(false)
} }
} }

View File

@ -40,6 +40,9 @@ type fileLogWriter struct {
MaxLines int `json:"maxlines"` MaxLines int `json:"maxlines"`
maxLinesCurLines int maxLinesCurLines int
MaxFiles int `json:"maxfiles"`
MaxFilesCurFiles int
// Rotate at size // Rotate at size
MaxSize int `json:"maxsize"` MaxSize int `json:"maxsize"`
maxSizeCurSize int maxSizeCurSize int
@ -70,6 +73,9 @@ func newFileWriter() Logger {
RotatePerm: "0440", RotatePerm: "0440",
Level: LevelTrace, Level: LevelTrace,
Perm: "0660", Perm: "0660",
MaxLines: 10000000,
MaxFiles: 999,
MaxSize: 1 << 28,
} }
return w return w
} }
@ -238,7 +244,7 @@ func (w *fileLogWriter) lines() (int, error) {
func (w *fileLogWriter) doRotate(logTime time.Time) error { func (w *fileLogWriter) doRotate(logTime time.Time) error {
// file exists // file exists
// Find the next available number // Find the next available number
num := 1 num := w.MaxFilesCurFiles + 1
fName := "" fName := ""
rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64) rotatePerm, err := strconv.ParseInt(w.RotatePerm, 8, 64)
if err != nil { if err != nil {
@ -251,18 +257,16 @@ func (w *fileLogWriter) doRotate(logTime time.Time) error {
goto RESTART_LOGGER goto RESTART_LOGGER
} }
// only when one of them be setted, then the file would be splited
if w.MaxLines > 0 || w.MaxSize > 0 { if w.MaxLines > 0 || w.MaxSize > 0 {
for ; err == nil && num <= 999; num++ { for ; err == nil && num <= w.MaxFiles; num++ {
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix) fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", logTime.Format("2006-01-02"), num, w.suffix)
_, err = os.Lstat(fName) _, err = os.Lstat(fName)
} }
} else { } else {
fName = fmt.Sprintf("%s.%s%s", w.fileNameOnly, w.dailyOpenTime.Format("2006-01-02"), w.suffix) fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format("2006-01-02"), num, w.suffix)
_, err = os.Lstat(fName) _, err = os.Lstat(fName)
for ; err == nil && num <= 999; num++ { w.MaxFilesCurFiles = num
fName = w.fileNameOnly + fmt.Sprintf(".%s.%03d%s", w.dailyOpenTime.Format("2006-01-02"), num, w.suffix)
_, err = os.Lstat(fName)
}
} }
// return error if the last file checked still existed // return error if the last file checked still existed
if err == nil { if err == nil {
@ -308,7 +312,7 @@ func (w *fileLogWriter) deleteOldLog() {
return return
} }
if !info.IsDir() && info.ModTime().Add(24*time.Hour*time.Duration(w.MaxDays)).Before(time.Now()) { if !info.IsDir() && info.ModTime().Add(24 * time.Hour * time.Duration(w.MaxDays)).Before(time.Now()) {
if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) && if strings.HasPrefix(filepath.Base(path), filepath.Base(w.fileNameOnly)) &&
strings.HasSuffix(filepath.Base(path), w.suffix) { strings.HasSuffix(filepath.Base(path), w.suffix) {
os.Remove(path) os.Remove(path)

View File

@ -135,7 +135,7 @@ func TestFileRotate_01(t *testing.T) {
func TestFileRotate_02(t *testing.T) { func TestFileRotate_02(t *testing.T) {
fn1 := "rotate_day.log" fn1 := "rotate_day.log"
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log" fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
testFileRotate(t, fn1, fn2) testFileRotate(t, fn1, fn2)
} }
@ -150,7 +150,7 @@ func TestFileRotate_03(t *testing.T) {
func TestFileRotate_04(t *testing.T) { func TestFileRotate_04(t *testing.T) {
fn1 := "rotate_day.log" fn1 := "rotate_day.log"
fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".log" fn2 := "rotate_day." + time.Now().Add(-24*time.Hour).Format("2006-01-02") + ".001.log"
testFileDailyRotate(t, fn1, fn2) testFileDailyRotate(t, fn1, fn2)
} }
@ -200,6 +200,7 @@ func testFileRotate(t *testing.T, fn1, fn2 string) {
for _, file := range []string{fn1, fn2} { for _, file := range []string{fn1, fn2} {
_, err := os.Stat(file) _, err := os.Stat(file)
if err != nil { if err != nil {
t.Log(err)
t.FailNow() t.FailNow()
} }
os.Remove(file) os.Remove(file)

View File

@ -47,7 +47,7 @@ import (
// RFC5424 log message levels. // RFC5424 log message levels.
const ( const (
LevelEmergency = iota LevelEmergency = iota
LevelAlert LevelAlert
LevelCritical LevelCritical
LevelError LevelError
@ -116,6 +116,7 @@ type BeeLogger struct {
enableFuncCallDepth bool enableFuncCallDepth bool
loggerFuncCallDepth int loggerFuncCallDepth int
asynchronous bool asynchronous bool
prefix string
msgChanLen int64 msgChanLen int64
msgChan chan *logMsg msgChan chan *logMsg
signalChan chan string signalChan chan string
@ -247,7 +248,7 @@ func (bl *BeeLogger) Write(p []byte) (n int, err error) {
} }
// writeMsg will always add a '\n' character // writeMsg will always add a '\n' character
if p[len(p)-1] == '\n' { if p[len(p)-1] == '\n' {
p = p[0 : len(p)-1] p = p[0: len(p)-1]
} }
// set levelLoggerImpl to ensure all log message will be write out // set levelLoggerImpl to ensure all log message will be write out
err = bl.writeMsg(levelLoggerImpl, string(p)) err = bl.writeMsg(levelLoggerImpl, string(p))
@ -267,6 +268,9 @@ func (bl *BeeLogger) writeMsg(logLevel int, msg string, v ...interface{}) error
if len(v) > 0 { if len(v) > 0 {
msg = fmt.Sprintf(msg, v...) msg = fmt.Sprintf(msg, v...)
} }
msg = bl.prefix + " " + msg
when := time.Now() when := time.Now()
if bl.enableFuncCallDepth { if bl.enableFuncCallDepth {
_, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth) _, file, line, ok := runtime.Caller(bl.loggerFuncCallDepth)
@ -305,6 +309,11 @@ func (bl *BeeLogger) SetLevel(l int) {
bl.level = l bl.level = l
} }
// GetLevel Get Current log message level.
func (bl *BeeLogger) GetLevel() int {
return bl.level
}
// SetLogFuncCallDepth set log funcCallDepth // SetLogFuncCallDepth set log funcCallDepth
func (bl *BeeLogger) SetLogFuncCallDepth(d int) { func (bl *BeeLogger) SetLogFuncCallDepth(d int) {
bl.loggerFuncCallDepth = d bl.loggerFuncCallDepth = d
@ -320,6 +329,11 @@ func (bl *BeeLogger) EnableFuncCallDepth(b bool) {
bl.enableFuncCallDepth = b bl.enableFuncCallDepth = b
} }
// set prefix
func (bl *BeeLogger) SetPrefix(s string) {
bl.prefix = s
}
// start logger chan reading. // start logger chan reading.
// when chan is not empty, write logs. // when chan is not empty, write logs.
func (bl *BeeLogger) startLogger() { func (bl *BeeLogger) startLogger() {
@ -544,6 +558,11 @@ func SetLevel(l int) {
beeLogger.SetLevel(l) beeLogger.SetLevel(l)
} }
// SetPrefix sets the prefix
func SetPrefix(s string) {
beeLogger.SetPrefix(s)
}
// EnableFuncCallDepth enable log funcCallDepth // EnableFuncCallDepth enable log funcCallDepth
func EnableFuncCallDepth(b bool) { func EnableFuncCallDepth(b bool) {
beeLogger.enableFuncCallDepth = b beeLogger.enableFuncCallDepth = b

View File

@ -93,7 +93,7 @@ const (
func formatTimeHeader(when time.Time) ([]byte, int) { func formatTimeHeader(when time.Time) ([]byte, int) {
y, mo, d := when.Date() y, mo, d := when.Date()
h, mi, s := when.Clock() h, mi, s := when.Clock()
ns := when.Nanosecond()/1000000 ns := when.Nanosecond() / 1000000
//len("2006/01/02 15:04:05.123 ")==24 //len("2006/01/02 15:04:05.123 ")==24
var buf [24]byte var buf [24]byte

View File

@ -31,7 +31,7 @@ func TestFormatHeader_0(t *testing.T) {
break break
} }
h, _ := formatTimeHeader(tm) h, _ := formatTimeHeader(tm)
if tm.Format("2006/01/02 15:04:05.999 ") != string(h) { if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
t.Log(tm) t.Log(tm)
t.FailNow() t.FailNow()
} }
@ -49,7 +49,7 @@ func TestFormatHeader_1(t *testing.T) {
break break
} }
h, _ := formatTimeHeader(tm) h, _ := formatTimeHeader(tm)
if tm.Format("2006/01/02 15:04:05.999 ") != string(h) { if tm.Format("2006/01/02 15:04:05.000 ") != string(h) {
t.Log(tm) t.Log(tm)
t.FailNow() t.FailNow()
} }

View File

@ -67,7 +67,10 @@ func (f *multiFileLogWriter) Init(config string) error {
jsonMap["level"] = i jsonMap["level"] = i
bs, _ := json.Marshal(jsonMap) bs, _ := json.Marshal(jsonMap)
writer = newFileWriter().(*fileLogWriter) writer = newFileWriter().(*fileLogWriter)
writer.Init(string(bs)) err := writer.Init(string(bs))
if err != nil {
return err
}
f.writers[i] = writer f.writers[i] = writer
} }
} }

View File

@ -322,7 +322,7 @@ func (m *Migration) GetSQL() (sql string) {
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name) sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
} }
if len(m.Columns) > index { if len(m.Columns) > index+1 {
sql += "," sql += ","
} }
} }
@ -355,7 +355,7 @@ func (m *Migration) GetSQL() (sql string) {
} else { } else {
sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name) sql += fmt.Sprintf("\n DROP COLUMN `%s`", column.Name)
} }
if len(m.Columns) > index { if len(m.Columns) > index+1 {
sql += "," sql += ","
} }
} }
@ -366,14 +366,14 @@ func (m *Migration) GetSQL() (sql string) {
for index, unique := range m.Uniques { for index, unique := range m.Uniques {
sql += fmt.Sprintf("\n DROP KEY `%s`", unique.Definition) sql += fmt.Sprintf("\n DROP KEY `%s`", unique.Definition)
if len(m.Uniques) > index { if len(m.Uniques) > index+1 {
sql += "," sql += ","
} }
} }
for index, column := range m.Renames { for index, column := range m.Renames {
sql += fmt.Sprintf("\n CHANGE COLUMN `%s` `%s` %s %s %s %s", column.NewName, column.OldName, column.OldDataType, column.OldUnsign, column.OldNull, column.OldDefault) sql += fmt.Sprintf("\n CHANGE COLUMN `%s` `%s` %s %s %s %s", column.NewName, column.OldName, column.OldDataType, column.OldUnsign, column.OldNull, column.OldDefault)
if len(m.Renames) > index { if len(m.Renames) > index+1 {
sql += "," sql += ","
} }
} }

View File

@ -197,6 +197,10 @@ func getDbCreateSQL(al *alias) (sqls []string, tableIndexes map[string][]dbIndex
if strings.Contains(column, "%COL%") { if strings.Contains(column, "%COL%") {
column = strings.Replace(column, "%COL%", fi.column, -1) column = strings.Replace(column, "%COL%", fi.column, -1)
} }
if fi.description != "" {
column += " " + fmt.Sprintf("COMMENT '%s'",fi.description)
}
columns = append(columns, column) columns = append(columns, column)
} }

View File

@ -969,6 +969,10 @@ func (d *dbBase) ReadBatch(q dbQuerier, qs *querySet, mi *modelInfo, cond *Condi
} }
query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s", sqlSelect, sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit) query := fmt.Sprintf("%s %s FROM %s%s%s T0 %s%s%s%s%s", sqlSelect, sels, Q, mi.table, Q, join, where, groupBy, orderBy, limit)
if qs.forupdate {
query += " FOR UPDATE"
}
d.ins.ReplaceMarks(&query) d.ins.ReplaceMarks(&query)
var rs *sql.Rows var rs *sql.Rows

View File

@ -136,6 +136,7 @@ type fieldInfo struct {
decimals int decimals int
isFielder bool // implement Fielder interface isFielder bool // implement Fielder interface
onDelete string onDelete string
description string
} }
// new field info // new field info
@ -300,6 +301,7 @@ checkType:
fi.sf = sf fi.sf = sf
fi.fullName = mi.fullName + mName + "." + sf.Name fi.fullName = mi.fullName + mName + "." + sf.Name
fi.description = sf.Tag.Get("description")
fi.null = attrs["null"] fi.null = attrs["null"]
fi.index = attrs["index"] fi.index = attrs["index"]
fi.auto = attrs["auto"] fi.auto = attrs["auto"]

View File

@ -55,16 +55,17 @@ func ColValue(opt operator, value interface{}) interface{} {
// real query struct // real query struct
type querySet struct { type querySet struct {
mi *modelInfo mi *modelInfo
cond *Condition cond *Condition
related []string related []string
relDepth int relDepth int
limit int64 limit int64
offset int64 offset int64
groups []string groups []string
orders []string orders []string
distinct bool distinct bool
orm *orm forupdate bool
orm *orm
} }
var _ QuerySeter = new(querySet) var _ QuerySeter = new(querySet)
@ -127,6 +128,12 @@ func (o querySet) Distinct() QuerySeter {
return &o return &o
} }
// add FOR UPDATE to SELECT
func (o querySet) ForUpdate() QuerySeter {
o.forupdate = true
return &o
}
// set relation model to query together. // set relation model to query together.
// it will query relation models and assign to parent model. // it will query relation models and assign to parent model.
func (o querySet) RelatedSel(params ...interface{}) QuerySeter { func (o querySet) RelatedSel(params ...interface{}) QuerySeter {
@ -191,7 +198,11 @@ func (o *querySet) PrepareInsert() (Inserter, error) {
// query all data and map to containers. // query all data and map to containers.
// cols means the columns when querying. // cols means the columns when querying.
func (o *querySet) All(container interface{}, cols ...string) (int64, error) { func (o *querySet) All(container interface{}, cols ...string) (int64, error) {
return o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols) num, err := o.orm.alias.DbBaser.ReadBatch(o.orm.db, o, o.mi, o.cond, container, o.orm.alias.TZ, cols)
if num == 0 {
return 0, ErrNoRows
}
return num, err
} }
// query one row data and map to containers. // query one row data and map to containers.

View File

@ -190,6 +190,10 @@ type QuerySeter interface {
// Distinct(). // Distinct().
// All(&permissions) // All(&permissions)
Distinct() QuerySeter Distinct() QuerySeter
// set FOR UPDATE to query.
// for example:
// o.QueryTable("user").Filter("uid", uid).ForUpdate().All(&users)
ForUpdate() QuerySeter
// return QuerySeter execution result number // return QuerySeter execution result number
// for example: // for example:
// num, err = qs.Filter("profile__age__gt", 28).Count() // num, err = qs.Filter("profile__age__gt", 28).Count()

View File

@ -114,20 +114,21 @@ type parsedParam struct {
func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error { func parserComments(f *ast.FuncDecl, controllerName, pkgpath string) error {
if f.Doc != nil { if f.Doc != nil {
parsedComment, err := parseComment(f.Doc.List) parsedComments, err := parseComment(f.Doc.List)
if err != nil { if err != nil {
return err return err
} }
if parsedComment.routerPath != "" { for _, parsedComment := range parsedComments {
key := pkgpath + ":" + controllerName if parsedComment.routerPath != "" {
cc := ControllerComments{} key := pkgpath + ":" + controllerName
cc.Method = f.Name.String() cc := ControllerComments{}
cc.Router = parsedComment.routerPath cc.Method = f.Name.String()
cc.AllowHTTPMethods = parsedComment.methods cc.Router = parsedComment.routerPath
cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment) cc.AllowHTTPMethods = parsedComment.methods
genInfoList[key] = append(genInfoList[key], cc) cc.MethodParams = buildMethodParams(f.Type.Params.List, parsedComment)
genInfoList[key] = append(genInfoList[key], cc)
}
} }
} }
return nil return nil
} }
@ -177,26 +178,13 @@ func paramInPath(name, route string) bool {
var routeRegex = regexp.MustCompile(`@router\s+(\S+)(?:\s+\[(\S+)\])?`) var routeRegex = regexp.MustCompile(`@router\s+(\S+)(?:\s+\[(\S+)\])?`)
func parseComment(lines []*ast.Comment) (pc *parsedComment, err error) { func parseComment(lines []*ast.Comment) (pcs []*parsedComment, err error) {
pc = &parsedComment{} pcs = []*parsedComment{}
params := map[string]parsedParam{}
for _, c := range lines { for _, c := range lines {
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//")) t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
if strings.HasPrefix(t, "@router") { if strings.HasPrefix(t, "@Param") {
matches := routeRegex.FindStringSubmatch(t)
if len(matches) == 3 {
pc.routerPath = matches[1]
methods := matches[2]
if methods == "" {
pc.methods = []string{"get"}
//pc.hasGet = true
} else {
pc.methods = strings.Split(methods, ",")
//pc.hasGet = strings.Contains(methods, "get")
}
} else {
return nil, errors.New("Router information is missing")
}
} else if strings.HasPrefix(t, "@Param") {
pv := getparams(strings.TrimSpace(strings.TrimLeft(t, "@Param"))) pv := getparams(strings.TrimSpace(strings.TrimLeft(t, "@Param")))
if len(pv) < 4 { if len(pv) < 4 {
logs.Error("Invalid @Param format. Needs at least 4 parameters") logs.Error("Invalid @Param format. Needs at least 4 parameters")
@ -217,10 +205,32 @@ func parseComment(lines []*ast.Comment) (pc *parsedComment, err error) {
p.defValue = pv[3] p.defValue = pv[3]
p.required, _ = strconv.ParseBool(pv[4]) p.required, _ = strconv.ParseBool(pv[4])
} }
if pc.params == nil { params[funcParamName] = p
pc.params = map[string]parsedParam{} }
}
for _, c := range lines {
var pc = &parsedComment{}
pc.params = params
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
if strings.HasPrefix(t, "@router") {
t := strings.TrimSpace(strings.TrimLeft(c.Text, "//"))
matches := routeRegex.FindStringSubmatch(t)
if len(matches) == 3 {
pc.routerPath = matches[1]
methods := matches[2]
if methods == "" {
pc.methods = []string{"get"}
//pc.hasGet = true
} else {
pc.methods = strings.Split(methods, ",")
//pc.hasGet = strings.Contains(methods, "get")
}
pcs = append(pcs, pc)
} else {
return nil, errors.New("Router information is missing")
} }
pc.params[funcParamName] = p
} }
} }
return return

View File

@ -202,10 +202,10 @@ func (p *ControllerRegister) addWithMethodParams(pattern string, c ControllerInt
numOfFields := elemVal.NumField() numOfFields := elemVal.NumField()
for i := 0; i < numOfFields; i++ { for i := 0; i < numOfFields; i++ {
fieldType := elemType.Field(i) fieldType := elemType.Field(i)
elemField := execElem.FieldByName(fieldType.Name)
if execElem.FieldByName(fieldType.Name).CanSet() { if elemField.CanSet() {
fieldVal := elemVal.Field(i) fieldVal := elemVal.Field(i)
execElem.FieldByName(fieldType.Name).Set(fieldVal) elemField.Set(fieldVal)
} }
} }

View File

@ -14,9 +14,9 @@
// Package redis for session provider // Package redis for session provider
// //
// depend on github.com/garyburd/redigo/redis // depend on github.com/gomodule/redigo/redis
// //
// go install github.com/garyburd/redigo/redis // go install github.com/gomodule/redigo/redis
// //
// Usage: // Usage:
// import( // import(
@ -24,10 +24,10 @@
// "github.com/astaxie/beego/session" // "github.com/astaxie/beego/session"
// ) // )
// //
// func init() { // func init() {
// globalSessions, _ = session.NewManager("redis", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070"}``) // globalSessions, _ = session.NewManager("redis", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070"}``)
// go globalSessions.GC() // go globalSessions.GC()
// } // }
// //
// more docs: http://beego.me/docs/module/session.md // more docs: http://beego.me/docs/module/session.md
package redis package redis
@ -37,10 +37,11 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time"
"github.com/astaxie/beego/session" "github.com/astaxie/beego/session"
"github.com/garyburd/redigo/redis" "github.com/gomodule/redigo/redis"
) )
var redispder = &Provider{} var redispder = &Provider{}
@ -118,8 +119,8 @@ type Provider struct {
} }
// SessionInit init redis session // SessionInit init redis session
// savepath like redis server addr,pool size,password,dbnum // savepath like redis server addr,pool size,password,dbnum,IdleTimeout second
// e.g. 127.0.0.1:6379,100,astaxie,0 // e.g. 127.0.0.1:6379,100,astaxie,0,30
func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error { func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
rp.maxlifetime = maxlifetime rp.maxlifetime = maxlifetime
configs := strings.Split(savePath, ",") configs := strings.Split(savePath, ",")
@ -149,27 +150,39 @@ func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
} else { } else {
rp.dbNum = 0 rp.dbNum = 0
} }
rp.poollist = redis.NewPool(func() (redis.Conn, error) { var idleTimeout time.Duration = 0
c, err := redis.Dial("tcp", rp.savePath) if len(configs) > 4 {
if err != nil { timeout, err := strconv.Atoi(configs[4])
return nil, err if err == nil && timeout > 0 {
idleTimeout = time.Duration(timeout) * time.Second
} }
if rp.password != "" { }
if _, err = c.Do("AUTH", rp.password); err != nil { rp.poollist = &redis.Pool{
c.Close() Dial: func() (redis.Conn, error) {
return nil, err c, err := redis.Dial("tcp", rp.savePath)
}
}
//some redis proxy such as twemproxy is not support select command
if rp.dbNum > 0 {
_, err = c.Do("SELECT", rp.dbNum)
if err != nil { if err != nil {
c.Close()
return nil, err return nil, err
} }
} if rp.password != "" {
return c, err if _, err = c.Do("AUTH", rp.password); err != nil {
}, rp.poolsize) c.Close()
return nil, err
}
}
// some redis proxy such as twemproxy is not support select command
if rp.dbNum > 0 {
_, err = c.Do("SELECT", rp.dbNum)
if err != nil {
c.Close()
return nil, err
}
}
return c, err
},
MaxIdle: rp.poolsize,
}
rp.poollist.IdleTimeout = idleTimeout
return rp.poollist.Get().Err() return rp.poollist.Get().Err()
} }

View File

@ -0,0 +1,220 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package redis for session provider
//
// depend on github.com/go-redis/redis
//
// go install github.com/go-redis/redis
//
// Usage:
// import(
// _ "github.com/astaxie/beego/session/redis_cluster"
// "github.com/astaxie/beego/session"
// )
//
// func init() {
// globalSessions, _ = session.NewManager("redis_cluster", ``{"cookieName":"gosessionid","gclifetime":3600,"ProviderConfig":"127.0.0.1:7070;127.0.0.1:7071"}``)
// go globalSessions.GC()
// }
//
// more docs: http://beego.me/docs/module/session.md
package redis_cluster
import (
"net/http"
"strconv"
"strings"
"sync"
"github.com/astaxie/beego/session"
rediss "github.com/go-redis/redis"
"time"
)
var redispder = &Provider{}
// MaxPoolSize redis_cluster max pool size
var MaxPoolSize = 1000
// SessionStore redis_cluster session store
type SessionStore struct {
p *rediss.ClusterClient
sid string
lock sync.RWMutex
values map[interface{}]interface{}
maxlifetime int64
}
// Set value in redis_cluster session
func (rs *SessionStore) Set(key, value interface{}) error {
rs.lock.Lock()
defer rs.lock.Unlock()
rs.values[key] = value
return nil
}
// Get value in redis_cluster session
func (rs *SessionStore) Get(key interface{}) interface{} {
rs.lock.RLock()
defer rs.lock.RUnlock()
if v, ok := rs.values[key]; ok {
return v
}
return nil
}
// Delete value in redis_cluster session
func (rs *SessionStore) Delete(key interface{}) error {
rs.lock.Lock()
defer rs.lock.Unlock()
delete(rs.values, key)
return nil
}
// Flush clear all values in redis_cluster session
func (rs *SessionStore) Flush() error {
rs.lock.Lock()
defer rs.lock.Unlock()
rs.values = make(map[interface{}]interface{})
return nil
}
// SessionID get redis_cluster session id
func (rs *SessionStore) SessionID() string {
return rs.sid
}
// SessionRelease save session values to redis_cluster
func (rs *SessionStore) SessionRelease(w http.ResponseWriter) {
b, err := session.EncodeGob(rs.values)
if err != nil {
return
}
c := rs.p
c.Set(rs.sid, string(b), time.Duration(rs.maxlifetime) * time.Second)
}
// Provider redis_cluster session provider
type Provider struct {
maxlifetime int64
savePath string
poolsize int
password string
dbNum int
poollist *rediss.ClusterClient
}
// SessionInit init redis_cluster session
// savepath like redis server addr,pool size,password,dbnum
// e.g. 127.0.0.1:6379;127.0.0.1:6380,100,test,0
func (rp *Provider) SessionInit(maxlifetime int64, savePath string) error {
rp.maxlifetime = maxlifetime
configs := strings.Split(savePath, ",")
if len(configs) > 0 {
rp.savePath = configs[0]
}
if len(configs) > 1 {
poolsize, err := strconv.Atoi(configs[1])
if err != nil || poolsize < 0 {
rp.poolsize = MaxPoolSize
} else {
rp.poolsize = poolsize
}
} else {
rp.poolsize = MaxPoolSize
}
if len(configs) > 2 {
rp.password = configs[2]
}
if len(configs) > 3 {
dbnum, err := strconv.Atoi(configs[3])
if err != nil || dbnum < 0 {
rp.dbNum = 0
} else {
rp.dbNum = dbnum
}
} else {
rp.dbNum = 0
}
rp.poollist = rediss.NewClusterClient(&rediss.ClusterOptions{
Addrs: strings.Split(rp.savePath, ";"),
Password: rp.password,
PoolSize: rp.poolsize,
})
return rp.poollist.Ping().Err()
}
// SessionRead read redis_cluster session by sid
func (rp *Provider) SessionRead(sid string) (session.Store, error) {
var kv map[interface{}]interface{}
kvs, err := rp.poollist.Get(sid).Result()
if err != nil && err != rediss.Nil {
return nil, err
}
if len(kvs) == 0 {
kv = make(map[interface{}]interface{})
} else {
if kv, err = session.DecodeGob([]byte(kvs)); err != nil {
return nil, err
}
}
rs := &SessionStore{p: rp.poollist, sid: sid, values: kv, maxlifetime: rp.maxlifetime}
return rs, nil
}
// SessionExist check redis_cluster session exist by sid
func (rp *Provider) SessionExist(sid string) bool {
c := rp.poollist
if existed, err := c.Exists(sid).Result(); err != nil || existed == 0 {
return false
}
return true
}
// SessionRegenerate generate new sid for redis_cluster session
func (rp *Provider) SessionRegenerate(oldsid, sid string) (session.Store, error) {
c := rp.poollist
if existed, err := c.Exists(oldsid).Result(); err != nil || existed == 0 {
// oldsid doesn't exists, set the new sid directly
// ignore error here, since if it return error
// the existed value will be 0
c.Set(sid, "", time.Duration(rp.maxlifetime) * time.Second)
} else {
c.Rename(oldsid, sid)
c.Expire(sid, time.Duration(rp.maxlifetime) * time.Second)
}
return rp.SessionRead(sid)
}
// SessionDestroy delete redis session by id
func (rp *Provider) SessionDestroy(sid string) error {
c := rp.poollist
c.Del(sid)
return nil
}
// SessionGC Impelment method, no used.
func (rp *Provider) SessionGC() {
}
// SessionAll return all activeSession
func (rp *Provider) SessionAll() int {
return 0
}
func init() {
session.Register("redis_cluster", redispder)
}

View File

@ -74,7 +74,7 @@ func serverStaticRouter(ctx *context.Context) {
if enableCompress { if enableCompress {
acceptEncoding = context.ParseEncoding(ctx.Request) acceptEncoding = context.ParseEncoding(ctx.Request)
} }
b, n, sch, err := openFile(filePath, fileInfo, acceptEncoding) b, n, sch, reader, err := openFile(filePath, fileInfo, acceptEncoding)
if err != nil { if err != nil {
if BConfig.RunMode == DEV { if BConfig.RunMode == DEV {
logs.Warn("Can't compress the file:", filePath, err) logs.Warn("Can't compress the file:", filePath, err)
@ -89,47 +89,53 @@ func serverStaticRouter(ctx *context.Context) {
ctx.Output.Header("Content-Length", strconv.FormatInt(sch.size, 10)) ctx.Output.Header("Content-Length", strconv.FormatInt(sch.size, 10))
} }
http.ServeContent(ctx.ResponseWriter, ctx.Request, filePath, sch.modTime, sch) http.ServeContent(ctx.ResponseWriter, ctx.Request, filePath, sch.modTime, reader)
} }
type serveContentHolder struct { type serveContentHolder struct {
*bytes.Reader data []byte
modTime time.Time modTime time.Time
size int64 size int64
encoding string encoding string
} }
type serveContentReader struct {
*bytes.Reader
}
var ( var (
staticFileMap = make(map[string]*serveContentHolder) staticFileMap = make(map[string]*serveContentHolder)
mapLock sync.RWMutex mapLock sync.RWMutex
) )
func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, error) { func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, *serveContentReader, error) {
mapKey := acceptEncoding + ":" + filePath mapKey := acceptEncoding + ":" + filePath
mapLock.RLock() mapLock.RLock()
mapFile := staticFileMap[mapKey] mapFile := staticFileMap[mapKey]
mapLock.RUnlock() mapLock.RUnlock()
if isOk(mapFile, fi) { if isOk(mapFile, fi) {
return mapFile.encoding != "", mapFile.encoding, mapFile, nil reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)}
return mapFile.encoding != "", mapFile.encoding, mapFile, reader, nil
} }
mapLock.Lock() mapLock.Lock()
defer mapLock.Unlock() defer mapLock.Unlock()
if mapFile = staticFileMap[mapKey]; !isOk(mapFile, fi) { if mapFile = staticFileMap[mapKey]; !isOk(mapFile, fi) {
file, err := os.Open(filePath) file, err := os.Open(filePath)
if err != nil { if err != nil {
return false, "", nil, err return false, "", nil, nil, err
} }
defer file.Close() defer file.Close()
var bufferWriter bytes.Buffer var bufferWriter bytes.Buffer
_, n, err := context.WriteFile(acceptEncoding, &bufferWriter, file) _, n, err := context.WriteFile(acceptEncoding, &bufferWriter, file)
if err != nil { if err != nil {
return false, "", nil, err return false, "", nil, nil, err
} }
mapFile = &serveContentHolder{Reader: bytes.NewReader(bufferWriter.Bytes()), modTime: fi.ModTime(), size: int64(bufferWriter.Len()), encoding: n} mapFile = &serveContentHolder{data: bufferWriter.Bytes(), modTime: fi.ModTime(), size: int64(bufferWriter.Len()), encoding: n}
staticFileMap[mapKey] = mapFile staticFileMap[mapKey] = mapFile
} }
return mapFile.encoding != "", mapFile.encoding, mapFile, nil reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)}
return mapFile.encoding != "", mapFile.encoding, mapFile, reader, nil
} }
func isOk(s *serveContentHolder, fi os.FileInfo) bool { func isOk(s *serveContentHolder, fi os.FileInfo) bool {

View File

@ -16,7 +16,7 @@ var licenseFile = filepath.Join(currentWorkDir, "LICENSE")
func testOpenFile(encoding string, content []byte, t *testing.T) { func testOpenFile(encoding string, content []byte, t *testing.T) {
fi, _ := os.Stat(licenseFile) fi, _ := os.Stat(licenseFile)
b, n, sch, err := openFile(licenseFile, fi, encoding) b, n, sch, reader, err := openFile(licenseFile, fi, encoding)
if err != nil { if err != nil {
t.Log(err) t.Log(err)
t.Fail() t.Fail()
@ -24,7 +24,7 @@ func testOpenFile(encoding string, content []byte, t *testing.T) {
t.Log("open static file encoding "+n, b) t.Log("open static file encoding "+n, b)
assetOpenFileAndContent(sch, content, t) assetOpenFileAndContent(sch, reader, content, t)
} }
func TestOpenStaticFile_1(t *testing.T) { func TestOpenStaticFile_1(t *testing.T) {
file, _ := os.Open(licenseFile) file, _ := os.Open(licenseFile)
@ -53,13 +53,13 @@ func TestOpenStaticFileDeflate_1(t *testing.T) {
testOpenFile("deflate", content, t) testOpenFile("deflate", content, t)
} }
func assetOpenFileAndContent(sch *serveContentHolder, content []byte, t *testing.T) { func assetOpenFileAndContent(sch *serveContentHolder, reader *serveContentReader, content []byte, t *testing.T) {
t.Log(sch.size, len(content)) t.Log(sch.size, len(content))
if sch.size != int64(len(content)) { if sch.size != int64(len(content)) {
t.Log("static content file size not same") t.Log("static content file size not same")
t.Fail() t.Fail()
} }
bs, _ := ioutil.ReadAll(sch) bs, _ := ioutil.ReadAll(reader)
for i, v := range content { for i, v := range content {
if v != bs[i] { if v != bs[i] {
t.Log("content not same") t.Log("content not same")

View File

@ -122,6 +122,7 @@ type Schema struct {
Items *Schema `json:"items,omitempty" yaml:"items,omitempty"` Items *Schema `json:"items,omitempty" yaml:"items,omitempty"`
Properties map[string]Propertie `json:"properties,omitempty" yaml:"properties,omitempty"` Properties map[string]Propertie `json:"properties,omitempty" yaml:"properties,omitempty"`
Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"` Enum []interface{} `json:"enum,omitempty" yaml:"enum,omitempty"`
Example interface{} `json:"example,omitempty" yaml:"example,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
@ -131,7 +132,7 @@ type Propertie struct {
Description string `json:"description,omitempty" yaml:"description,omitempty"` Description string `json:"description,omitempty" yaml:"description,omitempty"`
Default interface{} `json:"default,omitempty" yaml:"default,omitempty"` Default interface{} `json:"default,omitempty" yaml:"default,omitempty"`
Type string `json:"type,omitempty" yaml:"type,omitempty"` Type string `json:"type,omitempty" yaml:"type,omitempty"`
Example string `json:"example,omitempty" yaml:"example,omitempty"` Example interface{} `json:"example,omitempty" yaml:"example,omitempty"`
Required []string `json:"required,omitempty" yaml:"required,omitempty"` Required []string `json:"required,omitempty" yaml:"required,omitempty"`
Format string `json:"format,omitempty" yaml:"format,omitempty"` Format string `json:"format,omitempty" yaml:"format,omitempty"`
ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"` ReadOnly bool `json:"readOnly,omitempty" yaml:"readOnly,omitempty"`

View File

@ -218,6 +218,7 @@ func BuildTemplate(dir string, files ...string) error {
} }
if err != nil { if err != nil {
logs.Error("parse template err:", file, err) logs.Error("parse template err:", file, err)
templatesLock.Unlock()
return err return err
} }
beeTemplates[file] = t beeTemplates[file] = t

View File

@ -17,6 +17,7 @@ package beego
import ( import (
"errors" "errors"
"fmt" "fmt"
"html"
"html/template" "html/template"
"net/url" "net/url"
"reflect" "reflect"
@ -84,24 +85,24 @@ func DateFormat(t time.Time, layout string) (datestring string) {
var datePatterns = []string{ var datePatterns = []string{
// year // year
"Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003 "Y", "2006", // A full numeric representation of a year, 4 digits Examples: 1999 or 2003
"y", "06", //A two digit representation of a year Examples: 99 or 03 "y", "06", //A two digit representation of a year Examples: 99 or 03
// month // month
"m", "01", // Numeric representation of a month, with leading zeros 01 through 12 "m", "01", // Numeric representation of a month, with leading zeros 01 through 12
"n", "1", // Numeric representation of a month, without leading zeros 1 through 12 "n", "1", // Numeric representation of a month, without leading zeros 1 through 12
"M", "Jan", // A short textual representation of a month, three letters Jan through Dec "M", "Jan", // A short textual representation of a month, three letters Jan through Dec
"F", "January", // A full textual representation of a month, such as January or March January through December "F", "January", // A full textual representation of a month, such as January or March January through December
// day // day
"d", "02", // Day of the month, 2 digits with leading zeros 01 to 31 "d", "02", // Day of the month, 2 digits with leading zeros 01 to 31
"j", "2", // Day of the month without leading zeros 1 to 31 "j", "2", // Day of the month without leading zeros 1 to 31
// week // week
"D", "Mon", // A textual representation of a day, three letters Mon through Sun "D", "Mon", // A textual representation of a day, three letters Mon through Sun
"l", "Monday", // A full textual representation of the day of the week Sunday through Saturday "l", "Monday", // A full textual representation of the day of the week Sunday through Saturday
// time // time
"g", "3", // 12-hour format of an hour without leading zeros 1 through 12 "g", "3", // 12-hour format of an hour without leading zeros 1 through 12
"G", "15", // 24-hour format of an hour without leading zeros 0 through 23 "G", "15", // 24-hour format of an hour without leading zeros 0 through 23
"h", "03", // 12-hour format of an hour with leading zeros 01 through 12 "h", "03", // 12-hour format of an hour with leading zeros 01 through 12
"H", "15", // 24-hour format of an hour with leading zeros 00 through 23 "H", "15", // 24-hour format of an hour with leading zeros 00 through 23
@ -207,14 +208,12 @@ func Htmlquote(text string) string {
'&lt;&#39;&amp;&quot;&gt;' '&lt;&#39;&amp;&quot;&gt;'
*/ */
text = strings.Replace(text, "&", "&amp;", -1) // Must be done first! text = html.EscapeString(text)
text = strings.Replace(text, "<", "&lt;", -1) text = strings.NewReplacer(
text = strings.Replace(text, ">", "&gt;", -1) ``, "&ldquo;",
text = strings.Replace(text, "'", "&#39;", -1) ``, "&rdquo;",
text = strings.Replace(text, "\"", "&quot;", -1) ` `, "&nbsp;",
text = strings.Replace(text, "“", "&ldquo;", -1) ).Replace(text)
text = strings.Replace(text, "”", "&rdquo;", -1)
text = strings.Replace(text, " ", "&nbsp;", -1)
return strings.TrimSpace(text) return strings.TrimSpace(text)
} }
@ -228,17 +227,7 @@ func Htmlunquote(text string) string {
'<\\'&">' '<\\'&">'
*/ */
// strings.Replace(s, old, new, n) text = html.UnescapeString(text)
// 在s字符串中把old字符串替换为new字符串n表示替换的次数小于0表示全部替换
text = strings.Replace(text, "&nbsp;", " ", -1)
text = strings.Replace(text, "&rdquo;", "”", -1)
text = strings.Replace(text, "&ldquo;", "“", -1)
text = strings.Replace(text, "&quot;", "\"", -1)
text = strings.Replace(text, "&#39;", "'", -1)
text = strings.Replace(text, "&gt;", ">", -1)
text = strings.Replace(text, "&lt;", "<", -1)
text = strings.Replace(text, "&amp;", "&", -1) // Must be done last!
return strings.TrimSpace(text) return strings.TrimSpace(text)
} }

View File

@ -94,7 +94,7 @@ func TestCompareRelated(t *testing.T) {
} }
func TestHtmlquote(t *testing.T) { func TestHtmlquote(t *testing.T) {
h := `&lt;&#39;&nbsp;&rdquo;&ldquo;&amp;&quot;&gt;` h := `&lt;&#39;&nbsp;&rdquo;&ldquo;&amp;&#34;&gt;`
s := `<' ”“&">` s := `<' ”“&">`
if Htmlquote(s) != h { if Htmlquote(s) != h {
t.Error("should be equal") t.Error("should be equal")
@ -102,8 +102,8 @@ func TestHtmlquote(t *testing.T) {
} }
func TestHtmlunquote(t *testing.T) { func TestHtmlunquote(t *testing.T) {
h := `&lt;&#39;&nbsp;&rdquo;&ldquo;&amp;&quot;&gt;` h := `&lt;&#39;&nbsp;&rdquo;&ldquo;&amp;&#34;&gt;`
s := `<' ”“&">` s := `<' ”“&">`
if Htmlunquote(h) != s { if Htmlunquote(h) != s {
t.Error("should be equal") t.Error("should be equal")
} }

View File

@ -245,7 +245,21 @@ func (v *Validation) ZipCode(obj interface{}, key string) *Result {
} }
func (v *Validation) apply(chk Validator, obj interface{}) *Result { func (v *Validation) apply(chk Validator, obj interface{}) *Result {
if chk.IsSatisfied(obj) { if nil == obj {
if chk.IsSatisfied(obj) {
return &Result{Ok: true}
}
} else if reflect.TypeOf(obj).Kind() == reflect.Ptr {
if reflect.ValueOf(obj).IsNil() {
if chk.IsSatisfied(nil) {
return &Result{Ok: true}
}
} else {
if chk.IsSatisfied(reflect.ValueOf(obj).Elem().Interface()) {
return &Result{Ok: true}
}
}
} else if chk.IsSatisfied(obj) {
return &Result{Ok: true} return &Result{Ok: true}
} }
@ -351,13 +365,24 @@ func (v *Validation) Valid(obj interface{}) (b bool, err error) {
return return
} }
var hasReuired bool var hasRequired bool
for _, vf := range vfs { for _, vf := range vfs {
if vf.Name == "Required" { if vf.Name == "Required" {
hasReuired = true hasRequired = true
} }
if !hasReuired && v.RequiredFirst && len(objV.Field(i).String()) == 0 { currentField := objV.Field(i).Interface()
if objV.Field(i).Kind() == reflect.Ptr {
if objV.Field(i).IsNil() {
currentField = ""
} else {
currentField = objV.Field(i).Elem().Interface()
}
}
chk := Required{""}.IsSatisfied(currentField)
if !hasRequired && v.RequiredFirst && !chk {
if _, ok := CanSkipFuncs[vf.Name]; ok { if _, ok := CanSkipFuncs[vf.Name]; ok {
continue continue
} }
@ -414,3 +439,9 @@ func (v *Validation) RecursiveValid(objc interface{}) (bool, error) {
} }
return pass, err return pass, err
} }
func (v *Validation) CanSkipAlso(skipFunc string) {
if _, ok := CanSkipFuncs[skipFunc]; !ok {
CanSkipFuncs[skipFunc] = struct{}{}
}
}

View File

@ -442,3 +442,122 @@ func TestSkipValid(t *testing.T) {
t.Fatal("validation should be passed") t.Fatal("validation should be passed")
} }
} }
func TestPointer(t *testing.T) {
type User struct {
ID int
Email *string `valid:"Email"`
ReqEmail *string `valid:"Required;Email"`
}
u := User{
ReqEmail: nil,
Email: nil,
}
valid := Validation{}
b, err := valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
validEmail := "a@a.com"
u = User{
ReqEmail: &validEmail,
Email: nil,
}
valid = Validation{RequiredFirst: true}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if !b {
t.Fatal("validation should be passed")
}
u = User{
ReqEmail: &validEmail,
Email: nil,
}
valid = Validation{}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
invalidEmail := "a@a"
u = User{
ReqEmail: &validEmail,
Email: &invalidEmail,
}
valid = Validation{RequiredFirst: true}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
u = User{
ReqEmail: &validEmail,
Email: &invalidEmail,
}
valid = Validation{}
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
}
func TestCanSkipAlso(t *testing.T) {
type User struct {
ID int
Email string `valid:"Email"`
ReqEmail string `valid:"Required;Email"`
MatchRange int `valid:"Range(10, 20)"`
}
u := User{
ReqEmail: "a@a.com",
Email: "",
MatchRange: 0,
}
valid := Validation{RequiredFirst: true}
b, err := valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if b {
t.Fatal("validation should not be passed")
}
valid = Validation{RequiredFirst: true}
valid.CanSkipAlso("Range")
b, err = valid.Valid(u)
if err != nil {
t.Fatal(err)
}
if !b {
t.Fatal("validation should be passed")
}
}