mirror of
https://gitee.com/johng/gf
synced 2026-06-10 19:31:44 +08:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dd5cd31ef5 | |||
| de92e804fe | |||
| 7725d9aaaf | |||
| bd3e25adea | |||
| 0b1d49af4b | |||
| 7efa9e351e | |||
| 0b0141954b | |||
| 82b531fbfb | |||
| 67fb626339 | |||
| 9044d5f05d | |||
| 47663aa1f1 | |||
| 63c0aab19c | |||
| 261216f5e4 | |||
| f88b799d67 | |||
| 9c48dd172c | |||
| 09ce105eee | |||
| 651aa895f8 | |||
| 0509e41198 | |||
| 1b40d6a53a | |||
| f9189d48d1 | |||
| 849874a247 | |||
| 3f6510bae7 | |||
| a585a26c39 | |||
| 9943966a86 | |||
| 3617e51c01 | |||
| c931032f08 | |||
| 37b286eaa4 | |||
| 619287c273 | |||
| aeb9b68298 | |||
| bae8f6315b | |||
| a6f70f8935 | |||
| acca6f4009 | |||
| 727fdd2391 | |||
| 1d174e00c0 | |||
| a5e3e2f5ba | |||
| da43c2d52f | |||
| 262f27748c | |||
| a29aef7e1c | |||
| 1671391195 | |||
| 28b0d59c61 | |||
| 86433cef25 |
@ -1,10 +1,15 @@
|
||||
|
||||
# MySQL数据库配置
|
||||
# MySQL.
|
||||
[database]
|
||||
debug = true
|
||||
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local"
|
||||
MaxOpen = 100
|
||||
|
||||
# Redis.
|
||||
[redis]
|
||||
default = "127.0.0.1:6379,0"
|
||||
cache = "127.0.0.1:6379,1"
|
||||
|
||||
#[database]
|
||||
# [[database.default]]
|
||||
# type = "mysql"
|
||||
|
||||
@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"github.com/gogf/gf/database/gdb"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@ -10,7 +11,7 @@ func main() {
|
||||
Host: "127.0.0.1",
|
||||
Port: "3306",
|
||||
User: "root",
|
||||
Pass: "123456",
|
||||
Pass: "12345678",
|
||||
Name: "test",
|
||||
Type: "mysql",
|
||||
Role: "master",
|
||||
@ -20,19 +21,20 @@ func main() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
//db.GetCache().SetAdapter(adapter.NewRedis(g.Redis()))
|
||||
// 开启调试模式,以便于记录所有执行的SQL
|
||||
db.SetDebug(true)
|
||||
|
||||
// 执行2次查询并将查询结果缓存3秒,并可执行缓存名称(可选)
|
||||
for i := 0; i < 2; i++ {
|
||||
r, _ := db.Table("user").Cache(3, "vip-user").Where("uid=?", 1).One()
|
||||
gutil.Dump(r.ToMap())
|
||||
for i := 0; i < 3; i++ {
|
||||
r, _ := db.Table("user").Cache(3000*time.Second).Where("id=?", 1).One()
|
||||
gutil.Dump(r.Map())
|
||||
}
|
||||
|
||||
// 执行更新操作,并清理指定名称的查询缓存
|
||||
db.Table("user").Cache(-1, "vip-user").Data(gdb.Map{"name": "smith"}).Where("uid=?", 1).Update()
|
||||
//db.Table("user").Cache(-1, "vip-user").Data(gdb.Map{"name": "smith"}).Where("id=?", 1).Update()
|
||||
|
||||
// 再次执行查询,启用查询缓存特性
|
||||
r, _ := db.Table("user").Cache(3, "vip-user").Where("uid=?", 1).One()
|
||||
gutil.Dump(r.ToMap())
|
||||
//r, _ := db.Table("user").Cache(300000*time.Second, "vip-user").Where("id=?", 1).One()
|
||||
//gutil.Dump(r.Map())
|
||||
}
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := ghttp.GetServer()
|
||||
s.EnablePProf()
|
||||
s := g.Server()
|
||||
s.Domain("localhost").EnablePProf()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Writeln("哈喽世界!")
|
||||
})
|
||||
|
||||
@ -2,19 +2,18 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/frame/gmvc"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
gmvc.Controller
|
||||
}
|
||||
|
||||
func (c *Controller) Test() {
|
||||
c.View.Display("layout.html")
|
||||
}
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindControllerMethod("/", new(Controller), "Test")
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.WriteTpl("layout.html", g.Map{
|
||||
"header": "This is header",
|
||||
"container": "This is container",
|
||||
"footer": "This is footer",
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
{{define "container"}}
|
||||
<h1>CONTAINER</h1>
|
||||
<h1>{{.container}}</h1>
|
||||
{{end}}
|
||||
@ -1,3 +1,3 @@
|
||||
{{define "footer"}}
|
||||
<h1>FOOTER</h1>
|
||||
<h1>{{.footer}}</h1>
|
||||
{{end}}
|
||||
@ -1,3 +1,3 @@
|
||||
{{define "header"}}
|
||||
<h1>HEADER</h1>
|
||||
<h1>{{.header}}</h1>
|
||||
{{end}}
|
||||
@ -2,14 +2,14 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>GoFrame Layout</title>
|
||||
{{template "header"}}
|
||||
{{template "header" .}}
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
{{template "container"}}
|
||||
{{template "container" .}}
|
||||
</div>
|
||||
<div class="footer">
|
||||
{{template "footer"}}
|
||||
{{template "footer" .}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -9,11 +9,13 @@ func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/main1", func(r *ghttp.Request) {
|
||||
r.Response.WriteTpl("layout.html", g.Map{
|
||||
"name": "smith",
|
||||
"mainTpl": "main/main1.html",
|
||||
})
|
||||
})
|
||||
s.BindHandler("/main2", func(r *ghttp.Request) {
|
||||
r.Response.WriteTpl("layout.html", g.Map{
|
||||
"name": "john",
|
||||
"mainTpl": "main/main2.html",
|
||||
})
|
||||
})
|
||||
|
||||
@ -156,7 +156,7 @@ type DB interface {
|
||||
// Internal methods.
|
||||
// ===========================================================================
|
||||
|
||||
filterFields(schema, table string, data map[string]interface{}) map[string]interface{}
|
||||
mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error)
|
||||
convertValue(fieldValue interface{}, fieldType string) interface{}
|
||||
rowsToResult(rows *sql.Rows) (Result, error)
|
||||
}
|
||||
@ -166,7 +166,7 @@ type Core struct {
|
||||
DB DB // DB interface object.
|
||||
group string // Configuration group name.
|
||||
debug *gtype.Bool // Enable debug mode for the database.
|
||||
cache *gcache.Cache // Cache manager.
|
||||
cache *gcache.Cache // Cache manager, SQL result cache only.
|
||||
schema *gtype.String // Custom schema for this object.
|
||||
dryrun *gtype.Bool // Dry run.
|
||||
prefix string // Table prefix.
|
||||
@ -438,11 +438,11 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
||||
node = &n
|
||||
}
|
||||
// Cache the underlying connection pool object by node.
|
||||
v := c.cache.GetOrSetFuncLock(node.String(), func() interface{} {
|
||||
v, _ := gcache.GetOrSetFuncLock(node.String(), func() (interface{}, error) {
|
||||
sqlDb, err = c.DB.Open(node)
|
||||
if err != nil {
|
||||
intlog.Printf("DB open failed: %v, %+v", err, node)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
if c.maxIdleConnCount > 0 {
|
||||
sqlDb.SetMaxIdleConns(c.maxIdleConnCount)
|
||||
@ -461,7 +461,7 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
||||
} else if node.MaxConnLifetime > 0 {
|
||||
sqlDb.SetConnMaxLifetime(node.MaxConnLifetime * time.Second)
|
||||
}
|
||||
return sqlDb
|
||||
return sqlDb, nil
|
||||
}, 0)
|
||||
if v != nil && sqlDb == nil {
|
||||
sqlDb = v.(*sql.DB)
|
||||
|
||||
@ -310,6 +310,11 @@ func (c *Core) Transaction(f func(tx *TX) error) (err error) {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err == nil {
|
||||
if e := recover(); e != nil {
|
||||
err = fmt.Errorf("%v", e)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
if e := tx.Rollback(); e != nil {
|
||||
err = e
|
||||
@ -426,10 +431,10 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
|
||||
if _, ok := data.(apiInterfaces); ok {
|
||||
return c.DB.DoBatchInsert(link, table, data, option, batch...)
|
||||
} else {
|
||||
dataMap = DataToMapDeep(data)
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
}
|
||||
case reflect.Map:
|
||||
dataMap = DataToMapDeep(data)
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported data type:", reflectKind))
|
||||
}
|
||||
@ -521,10 +526,10 @@ func (c *Core) BatchSave(table string, list interface{}, batch ...int) (sql.Resu
|
||||
func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option int, batch ...int) (result sql.Result, err error) {
|
||||
table = c.DB.QuotePrefixTableName(table)
|
||||
var (
|
||||
keys []string
|
||||
values []string
|
||||
params []interface{}
|
||||
listMap List
|
||||
keys []string // Field names.
|
||||
values []string // Value holder string array, like: (?,?,?)
|
||||
params []interface{} // Values that will be committed to underlying database driver.
|
||||
listMap List // The data list that passed from caller.
|
||||
)
|
||||
switch value := list.(type) {
|
||||
case Result:
|
||||
@ -549,10 +554,10 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
case reflect.Slice, reflect.Array:
|
||||
listMap = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
listMap = List{DataToMapDeep(value)}
|
||||
listMap = List{ConvertDataForTableRecord(value)}
|
||||
case reflect.Struct:
|
||||
if v, ok := value.(apiInterfaces); ok {
|
||||
var (
|
||||
@ -560,11 +565,11 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
list = make(List, len(array))
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
list[i] = DataToMapDeep(array[i])
|
||||
list[i] = ConvertDataForTableRecord(array[i])
|
||||
}
|
||||
listMap = list
|
||||
} else {
|
||||
listMap = List{DataToMapDeep(value)}
|
||||
listMap = List{ConvertDataForTableRecord(value)}
|
||||
}
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
|
||||
@ -690,7 +695,7 @@ func (c *Core) DoUpdate(link Link, table string, data interface{}, condition str
|
||||
case reflect.Map, reflect.Struct:
|
||||
var (
|
||||
fields []string
|
||||
dataMap = DataToMapDeep(data)
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
)
|
||||
for k, v := range dataMap {
|
||||
fields = append(fields, c.DB.QuoteWord(k)+"=?")
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -145,15 +147,50 @@ func (c *Core) convertValue(fieldValue interface{}, fieldType string) interface{
|
||||
}
|
||||
|
||||
// filterFields removes all key-value pairs which are not the field of given table.
|
||||
func (c *Core) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} {
|
||||
// It must use data copy here to avoid its changing the origin data map.
|
||||
newDataMap := make(map[string]interface{}, len(data))
|
||||
if fields, err := c.DB.TableFields(table, schema); err == nil {
|
||||
for k, v := range data {
|
||||
if _, ok := fields[k]; ok {
|
||||
newDataMap[k] = v
|
||||
func (c *Core) mappingAndFilterData(schema, table string, data map[string]interface{}, filter bool) (map[string]interface{}, error) {
|
||||
if fieldsMap, err := c.DB.TableFields(table, schema); err == nil {
|
||||
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
|
||||
for k, _ := range fieldsMap {
|
||||
fieldsKeyMap[k] = nil
|
||||
}
|
||||
// Automatic data key to table field name mapping.
|
||||
var foundKey string
|
||||
for dataKey, dataValue := range data {
|
||||
if _, ok := fieldsKeyMap[dataKey]; !ok {
|
||||
foundKey, _ = gutil.MapPossibleItemByKey(fieldsKeyMap, dataKey)
|
||||
if foundKey != "" {
|
||||
data[foundKey] = dataValue
|
||||
delete(data, dataKey)
|
||||
} else if !filter {
|
||||
if schema != "" {
|
||||
return nil, gerror.Newf(`no column of name "%s" found for table "%s" in schema "%s"`, dataKey, table, schema)
|
||||
}
|
||||
return nil, gerror.Newf(`no column of name "%s" found for table "%s"`, dataKey, table)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Data filtering.
|
||||
if filter {
|
||||
for dataKey, _ := range data {
|
||||
if _, ok := fieldsMap[dataKey]; !ok {
|
||||
delete(data, dataKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return newDataMap
|
||||
return data, nil
|
||||
}
|
||||
|
||||
//// filterFields removes all key-value pairs which are not the field of given table.
|
||||
//func (c *Core) filterFields(schema, table string, data map[string]interface{}) map[string]interface{} {
|
||||
// // It must use data copy here to avoid its changing the origin data map.
|
||||
// newDataMap := make(map[string]interface{}, len(data))
|
||||
// if fields, err := c.DB.TableFields(table, schema); err == nil {
|
||||
// for k, v := range data {
|
||||
// if _, ok := fields[k]; ok {
|
||||
// newDataMap[k] = v
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return newDataMap
|
||||
//}
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -198,47 +199,58 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`mssql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mssql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
link *sql.DB
|
||||
)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
|
||||
SELECT a.name Field,
|
||||
structureSql := fmt.Sprintf(`
|
||||
SELECT
|
||||
a.name Field,
|
||||
CASE b.name
|
||||
WHEN 'datetime' THEN 'datetime'
|
||||
WHEN 'numeric' THEN b.name + '(' + convert(varchar(20),a.xprec) + ',' + convert(varchar(20),a.xscale) + ')'
|
||||
WHEN 'char' THEN b.name + '(' + convert(varchar(20),a.length)+ ')'
|
||||
WHEN 'varchar' THEN b.name + '(' + convert(varchar(20),a.length)+ ')'
|
||||
ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END as TYPE,
|
||||
case when a.isnullable=1 then 'YES'else 'NO' end as [Null],
|
||||
case when exists(SELECT 1 FROM sysobjects where xtype='PK' and name in (
|
||||
SELECT name FROM sysindexes WHERE indid in(
|
||||
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
|
||||
))) then 'PRI' else '' end AS [Key],
|
||||
case when COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 then 'auto_increment'else '' end Extra,
|
||||
isnull(e.text,'') as [Default],
|
||||
WHEN 'numeric' THEN b.name + '(' + convert(varchar(20), a.xprec) + ',' + convert(varchar(20), a.xscale) + ')'
|
||||
WHEN 'char' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
|
||||
WHEN 'varchar' THEN b.name + '(' + convert(varchar(20), a.length)+ ')'
|
||||
ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END AS Type,
|
||||
CASE WHEN a.isnullable=1 THEN 'YES' ELSE 'NO' end AS [Null],
|
||||
CASE WHEN exists (
|
||||
SELECT 1 FROM sysobjects WHERE xtype='PK' AND name IN (
|
||||
SELECT name FROM sysindexes WHERE indid IN (
|
||||
SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
|
||||
)
|
||||
)
|
||||
) THEN 'PRI' ELSE '' END AS [Key],
|
||||
CASE WHEN COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 THEN 'auto_increment' ELSE '' END Extra,
|
||||
isnull(e.text,'') AS [Default],
|
||||
isnull(g.[value],'') AS [Comment]
|
||||
FROM syscolumns a
|
||||
left join systypes b on a.xtype=b.xtype and a.xusertype=b.xusertype
|
||||
inner join sysobjects d on a.id=d.id and d.xtype='U' and d.name<>'dtproperties'
|
||||
left join syscomments e on a.cdefault=e.id
|
||||
left join sys.extended_properties g on a.id=g.major_id and a.colid=g.minor_id
|
||||
left join sys.extended_properties f on d.id=f.major_id and f.minor_id =0
|
||||
where d.name='%s'
|
||||
order by a.id,a.colorder`, strings.ToUpper(table)))
|
||||
FROM syscolumns a
|
||||
LEFT JOIN systypes b ON a.xtype=b.xtype AND a.xusertype=b.xusertype
|
||||
INNER JOIN sysobjects d ON a.id=d.id AND d.xtype='U' AND d.name<>'dtproperties'
|
||||
LEFT JOIN syscomments e ON a.cdefault=e.id
|
||||
LEFT JOIN sys.extended_properties g ON a.id=g.major_id AND a.colid=g.minor_id
|
||||
LEFT JOIN sys.extended_properties f ON d.id=f.major_id AND f.minor_id =0
|
||||
WHERE d.name='%s'
|
||||
ORDER BY a.id,a.colorder`,
|
||||
strings.ToUpper(table),
|
||||
)
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
result, err = d.DB.DoGetAll(link, structureSql)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
fields[strings.ToLower(m["FIELD"].String())] = &TableField{
|
||||
fields[strings.ToLower(m["Field"].String())] = &TableField{
|
||||
Index: i,
|
||||
Name: strings.ToLower(m["FIELD"].String()),
|
||||
Type: strings.ToLower(m["TYPE"].String()),
|
||||
Name: strings.ToLower(m["Field"].String()),
|
||||
Type: strings.ToLower(m["Type"].String()),
|
||||
Null: m["Null"].Bool(),
|
||||
Key: m["Key"].String(),
|
||||
Default: m["Default"].Val(),
|
||||
@ -246,7 +258,7 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
|
||||
Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
|
||||
@ -102,21 +103,23 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.cache.GetOrSetFunc(
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mysql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
link *sql.DB
|
||||
)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(
|
||||
link,
|
||||
fmt.Sprintf(`SHOW FULL COLUMNS FROM %s`, d.DB.QuoteWord(table)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
@ -131,7 +134,7 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st
|
||||
Comment: m["Comment"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@ -158,18 +159,24 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`oracle_table_fields_%s_%s`, table, checkSchema),
|
||||
func() interface{} {
|
||||
func() (interface{}, error) {
|
||||
result := (Result)(nil)
|
||||
result, err = d.DB.GetAll(fmt.Sprintf(`
|
||||
SELECT COLUMN_NAME AS FIELD, CASE DATA_TYPE
|
||||
WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE
|
||||
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, strings.ToUpper(table)))
|
||||
structureSql := fmt.Sprintf(`
|
||||
SELECT
|
||||
COLUMN_NAME AS FIELD,
|
||||
CASE DATA_TYPE
|
||||
WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE
|
||||
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
|
||||
strings.ToUpper(table),
|
||||
)
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
result, err = d.DB.GetAll(structureSql)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
@ -179,7 +186,7 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
|
||||
Type: strings.ToLower(m["TYPE"].String()),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
@ -189,24 +196,26 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
|
||||
|
||||
func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[string]string, err error) {
|
||||
table = strings.ToUpper(table)
|
||||
v := d.DB.GetCache().GetOrSetFunc("table_unique_index_"+table, func() interface{} {
|
||||
res := (Result)(nil)
|
||||
res, err = d.DB.GetAll(fmt.Sprintf(`
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
"table_unique_index_"+table,
|
||||
func() (interface{}, error) {
|
||||
res := (Result)(nil)
|
||||
res, err = d.DB.GetAll(fmt.Sprintf(`
|
||||
SELECT INDEX_NAME,COLUMN_NAME,CHAR_LENGTH FROM USER_IND_COLUMNS
|
||||
WHERE TABLE_NAME = '%s'
|
||||
AND INDEX_NAME IN(SELECT INDEX_NAME FROM USER_INDEXES WHERE TABLE_NAME='%s' AND UNIQUENESS='UNIQUE')
|
||||
ORDER BY INDEX_NAME,COLUMN_POSITION`, table, table))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields := make(map[string]map[string]string)
|
||||
for _, v := range res {
|
||||
mm := make(map[string]string)
|
||||
mm[v["COLUMN_NAME"].String()] = v["CHAR_LENGTH"].String()
|
||||
fields[v["INDEX_NAME"].String()] = mm
|
||||
}
|
||||
return fields
|
||||
}, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fields := make(map[string]map[string]string)
|
||||
for _, v := range res {
|
||||
mm := make(map[string]string)
|
||||
mm[v["COLUMN_NAME"].String()] = v["CHAR_LENGTH"].String()
|
||||
fields[v["INDEX_NAME"].String()] = mm
|
||||
}
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]map[string]string)
|
||||
}
|
||||
@ -232,7 +241,7 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
dataMap = DataToMapDeep(data)
|
||||
dataMap = ConvertDataForTableRecord(data)
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
|
||||
}
|
||||
@ -352,12 +361,12 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
|
||||
case reflect.Array:
|
||||
listMap = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
listMap[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Struct:
|
||||
listMap = List{Map(DataToMapDeep(list))}
|
||||
listMap = List{Map(ConvertDataForTableRecord(list))}
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"strings"
|
||||
|
||||
@ -107,21 +108,28 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`pgsql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`pgsql_table_fields_%s_%s`, table, checkSchema),
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
link *sql.DB
|
||||
)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
|
||||
SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
|
||||
LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
|
||||
WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
|
||||
ORDER BY a.attnum`, strings.ToLower(table)))
|
||||
structureSql := fmt.Sprintf(`
|
||||
SELECT a.attname AS field, t.typname AS type FROM pg_class c, pg_attribute a
|
||||
LEFT OUTER JOIN pg_description b ON a.attrelid=b.objoid AND a.attnum = b.objsubid,pg_type t
|
||||
WHERE c.relname = '%s' and a.attnum > 0 and a.attrelid = c.oid and a.atttypid = t.oid
|
||||
ORDER BY a.attnum`,
|
||||
strings.ToLower(table),
|
||||
)
|
||||
structureSql, _ = gregex.ReplaceString(`[\n\r\s]+`, " ", gstr.Trim(structureSql))
|
||||
result, err = d.DB.DoGetAll(link, structureSql)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fields = make(map[string]*TableField)
|
||||
@ -132,7 +140,7 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st
|
||||
Type: m["type"].String(),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"strings"
|
||||
@ -98,17 +99,20 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v := d.DB.GetCache().GetOrSetFunc(
|
||||
fmt.Sprintf(`sqlite_table_fields_%s_%s`, table, checkSchema), func() interface{} {
|
||||
var result Result
|
||||
var link *sql.DB
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`sqlite_table_fields_%s_%s`, table, checkSchema),
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
link *sql.DB
|
||||
)
|
||||
link, err = d.DB.GetSlave(checkSchema)
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
result, err = d.DB.DoGetAll(link, fmt.Sprintf(`PRAGMA TABLE_INFO(%s)`, table))
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
fields = make(map[string]*TableField)
|
||||
for i, m := range result {
|
||||
@ -118,7 +122,7 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s
|
||||
Type: strings.ToLower(m["type"].String()),
|
||||
}
|
||||
}
|
||||
return fields
|
||||
return fields, nil
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]*TableField)
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
@ -97,9 +98,52 @@ func GetInsertOperationByOption(option int) string {
|
||||
return operator
|
||||
}
|
||||
|
||||
// DataToMapDeep converts struct object to map type recursively.
|
||||
// ConvertDataForTableRecord is a very important function, which does converting for any data that
|
||||
// will be inserted into table as a record.
|
||||
//
|
||||
// The parameter <obj> should be type of *map/map/*struct/struct.
|
||||
// It supports inherit struct definition for struct.
|
||||
func ConvertDataForTableRecord(value interface{}) map[string]interface{} {
|
||||
var (
|
||||
rvValue reflect.Value
|
||||
rvKind reflect.Kind
|
||||
data = DataToMapDeep(value)
|
||||
)
|
||||
for k, v := range data {
|
||||
rvValue = reflect.ValueOf(v)
|
||||
rvKind = rvValue.Kind()
|
||||
for rvKind == reflect.Ptr {
|
||||
rvValue = rvValue.Elem()
|
||||
rvKind = rvValue.Kind()
|
||||
}
|
||||
switch rvKind {
|
||||
case reflect.Slice, reflect.Array, reflect.Map:
|
||||
// It should ignore the bytes type.
|
||||
if _, ok := v.([]byte); !ok {
|
||||
// Convert the value to JSON.
|
||||
data[k], _ = json.Marshal(v)
|
||||
}
|
||||
case reflect.Struct:
|
||||
switch v.(type) {
|
||||
case time.Time, *time.Time, gtime.Time, *gtime.Time:
|
||||
continue
|
||||
default:
|
||||
// Use string conversion in default.
|
||||
if s, ok := v.(apiString); ok {
|
||||
data[k] = s.String()
|
||||
} else {
|
||||
// Convert the value to JSON.
|
||||
data[k], _ = json.Marshal(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// DataToMapDeep converts <value> to map type recursively.
|
||||
// The parameter <value> should be type of *map/map/*struct/struct.
|
||||
// It supports inherit struct definition for struct.
|
||||
func DataToMapDeep(value interface{}) map[string]interface{} {
|
||||
if v, ok := value.(apiMapStrAny); ok {
|
||||
return v.MapStrAny()
|
||||
@ -342,9 +386,10 @@ func GetPrimaryKeyCondition(primary string, where ...interface{}) (newWhereCondi
|
||||
// The internal handleArguments function might be called twice during the SQL procedure,
|
||||
// but do not worry about it, it's safe and efficient.
|
||||
func formatSql(sql string, args []interface{}) (newSql string, newArgs []interface{}) {
|
||||
sql = gstr.Trim(sql)
|
||||
sql = gstr.Replace(sql, "\n", " ")
|
||||
sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
|
||||
// DO NOT do this as there may be multiple lines and comments in the sql.
|
||||
// sql = gstr.Trim(sql)
|
||||
// sql = gstr.Replace(sql, "\n", " ")
|
||||
// sql, _ = gregex.ReplaceString(`\s{2,}`, ` `, sql)
|
||||
return handleArguments(sql, args)
|
||||
}
|
||||
|
||||
|
||||
@ -12,13 +12,6 @@ import (
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
)
|
||||
|
||||
// Unscoped disables the soft deleting feature.
|
||||
func (m *Model) Unscoped() *Model {
|
||||
model := m.getModel()
|
||||
model.unscoped = true
|
||||
return model
|
||||
}
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the model.
|
||||
// The optional parameter <where> is the same as the parameter of Model.Where function,
|
||||
// see Model.Where.
|
||||
|
||||
@ -24,18 +24,24 @@ func (m *Model) Filter() *Model {
|
||||
}
|
||||
|
||||
// Fields sets the operation fields of the model, multiple fields joined using char ','.
|
||||
func (m *Model) Fields(fields string) *Model {
|
||||
model := m.getModel()
|
||||
model.fields = fields
|
||||
return model
|
||||
func (m *Model) Fields(fields ...string) *Model {
|
||||
if len(fields) > 0 {
|
||||
model := m.getModel()
|
||||
model.fields = gstr.Join(fields, ",")
|
||||
return model
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// FieldsEx sets the excluded operation fields of the model, multiple fields joined using char ','.
|
||||
// Note that this function supports only single table operations.
|
||||
func (m *Model) FieldsEx(fields string) *Model {
|
||||
model := m.getModel()
|
||||
model.fieldsEx = fields
|
||||
return model
|
||||
func (m *Model) FieldsEx(fields ...string) *Model {
|
||||
if len(fields) > 0 {
|
||||
model := m.getModel()
|
||||
model.fieldsEx = gstr.Join(fields, ",")
|
||||
return model
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Deprecated, use GetFieldsStr instead.
|
||||
|
||||
@ -56,8 +56,10 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
case Map:
|
||||
model.data = params
|
||||
default:
|
||||
rv := reflect.ValueOf(params)
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(params)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
@ -66,11 +68,11 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
case reflect.Slice, reflect.Array:
|
||||
list := make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
list[i] = DataToMapDeep(rv.Index(i).Interface())
|
||||
list[i] = ConvertDataForTableRecord(rv.Index(i).Interface())
|
||||
}
|
||||
model.data = list
|
||||
case reflect.Map:
|
||||
model.data = DataToMapDeep(data[0])
|
||||
model.data = ConvertDataForTableRecord(data[0])
|
||||
case reflect.Struct:
|
||||
if v, ok := data[0].(apiInterfaces); ok {
|
||||
var (
|
||||
@ -78,11 +80,11 @@ func (m *Model) Data(data ...interface{}) *Model {
|
||||
list = make(List, len(array))
|
||||
)
|
||||
for i := 0; i < len(array); i++ {
|
||||
list[i] = DataToMapDeep(array[i])
|
||||
list[i] = ConvertDataForTableRecord(array[i])
|
||||
}
|
||||
model.data = list
|
||||
} else {
|
||||
model.data = DataToMapDeep(data[0])
|
||||
model.data = ConvertDataForTableRecord(data[0])
|
||||
}
|
||||
default:
|
||||
model.data = data[0]
|
||||
@ -170,10 +172,14 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
|
||||
list[k] = v
|
||||
}
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(list)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.db.DoBatchInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(list),
|
||||
newData,
|
||||
option,
|
||||
batch,
|
||||
)
|
||||
@ -190,10 +196,14 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
|
||||
data[fieldNameUpdate] = nowString
|
||||
}
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.db.DoInsert(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(data),
|
||||
newData,
|
||||
option,
|
||||
)
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"reflect"
|
||||
@ -431,23 +432,35 @@ func (m *Model) FindScan(pointer interface{}, where ...interface{}) error {
|
||||
// doGetAllBySql does the select statement on the database.
|
||||
func (m *Model) doGetAllBySql(sql string, args ...interface{}) (result Result, err error) {
|
||||
cacheKey := ""
|
||||
cacheObj := m.db.GetCache()
|
||||
// Retrieve from cache.
|
||||
if m.cacheEnabled && m.tx == nil {
|
||||
cacheKey = m.cacheName
|
||||
if len(cacheKey) == 0 {
|
||||
cacheKey = sql + "/" + gconv.String(args)
|
||||
cacheKey = sql + ", @PARAMS:" + gconv.String(args)
|
||||
}
|
||||
if v := m.db.GetCache().Get(cacheKey); v != nil {
|
||||
return v.(Result), nil
|
||||
if v, _ := cacheObj.GetVar(cacheKey); !v.IsNil() {
|
||||
if result, ok := v.Val().(Result); ok {
|
||||
// In-memory cache.
|
||||
return result, nil
|
||||
} else {
|
||||
// Other cache, it needs conversion.
|
||||
var result Result
|
||||
if err = json.Unmarshal(v.Bytes(), &result); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return result, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result, err = m.db.DoGetAll(m.getLink(false), sql, m.mergeArguments(args)...)
|
||||
// Cache the result.
|
||||
if cacheKey != "" && err == nil {
|
||||
if m.cacheDuration < 0 {
|
||||
m.db.GetCache().Remove(cacheKey)
|
||||
cacheObj.Remove(cacheKey)
|
||||
} else {
|
||||
m.db.GetCache().Set(cacheKey, result, m.cacheDuration)
|
||||
cacheObj.Set(cacheKey, result, m.cacheDuration)
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
|
||||
@ -21,6 +21,13 @@ const (
|
||||
gSOFT_FIELD_NAME_DELETE = "delete_at"
|
||||
)
|
||||
|
||||
// Unscoped disables the auto-update time feature for insert, update and delete options.
|
||||
func (m *Model) Unscoped() *Model {
|
||||
model := m.getModel()
|
||||
model.unscoped = true
|
||||
return model
|
||||
}
|
||||
|
||||
// getSoftFieldNameCreate checks and returns the field name for record creating time.
|
||||
// If there's no field name for storing creating time, it returns an empty string.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
|
||||
@ -59,7 +59,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
}
|
||||
switch refKind {
|
||||
case reflect.Map, reflect.Struct:
|
||||
dataMap := DataToMapDeep(m.data)
|
||||
dataMap := ConvertDataForTableRecord(m.data)
|
||||
gutil.MapDelete(dataMap, fieldNameCreate, fieldNameUpdate, fieldNameDelete)
|
||||
if fieldNameUpdate != "" {
|
||||
dataMap[fieldNameUpdate] = gtime.Now().String()
|
||||
@ -73,10 +73,14 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
updateData = updates
|
||||
}
|
||||
}
|
||||
newData, err := m.filterDataForInsertOrUpdate(updateData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m.db.DoUpdate(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
m.filterDataForInsertOrUpdate(updateData),
|
||||
newData,
|
||||
conditionWhere+conditionExtra,
|
||||
m.mergeArguments(conditionArgs)...,
|
||||
)
|
||||
|
||||
@ -28,27 +28,33 @@ func (m *Model) getModel() *Model {
|
||||
|
||||
// filterDataForInsertOrUpdate does filter feature with data for inserting/updating operations.
|
||||
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
|
||||
func (m *Model) filterDataForInsertOrUpdate(data interface{}) interface{} {
|
||||
func (m *Model) filterDataForInsertOrUpdate(data interface{}) (interface{}, error) {
|
||||
var err error
|
||||
switch value := data.(type) {
|
||||
case List:
|
||||
for k, item := range value {
|
||||
value[k] = m.doFilterDataMapForInsertOrUpdate(item, false)
|
||||
value[k], err = m.doMappingAndFilterForInsertOrUpdateDataMap(item, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return value
|
||||
return value, nil
|
||||
|
||||
case Map:
|
||||
return m.doFilterDataMapForInsertOrUpdate(value, true)
|
||||
return m.doMappingAndFilterForInsertOrUpdateDataMap(value, true)
|
||||
|
||||
default:
|
||||
return data
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
||||
// doFilterDataMapForInsertOrUpdate does the filter features for map.
|
||||
// doMappingAndFilterForInsertOrUpdateDataMap does the filter features for map.
|
||||
// Note that, it does not filter list item, which is also type of map, for "omit empty" feature.
|
||||
func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool) Map {
|
||||
if m.filter {
|
||||
data = m.db.filterFields(m.schema, m.tables, data)
|
||||
func (m *Model) doMappingAndFilterForInsertOrUpdateDataMap(data Map, allowOmitEmpty bool) (Map, error) {
|
||||
var err error
|
||||
data, err = m.db.mappingAndFilterData(m.schema, m.tables, data, m.filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Remove key-value pairs of which the value is empty.
|
||||
if allowOmitEmpty && m.option&OPTION_OMITEMPTY > 0 {
|
||||
@ -103,7 +109,7 @@ func (m *Model) doFilterDataMapForInsertOrUpdate(data Map, allowOmitEmpty bool)
|
||||
delete(data, v)
|
||||
}
|
||||
}
|
||||
return data
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// getLink returns the underlying database link object with configured <linkType> attribute.
|
||||
|
||||
@ -181,7 +181,7 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
|
||||
}
|
||||
}
|
||||
if len(relationDataMap) > 0 && !relationValue.IsValid() {
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
|
||||
}
|
||||
switch attrKind {
|
||||
case reflect.Array, reflect.Slice:
|
||||
@ -196,7 +196,7 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf(`relationKey should not be empty as field "%s" is slice`, attributeName)
|
||||
@ -207,15 +207,25 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
|
||||
if len(relationDataMap) > 0 {
|
||||
relationField = relationValue.FieldByName(relationAttrName)
|
||||
if relationField.IsValid() {
|
||||
if err = gconv.Struct(relationDataMap[gconv.String(relationField.Interface())], e); err != nil {
|
||||
v := relationDataMap[gconv.String(relationField.Interface())]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, e); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
|
||||
}
|
||||
} else {
|
||||
if err = gconv.Struct(r[i], e); err != nil {
|
||||
v := r[i]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -226,15 +236,25 @@ func (r Result) ScanList(listPointer interface{}, attributeName string, relation
|
||||
if len(relationDataMap) > 0 {
|
||||
relationField = relationValue.FieldByName(relationAttrName)
|
||||
if relationField.IsValid() {
|
||||
if err = gconv.Struct(relationDataMap[gconv.String(relationField.Interface())], e); err != nil {
|
||||
v := relationDataMap[gconv.String(relationField.Interface())]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, e); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// May be the attribute does not exist yet.
|
||||
return fmt.Errorf(`invalid relation: %s, %s`, relation[0], relation[1])
|
||||
return fmt.Errorf(`invalid relation: "%s:%s"`, relation[0], relation[1])
|
||||
}
|
||||
} else {
|
||||
if err = gconv.Struct(r[i], e); err != nil {
|
||||
v := r[i]
|
||||
if v == nil {
|
||||
// There's no relational data.
|
||||
continue
|
||||
}
|
||||
if err = gconv.Struct(v, e); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,7 +17,152 @@ import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Table_Relation(t *testing.T) {
|
||||
func Test_Table_Relation_One(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user_" + gtime.TimestampMicroStr()
|
||||
tableUserDetail = "user_detail_" + gtime.TimestampMicroStr()
|
||||
tableUserScores = "user_scores_" + gtime.TimestampMicroStr()
|
||||
)
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
name varchar(45) NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUser)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUser)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
uid int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
address varchar(45) NOT NULL,
|
||||
PRIMARY KEY (uid)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserDetail)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserDetail)
|
||||
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
uid int(10) unsigned NOT NULL,
|
||||
score int(10) unsigned NOT NULL,
|
||||
course varchar(45) NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`, tableUserScores)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(tableUserScores)
|
||||
|
||||
type EntityUser struct {
|
||||
Uid int `orm:"uid"`
|
||||
Name string `orm:"name"`
|
||||
}
|
||||
|
||||
type EntityUserDetail struct {
|
||||
Uid int `orm:"uid"`
|
||||
Address string `orm:"address"`
|
||||
}
|
||||
|
||||
type EntityUserScores struct {
|
||||
Id int `orm:"id"`
|
||||
Uid int `orm:"uid"`
|
||||
Score int `orm:"score"`
|
||||
Course string `orm:"course"`
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
User *EntityUser
|
||||
UserDetail *EntityUserDetail
|
||||
UserScores []*EntityUserScores
|
||||
}
|
||||
|
||||
// Initialize the data.
|
||||
var err error
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err = db.Transaction(func(tx *gdb.TX) error {
|
||||
r, err := tx.Table(tableUser).Save(EntityUser{
|
||||
Name: "john",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
uid, err := r.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Table(tableUserDetail).Save(EntityUserDetail{
|
||||
Uid: int(uid),
|
||||
Address: "Beijing DongZhiMen #66",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Table(tableUserScores).Save(g.Slice{
|
||||
EntityUserScores{Uid: int(uid), Score: 100, Course: "math"},
|
||||
EntityUserScores{Uid: int(uid), Score: 99, Course: "physics"},
|
||||
})
|
||||
return err
|
||||
})
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
// Data check.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r, err := db.Table(tableUser).All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r.Len(), 1)
|
||||
t.Assert(r[0]["uid"].Int(), 1)
|
||||
t.Assert(r[0]["name"].String(), "john")
|
||||
|
||||
r, err = db.Table(tableUserDetail).Where("uid", r[0]["uid"].Int()).All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r.Len(), 1)
|
||||
t.Assert(r[0]["uid"].Int(), 1)
|
||||
t.Assert(r[0]["address"].String(), `Beijing DongZhiMen #66`)
|
||||
|
||||
r, err = db.Table(tableUserScores).Where("uid", r[0]["uid"].Int()).All()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r.Len(), 2)
|
||||
t.Assert(r[0]["uid"].Int(), 1)
|
||||
t.Assert(r[1]["uid"].Int(), 1)
|
||||
t.Assert(r[0]["course"].String(), `math`)
|
||||
t.Assert(r[1]["course"].String(), `physics`)
|
||||
})
|
||||
// Entity query.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var user Entity
|
||||
// SELECT * FROM `user` WHERE `name`='john'
|
||||
err := db.Table(tableUser).Scan(&user.User, "name", "john")
|
||||
t.Assert(err, nil)
|
||||
|
||||
// SELECT * FROM `user_detail` WHERE `uid`=1
|
||||
err = db.Table(tableUserDetail).Scan(&user.UserDetail, "uid", user.User.Uid)
|
||||
t.Assert(err, nil)
|
||||
|
||||
// SELECT * FROM `user_scores` WHERE `uid`=1
|
||||
err = db.Table(tableUserScores).Scan(&user.UserScores, "uid", user.User.Uid)
|
||||
t.Assert(err, nil)
|
||||
|
||||
t.Assert(user.User, EntityUser{
|
||||
Uid: 1,
|
||||
Name: "john",
|
||||
})
|
||||
t.Assert(user.UserDetail, EntityUserDetail{
|
||||
Uid: 1,
|
||||
Address: "Beijing DongZhiMen #66",
|
||||
})
|
||||
t.Assert(user.UserScores, []EntityUserScores{
|
||||
{Id: 1, Uid: 1, Course: "math", Score: 100},
|
||||
{Id: 2, Uid: 1, Course: "physics", Score: 99},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Table_Relation_Many(t *testing.T) {
|
||||
var (
|
||||
tableUser = "user_" + gtime.TimestampMicroStr()
|
||||
tableUserDetail = "user_detail_" + gtime.TimestampMicroStr()
|
||||
@ -292,12 +292,12 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
// Fix issue: https://github.com/gogf/gf/issues/819
|
||||
func Test_Func_DataToMapDeep(t *testing.T) {
|
||||
func Test_Func_ConvertDataForTableRecord(t *testing.T) {
|
||||
type Test struct {
|
||||
ResetPasswordTokenAt mysql.NullTime `orm:"reset_password_token_at"`
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := DataToMapDeep(new(Test))
|
||||
m := ConvertDataForTableRecord(new(Test))
|
||||
t.Assert(len(m), 1)
|
||||
t.AssertNE(m["reset_password_token_at"], nil)
|
||||
t.Assert(m["reset_password_token_at"], new(mysql.NullTime))
|
||||
|
||||
@ -9,6 +9,7 @@ package gdb_test
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/encoding/gparser"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -183,6 +184,119 @@ func Test_DB_Insert(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// Fix issue: https://github.com/gogf/gf/issues/819
|
||||
func Test_DB_Insert_WithStructAndSliceAttribute(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Password struct {
|
||||
Salt string `json:"salt"`
|
||||
Pass string `json:"pass"`
|
||||
}
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "t1",
|
||||
"password": &Password{"123", "456"},
|
||||
"nickname": []string{"A", "B", "C"},
|
||||
"create_time": gtime.Now().String(),
|
||||
}
|
||||
_, err := db.Insert(table, data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["create_time"], data["create_time"])
|
||||
t.Assert(one["nickname"], gparser.MustToJson(data["nickname"]))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Insert_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_1",
|
||||
Password: "pass_1",
|
||||
Nickname: "name_1",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Insert(table, data)
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Upadte_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_10",
|
||||
Password: "pass_10",
|
||||
Nickname: "name_10",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Update(table, data, "id=1")
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.GetOne(fmt.Sprintf("SELECT * FROM %s WHERE id=?", table), 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_Insert_KeyFieldNameMapping_Error(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
NoneExistField string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_1",
|
||||
Password: "pass_1",
|
||||
Nickname: "name_1",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Insert(table, data)
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DB_InsertIgnore(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/encoding/gparser"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"testing"
|
||||
"time"
|
||||
@ -96,6 +97,140 @@ func Test_Model_Insert(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// Fix issue: https://github.com/gogf/gf/issues/819
|
||||
func Test_Model_Insert_WithStructAndSliceAttribute(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Password struct {
|
||||
Salt string `json:"salt"`
|
||||
Pass string `json:"pass"`
|
||||
}
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "t1",
|
||||
"password": &Password{"123", "456"},
|
||||
"nickname": []string{"A", "B", "C"},
|
||||
"create_time": gtime.Now().String(),
|
||||
}
|
||||
_, err := db.Table(table).Data(data).Insert()
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.Table(table).One("id", 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["create_time"], data["create_time"])
|
||||
t.Assert(one["nickname"], gparser.MustToJson(data["nickname"]))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Insert_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_1",
|
||||
Password: "pass_1",
|
||||
Nickname: "name_1",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Model(table).Data(data).Insert()
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Update_KeyFieldNameMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_10",
|
||||
Password: "pass_10",
|
||||
Nickname: "name_10",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Model(table).Data(data).WherePri(1).Update()
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.Model(table).FindOne(1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data.Passport)
|
||||
t.Assert(one["create_time"], data.CreateTime)
|
||||
t.Assert(one["nickname"], data.Nickname)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Insert_KeyFieldNameMapping_Error(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
Nickname string
|
||||
CreateTime string
|
||||
NoneExistFiled string
|
||||
}
|
||||
data := User{
|
||||
Id: 1,
|
||||
Passport: "user_1",
|
||||
Password: "pass_1",
|
||||
Nickname: "name_1",
|
||||
CreateTime: "2020-10-10 12:00:01",
|
||||
}
|
||||
_, err := db.Model(table).Data(data).Insert()
|
||||
t.AssertNE(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Insert_Time(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"passport": "t1",
|
||||
"password": "p1",
|
||||
"nickname": "n1",
|
||||
"create_time": "2020-10-10 20:09:18.334",
|
||||
}
|
||||
_, err := db.Table(table).Data(data).Insert()
|
||||
t.Assert(err, nil)
|
||||
|
||||
one, err := db.Table(table).One("id", 1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(one["passport"], data["passport"])
|
||||
t.Assert(one["create_time"], "2020-10-10 20:09:18")
|
||||
t.Assert(one["nickname"], data["nickname"])
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_BatchInsertWithArrayStruct(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
@ -151,7 +151,7 @@ CREATE TABLE %s (
|
||||
}
|
||||
|
||||
func Test_SoftUpdateTime(t *testing.T) {
|
||||
table := "time_test_table"
|
||||
table := "time_test_table_" + gtime.TimestampNanoStr()
|
||||
if _, err := db.Exec(fmt.Sprintf(`
|
||||
CREATE TABLE %s (
|
||||
id int(11) NOT NULL,
|
||||
|
||||
@ -764,3 +764,31 @@ func Test_Transaction(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Transaction_Panic(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := db.Transaction(func(tx *gdb.TX) error {
|
||||
if _, err := tx.Replace(table, g.Map{
|
||||
"id": 1,
|
||||
"passport": "USER_1",
|
||||
"password": "PASS_1",
|
||||
"nickname": "NAME_1",
|
||||
"create_time": gtime.Now().String(),
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
panic("error")
|
||||
return nil
|
||||
})
|
||||
t.AssertNE(err, nil)
|
||||
|
||||
if value, err := db.Table(table).Fields("nickname").Where("id", 1).Value(); err != nil {
|
||||
gtest.Error(err)
|
||||
} else {
|
||||
t.Assert(value.String(), "name_1")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -68,6 +68,7 @@ func StackWithFilters(filters []string, skip ...int) string {
|
||||
file[0:len(goRootForFilter)] == goRootForFilter {
|
||||
continue
|
||||
}
|
||||
// Custom filtering.
|
||||
filtered = false
|
||||
for _, filter := range filters {
|
||||
if filter != "" && strings.Contains(file, filter) {
|
||||
|
||||
@ -207,6 +207,21 @@ func LoadContentType(dataType string, data interface{}, safe ...bool) (*Json, er
|
||||
return doLoadContent(dataType, content, safe...)
|
||||
}
|
||||
|
||||
// IsValidDataType checks and returns whether given <dataType> a valid data type for loading.
|
||||
func IsValidDataType(dataType string) bool {
|
||||
if dataType == "" {
|
||||
return false
|
||||
}
|
||||
if dataType[0] == '.' {
|
||||
dataType = dataType[1:]
|
||||
}
|
||||
switch dataType {
|
||||
case "json", "js", "xml", "yaml", "yml", "toml", "ini":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checkDataType automatically checks and returns the data type for <content>.
|
||||
// Note that it uses regular expression for loose checking, you can use LoadXXX/LoadContentType
|
||||
// functions to load the content for certain content type.
|
||||
@ -215,8 +230,9 @@ func checkDataType(content []byte) string {
|
||||
return "json"
|
||||
} else if gregex.IsMatch(`^<.+>[\S\s]+<.+>$`, content) {
|
||||
return "xml"
|
||||
} else if (gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*\w+`, content)) ||
|
||||
(gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, content)) {
|
||||
} else if !gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*"""[\s\S]+"""`, content) && !gregex.IsMatch(`[\n\r]*[\s\t\w\-\."]+\s*=\s*'''[\s\S]+'''`, content) &&
|
||||
((gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`^[\n\r]*[\w\-\s\t]+\s*:\s*\w+`, content)) ||
|
||||
(gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*".+"`, content) || gregex.IsMatch(`[\n\r]+[\w\-\s\t]+\s*:\s*\w+`, content))) {
|
||||
return "yml"
|
||||
} else if !gregex.IsMatch(`^[\s\t\n\r]*;.+`, content) &&
|
||||
!gregex.IsMatch(`[\s\t\n\r]+;.+`, content) &&
|
||||
|
||||
@ -112,4 +112,22 @@ app_conf = ./config/app.ini
|
||||
`)
|
||||
t.Assert(checkDataType(data), "ini")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := []byte(`
|
||||
# API Server
|
||||
[server]
|
||||
address = ":8199"
|
||||
|
||||
# Jenkins
|
||||
[jenkins]
|
||||
url = "https://jenkins-swimlane.com"
|
||||
nodeJsStaticBuildCmdTpl = """
|
||||
npm i --registry=https://registry.npm.taobao.org
|
||||
wget http://consul.infra:8500/v1/kv/app_{{.SwimlaneName}}/{{.RepoName}}/.env.qa?raw=true -O ./env.qa
|
||||
npm run build:qa
|
||||
"""
|
||||
`)
|
||||
t.Assert(checkDataType(data), "toml")
|
||||
})
|
||||
}
|
||||
|
||||
@ -26,6 +26,12 @@ type ApiCause interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
// ApiLevel is the interface for Current/Next feature.
|
||||
type ApiLevel interface {
|
||||
Current() error
|
||||
Next() error
|
||||
}
|
||||
|
||||
// New creates and returns an error which is formatted from given text.
|
||||
func New(text string) error {
|
||||
if text == "" {
|
||||
@ -120,3 +126,27 @@ func Stack(err error) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Current creates and returns the current level error.
|
||||
// It returns nil if current level error is nil.
|
||||
func Current(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if e, ok := err.(ApiLevel); ok {
|
||||
return e.Current()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Next returns the next level error.
|
||||
// It returns nil if current level error or the next level error is nil.
|
||||
func Next(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if e, ok := err.(ApiLevel); ok {
|
||||
return e.Next()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ const (
|
||||
|
||||
var (
|
||||
// goRootForFilter is used for stack filtering purpose.
|
||||
// Mainly for development environment.
|
||||
goRootForFilter = runtime.GOROOT()
|
||||
)
|
||||
|
||||
@ -36,8 +37,11 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// Error implements the interface of Error, it returns the error as string.
|
||||
// Error implements the interface of Error, it returns all the error as string.
|
||||
func (err *Error) Error() string {
|
||||
if err == nil {
|
||||
return ""
|
||||
}
|
||||
if err.text != "" {
|
||||
if err.error != nil {
|
||||
return err.text + ": " + err.error.Error()
|
||||
@ -49,6 +53,9 @@ func (err *Error) Error() string {
|
||||
|
||||
// Cause returns the root cause error.
|
||||
func (err *Error) Cause() error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
loop := err
|
||||
for loop != nil {
|
||||
if loop.error != nil {
|
||||
@ -66,8 +73,8 @@ func (err *Error) Cause() error {
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %v, %s : Print the error string;
|
||||
// %-v, %-s : Print current error string;
|
||||
// %v, %s : Print all the error string;
|
||||
// %-v, %-s : Print current level error string;
|
||||
// %+s : Print full stack error list;
|
||||
// %+v : Print the error string and full stack error list;
|
||||
func (err *Error) Format(s fmt.State, verb rune) {
|
||||
@ -120,6 +127,28 @@ func (err *Error) Stack() string {
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// Current creates and returns the current level error.
|
||||
// It returns nil if current level error is nil.
|
||||
func (err *Error) Current() error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &Error{
|
||||
error: nil,
|
||||
stack: err.stack,
|
||||
text: err.text,
|
||||
}
|
||||
}
|
||||
|
||||
// Next returns the next level error.
|
||||
// It returns nil if current level error or the next level error is nil.
|
||||
func (err *Error) Next() error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return err.error
|
||||
}
|
||||
|
||||
// formatSubStack formats the stack for error.
|
||||
func formatSubStack(st stack, buffer *bytes.Buffer) {
|
||||
index := 1
|
||||
|
||||
@ -127,3 +127,31 @@ func Test_Stack(t *testing.T) {
|
||||
//fmt.Printf("%+v", err)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Current(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := errors.New("1")
|
||||
err = gerror.Wrap(err, "2")
|
||||
err = gerror.Wrap(err, "3")
|
||||
t.Assert(err.Error(), "3: 2: 1")
|
||||
t.Assert(gerror.Current(err).Error(), "3")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Next(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
err := errors.New("1")
|
||||
err = gerror.Wrap(err, "2")
|
||||
err = gerror.Wrap(err, "3")
|
||||
t.Assert(err.Error(), "3: 2: 1")
|
||||
|
||||
err = gerror.Next(err)
|
||||
t.Assert(err.Error(), "2: 1")
|
||||
|
||||
err = gerror.Next(err)
|
||||
t.Assert(err.Error(), "1")
|
||||
|
||||
err = gerror.Next(err)
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
@ -10,17 +10,14 @@ import (
|
||||
"github.com/gogf/gf/os/glog"
|
||||
)
|
||||
|
||||
// SetDebug disables/enables debug level for logging component globally.
|
||||
func SetDebug(debug bool) {
|
||||
glog.SetDebug(debug)
|
||||
}
|
||||
|
||||
// SetLogLevel sets the logging level globally.
|
||||
// Deprecated, use functions of package glog or g.Log() instead.
|
||||
func SetLogLevel(level int) {
|
||||
glog.SetLevel(level)
|
||||
}
|
||||
|
||||
// GetLogLevel returns the global logging level.
|
||||
// Deprecated, use functions of package glog or g.Log() instead.
|
||||
func GetLogLevel() int {
|
||||
return glog.GetLevel()
|
||||
}
|
||||
|
||||
@ -6,7 +6,17 @@
|
||||
|
||||
package g
|
||||
|
||||
import "github.com/gogf/gf/net/ghttp"
|
||||
import (
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
)
|
||||
|
||||
// SetEnabled enables/disables the GoFrame internal logging manually.
|
||||
// Note that this function is not concurrent safe, be aware of the DATA RACE,
|
||||
// which means you should call this function in your boot but not the runtime.
|
||||
func SetDebug(enabled bool) {
|
||||
intlog.SetEnabled(enabled)
|
||||
}
|
||||
|
||||
// SetServerGraceful enables/disables graceful reload feature of http Web Server.
|
||||
// This feature is disabled in default.
|
||||
|
||||
3
go.mod
3
go.mod
@ -5,7 +5,7 @@ go 1.11
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/clbanning/mxj v1.8.5-0.20200714211355-ff02cfb8ea28
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gomodule/redigo v2.0.0+incompatible
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
@ -15,7 +15,6 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.1
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
|
||||
golang.org/x/text v0.3.2
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
)
|
||||
|
||||
@ -33,9 +33,12 @@ func init() {
|
||||
}
|
||||
|
||||
// SetEnabled enables/disables the internal logging manually.
|
||||
// Note that this function is not current safe, be aware of the DATA RACE.
|
||||
// Note that this function is not concurrent safe, be aware of the DATA RACE.
|
||||
func SetEnabled(enabled bool) {
|
||||
isGFDebug = enabled
|
||||
// If they're the same, it does not write the <isGFDebug> but only reading operation.
|
||||
if isGFDebug != enabled {
|
||||
isGFDebug = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// IsEnabled checks and returns whether current process is in GF development.
|
||||
|
||||
@ -276,6 +276,10 @@ func (r *Request) parseBody() {
|
||||
return
|
||||
}
|
||||
r.parsedBody = true
|
||||
// There's no data posted.
|
||||
if r.ContentLength == 0 {
|
||||
return
|
||||
}
|
||||
if body := r.GetBody(); len(body) > 0 {
|
||||
// Trim space/new line characters.
|
||||
body = bytes.TrimSpace(body)
|
||||
@ -306,6 +310,10 @@ func (r *Request) parseForm() {
|
||||
return
|
||||
}
|
||||
r.parsedForm = true
|
||||
// There's no data posted.
|
||||
if r.ContentLength == 0 {
|
||||
return
|
||||
}
|
||||
if contentType := r.Header.Get("Content-Type"); contentType != "" {
|
||||
var err error
|
||||
if gstr.Contains(contentType, "multipart/") {
|
||||
|
||||
@ -92,7 +92,7 @@ type ServerConfig struct {
|
||||
// size of the request body.
|
||||
//
|
||||
// It can be configured in configuration file using string like: 1m, 10m, 500kb etc.
|
||||
// It's 1024 bytes in default.
|
||||
// It's 10240 bytes in default.
|
||||
MaxHeaderBytes int
|
||||
|
||||
// KeepAlive enables HTTP keep-alive.
|
||||
@ -157,6 +157,9 @@ type ServerConfig struct {
|
||||
// SessionIdName specifies the session id name.
|
||||
SessionIdName string
|
||||
|
||||
// SessionCookieOutput specifies whether automatic outputting session id to cookie.
|
||||
SessionCookieOutput bool
|
||||
|
||||
// SessionPath specifies the session storage directory path for storing session files.
|
||||
// It only makes sense if the session storage is type of file storage.
|
||||
SessionPath string
|
||||
@ -231,50 +234,56 @@ type ServerConfig struct {
|
||||
Graceful bool
|
||||
}
|
||||
|
||||
// Config creates and returns a ServerConfig object with default configurations.
|
||||
// Deprecated. Use NewConfig instead.
|
||||
func Config() ServerConfig {
|
||||
return NewConfig()
|
||||
}
|
||||
|
||||
// NewConfig creates and returns a ServerConfig object with default configurations.
|
||||
// Note that, do not define this default configuration to local package variable, as there're
|
||||
// some pointer attributes that may be shared in different servers.
|
||||
func Config() ServerConfig {
|
||||
func NewConfig() ServerConfig {
|
||||
return ServerConfig{
|
||||
Address: "",
|
||||
HTTPSAddr: "",
|
||||
Handler: nil,
|
||||
ReadTimeout: 60 * time.Second,
|
||||
WriteTimeout: 0, // No timeout.
|
||||
IdleTimeout: 60 * time.Second,
|
||||
MaxHeaderBytes: 10240, // 10KB
|
||||
KeepAlive: true,
|
||||
IndexFiles: []string{"index.html", "index.htm"},
|
||||
IndexFolder: false,
|
||||
ServerAgent: "GF HTTP Server",
|
||||
ServerRoot: "",
|
||||
StaticPaths: make([]staticPathItem, 0),
|
||||
FileServerEnabled: false,
|
||||
CookieMaxAge: time.Hour * 24 * 365,
|
||||
CookiePath: "/",
|
||||
CookieDomain: "",
|
||||
SessionMaxAge: time.Hour * 24,
|
||||
SessionIdName: "gfsessionid",
|
||||
SessionPath: gsession.DefaultStorageFilePath,
|
||||
Logger: glog.New(),
|
||||
LogStdout: true,
|
||||
ErrorStack: true,
|
||||
ErrorLogEnabled: true,
|
||||
ErrorLogPattern: "error-{Ymd}.log",
|
||||
AccessLogEnabled: false,
|
||||
AccessLogPattern: "access-{Ymd}.log",
|
||||
DumpRouterMap: true,
|
||||
ClientMaxBodySize: 8 * 1024 * 1024, // 8MB
|
||||
FormParsingMemory: 1024 * 1024, // 1MB
|
||||
Rewrites: make(map[string]string),
|
||||
Graceful: false,
|
||||
Address: "",
|
||||
HTTPSAddr: "",
|
||||
Handler: nil,
|
||||
ReadTimeout: 60 * time.Second,
|
||||
WriteTimeout: 0, // No timeout.
|
||||
IdleTimeout: 60 * time.Second,
|
||||
MaxHeaderBytes: 10240, // 10KB
|
||||
KeepAlive: true,
|
||||
IndexFiles: []string{"index.html", "index.htm"},
|
||||
IndexFolder: false,
|
||||
ServerAgent: "GF HTTP Server",
|
||||
ServerRoot: "",
|
||||
StaticPaths: make([]staticPathItem, 0),
|
||||
FileServerEnabled: false,
|
||||
CookieMaxAge: time.Hour * 24 * 365,
|
||||
CookiePath: "/",
|
||||
CookieDomain: "",
|
||||
SessionMaxAge: time.Hour * 24,
|
||||
SessionIdName: "gfsessionid",
|
||||
SessionPath: gsession.DefaultStorageFilePath,
|
||||
SessionCookieOutput: true,
|
||||
Logger: glog.New(),
|
||||
LogStdout: true,
|
||||
ErrorStack: true,
|
||||
ErrorLogEnabled: true,
|
||||
ErrorLogPattern: "error-{Ymd}.log",
|
||||
AccessLogEnabled: false,
|
||||
AccessLogPattern: "access-{Ymd}.log",
|
||||
DumpRouterMap: true,
|
||||
ClientMaxBodySize: 8 * 1024 * 1024, // 8MB
|
||||
FormParsingMemory: 1024 * 1024, // 1MB
|
||||
Rewrites: make(map[string]string),
|
||||
Graceful: false,
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigFromMap creates and returns a ServerConfig object with given map and
|
||||
// default configuration object.
|
||||
func ConfigFromMap(m map[string]interface{}) (ServerConfig, error) {
|
||||
config := Config()
|
||||
config := NewConfig()
|
||||
if err := gconv.Struct(m, &config); err != nil {
|
||||
return config, err
|
||||
}
|
||||
|
||||
@ -27,6 +27,11 @@ func (s *Server) SetSessionStorage(storage gsession.Storage) {
|
||||
s.config.SessionStorage = storage
|
||||
}
|
||||
|
||||
// SetSessionCookieOutput sets the SetSessionCookieOutput for server.
|
||||
func (s *Server) SetSessionCookieOutput(enabled bool) {
|
||||
s.config.SessionCookieOutput = enabled
|
||||
}
|
||||
|
||||
// GetSessionMaxAge returns the SessionMaxAge of server.
|
||||
func (s *Server) GetSessionMaxAge() time.Duration {
|
||||
return s.config.SessionMaxAge
|
||||
|
||||
@ -161,7 +161,9 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Automatically set the session id to cookie
|
||||
// if it creates a new session id in this request.
|
||||
if request.Session.IsDirty() && request.Session.Id() != request.GetSessionId() {
|
||||
if s.config.SessionCookieOutput &&
|
||||
request.Session.IsDirty() &&
|
||||
request.Session.Id() != request.GetSessionId() {
|
||||
request.Cookie.SetSessionId(request.Session.Id())
|
||||
}
|
||||
// Output the cookie content to client.
|
||||
|
||||
@ -24,14 +24,19 @@ const (
|
||||
|
||||
// EnablePProf enables PProf feature for server.
|
||||
func (s *Server) EnablePProf(pattern ...string) {
|
||||
s.Domain(gDEFAULT_DOMAIN).EnablePProf(pattern...)
|
||||
}
|
||||
|
||||
// EnablePProf enables PProf feature for server of specified domain.
|
||||
func (d *Domain) EnablePProf(pattern ...string) {
|
||||
p := gDEFAULT_PPROF_PATTERN
|
||||
if len(pattern) > 0 && pattern[0] != "" {
|
||||
p = pattern[0]
|
||||
}
|
||||
up := &utilPProf{}
|
||||
_, _, uri, _ := s.parsePattern(p)
|
||||
_, _, uri, _ := d.server.parsePattern(p)
|
||||
uri = strings.TrimRight(uri, "/")
|
||||
s.Group(uri, func(group *RouterGroup) {
|
||||
d.Group(uri, func(group *RouterGroup) {
|
||||
group.ALL("/*action", up.Index)
|
||||
group.ALL("/cmdline", up.Cmdline)
|
||||
group.ALL("/profile", up.Profile)
|
||||
|
||||
@ -44,13 +44,15 @@ func (s *Server) getHandlersWithCache(r *Request) (parsedItems []*handlerParsedI
|
||||
}
|
||||
}
|
||||
// Search and cache the router handlers.
|
||||
value := s.serveCache.GetOrSetFunc(s.serveHandlerKey(method, r.URL.Path, r.GetHost()), func() interface{} {
|
||||
parsedItems, hasHook, hasServe = s.searchHandlers(method, r.URL.Path, r.GetHost())
|
||||
if parsedItems != nil {
|
||||
return &handlerCacheItem{parsedItems, hasHook, hasServe}
|
||||
}
|
||||
return nil
|
||||
}, gROUTE_CACHE_DURATION)
|
||||
value, _ := s.serveCache.GetOrSetFunc(
|
||||
s.serveHandlerKey(method, r.URL.Path, r.GetHost()),
|
||||
func() (interface{}, error) {
|
||||
parsedItems, hasHook, hasServe = s.searchHandlers(method, r.URL.Path, r.GetHost())
|
||||
if parsedItems != nil {
|
||||
return &handlerCacheItem{parsedItems, hasHook, hasServe}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}, gROUTE_CACHE_DURATION)
|
||||
if value != nil {
|
||||
item := value.(*handlerCacheItem)
|
||||
return item.parsedItems, item.hasHook, item.hasServe
|
||||
|
||||
@ -152,3 +152,47 @@ func Test_Session_StorageFile(t *testing.T) {
|
||||
t.Assert(client.GetContent("/get?k=key1"), "")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Session_Custom_Id(t *testing.T) {
|
||||
var (
|
||||
sessionId = "1234567890"
|
||||
key = "key"
|
||||
value = "value"
|
||||
p, _ = ports.PopRand()
|
||||
s = g.Server(p)
|
||||
)
|
||||
s.BindHandler("/id", func(r *ghttp.Request) {
|
||||
if err := r.Session.SetId(sessionId); err != nil {
|
||||
r.Response.WriteExit(err.Error())
|
||||
}
|
||||
if err := r.Session.Set(key, value); err != nil {
|
||||
r.Response.WriteExit(err.Error())
|
||||
}
|
||||
r.Response.WriteExit(r.Session.Id())
|
||||
})
|
||||
s.BindHandler("/value", func(r *ghttp.Request) {
|
||||
r.Response.WriteExit(r.Session.Get(key))
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
r, err := client.Get("/id")
|
||||
t.Assert(err, nil)
|
||||
defer r.Close()
|
||||
t.Assert(r.ReadAllString(), sessionId)
|
||||
t.Assert(r.GetCookie(s.GetSessionIdName()), sessionId)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
client.SetHeader(s.GetSessionIdName(), sessionId)
|
||||
t.Assert(client.GetContent("/value"), value)
|
||||
})
|
||||
}
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gcache provides high performance and concurrent-safe in-memory cache for process.
|
||||
// Package gcache provides kinds of cache management for process.
|
||||
// It default provides a concurrent-safe in-memory cache adapter for process.
|
||||
package gcache
|
||||
|
||||
import (
|
||||
@ -21,33 +22,27 @@ func Set(key interface{}, value interface{}, duration time.Duration) {
|
||||
defaultCache.Set(key, value, duration)
|
||||
}
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned <exist> value is false if the <key> does not exist in the cache.
|
||||
func Update(key interface{}, value interface{}) (oldValue interface{}, exist bool) {
|
||||
return defaultCache.Update(key, value)
|
||||
}
|
||||
|
||||
// SetIfNotExist sets cache with <key>-<value> pair if <key> does not exist in the cache,
|
||||
// which is expired after <duration>. It does not expire if <duration> == 0.
|
||||
func SetIfNotExist(key interface{}, value interface{}, duration time.Duration) bool {
|
||||
func SetIfNotExist(key interface{}, value interface{}, duration time.Duration) (bool, error) {
|
||||
return defaultCache.SetIfNotExist(key, value, duration)
|
||||
}
|
||||
|
||||
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func Sets(data map[interface{}]interface{}, duration time.Duration) {
|
||||
defaultCache.Sets(data, duration)
|
||||
func Sets(data map[interface{}]interface{}, duration time.Duration) error {
|
||||
return defaultCache.Sets(data, duration)
|
||||
}
|
||||
|
||||
// Get returns the value of <key>.
|
||||
// It returns nil if it does not exist or its value is nil.
|
||||
func Get(key interface{}) interface{} {
|
||||
func Get(key interface{}) (interface{}, error) {
|
||||
return defaultCache.Get(key)
|
||||
}
|
||||
|
||||
// GetVar retrieves and returns the value of <key> as gvar.Var.
|
||||
func GetVar(key interface{}) *gvar.Var {
|
||||
func GetVar(key interface{}) (*gvar.Var, error) {
|
||||
return defaultCache.GetVar(key)
|
||||
}
|
||||
|
||||
@ -56,14 +51,14 @@ func GetVar(key interface{}) *gvar.Var {
|
||||
// The key-value pair expires after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func GetOrSet(key interface{}, value interface{}, duration time.Duration) interface{} {
|
||||
func GetOrSet(key interface{}, value interface{}, duration time.Duration) (interface{}, error) {
|
||||
return defaultCache.GetOrSet(key, value, duration)
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value of <key>, or sets <key> with result of function <f>
|
||||
// and returns its result if <key> does not exist in the cache. The key-value pair expires
|
||||
// after <duration>. It does not expire if <duration> == 0.
|
||||
func GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
func GetOrSetFunc(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error) {
|
||||
return defaultCache.GetOrSetFunc(key, f, duration)
|
||||
}
|
||||
|
||||
@ -72,18 +67,18 @@ func GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration)
|
||||
// after <duration>. It does not expire if <duration> == 0.
|
||||
//
|
||||
// Note that the function <f> is executed within writing mutex lock.
|
||||
func GetOrSetFuncLock(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
func GetOrSetFuncLock(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error) {
|
||||
return defaultCache.GetOrSetFuncLock(key, f, duration)
|
||||
}
|
||||
|
||||
// Contains returns true if <key> exists in the cache, or else returns false.
|
||||
func Contains(key interface{}) bool {
|
||||
func Contains(key interface{}) (bool, error) {
|
||||
return defaultCache.Contains(key)
|
||||
}
|
||||
|
||||
// Remove deletes the one or more keys from cache, and returns its value.
|
||||
// If multiple keys are given, it returns the value of the deleted last item.
|
||||
func Remove(keys ...interface{}) (value interface{}) {
|
||||
func Remove(keys ...interface{}) (value interface{}, err error) {
|
||||
return defaultCache.Remove(keys...)
|
||||
}
|
||||
|
||||
@ -94,38 +89,44 @@ func Removes(keys []interface{}) {
|
||||
}
|
||||
|
||||
// Data returns a copy of all key-value pairs in the cache as map type.
|
||||
func Data() map[interface{}]interface{} {
|
||||
func Data() (map[interface{}]interface{}, error) {
|
||||
return defaultCache.Data()
|
||||
}
|
||||
|
||||
// Keys returns all keys in the cache as slice.
|
||||
func Keys() []interface{} {
|
||||
func Keys() ([]interface{}, error) {
|
||||
return defaultCache.Keys()
|
||||
}
|
||||
|
||||
// KeyStrings returns all keys in the cache as string slice.
|
||||
func KeyStrings() []string {
|
||||
func KeyStrings() ([]string, error) {
|
||||
return defaultCache.KeyStrings()
|
||||
}
|
||||
|
||||
// Values returns all values in the cache as slice.
|
||||
func Values() []interface{} {
|
||||
func Values() ([]interface{}, error) {
|
||||
return defaultCache.Values()
|
||||
}
|
||||
|
||||
// Size returns the size of the cache.
|
||||
func Size() int {
|
||||
func Size() (int, error) {
|
||||
return defaultCache.Size()
|
||||
}
|
||||
|
||||
// GetExpire retrieves and returns the expiration of <key>.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func GetExpire(key interface{}) time.Duration {
|
||||
func GetExpire(key interface{}) (time.Duration, error) {
|
||||
return defaultCache.GetExpire(key)
|
||||
}
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned <exist> value is false if the <key> does not exist in the cache.
|
||||
func Update(key interface{}, value interface{}) (oldValue interface{}, exist bool, err error) {
|
||||
return defaultCache.Update(key, value)
|
||||
}
|
||||
|
||||
// UpdateExpire updates the expiration of <key> and returns the old expiration duration value.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration) {
|
||||
func UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration, err error) {
|
||||
return defaultCache.UpdateExpire(key, duration)
|
||||
}
|
||||
|
||||
117
os/gcache/gcache_adapter.go
Normal file
117
os/gcache/gcache_adapter.go
Normal file
@ -0,0 +1,117 @@
|
||||
// Copyright 2020 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Adapter is the adapter for cache features implements.
|
||||
type Adapter interface {
|
||||
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0.
|
||||
Set(key interface{}, value interface{}, duration time.Duration) error
|
||||
|
||||
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the keys of <data> if <duration> < 0 or given <value> is nil.
|
||||
Sets(data map[interface{}]interface{}, duration time.Duration) error
|
||||
|
||||
// SetIfNotExist sets cache with <key>-<value> pair which is expired after <duration>
|
||||
// if <key> does not exist in the cache. It returns true the <key> dose not exist in the
|
||||
// cache and it sets <value> successfully to the cache, or else it returns false.
|
||||
//
|
||||
// The parameter <value> can be type of <func() interface{}>, but it dose nothing if its
|
||||
// result is nil.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0 or given <value> is nil.
|
||||
SetIfNotExist(key interface{}, value interface{}, duration time.Duration) (bool, error)
|
||||
|
||||
// Get retrieves and returns the associated value of given <key>.
|
||||
// It returns nil if it does not exist or its value is nil.
|
||||
Get(key interface{}) (interface{}, error)
|
||||
|
||||
// GetOrSet retrieves and returns the value of <key>, or sets <key>-<value> pair and
|
||||
// returns <value> if <key> does not exist in the cache. The key-value pair expires
|
||||
// after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0 or given <value> is nil, but it does nothing
|
||||
// if <value> is a function and the function result is nil.
|
||||
GetOrSet(key interface{}, value interface{}, duration time.Duration) (interface{}, error)
|
||||
|
||||
// GetOrSetFunc retrieves and returns the value of <key>, or sets <key> with result of
|
||||
// function <f> and returns its result if <key> does not exist in the cache. The key-value
|
||||
// pair expires after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0 or given <value> is nil, but it does nothing
|
||||
// if <value> is a function and the function result is nil.
|
||||
GetOrSetFunc(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error)
|
||||
|
||||
// GetOrSetFuncLock retrieves and returns the value of <key>, or sets <key> with result of
|
||||
// function <f> and returns its result if <key> does not exist in the cache. The key-value
|
||||
// pair expires after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It does nothing if function <f> returns nil.
|
||||
//
|
||||
// Note that the function <f> should be executed within writing mutex lock for concurrent
|
||||
// safety purpose.
|
||||
GetOrSetFuncLock(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error)
|
||||
|
||||
// Contains returns true if <key> exists in the cache, or else returns false.
|
||||
Contains(key interface{}) (bool, error)
|
||||
|
||||
// GetExpire retrieves and returns the expiration of <key> in the cache.
|
||||
//
|
||||
// It returns 0 if the <key> does not expire.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
GetExpire(key interface{}) (time.Duration, error)
|
||||
|
||||
// Remove deletes one or more keys from cache, and returns its value.
|
||||
// If multiple keys are given, it returns the value of the last deleted item.
|
||||
Remove(keys ...interface{}) (value interface{}, err error)
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned value <exist> is false if the <key> does not exist in the cache.
|
||||
//
|
||||
// It deletes the <key> if given <value> is nil.
|
||||
// It does nothing if <key> does not exist in the cache.
|
||||
Update(key interface{}, value interface{}) (oldValue interface{}, exist bool, err error)
|
||||
|
||||
// UpdateExpire updates the expiration of <key> and returns the old expiration duration value.
|
||||
//
|
||||
// It returns -1 and does nothing if the <key> does not exist in the cache.
|
||||
// It deletes the <key> if <duration> < 0.
|
||||
UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration, err error)
|
||||
|
||||
// Size returns the number of items in the cache.
|
||||
Size() (size int, err error)
|
||||
|
||||
// Data returns a copy of all key-value pairs in the cache as map type.
|
||||
// Note that this function may leads lots of memory usage, you can implement this function
|
||||
// if necessary.
|
||||
Data() (map[interface{}]interface{}, error)
|
||||
|
||||
// Keys returns all keys in the cache as slice.
|
||||
Keys() ([]interface{}, error)
|
||||
|
||||
// Values returns all values in the cache as slice.
|
||||
Values() ([]interface{}, error)
|
||||
|
||||
// Clear clears all data of the cache.
|
||||
// Note that this function is sensitive and should be carefully used.
|
||||
Clear() error
|
||||
|
||||
// Close closes the cache if necessary.
|
||||
Close() error
|
||||
}
|
||||
@ -7,7 +7,6 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
@ -17,11 +16,10 @@ import (
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/os/gtimer"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
// Internal cache object.
|
||||
type memCache struct {
|
||||
type adapterMemory struct {
|
||||
// dataMu ensures the concurrent safety of underlying data map.
|
||||
dataMu sync.RWMutex
|
||||
|
||||
@ -38,7 +36,7 @@ type memCache struct {
|
||||
cap int
|
||||
|
||||
// data is the underlying cache data which is stored in a hash table.
|
||||
data map[interface{}]memCacheItem
|
||||
data map[interface{}]adapterMemoryItem
|
||||
|
||||
// expireTimes is the expiring key to its timestamp mapping,
|
||||
// which is used for quick indexing and deleting.
|
||||
@ -49,7 +47,7 @@ type memCache struct {
|
||||
expireSets map[int64]*gset.Set
|
||||
|
||||
// lru is the LRU manager, which is enabled when attribute cap > 0.
|
||||
lru *memCacheLru
|
||||
lru *adapterMemoryLru
|
||||
|
||||
// lruGetList is the LRU history according with Get function.
|
||||
lruGetList *glist.List
|
||||
@ -62,13 +60,13 @@ type memCache struct {
|
||||
}
|
||||
|
||||
// Internal cache item.
|
||||
type memCacheItem struct {
|
||||
type adapterMemoryItem struct {
|
||||
v interface{} // Value.
|
||||
e int64 // Expire timestamp in milliseconds.
|
||||
}
|
||||
|
||||
// Internal event item.
|
||||
type memCacheEvent struct {
|
||||
type adapterMemoryEvent struct {
|
||||
k interface{} // Key.
|
||||
e int64 // Expire time in milliseconds.
|
||||
}
|
||||
@ -79,11 +77,11 @@ const (
|
||||
gDEFAULT_MAX_EXPIRE = 9223372036854
|
||||
)
|
||||
|
||||
// newMemCache creates and returns a new memory cache object.
|
||||
func newMemCache(lruCap ...int) *memCache {
|
||||
c := &memCache{
|
||||
// newAdapterMemory creates and returns a new memory cache object.
|
||||
func newAdapterMemory(lruCap ...int) *adapterMemory {
|
||||
c := &adapterMemory{
|
||||
lruGetList: glist.New(true),
|
||||
data: make(map[interface{}]memCacheItem),
|
||||
data: make(map[interface{}]adapterMemoryItem),
|
||||
expireTimes: make(map[interface{}]int64),
|
||||
expireSets: make(map[int64]*gset.Set),
|
||||
eventList: glist.New(true),
|
||||
@ -97,96 +95,330 @@ func newMemCache(lruCap ...int) *memCache {
|
||||
}
|
||||
|
||||
// Set sets cache with <key>-<value> pair, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func (c *memCache) Set(key interface{}, value interface{}, duration time.Duration) {
|
||||
// It deletes the <key> if <duration> < 0.
|
||||
func (c *adapterMemory) Set(key interface{}, value interface{}, duration time.Duration) error {
|
||||
expireTime := c.getInternalExpire(duration)
|
||||
c.dataMu.Lock()
|
||||
c.data[key] = memCacheItem{
|
||||
c.data[key] = adapterMemoryItem{
|
||||
v: value,
|
||||
e: expireTime,
|
||||
}
|
||||
c.dataMu.Unlock()
|
||||
c.eventList.PushBack(&memCacheEvent{
|
||||
c.eventList.PushBack(&adapterMemoryEvent{
|
||||
k: key,
|
||||
e: expireTime,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update updates the value of <key> without changing its expiration and returns the old value.
|
||||
// The returned <exist> value is false if the <key> does not exist in the cache.
|
||||
func (c *memCache) Update(key interface{}, value interface{}) (oldValue interface{}, exist bool) {
|
||||
// The returned value <exist> is false if the <key> does not exist in the cache.
|
||||
//
|
||||
// It deletes the <key> if given <value> is nil.
|
||||
// It does nothing if <key> does not exist in the cache.
|
||||
func (c *adapterMemory) Update(key interface{}, value interface{}) (oldValue interface{}, exist bool, err error) {
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
c.data[key] = memCacheItem{
|
||||
c.data[key] = adapterMemoryItem{
|
||||
v: value,
|
||||
e: item.e,
|
||||
}
|
||||
return item.v, true
|
||||
return item.v, true, nil
|
||||
}
|
||||
return nil, false
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// UpdateExpire updates the expiration of <key> and returns the old expiration duration value.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func (c *memCache) UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration) {
|
||||
//
|
||||
// It returns -1 and does nothing if the <key> does not exist in the cache.
|
||||
// It deletes the <key> if <duration> < 0.
|
||||
func (c *adapterMemory) UpdateExpire(key interface{}, duration time.Duration) (oldDuration time.Duration, err error) {
|
||||
newExpireTime := c.getInternalExpire(duration)
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
c.data[key] = memCacheItem{
|
||||
c.data[key] = adapterMemoryItem{
|
||||
v: item.v,
|
||||
e: newExpireTime,
|
||||
}
|
||||
c.eventList.PushBack(&memCacheEvent{
|
||||
c.eventList.PushBack(&adapterMemoryEvent{
|
||||
k: key,
|
||||
e: newExpireTime,
|
||||
})
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond, nil
|
||||
}
|
||||
return -1
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// GetExpire retrieves and returns the expiration of <key>.
|
||||
// GetExpire retrieves and returns the expiration of <key> in the cache.
|
||||
//
|
||||
// It returns 0 if the <key> does not expire.
|
||||
// It returns -1 if the <key> does not exist in the cache.
|
||||
func (c *memCache) GetExpire(key interface{}) time.Duration {
|
||||
func (c *adapterMemory) GetExpire(key interface{}) (time.Duration, error) {
|
||||
c.dataMu.RLock()
|
||||
defer c.dataMu.RUnlock()
|
||||
if item, ok := c.data[key]; ok {
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond
|
||||
return time.Duration(item.e-gtime.TimestampMilli()) * time.Millisecond, nil
|
||||
}
|
||||
return -1
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// SetIfNotExist sets cache with <key>-<value> pair which is expired after <duration>
|
||||
// if <key> does not exist in the cache. It returns true the <key> dose not exist in the
|
||||
// cache and it sets <value> successfully to the cache, or else it returns false.
|
||||
// The parameter <value> can be type of <func() interface{}>, but it dose nothing if its
|
||||
// result is nil.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0 or given <value> is nil.
|
||||
func (c *adapterMemory) SetIfNotExist(key interface{}, value interface{}, duration time.Duration) (bool, error) {
|
||||
isContained, err := c.Contains(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !isContained {
|
||||
_, err := c.doSetWithLockCheck(key, value, duration)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the keys of <data> if <duration> < 0 or given <value> is nil.
|
||||
func (c *adapterMemory) Sets(data map[interface{}]interface{}, duration time.Duration) error {
|
||||
expireTime := c.getInternalExpire(duration)
|
||||
for k, v := range data {
|
||||
c.dataMu.Lock()
|
||||
c.data[k] = adapterMemoryItem{
|
||||
v: v,
|
||||
e: expireTime,
|
||||
}
|
||||
c.dataMu.Unlock()
|
||||
c.eventList.PushBack(&adapterMemoryEvent{
|
||||
k: k,
|
||||
e: expireTime,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get retrieves and returns the associated value of given <key>.
|
||||
// It returns nil if it does not exist or its value is nil.
|
||||
func (c *adapterMemory) Get(key interface{}) (interface{}, error) {
|
||||
c.dataMu.RLock()
|
||||
item, ok := c.data[key]
|
||||
c.dataMu.RUnlock()
|
||||
if ok && !item.IsExpired() {
|
||||
// Adding to LRU history if LRU feature is enabled.
|
||||
if c.cap > 0 {
|
||||
c.lruGetList.PushBack(key)
|
||||
}
|
||||
return item.v, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetOrSet retrieves and returns the value of <key>, or sets <key>-<value> pair and
|
||||
// returns <value> if <key> does not exist in the cache. The key-value pair expires
|
||||
// after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0 or given <value> is nil, but it does nothing
|
||||
// if <value> is a function and the function result is nil.
|
||||
func (c *adapterMemory) GetOrSet(key interface{}, value interface{}, duration time.Duration) (interface{}, error) {
|
||||
v, err := c.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v == nil {
|
||||
return c.doSetWithLockCheck(key, value, duration)
|
||||
} else {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc retrieves and returns the value of <key>, or sets <key> with result of
|
||||
// function <f> and returns its result if <key> does not exist in the cache. The key-value
|
||||
// pair expires after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It deletes the <key> if <duration> < 0 or given <value> is nil, but it does nothing
|
||||
// if <value> is a function and the function result is nil.
|
||||
func (c *adapterMemory) GetOrSetFunc(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error) {
|
||||
v, err := c.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v == nil {
|
||||
value, err := f()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if value == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return c.doSetWithLockCheck(key, value, duration)
|
||||
} else {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock retrieves and returns the value of <key>, or sets <key> with result of
|
||||
// function <f> and returns its result if <key> does not exist in the cache. The key-value
|
||||
// pair expires after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It does nothing if function <f> returns nil.
|
||||
//
|
||||
// Note that the function <f> should be executed within writing mutex lock for concurrent
|
||||
// safety purpose.
|
||||
func (c *adapterMemory) GetOrSetFuncLock(key interface{}, f func() (interface{}, error), duration time.Duration) (interface{}, error) {
|
||||
v, err := c.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v == nil {
|
||||
return c.doSetWithLockCheck(key, f, duration)
|
||||
} else {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if <key> exists in the cache, or else returns false.
|
||||
func (c *adapterMemory) Contains(key interface{}) (bool, error) {
|
||||
v, err := c.Get(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return v != nil, nil
|
||||
}
|
||||
|
||||
// Remove deletes the one or more keys from cache, and returns its value.
|
||||
// If multiple keys are given, it returns the value of the deleted last item.
|
||||
func (c *adapterMemory) Remove(keys ...interface{}) (value interface{}, err error) {
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
for _, key := range keys {
|
||||
item, ok := c.data[key]
|
||||
if ok {
|
||||
value = item.v
|
||||
delete(c.data, key)
|
||||
c.eventList.PushBack(&adapterMemoryEvent{
|
||||
k: key,
|
||||
e: gtime.TimestampMilli() - 1000,
|
||||
})
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Data returns a copy of all key-value pairs in the cache as map type.
|
||||
func (c *adapterMemory) Data() (map[interface{}]interface{}, error) {
|
||||
m := make(map[interface{}]interface{})
|
||||
c.dataMu.RLock()
|
||||
for k, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
m[k] = v.v
|
||||
}
|
||||
}
|
||||
c.dataMu.RUnlock()
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// Keys returns all keys in the cache as slice.
|
||||
func (c *adapterMemory) Keys() ([]interface{}, error) {
|
||||
keys := make([]interface{}, 0)
|
||||
c.dataMu.RLock()
|
||||
for k, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
c.dataMu.RUnlock()
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// Values returns all values in the cache as slice.
|
||||
func (c *adapterMemory) Values() ([]interface{}, error) {
|
||||
values := make([]interface{}, 0)
|
||||
c.dataMu.RLock()
|
||||
for _, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
values = append(values, v.v)
|
||||
}
|
||||
}
|
||||
c.dataMu.RUnlock()
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// Size returns the size of the cache.
|
||||
func (c *adapterMemory) Size() (size int, err error) {
|
||||
c.dataMu.RLock()
|
||||
size = len(c.data)
|
||||
c.dataMu.RUnlock()
|
||||
return size, nil
|
||||
}
|
||||
|
||||
// Clear clears all data of the cache.
|
||||
// Note that this function is sensitive and should be carefully used.
|
||||
func (c *adapterMemory) Clear() error {
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
c.data = make(map[interface{}]adapterMemoryItem)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the cache.
|
||||
func (c *adapterMemory) Close() error {
|
||||
if c.cap > 0 {
|
||||
c.lru.Close()
|
||||
}
|
||||
c.closed.Set(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// doSetWithLockCheck sets cache with <key>-<value> pair if <key> does not exist in the
|
||||
// cache, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// The parameter <value> can be type of <func() interface{}>, but it dose nothing if its
|
||||
// result is nil.
|
||||
// The parameter <value> can be type of <func() interface{}>, but it dose nothing if the
|
||||
// function result is nil.
|
||||
//
|
||||
// It doubly checks the <key> whether exists in the cache using mutex writing lock
|
||||
// before setting it to the cache.
|
||||
func (c *memCache) doSetWithLockCheck(key interface{}, value interface{}, duration time.Duration) interface{} {
|
||||
func (c *adapterMemory) doSetWithLockCheck(key interface{}, value interface{}, duration time.Duration) (interface{}, error) {
|
||||
expireTimestamp := c.getInternalExpire(duration)
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
if v, ok := c.data[key]; ok && !v.IsExpired() {
|
||||
return v.v
|
||||
return v.v, nil
|
||||
}
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
if value == nil {
|
||||
return nil
|
||||
if f, ok := value.(func() (interface{}, error)); ok {
|
||||
v, err := f()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
} else {
|
||||
value = v
|
||||
}
|
||||
}
|
||||
c.data[key] = memCacheItem{v: value, e: expireTimestamp}
|
||||
c.eventList.PushBack(&memCacheEvent{k: key, e: expireTimestamp})
|
||||
return value
|
||||
c.data[key] = adapterMemoryItem{v: value, e: expireTimestamp}
|
||||
c.eventList.PushBack(&adapterMemoryEvent{k: key, e: expireTimestamp})
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// getInternalExpire converts and returns the expire time with given expired duration in milliseconds.
|
||||
func (c *memCache) getInternalExpire(duration time.Duration) int64 {
|
||||
func (c *adapterMemory) getInternalExpire(duration time.Duration) int64 {
|
||||
if duration == 0 {
|
||||
return gDEFAULT_MAX_EXPIRE
|
||||
} else {
|
||||
@ -195,12 +427,12 @@ func (c *memCache) getInternalExpire(duration time.Duration) int64 {
|
||||
}
|
||||
|
||||
// makeExpireKey groups the <expire> in milliseconds to its according seconds.
|
||||
func (c *memCache) makeExpireKey(expire int64) int64 {
|
||||
func (c *adapterMemory) makeExpireKey(expire int64) int64 {
|
||||
return int64(math.Ceil(float64(expire/1000)+1) * 1000)
|
||||
}
|
||||
|
||||
// getExpireSet returns the expire set for given <expire> in seconds.
|
||||
func (c *memCache) getExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
func (c *adapterMemory) getExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
c.expireSetMu.RLock()
|
||||
expireSet, _ = c.expireSets[expire]
|
||||
c.expireSetMu.RUnlock()
|
||||
@ -209,7 +441,7 @@ func (c *memCache) getExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
|
||||
// getOrNewExpireSet returns the expire set for given <expire> in seconds.
|
||||
// It creates and returns a new set for <expire> if it does not exist.
|
||||
func (c *memCache) getOrNewExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
func (c *adapterMemory) getOrNewExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
if expireSet = c.getExpireSet(expire); expireSet == nil {
|
||||
expireSet = gset.New(true)
|
||||
c.expireSetMu.Lock()
|
||||
@ -223,202 +455,17 @@ func (c *memCache) getOrNewExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
return
|
||||
}
|
||||
|
||||
// SetIfNotExist sets cache with <key>-<value> pair if <key> does not exist in the cache,
|
||||
// which is expired after <duration>. It does not expire if <duration> == 0.
|
||||
func (c *memCache) SetIfNotExist(key interface{}, value interface{}, duration time.Duration) bool {
|
||||
if !c.Contains(key) {
|
||||
c.doSetWithLockCheck(key, value, duration)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Sets batch sets cache with key-value pairs by <data>, which is expired after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
func (c *memCache) Sets(data map[interface{}]interface{}, duration time.Duration) {
|
||||
expireTime := c.getInternalExpire(duration)
|
||||
for k, v := range data {
|
||||
c.dataMu.Lock()
|
||||
c.data[k] = memCacheItem{
|
||||
v: v,
|
||||
e: expireTime,
|
||||
}
|
||||
c.dataMu.Unlock()
|
||||
c.eventList.PushBack(&memCacheEvent{
|
||||
k: k,
|
||||
e: expireTime,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the value of <key>.
|
||||
// It returns nil if it does not exist or its value is nil.
|
||||
func (c *memCache) Get(key interface{}) interface{} {
|
||||
c.dataMu.RLock()
|
||||
item, ok := c.data[key]
|
||||
c.dataMu.RUnlock()
|
||||
if ok && !item.IsExpired() {
|
||||
// Adding to LRU history if LRU feature is enabled.
|
||||
if c.cap > 0 {
|
||||
c.lruGetList.PushBack(key)
|
||||
}
|
||||
return item.v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVar retrieves and returns the value of <key> as gvar.Var.
|
||||
func (c *memCache) GetVar(key interface{}) *gvar.Var {
|
||||
return gvar.New(c.Get(key))
|
||||
}
|
||||
|
||||
// GetOrSet returns the value of <key>, or sets <key>-<value> pair and returns <value> if <key>
|
||||
// does not exist in the cache. The key-value pair expires after <duration>. It does not expire
|
||||
// if <duration> == 0.
|
||||
func (c *memCache) GetOrSet(key interface{}, value interface{}, duration time.Duration) interface{} {
|
||||
if v := c.Get(key); v == nil {
|
||||
return c.doSetWithLockCheck(key, value, duration)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFunc returns the value of <key>, or sets <key> with result of function <f>
|
||||
// and returns its result if <key> does not exist in the cache. The key-value pair expires
|
||||
// after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It does nothing if function <f> returns nil.
|
||||
func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
if v := c.Get(key); v == nil {
|
||||
value := f()
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return c.doSetWithLockCheck(key, value, duration)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrSetFuncLock returns the value of <key>, or sets <key> with result of function <f>
|
||||
// and returns its result if <key> does not exist in the cache. The key-value pair expires
|
||||
// after <duration>.
|
||||
//
|
||||
// It does not expire if <duration> == 0.
|
||||
// It does nothing if function <f> returns nil.
|
||||
//
|
||||
// Note that the function <f> is executed within writing mutex lock.
|
||||
func (c *memCache) GetOrSetFuncLock(key interface{}, f func() interface{}, duration time.Duration) interface{} {
|
||||
if v := c.Get(key); v == nil {
|
||||
return c.doSetWithLockCheck(key, f, duration)
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// Contains returns true if <key> exists in the cache, or else returns false.
|
||||
func (c *memCache) Contains(key interface{}) bool {
|
||||
return c.Get(key) != nil
|
||||
}
|
||||
|
||||
// Remove deletes the one or more keys from cache, and returns its value.
|
||||
// If multiple keys are given, it returns the value of the deleted last item.
|
||||
func (c *memCache) Remove(keys ...interface{}) (value interface{}) {
|
||||
c.dataMu.Lock()
|
||||
defer c.dataMu.Unlock()
|
||||
for _, key := range keys {
|
||||
item, ok := c.data[key]
|
||||
if ok {
|
||||
value = item.v
|
||||
delete(c.data, key)
|
||||
c.eventList.PushBack(&memCacheEvent{
|
||||
k: key,
|
||||
e: gtime.TimestampMilli() - 1000,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Removes deletes <keys> in the cache.
|
||||
// Deprecated, use Remove instead.
|
||||
func (c *memCache) Removes(keys []interface{}) {
|
||||
c.Remove(keys...)
|
||||
}
|
||||
|
||||
// Data returns a copy of all key-value pairs in the cache as map type.
|
||||
func (c *memCache) Data() map[interface{}]interface{} {
|
||||
m := make(map[interface{}]interface{})
|
||||
c.dataMu.RLock()
|
||||
for k, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
m[k] = v.v
|
||||
}
|
||||
}
|
||||
c.dataMu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// Keys returns all keys in the cache as slice.
|
||||
func (c *memCache) Keys() []interface{} {
|
||||
keys := make([]interface{}, 0)
|
||||
c.dataMu.RLock()
|
||||
for k, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
c.dataMu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
// KeyStrings returns all keys in the cache as string slice.
|
||||
func (c *memCache) KeyStrings() []string {
|
||||
return gconv.Strings(c.Keys())
|
||||
}
|
||||
|
||||
// Values returns all values in the cache as slice.
|
||||
func (c *memCache) Values() []interface{} {
|
||||
values := make([]interface{}, 0)
|
||||
c.dataMu.RLock()
|
||||
for _, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
values = append(values, v.v)
|
||||
}
|
||||
}
|
||||
c.dataMu.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// Size returns the size of the cache.
|
||||
func (c *memCache) Size() (size int) {
|
||||
c.dataMu.RLock()
|
||||
size = len(c.data)
|
||||
c.dataMu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes the cache.
|
||||
func (c *memCache) Close() {
|
||||
if c.cap > 0 {
|
||||
c.lru.Close()
|
||||
}
|
||||
c.closed.Set(true)
|
||||
}
|
||||
|
||||
// syncEventAndClearExpired does the asynchronous task loop:
|
||||
// 1. Asynchronously process the data in the event list,
|
||||
// and synchronize the results to the <expireTimes> and <expireSets> properties.
|
||||
// 2. Clean up the expired key-value pair data.
|
||||
func (c *memCache) syncEventAndClearExpired() {
|
||||
func (c *adapterMemory) syncEventAndClearExpired() {
|
||||
if c.closed.Val() {
|
||||
gtimer.Exit()
|
||||
return
|
||||
}
|
||||
var (
|
||||
event *memCacheEvent
|
||||
event *adapterMemoryEvent
|
||||
oldExpireTime int64
|
||||
newExpireTime int64
|
||||
)
|
||||
@ -430,7 +477,7 @@ func (c *memCache) syncEventAndClearExpired() {
|
||||
if v == nil {
|
||||
break
|
||||
}
|
||||
event = v.(*memCacheEvent)
|
||||
event = v.(*adapterMemoryEvent)
|
||||
// Fetching the old expire set.
|
||||
c.expireTimeMu.RLock()
|
||||
oldExpireTime = c.expireTimes[event.k]
|
||||
@ -487,7 +534,7 @@ func (c *memCache) syncEventAndClearExpired() {
|
||||
|
||||
// clearByKey deletes the key-value pair with given <key>.
|
||||
// The parameter <force> specifies whether doing this deleting forcibly.
|
||||
func (c *memCache) clearByKey(key interface{}, force ...bool) {
|
||||
func (c *adapterMemory) clearByKey(key interface{}, force ...bool) {
|
||||
c.dataMu.Lock()
|
||||
// Doubly check before really deleting it from cache.
|
||||
if item, ok := c.data[key]; (ok && item.IsExpired()) || (len(force) > 0 && force[0]) {
|
||||
@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
// IsExpired checks whether <item> is expired.
|
||||
func (item *memCacheItem) IsExpired() bool {
|
||||
func (item *adapterMemoryItem) IsExpired() bool {
|
||||
// Note that it should use greater than or equal judgement here
|
||||
// imagining that the cache time is only 1 millisecond.
|
||||
if item.e >= gtime.TimestampMilli() {
|
||||
@ -17,17 +17,17 @@ import (
|
||||
|
||||
// LRU cache object.
|
||||
// It uses list.List from stdlib for its underlying doubly linked list.
|
||||
type memCacheLru struct {
|
||||
cache *memCache // Parent cache object.
|
||||
data *gmap.Map // Key mapping to the item of the list.
|
||||
list *glist.List // Key list.
|
||||
rawList *glist.List // History for key adding.
|
||||
closed *gtype.Bool // Closed or not.
|
||||
type adapterMemoryLru struct {
|
||||
cache *adapterMemory // Parent cache object.
|
||||
data *gmap.Map // Key mapping to the item of the list.
|
||||
list *glist.List // Key list.
|
||||
rawList *glist.List // History for key adding.
|
||||
closed *gtype.Bool // Closed or not.
|
||||
}
|
||||
|
||||
// newMemCacheLru creates and returns a new LRU object.
|
||||
func newMemCacheLru(cache *memCache) *memCacheLru {
|
||||
lru := &memCacheLru{
|
||||
func newMemCacheLru(cache *adapterMemory) *adapterMemoryLru {
|
||||
lru := &adapterMemoryLru{
|
||||
cache: cache,
|
||||
data: gmap.New(true),
|
||||
list: glist.New(true),
|
||||
@ -39,12 +39,12 @@ func newMemCacheLru(cache *memCache) *memCacheLru {
|
||||
}
|
||||
|
||||
// Close closes the LRU object.
|
||||
func (lru *memCacheLru) Close() {
|
||||
func (lru *adapterMemoryLru) Close() {
|
||||
lru.closed.Set(true)
|
||||
}
|
||||
|
||||
// Remove deletes the <key> FROM <lru>.
|
||||
func (lru *memCacheLru) Remove(key interface{}) {
|
||||
func (lru *adapterMemoryLru) Remove(key interface{}) {
|
||||
if v := lru.data.Get(key); v != nil {
|
||||
lru.data.Remove(key)
|
||||
lru.list.Remove(v.(*glist.Element))
|
||||
@ -52,17 +52,17 @@ func (lru *memCacheLru) Remove(key interface{}) {
|
||||
}
|
||||
|
||||
// Size returns the size of <lru>.
|
||||
func (lru *memCacheLru) Size() int {
|
||||
func (lru *adapterMemoryLru) Size() int {
|
||||
return lru.data.Size()
|
||||
}
|
||||
|
||||
// Push pushes <key> to the tail of <lru>.
|
||||
func (lru *memCacheLru) Push(key interface{}) {
|
||||
func (lru *adapterMemoryLru) Push(key interface{}) {
|
||||
lru.rawList.PushBack(key)
|
||||
}
|
||||
|
||||
// Pop deletes and returns the key from tail of <lru>.
|
||||
func (lru *memCacheLru) Pop() interface{} {
|
||||
func (lru *adapterMemoryLru) Pop() interface{} {
|
||||
if v := lru.list.PopBack(); v != nil {
|
||||
lru.data.Remove(v)
|
||||
return v
|
||||
@ -71,7 +71,7 @@ func (lru *memCacheLru) Pop() interface{} {
|
||||
}
|
||||
|
||||
// Print is used for test only.
|
||||
//func (lru *memCacheLru) Print() {
|
||||
//func (lru *adapterMemoryLru) Print() {
|
||||
// for _, v := range lru.list.FrontAll() {
|
||||
// fmt.Printf("%v ", v)
|
||||
// }
|
||||
@ -80,7 +80,7 @@ func (lru *memCacheLru) Pop() interface{} {
|
||||
|
||||
// SyncAndClear synchronizes the keys from <rawList> to <list> and <data>
|
||||
// using Least Recently Used algorithm.
|
||||
func (lru *memCacheLru) SyncAndClear() {
|
||||
func (lru *adapterMemoryLru) SyncAndClear() {
|
||||
if lru.closed.Val() {
|
||||
gtimer.Exit()
|
||||
return
|
||||
@ -7,31 +7,55 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/os/gtimer"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Cache struct.
|
||||
type Cache struct {
|
||||
*memCache
|
||||
Adapter // Adapter for cache features.
|
||||
}
|
||||
|
||||
// New creates and returns a new cache object.
|
||||
// New creates and returns a new cache object using default memory adapter.
|
||||
// Note that the LRU feature is only available using memory adapter.
|
||||
func New(lruCap ...int) *Cache {
|
||||
memAdapter := newAdapterMemory(lruCap...)
|
||||
c := &Cache{
|
||||
memCache: newMemCache(lruCap...),
|
||||
Adapter: memAdapter,
|
||||
}
|
||||
gtimer.AddSingleton(time.Second, c.syncEventAndClearExpired)
|
||||
// Here may be a "timer leak" if adapter is manually changed from memory adapter.
|
||||
// Do not worry about this, as adapter is less changed and it dose nothing if it's not used.
|
||||
gtimer.AddSingleton(time.Second, memAdapter.syncEventAndClearExpired)
|
||||
return c
|
||||
}
|
||||
|
||||
// Clear clears all data of the cache.
|
||||
func (c *Cache) Clear() {
|
||||
// atomic swap to ensure atomicity.
|
||||
old := atomic.SwapPointer((*unsafe.Pointer)(unsafe.Pointer(&c.memCache)), unsafe.Pointer(newMemCache()))
|
||||
// close the old cache object.
|
||||
(*memCache)(old).Close()
|
||||
// SetAdapter changes the adapter for this cache.
|
||||
// Be very note that, this setting function is not concurrent-safe, which means you should not call
|
||||
// this setting function concurrently in multiple goroutines.
|
||||
func (c *Cache) SetAdapter(adapter Adapter) {
|
||||
c.Adapter = adapter
|
||||
}
|
||||
|
||||
// GetVar retrieves and returns the value of <key> as gvar.Var.
|
||||
func (c *Cache) GetVar(key interface{}) (*gvar.Var, error) {
|
||||
v, err := c.Get(key)
|
||||
return gvar.New(v), err
|
||||
}
|
||||
|
||||
// Removes deletes <keys> in the cache.
|
||||
// Deprecated, use Remove instead.
|
||||
func (c *Cache) Removes(keys []interface{}) error {
|
||||
_, err := c.Remove(keys...)
|
||||
return err
|
||||
}
|
||||
|
||||
// KeyStrings returns all keys in the cache as string slice.
|
||||
func (c *Cache) KeyStrings() ([]string, error) {
|
||||
keys, err := c.Keys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return gconv.Strings(keys), nil
|
||||
}
|
||||
|
||||
@ -25,8 +25,10 @@ func TestCache_GCache_Set(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
gcache.Set(1, 11, 0)
|
||||
defer gcache.Removes(g.Slice{1, 2, 3})
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
t.Assert(gcache.Contains(1), true)
|
||||
v, _ := gcache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
b, _ := gcache.Contains(1)
|
||||
t.Assert(b, true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -34,44 +36,57 @@ func TestCache_Set(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := gcache.New()
|
||||
defer c.Close()
|
||||
c.Set(1, 11, 0)
|
||||
t.Assert(c.Get(1), 11)
|
||||
t.Assert(c.Contains(1), true)
|
||||
t.Assert(c.Set(1, 11, 0), nil)
|
||||
v, _ := c.Get(1)
|
||||
t.Assert(v, 11)
|
||||
b, _ := c.Contains(1)
|
||||
t.Assert(b, true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_GetVar(t *testing.T) {
|
||||
c := gcache.New()
|
||||
defer c.Close()
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
c := gcache.New()
|
||||
defer c.Close()
|
||||
c.Set(1, 11, 0)
|
||||
t.Assert(c.Get(1), 11)
|
||||
t.Assert(c.Contains(1), true)
|
||||
t.Assert(c.GetVar(1).Int(), 11)
|
||||
t.Assert(c.GetVar(2).Int(), 0)
|
||||
t.Assert(c.GetVar(2).IsNil(), true)
|
||||
t.Assert(c.GetVar(2).IsEmpty(), true)
|
||||
t.Assert(c.Set(1, 11, 0), nil)
|
||||
v, _ := c.Get(1)
|
||||
t.Assert(v, 11)
|
||||
b, _ := c.Contains(1)
|
||||
t.Assert(b, true)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, _ := c.GetVar(1)
|
||||
t.Assert(v.Int(), 11)
|
||||
v, _ = c.GetVar(2)
|
||||
t.Assert(v.Int(), 0)
|
||||
t.Assert(v.IsNil(), true)
|
||||
t.Assert(v.IsEmpty(), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_Set_Expire(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(2, 22, 100*time.Millisecond)
|
||||
t.Assert(cache.Get(2), 22)
|
||||
t.Assert(cache.Set(2, 22, 100*time.Millisecond), nil)
|
||||
v, _ := cache.Get(2)
|
||||
t.Assert(v, 22)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
t.Assert(cache.Get(2), nil)
|
||||
v, _ = cache.Get(2)
|
||||
t.Assert(v, nil)
|
||||
time.Sleep(3 * time.Second)
|
||||
t.Assert(cache.Size(), 0)
|
||||
cache.Close()
|
||||
n, _ := cache.Size()
|
||||
t.Assert(n, 0)
|
||||
t.Assert(cache.Close(), nil)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(1, 11, 100*time.Millisecond)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
t.Assert(cache.Set(1, 11, 100*time.Millisecond), nil)
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
t.Assert(cache.Get(1), nil)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, nil)
|
||||
})
|
||||
}
|
||||
|
||||
@ -80,20 +95,22 @@ func TestCache_Update_GetExpire(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
key := guid.S()
|
||||
gcache.Set(key, 11, 3*time.Second)
|
||||
expire1 := gcache.GetExpire(key)
|
||||
expire1, _ := gcache.GetExpire(key)
|
||||
gcache.Update(key, 12)
|
||||
expire2 := gcache.GetExpire(key)
|
||||
t.Assert(gcache.GetVar(key), 12)
|
||||
expire2, _ := gcache.GetExpire(key)
|
||||
v, _ := gcache.GetVar(key)
|
||||
t.Assert(v, 12)
|
||||
t.Assert(math.Ceil(expire1.Seconds()), math.Ceil(expire2.Seconds()))
|
||||
})
|
||||
// gcache.Cache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(1, 11, 3*time.Second)
|
||||
expire1 := cache.GetExpire(1)
|
||||
expire1, _ := cache.GetExpire(1)
|
||||
cache.Update(1, 12)
|
||||
expire2 := cache.GetExpire(1)
|
||||
t.Assert(cache.GetVar(1), 12)
|
||||
expire2, _ := cache.GetExpire(1)
|
||||
v, _ := cache.GetVar(1)
|
||||
t.Assert(v, 12)
|
||||
t.Assert(math.Ceil(expire1.Seconds()), math.Ceil(expire2.Seconds()))
|
||||
})
|
||||
}
|
||||
@ -104,34 +121,43 @@ func TestCache_UpdateExpire(t *testing.T) {
|
||||
key := guid.S()
|
||||
gcache.Set(key, 11, 3*time.Second)
|
||||
defer gcache.Remove(key)
|
||||
oldExpire := gcache.GetExpire(key)
|
||||
oldExpire, _ := gcache.GetExpire(key)
|
||||
newExpire := 10 * time.Second
|
||||
gcache.UpdateExpire(key, newExpire)
|
||||
t.AssertNE(gcache.GetExpire(key), oldExpire)
|
||||
t.Assert(math.Ceil(gcache.GetExpire(key).Seconds()), 10)
|
||||
e, _ := gcache.GetExpire(key)
|
||||
t.AssertNE(e, oldExpire)
|
||||
e, _ = gcache.GetExpire(key)
|
||||
t.Assert(math.Ceil(e.Seconds()), 10)
|
||||
})
|
||||
// gcache.Cache
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Set(1, 11, 3*time.Second)
|
||||
oldExpire := cache.GetExpire(1)
|
||||
oldExpire, _ := cache.GetExpire(1)
|
||||
newExpire := 10 * time.Second
|
||||
cache.UpdateExpire(1, newExpire)
|
||||
t.AssertNE(cache.GetExpire(1), oldExpire)
|
||||
t.Assert(math.Ceil(cache.GetExpire(1).Seconds()), 10)
|
||||
e, _ := cache.GetExpire(1)
|
||||
t.AssertNE(e, oldExpire)
|
||||
|
||||
e, _ = cache.GetExpire(1)
|
||||
t.Assert(math.Ceil(e.Seconds()), 10)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_Keys_Values(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
c := gcache.New()
|
||||
for i := 0; i < 10; i++ {
|
||||
cache.Set(i, i*10, 0)
|
||||
t.Assert(c.Set(i, i*10, 0), nil)
|
||||
}
|
||||
t.Assert(len(cache.Keys()), 10)
|
||||
t.Assert(len(cache.Values()), 10)
|
||||
t.AssertIN(0, cache.Keys())
|
||||
t.AssertIN(90, cache.Values())
|
||||
var (
|
||||
keys, _ = c.Keys()
|
||||
values, _ = c.Values()
|
||||
)
|
||||
t.Assert(len(keys), 10)
|
||||
t.Assert(len(values), 10)
|
||||
t.AssertIN(0, keys)
|
||||
t.AssertIN(90, values)
|
||||
})
|
||||
}
|
||||
|
||||
@ -141,22 +167,30 @@ func TestCache_LRU(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
cache.Set(i, i, 0)
|
||||
}
|
||||
t.Assert(cache.Size(), 10)
|
||||
t.Assert(cache.Get(6), 6)
|
||||
n, _ := cache.Size()
|
||||
t.Assert(n, 10)
|
||||
v, _ := cache.Get(6)
|
||||
t.Assert(v, 6)
|
||||
time.Sleep(4 * time.Second)
|
||||
t.Assert(cache.Size(), 2)
|
||||
t.Assert(cache.Get(6), 6)
|
||||
t.Assert(cache.Get(1), nil)
|
||||
cache.Close()
|
||||
n, _ = cache.Size()
|
||||
t.Assert(n, 2)
|
||||
v, _ = cache.Get(6)
|
||||
t.Assert(v, 6)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, nil)
|
||||
t.Assert(cache.Close(), nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_LRU_expire(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New(2)
|
||||
cache.Set(1, nil, 1000)
|
||||
t.Assert(cache.Size(), 1)
|
||||
t.Assert(cache.Get(1), nil)
|
||||
t.Assert(cache.Set(1, nil, 1000), nil)
|
||||
n, _ := cache.Size()
|
||||
t.Assert(n, 1)
|
||||
v, _ := cache.Get(1)
|
||||
|
||||
t.Assert(v, nil)
|
||||
})
|
||||
}
|
||||
|
||||
@ -164,17 +198,22 @@ func TestCache_SetIfNotExist(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.SetIfNotExist(1, 11, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
cache.SetIfNotExist(1, 22, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
cache.SetIfNotExist(2, 22, 0)
|
||||
t.Assert(cache.Get(2), 22)
|
||||
v, _ = cache.Get(2)
|
||||
t.Assert(v, 22)
|
||||
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.SetIfNotExist(1, 11, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
v, _ = gcache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
gcache.SetIfNotExist(1, 22, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
v, _ = gcache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
})
|
||||
}
|
||||
|
||||
@ -182,11 +221,13 @@ func TestCache_Sets(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
})
|
||||
}
|
||||
|
||||
@ -194,63 +235,82 @@ func TestCache_GetOrSet(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.GetOrSet(1, 11, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
cache.GetOrSet(1, 111, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.GetOrSet(1, 11, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
gcache.GetOrSet(1, 111, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_GetOrSetFunc(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.GetOrSetFunc(1, func() interface{} {
|
||||
return 11
|
||||
cache.GetOrSetFunc(1, func() (interface{}, error) {
|
||||
return 11, nil
|
||||
}, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
cache.GetOrSetFunc(1, func() interface{} {
|
||||
return 111
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
cache.GetOrSetFunc(1, func() (interface{}, error) {
|
||||
return 111, nil
|
||||
}, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.GetOrSetFunc(1, func() interface{} {
|
||||
return 11
|
||||
|
||||
gcache.GetOrSetFunc(1, func() (interface{}, error) {
|
||||
return 11, nil
|
||||
}, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
gcache.GetOrSetFunc(1, func() interface{} {
|
||||
return 111
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
gcache.GetOrSetFunc(1, func() (interface{}, error) {
|
||||
return 111, nil
|
||||
}, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCache_GetOrSetFuncLock(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
cache := gcache.New()
|
||||
cache.GetOrSetFuncLock(1, func() interface{} {
|
||||
return 11
|
||||
cache.GetOrSetFuncLock(1, func() (interface{}, error) {
|
||||
return 11, nil
|
||||
}, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
cache.GetOrSetFuncLock(1, func() interface{} {
|
||||
return 111
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
cache.GetOrSetFuncLock(1, func() (interface{}, error) {
|
||||
return 111, nil
|
||||
}, 0)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
gcache.Removes(g.Slice{1, 2, 3})
|
||||
gcache.GetOrSetFuncLock(1, func() interface{} {
|
||||
return 11
|
||||
gcache.GetOrSetFuncLock(1, func() (interface{}, error) {
|
||||
return 11, nil
|
||||
}, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
gcache.GetOrSetFuncLock(1, func() interface{} {
|
||||
return 111
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
|
||||
gcache.GetOrSetFuncLock(1, func() (interface{}, error) {
|
||||
return 111, nil
|
||||
}, 0)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
v, _ = cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
})
|
||||
}
|
||||
|
||||
@ -259,7 +319,8 @@ func TestCache_Clear(t *testing.T) {
|
||||
cache := gcache.New()
|
||||
cache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
cache.Clear()
|
||||
t.Assert(cache.Size(), 0)
|
||||
n, _ := cache.Size()
|
||||
t.Assert(n, 0)
|
||||
})
|
||||
}
|
||||
|
||||
@ -298,47 +359,57 @@ func TestCache_Basic(t *testing.T) {
|
||||
{
|
||||
cache := gcache.New()
|
||||
cache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(cache.Contains(1), true)
|
||||
t.Assert(cache.Get(1), 11)
|
||||
data := cache.Data()
|
||||
b, _ := cache.Contains(1)
|
||||
t.Assert(b, true)
|
||||
v, _ := cache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
data, _ := cache.Data()
|
||||
t.Assert(data[1], 11)
|
||||
t.Assert(data[2], 22)
|
||||
t.Assert(data[3], nil)
|
||||
t.Assert(cache.Size(), 2)
|
||||
keys := cache.Keys()
|
||||
n, _ := cache.Size()
|
||||
t.Assert(n, 2)
|
||||
keys, _ := cache.Keys()
|
||||
t.Assert(gset.NewFrom(g.Slice{1, 2}).Equal(gset.NewFrom(keys)), true)
|
||||
keyStrs := cache.KeyStrings()
|
||||
keyStrs, _ := cache.KeyStrings()
|
||||
t.Assert(gset.NewFrom(g.Slice{"1", "2"}).Equal(gset.NewFrom(keyStrs)), true)
|
||||
values := cache.Values()
|
||||
values, _ := cache.Values()
|
||||
t.Assert(gset.NewFrom(g.Slice{11, 22}).Equal(gset.NewFrom(values)), true)
|
||||
removeData1 := cache.Remove(1)
|
||||
removeData1, _ := cache.Remove(1)
|
||||
t.Assert(removeData1, 11)
|
||||
t.Assert(cache.Size(), 1)
|
||||
n, _ = cache.Size()
|
||||
t.Assert(n, 1)
|
||||
cache.Removes(g.Slice{2})
|
||||
t.Assert(cache.Size(), 0)
|
||||
n, _ = cache.Size()
|
||||
t.Assert(n, 0)
|
||||
}
|
||||
|
||||
gcache.Remove(g.Slice{1, 2, 3}...)
|
||||
{
|
||||
gcache.Sets(g.MapAnyAny{1: 11, 2: 22}, 0)
|
||||
t.Assert(gcache.Contains(1), true)
|
||||
t.Assert(gcache.Get(1), 11)
|
||||
data := gcache.Data()
|
||||
b, _ := gcache.Contains(1)
|
||||
t.Assert(b, true)
|
||||
v, _ := gcache.Get(1)
|
||||
t.Assert(v, 11)
|
||||
data, _ := gcache.Data()
|
||||
t.Assert(data[1], 11)
|
||||
t.Assert(data[2], 22)
|
||||
t.Assert(data[3], nil)
|
||||
t.Assert(gcache.Size(), 2)
|
||||
keys := gcache.Keys()
|
||||
n, _ := gcache.Size()
|
||||
t.Assert(n, 2)
|
||||
keys, _ := gcache.Keys()
|
||||
t.Assert(gset.NewFrom(g.Slice{1, 2}).Equal(gset.NewFrom(keys)), true)
|
||||
keyStrs := gcache.KeyStrings()
|
||||
keyStrs, _ := gcache.KeyStrings()
|
||||
t.Assert(gset.NewFrom(g.Slice{"1", "2"}).Equal(gset.NewFrom(keyStrs)), true)
|
||||
values := gcache.Values()
|
||||
values, _ := gcache.Values()
|
||||
t.Assert(gset.NewFrom(g.Slice{11, 22}).Equal(gset.NewFrom(values)), true)
|
||||
removeData1 := gcache.Remove(1)
|
||||
removeData1, _ := gcache.Remove(1)
|
||||
t.Assert(removeData1, 11)
|
||||
t.Assert(gcache.Size(), 1)
|
||||
n, _ = gcache.Size()
|
||||
t.Assert(n, 1)
|
||||
gcache.Removes(g.Slice{2})
|
||||
t.Assert(gcache.Size(), 0)
|
||||
n, _ = gcache.Size()
|
||||
t.Assert(n, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -334,7 +334,10 @@ func (c *Config) getJson(file ...string) *gjson.Json {
|
||||
content = ""
|
||||
filePath = ""
|
||||
)
|
||||
// The configured content can be any kind of data type different from its file type.
|
||||
isFromConfigContent := true
|
||||
if content = GetContent(name); content == "" {
|
||||
isFromConfigContent = false
|
||||
filePath = c.filePath(name)
|
||||
if filePath == "" {
|
||||
return nil
|
||||
@ -346,7 +349,17 @@ func (c *Config) getJson(file ...string) *gjson.Json {
|
||||
}
|
||||
}
|
||||
// Note that the underlying configuration json object operations are concurrent safe.
|
||||
if j, err := gjson.LoadContent(content, true); err == nil {
|
||||
var (
|
||||
j *gjson.Json
|
||||
err error
|
||||
)
|
||||
dataType := gfile.ExtName(name)
|
||||
if gjson.IsValidDataType(dataType) && !isFromConfigContent {
|
||||
j, err = gjson.LoadContentType(dataType, content, true)
|
||||
} else {
|
||||
j, err = gjson.LoadContent(content, true)
|
||||
}
|
||||
if err == nil {
|
||||
j.SetViolenceCheck(c.vc)
|
||||
// Add monitor for this configuration file,
|
||||
// any changes of this file will refresh its cache in Config object.
|
||||
|
||||
@ -231,21 +231,16 @@ func Test_SetFileName(t *testing.T) {
|
||||
|
||||
func Test_Instance(t *testing.T) {
|
||||
config := `
|
||||
{
|
||||
"array": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"redis": {
|
||||
"cache": "127.0.0.1:6379,1",
|
||||
"disk": "127.0.0.1:6379,0"
|
||||
},
|
||||
"v1": 1,
|
||||
"v2": "true",
|
||||
"v3": "off",
|
||||
"v4": "1.234"
|
||||
}
|
||||
array = [1.0, 2.0, 3.0]
|
||||
v1 = 1.0
|
||||
v2 = "true"
|
||||
v3 = "off"
|
||||
v4 = "1.234"
|
||||
|
||||
[redis]
|
||||
cache = "127.0.0.1:6379,1"
|
||||
disk = "127.0.0.1:6379,0"
|
||||
|
||||
`
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
path := gcfg.DEFAULT_CONFIG_FILE
|
||||
|
||||
@ -39,7 +39,7 @@ func GetBytesWithCache(path string, duration ...time.Duration) []byte {
|
||||
if len(duration) > 0 {
|
||||
expire = duration[0]
|
||||
}
|
||||
r := gcache.GetOrSetFuncLock(key, func() interface{} {
|
||||
r, _ := gcache.GetOrSetFuncLock(key, func() (interface{}, error) {
|
||||
b := GetBytes(path)
|
||||
if b != nil {
|
||||
// Adding this <path> to gfsnotify,
|
||||
@ -49,7 +49,7 @@ func GetBytesWithCache(path string, duration ...time.Duration) []byte {
|
||||
gfsnotify.Exit()
|
||||
})
|
||||
}
|
||||
return b
|
||||
return b, nil
|
||||
}, expire)
|
||||
if r != nil {
|
||||
return r.([]byte)
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
@ -67,20 +68,12 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
mu sync.Mutex // Mutex for concurrent safety of defaultWatcher.
|
||||
defaultWatcher *Watcher // Default watcher.
|
||||
callbackIdMap = gmap.NewIntAnyMap(true) // Id to callback mapping.
|
||||
callbackIdGenerator = gtype.NewInt() // Atomic id generator for callback.
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
defaultWatcher, err = New()
|
||||
if err != nil {
|
||||
// Default watcher object must be created, or else it panics.
|
||||
panic(fmt.Sprintf(`creating default fsnotify watcher failed: %s`, err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
// New creates and returns a new watcher.
|
||||
// Note that the watcher number is limited by the file handle setting of the system.
|
||||
// Eg: fs.inotify.max_user_instances system variable in linux systems.
|
||||
@ -106,7 +99,11 @@ func New() (*Watcher, error) {
|
||||
// Add monitors <path> using default watcher with callback function <callbackFunc>.
|
||||
// The optional parameter <recursive> specifies whether monitoring the <path> recursively, which is true in default.
|
||||
func Add(path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) {
|
||||
return defaultWatcher.Add(path, callbackFunc, recursive...)
|
||||
w, err := getDefaultWatcher()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return w.Add(path, callbackFunc, recursive...)
|
||||
}
|
||||
|
||||
// AddOnce monitors <path> using default watcher with callback function <callbackFunc> only once using unique name <name>.
|
||||
@ -115,16 +112,28 @@ func Add(path string, callbackFunc func(event *Event), recursive ...bool) (callb
|
||||
//
|
||||
// The optional parameter <recursive> specifies whether monitoring the <path> recursively, which is true in default.
|
||||
func AddOnce(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) {
|
||||
return defaultWatcher.AddOnce(name, path, callbackFunc, recursive...)
|
||||
w, err := getDefaultWatcher()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return w.AddOnce(name, path, callbackFunc, recursive...)
|
||||
}
|
||||
|
||||
// Remove removes all monitoring callbacks of given <path> from watcher recursively.
|
||||
func Remove(path string) error {
|
||||
return defaultWatcher.Remove(path)
|
||||
w, err := getDefaultWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.Remove(path)
|
||||
}
|
||||
|
||||
// RemoveCallback removes specified callback with given id from watcher.
|
||||
func RemoveCallback(callbackId int) error {
|
||||
w, err := getDefaultWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
callback := (*Callback)(nil)
|
||||
if r := callbackIdMap.Get(callbackId); r != nil {
|
||||
callback = r.(*Callback)
|
||||
@ -132,7 +141,7 @@ func RemoveCallback(callbackId int) error {
|
||||
if callback == nil {
|
||||
return errors.New(fmt.Sprintf(`callback for id %d not found`, callbackId))
|
||||
}
|
||||
defaultWatcher.RemoveCallback(callbackId)
|
||||
w.RemoveCallback(callbackId)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -141,3 +150,16 @@ func RemoveCallback(callbackId int) error {
|
||||
func Exit() {
|
||||
panic(callbackExitEventPanicStr)
|
||||
}
|
||||
|
||||
// getDefaultWatcher creates and returns the default watcher.
|
||||
// This is used for lazy initialization purpose.
|
||||
func getDefaultWatcher() (*Watcher, error) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if defaultWatcher != nil {
|
||||
return defaultWatcher, nil
|
||||
}
|
||||
var err error
|
||||
defaultWatcher, err = New()
|
||||
return defaultWatcher, err
|
||||
}
|
||||
|
||||
@ -23,14 +23,14 @@ func (w *Watcher) startWatchLoop() {
|
||||
// Event listening.
|
||||
case ev := <-w.watcher.Events:
|
||||
// Filter the repeated event in custom duration.
|
||||
w.cache.SetIfNotExist(ev.String(), func() interface{} {
|
||||
w.cache.SetIfNotExist(ev.String(), func() (interface{}, error) {
|
||||
w.events.Push(&Event{
|
||||
event: ev,
|
||||
Path: ev.Name,
|
||||
Op: Op(ev.Op),
|
||||
Watcher: w,
|
||||
})
|
||||
return struct{}{}
|
||||
return struct{}{}, nil
|
||||
}, repeatEventFilterDuration)
|
||||
|
||||
case err := <-w.watcher.Errors:
|
||||
|
||||
@ -40,7 +40,7 @@ func (s *Session) init() {
|
||||
if s.id != "" {
|
||||
var err error
|
||||
// Retrieve memory session data from manager.
|
||||
if r := s.manager.sessionData.Get(s.id); r != nil {
|
||||
if r, _ := s.manager.sessionData.Get(s.id); r != nil {
|
||||
s.data = r.(*gmap.StrAnyMap)
|
||||
intlog.Print("session init data:", s.data)
|
||||
}
|
||||
@ -50,11 +50,6 @@ func (s *Session) init() {
|
||||
intlog.Errorf("session restoring failed for id '%s': %v", s.id, err)
|
||||
}
|
||||
}
|
||||
// If it's an invalid or expired session id,
|
||||
// it should create a new session id.
|
||||
if s.data == nil {
|
||||
s.id = ""
|
||||
}
|
||||
}
|
||||
// Use custom session id creating function.
|
||||
if s.id == "" && s.idFunc != nil {
|
||||
|
||||
@ -17,13 +17,21 @@ type Time struct {
|
||||
TimeWrapper
|
||||
}
|
||||
|
||||
// apiUnixNano is an interface definition commonly for custom time.Time wrapper.
|
||||
type apiUnixNano interface {
|
||||
UnixNano() int64
|
||||
}
|
||||
|
||||
// New creates and returns a Time object with given parameter.
|
||||
// The optional parameter can be type of: time.Time, string or integer.
|
||||
// The optional parameter can be type of: time.Time/*time.Time, string or integer.
|
||||
func New(param ...interface{}) *Time {
|
||||
if len(param) > 0 {
|
||||
switch r := param[0].(type) {
|
||||
case time.Time:
|
||||
r.Nanosecond()
|
||||
return NewFromTime(r)
|
||||
case *time.Time:
|
||||
return NewFromTime(*r)
|
||||
case string:
|
||||
return NewFromStr(r)
|
||||
case []byte:
|
||||
@ -32,6 +40,10 @@ func New(param ...interface{}) *Time {
|
||||
return NewFromTimeStamp(int64(r))
|
||||
case int64:
|
||||
return NewFromTimeStamp(r)
|
||||
default:
|
||||
if v, ok := r.(apiUnixNano); ok {
|
||||
return NewFromTimeStamp(v.UnixNano())
|
||||
}
|
||||
}
|
||||
}
|
||||
return &Time{
|
||||
@ -84,6 +96,7 @@ func NewFromStrLayout(str string, layout string) *Time {
|
||||
|
||||
// NewFromTimeStamp creates and returns a Time object with given timestamp,
|
||||
// which can be in seconds to nanoseconds.
|
||||
// Eg: 1600443866 and 1600443866199266000 are both considered as valid timestamp number.
|
||||
func NewFromTimeStamp(timestamp int64) *Time {
|
||||
if timestamp == 0 {
|
||||
return &Time{}
|
||||
@ -189,31 +202,31 @@ func (t *Time) Clone() *Time {
|
||||
|
||||
// Add adds the duration to current time.
|
||||
func (t *Time) Add(d time.Duration) *Time {
|
||||
t.Time = t.Time.Add(d)
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.Add(d)
|
||||
return newTime
|
||||
}
|
||||
|
||||
// AddStr parses the given duration as string and adds it to current time.
|
||||
func (t *Time) AddStr(duration string) error {
|
||||
func (t *Time) AddStr(duration string) (*Time, error) {
|
||||
if d, err := time.ParseDuration(duration); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
} else {
|
||||
t.Time = t.Time.Add(d)
|
||||
return t.Add(d), nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToLocation converts current time to specified location.
|
||||
func (t *Time) ToLocation(location *time.Location) *Time {
|
||||
t.Time = t.Time.In(location)
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.In(location)
|
||||
return newTime
|
||||
}
|
||||
|
||||
// ToZone converts current time to specified zone like: Asia/Shanghai.
|
||||
func (t *Time) ToZone(zone string) (*Time, error) {
|
||||
if l, err := time.LoadLocation(zone); err == nil {
|
||||
t.Time = t.Time.In(l)
|
||||
return t, nil
|
||||
return t.ToLocation(l), nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
@ -221,8 +234,9 @@ func (t *Time) ToZone(zone string) (*Time, error) {
|
||||
|
||||
// UTC converts current time to UTC timezone.
|
||||
func (t *Time) UTC() *Time {
|
||||
t.Time = t.Time.UTC()
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.UTC()
|
||||
return newTime
|
||||
}
|
||||
|
||||
// ISO8601 formats the time as ISO8601 and returns it as string.
|
||||
@ -237,14 +251,16 @@ func (t *Time) RFC822() string {
|
||||
|
||||
// Local converts the time to local timezone.
|
||||
func (t *Time) Local() *Time {
|
||||
t.Time = t.Time.Local()
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.Local()
|
||||
return newTime
|
||||
}
|
||||
|
||||
// AddDate adds year, month and day to the time.
|
||||
func (t *Time) AddDate(years int, months int, days int) *Time {
|
||||
t.Time = t.Time.AddDate(years, months, days)
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.AddDate(years, months, days)
|
||||
return newTime
|
||||
}
|
||||
|
||||
// Round returns the result of rounding t to the nearest multiple of d (since the zero time).
|
||||
@ -256,8 +272,9 @@ func (t *Time) AddDate(years int, months int, days int) *Time {
|
||||
// time. Thus, Round(Hour) may return a time with a non-zero
|
||||
// minute, depending on the time's Location.
|
||||
func (t *Time) Round(d time.Duration) *Time {
|
||||
t.Time = t.Time.Round(d)
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.Round(d)
|
||||
return newTime
|
||||
}
|
||||
|
||||
// Truncate returns the result of rounding t down to a multiple of d (since the zero time).
|
||||
@ -268,8 +285,9 @@ func (t *Time) Round(d time.Duration) *Time {
|
||||
// time. Thus, Truncate(Hour) may return a time with a non-zero
|
||||
// minute, depending on the time's Location.
|
||||
func (t *Time) Truncate(d time.Duration) *Time {
|
||||
t.Time = t.Time.Truncate(d)
|
||||
return t
|
||||
newTime := t.Clone()
|
||||
newTime.Time = newTime.Time.Truncate(d)
|
||||
return newTime
|
||||
}
|
||||
|
||||
// Equal reports whether t and u represent the same time instant.
|
||||
|
||||
@ -188,7 +188,7 @@ func Test_ToTime(t *testing.T) {
|
||||
func Test_Add(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeTemp := gtime.NewFromStr("2006-01-02 15:04:05")
|
||||
timeTemp.Add(time.Second)
|
||||
timeTemp = timeTemp.Add(time.Second)
|
||||
t.Assert(timeTemp.Format("Y-m-d H:i:s"), "2006-01-02 15:04:06")
|
||||
})
|
||||
}
|
||||
@ -196,15 +196,14 @@ func Test_Add(t *testing.T) {
|
||||
func Test_ToZone(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeTemp := gtime.Now()
|
||||
//
|
||||
timeTemp.ToZone("America/Los_Angeles")
|
||||
timeTemp, _ = timeTemp.ToZone("America/Los_Angeles")
|
||||
t.Assert(timeTemp.Time.Location().String(), "America/Los_Angeles")
|
||||
|
||||
loc, err := time.LoadLocation("Asia/Shanghai")
|
||||
if err != nil {
|
||||
t.Error("test fail")
|
||||
}
|
||||
timeTemp.ToLocation(loc)
|
||||
timeTemp = timeTemp.ToLocation(loc)
|
||||
t.Assert(timeTemp.Time.Location().String(), "Asia/Shanghai")
|
||||
|
||||
timeTemp1, _ := timeTemp.ToZone("errZone")
|
||||
@ -217,7 +216,7 @@ func Test_ToZone(t *testing.T) {
|
||||
func Test_AddDate(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeTemp := gtime.NewFromStr("2006-01-02 15:04:05")
|
||||
timeTemp.AddDate(1, 2, 3)
|
||||
timeTemp = timeTemp.AddDate(1, 2, 3)
|
||||
t.Assert(timeTemp.Format("Y-m-d H:i:s"), "2007-03-05 15:04:05")
|
||||
})
|
||||
}
|
||||
@ -244,7 +243,7 @@ func Test_Round(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeTemp := gtime.Now()
|
||||
timeTemp1 := timeTemp.Time
|
||||
timeTemp.Round(time.Hour)
|
||||
timeTemp = timeTemp.Round(time.Hour)
|
||||
t.Assert(timeTemp.UnixNano(), timeTemp1.Round(time.Hour).UnixNano())
|
||||
})
|
||||
}
|
||||
@ -253,7 +252,7 @@ func Test_Truncate(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timeTemp := gtime.Now()
|
||||
timeTemp1 := timeTemp.Time
|
||||
timeTemp.Truncate(time.Hour)
|
||||
timeTemp = timeTemp.Truncate(time.Hour)
|
||||
t.Assert(timeTemp.UnixNano(), timeTemp1.Truncate(time.Hour).UnixNano())
|
||||
})
|
||||
}
|
||||
|
||||
@ -24,8 +24,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
numberSequence = regexp.MustCompile(`([a-zA-Z])(\d+)([a-zA-Z]?)`)
|
||||
numberReplacement = []byte(`$1 $2 $3`)
|
||||
numberSequence = regexp.MustCompile(`([a-zA-Z]{0,1})(\d+)([a-zA-Z]{0,1})`)
|
||||
firstCamelCaseStart = regexp.MustCompile(`([A-Z]+)([A-Z]?[_a-z\d]+)|$`)
|
||||
firstCamelCaseEnd = regexp.MustCompile(`([\w\W]*?)([_]?[A-Z]+)$`)
|
||||
)
|
||||
@ -139,9 +138,21 @@ func DelimitedScreamingCase(s string, del uint8, screaming bool) string {
|
||||
}
|
||||
|
||||
func addWordBoundariesToNumbers(s string) string {
|
||||
b := []byte(s)
|
||||
b = numberSequence.ReplaceAll(b, numberReplacement)
|
||||
return string(b)
|
||||
r := numberSequence.ReplaceAllFunc([]byte(s), func(bytes []byte) []byte {
|
||||
var result []byte
|
||||
match := numberSequence.FindSubmatch(bytes)
|
||||
if len(match[1]) > 0 {
|
||||
result = append(result, match[1]...)
|
||||
result = append(result, []byte(" ")...)
|
||||
}
|
||||
result = append(result, match[2]...)
|
||||
if len(match[3]) > 0 {
|
||||
result = append(result, []byte(" ")...)
|
||||
result = append(result, match[3]...)
|
||||
}
|
||||
return result
|
||||
})
|
||||
return string(r)
|
||||
}
|
||||
|
||||
// Converts a string to CamelCase
|
||||
|
||||
@ -130,6 +130,7 @@ func Test_SnakeScreamingCase(t *testing.T) {
|
||||
func Test_KebabCase(t *testing.T) {
|
||||
cases := [][]string{
|
||||
{"testCase", "test-case"},
|
||||
{"optimization1.0.0", "optimization-1-0-0"},
|
||||
}
|
||||
for _, i := range cases {
|
||||
in := i[0]
|
||||
|
||||
@ -381,8 +381,10 @@ func String(i interface{}) string {
|
||||
return f.Error()
|
||||
}
|
||||
// Reflect checks.
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
var (
|
||||
rv = reflect.ValueOf(value)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
switch kind {
|
||||
case reflect.Chan,
|
||||
reflect.Map,
|
||||
@ -394,6 +396,8 @@ func String(i interface{}) string {
|
||||
if rv.IsNil() {
|
||||
return ""
|
||||
}
|
||||
case reflect.String:
|
||||
return rv.String()
|
||||
}
|
||||
if kind == reflect.Ptr {
|
||||
return String(rv.Elem().Interface())
|
||||
|
||||
@ -31,6 +31,7 @@ func SliceStructDeep(params interface{}, pointer interface{}, mapping ...map[str
|
||||
}
|
||||
|
||||
// Maps converts <i> to []map[string]interface{}.
|
||||
// Note that it automatically checks and converts json string to []map if <value> is string/[]byte.
|
||||
func Maps(value interface{}, tags ...string) []map[string]interface{} {
|
||||
if value == nil {
|
||||
return nil
|
||||
|
||||
@ -50,7 +50,8 @@ func StructDeep(params interface{}, pointer interface{}, mapping ...map[string]s
|
||||
// doStruct is the core internal converting function for any data to struct recursively or not.
|
||||
func doStruct(params interface{}, pointer interface{}, recursive bool, mapping ...map[string]string) (err error) {
|
||||
if params == nil {
|
||||
return gerror.New("params cannot be nil")
|
||||
// If <params> is nil, no conversion.
|
||||
return nil
|
||||
}
|
||||
if pointer == nil {
|
||||
return gerror.New("object pointer cannot be nil")
|
||||
@ -73,7 +74,7 @@ func doStruct(params interface{}, pointer interface{}, recursive bool, mapping .
|
||||
// DO NOT use MapDeep here.
|
||||
paramsMap := Map(params)
|
||||
if paramsMap == nil {
|
||||
return gerror.Newf("invalid params: %v", params)
|
||||
return gerror.Newf("convert params to map failed: %v", params)
|
||||
}
|
||||
|
||||
// Using reflect to do the converting,
|
||||
|
||||
@ -23,14 +23,15 @@ func StructsDeep(params interface{}, pointer interface{}, mapping ...map[string]
|
||||
|
||||
// doStructs converts any slice to given struct slice.
|
||||
//
|
||||
// The parameter <params> should be type of slice.
|
||||
// It automatically checks and converts json string to []map if <params> is string/[]byte.
|
||||
//
|
||||
// The parameter <pointer> should be type of pointer to slice of struct.
|
||||
// Note that if <pointer> is a pointer to another pointer of type of slice of struct,
|
||||
// it will create the struct/pointer internally.
|
||||
func doStructs(params interface{}, pointer interface{}, deep bool, mapping ...map[string]string) (err error) {
|
||||
if params == nil {
|
||||
return gerror.New("params cannot be nil")
|
||||
// If <params> is nil, no conversion.
|
||||
return nil
|
||||
}
|
||||
if pointer == nil {
|
||||
return gerror.New("object pointer cannot be nil")
|
||||
@ -48,57 +49,45 @@ func doStructs(params interface{}, pointer interface{}, deep bool, mapping ...ma
|
||||
return gerror.Newf("pointer should be type of pointer, but got: %v", kind)
|
||||
}
|
||||
}
|
||||
params = Maps(params)
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(params)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
// If <params> is an empty slice, no conversion.
|
||||
if reflectValue.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
array = reflect.MakeSlice(pointerRv.Type().Elem(), reflectValue.Len(), reflectValue.Len())
|
||||
itemType = array.Index(0).Type()
|
||||
)
|
||||
for i := 0; i < reflectValue.Len(); i++ {
|
||||
if itemType.Kind() == reflect.Ptr {
|
||||
// Slice element is type pointer.
|
||||
e := reflect.New(itemType.Elem()).Elem()
|
||||
if deep {
|
||||
if err = StructDeep(reflectValue.Index(i).Interface(), e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(reflectValue.Index(i).Interface(), e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
array.Index(i).Set(e.Addr())
|
||||
} else {
|
||||
// Slice element is not type of pointer.
|
||||
e := reflect.New(itemType).Elem()
|
||||
if deep {
|
||||
if err = StructDeep(reflectValue.Index(i).Interface(), e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(reflectValue.Index(i).Interface(), e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
array.Index(i).Set(e)
|
||||
}
|
||||
}
|
||||
pointerRv.Elem().Set(array)
|
||||
paramsMaps := Maps(params)
|
||||
// If <params> is an empty slice, no conversion.
|
||||
if len(paramsMaps) == 0 {
|
||||
return nil
|
||||
default:
|
||||
return gerror.Newf("params should be type of slice, but got: %v", reflectKind)
|
||||
}
|
||||
var (
|
||||
array = reflect.MakeSlice(pointerRv.Type().Elem(), len(paramsMaps), len(paramsMaps))
|
||||
itemType = array.Index(0).Type()
|
||||
)
|
||||
for i := 0; i < len(paramsMaps); i++ {
|
||||
if itemType.Kind() == reflect.Ptr {
|
||||
// Slice element is type pointer.
|
||||
e := reflect.New(itemType.Elem()).Elem()
|
||||
if deep {
|
||||
if err = StructDeep(paramsMaps[i], e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(paramsMaps[i], e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
array.Index(i).Set(e.Addr())
|
||||
} else {
|
||||
// Slice element is not type of pointer.
|
||||
e := reflect.New(itemType).Elem()
|
||||
if deep {
|
||||
if err = StructDeep(paramsMaps[i], e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(paramsMaps[i], e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
array.Index(i).Set(e)
|
||||
}
|
||||
}
|
||||
pointerRv.Elem().Set(array)
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gvalid
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
"github.com/gogf/gf/net/gipv4"
|
||||
"github.com/gogf/gf/net/gipv6"
|
||||
@ -124,9 +125,8 @@ func doCheck(key string, value interface{}, rules string, messages interface{},
|
||||
// It converts value to string and then does the validation.
|
||||
var (
|
||||
// Do not trim it as the space is also part of the value.
|
||||
val = gconv.String(value)
|
||||
data = make(map[string]string)
|
||||
errorMsgs = make(map[string]string)
|
||||
data = make(map[string]string)
|
||||
errorMsgArray = make(map[string]string)
|
||||
)
|
||||
if len(params) > 0 {
|
||||
for k, v := range gconv.Map(params[0]) {
|
||||
@ -151,7 +151,8 @@ func doCheck(key string, value interface{}, rules string, messages interface{},
|
||||
ruleItems := strings.Split(strings.TrimSpace(rules), "|")
|
||||
for i := 0; ; {
|
||||
array := strings.Split(ruleItems[i], ":")
|
||||
if _, ok := allSupportedRules[array[0]]; !ok {
|
||||
_, ok := allSupportedRules[array[0]]
|
||||
if !ok && customRuleFuncMap[array[0]] == nil {
|
||||
if i > 0 && ruleItems[i-1][:5] == "regex" {
|
||||
ruleItems[i-1] += "|" + ruleItems[i]
|
||||
ruleItems = append(ruleItems[:i], ruleItems[i+1:]...)
|
||||
@ -170,303 +171,36 @@ func doCheck(key string, value interface{}, rules string, messages interface{},
|
||||
}
|
||||
for index := 0; index < len(ruleItems); {
|
||||
var (
|
||||
item = ruleItems[index]
|
||||
match = false
|
||||
results = ruleRegex.FindStringSubmatch(item)
|
||||
ruleKey = strings.TrimSpace(results[1])
|
||||
ruleVal = strings.TrimSpace(results[2])
|
||||
err error
|
||||
match = false
|
||||
results = ruleRegex.FindStringSubmatch(ruleItems[index])
|
||||
ruleKey = strings.TrimSpace(results[1])
|
||||
rulePattern = strings.TrimSpace(results[2])
|
||||
)
|
||||
if len(msgArray) > index {
|
||||
customMsgMap[ruleKey] = strings.TrimSpace(msgArray[index])
|
||||
}
|
||||
switch ruleKey {
|
||||
// Required rules.
|
||||
case
|
||||
"required",
|
||||
"required-if",
|
||||
"required-unless",
|
||||
"required-with",
|
||||
"required-with-all",
|
||||
"required-without",
|
||||
"required-without-all":
|
||||
match = checkRequired(val, ruleKey, ruleVal, data)
|
||||
|
||||
// Length rules.
|
||||
// It also supports length of unicode string.
|
||||
case
|
||||
"length",
|
||||
"min-length",
|
||||
"max-length":
|
||||
if msg := checkLength(val, ruleKey, ruleVal, customMsgMap); msg != "" {
|
||||
errorMsgs[ruleKey] = msg
|
||||
if f, ok := customRuleFuncMap[ruleKey]; ok {
|
||||
// It checks custom validation rules with most priority.
|
||||
var (
|
||||
dataMap map[string]interface{}
|
||||
message = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
)
|
||||
if len(params) > 0 {
|
||||
dataMap = gconv.Map(params[0])
|
||||
}
|
||||
if err := f(ruleItems[index], value, message, dataMap); err != nil {
|
||||
match = false
|
||||
errorMsgArray[ruleKey] = err.Error()
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Range rules.
|
||||
case
|
||||
"min",
|
||||
"max",
|
||||
"between":
|
||||
if msg := checkRange(val, ruleKey, ruleVal, customMsgMap); msg != "" {
|
||||
errorMsgs[ruleKey] = msg
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Custom regular expression.
|
||||
case "regex":
|
||||
// It here should check the rule as there might be special char '|' in it.
|
||||
for i := index + 1; i < len(ruleItems); i++ {
|
||||
if !gregex.IsMatchString(gSINGLE_RULE_PATTERN, ruleItems[i]) {
|
||||
ruleVal += "|" + ruleItems[i]
|
||||
index++
|
||||
}
|
||||
}
|
||||
match = gregex.IsMatchString(ruleVal, val)
|
||||
|
||||
// Date rules.
|
||||
case "date":
|
||||
// Standard date string, which must contain char '-' or '.'.
|
||||
if _, err := gtime.StrToTime(val); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
// Date that not contains char '-' or '.'.
|
||||
if _, err := gtime.StrToTime(val, "Ymd"); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
|
||||
// Date rule with specified format.
|
||||
case "date-format":
|
||||
if _, err := gtime.StrToTimeFormat(val, ruleVal); err == nil {
|
||||
match = true
|
||||
} else {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":format", ruleVal, -1)
|
||||
errorMsgs[ruleKey] = msg
|
||||
}
|
||||
|
||||
// Values of two fields should be equal as string.
|
||||
case "same":
|
||||
if v, ok := data[ruleVal]; ok {
|
||||
if strings.Compare(val, v) == 0 {
|
||||
match = true
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":field", ruleVal, -1)
|
||||
errorMsgs[ruleKey] = msg
|
||||
}
|
||||
|
||||
// Values of two fields should not be equal as string.
|
||||
case "different":
|
||||
match = true
|
||||
if v, ok := data[ruleVal]; ok {
|
||||
if strings.Compare(val, v) == 0 {
|
||||
match = false
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":field", ruleVal, -1)
|
||||
errorMsgs[ruleKey] = msg
|
||||
}
|
||||
|
||||
// Field value should be in range of.
|
||||
case "in":
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(val, strings.TrimSpace(v)) == 0 {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Field value should not be in range of.
|
||||
case "not-in":
|
||||
match = true
|
||||
array := strings.Split(ruleVal, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(val, strings.TrimSpace(v)) == 0 {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Phone format validation.
|
||||
// 1. China Mobile:
|
||||
// 134, 135, 136, 137, 138, 139, 150, 151, 152, 157, 158, 159, 182, 183, 184, 187, 188,
|
||||
// 178(4G), 147(Net);
|
||||
//
|
||||
// 2. China Unicom:
|
||||
// 130, 131, 132, 155, 156, 185, 186 ,176(4G), 145(Net), 175
|
||||
//
|
||||
// 3. China Telecom:
|
||||
// 133, 153, 180, 181, 189, 177(4G)
|
||||
//
|
||||
// 4. Satelite:
|
||||
// 1349
|
||||
//
|
||||
// 5. Virtual:
|
||||
// 170, 173
|
||||
//
|
||||
// 6. 2018:
|
||||
// 16x, 19x
|
||||
case "phone":
|
||||
match = gregex.IsMatchString(`^13[\d]{9}$|^14[5,7]{1}\d{8}$|^15[^4]{1}\d{8}$|^16[\d]{9}$|^17[0,3,5,6,7,8]{1}\d{8}$|^18[\d]{9}$|^19[\d]{9}$`, val)
|
||||
|
||||
// Telephone number:
|
||||
// "XXXX-XXXXXXX"
|
||||
// "XXXX-XXXXXXXX"
|
||||
// "XXX-XXXXXXX"
|
||||
// "XXX-XXXXXXXX"
|
||||
// "XXXXXXX"
|
||||
// "XXXXXXXX"
|
||||
case "telephone":
|
||||
match = gregex.IsMatchString(`^((\d{3,4})|\d{3,4}-)?\d{7,8}$`, val)
|
||||
|
||||
// QQ number: from 10000.
|
||||
case "qq":
|
||||
match = gregex.IsMatchString(`^[1-9][0-9]{4,}$`, val)
|
||||
|
||||
// Postcode number.
|
||||
case "postcode":
|
||||
match = gregex.IsMatchString(`^\d{6}$`, val)
|
||||
|
||||
// China resident id number.
|
||||
//
|
||||
// xxxxxx yyyy MM dd 375 0 十八位
|
||||
// xxxxxx yy MM dd 75 0 十五位
|
||||
//
|
||||
// 地区: [1-9]\d{5}
|
||||
// 年的前两位:(18|19|([23]\d)) 1800-2399
|
||||
// 年的后两位:\d{2}
|
||||
// 月份: ((0[1-9])|(10|11|12))
|
||||
// 天数: (([0-2][1-9])|10|20|30|31) 闰年不能禁止29+
|
||||
//
|
||||
// 三位顺序码:\d{3}
|
||||
// 两位顺序码:\d{2}
|
||||
// 校验码: [0-9Xx]
|
||||
//
|
||||
// 十八位:^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$
|
||||
// 十五位:^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$
|
||||
//
|
||||
// 总:
|
||||
// (^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)
|
||||
case "resident-id":
|
||||
match = checkResidentId(val)
|
||||
|
||||
// Bank card number using LUHN algorithm.
|
||||
case "bank-card":
|
||||
match = checkLuHn(val)
|
||||
|
||||
// Universal passport format rule:
|
||||
// Starting with letter, containing only numbers or underscores, length between 6 and 18.
|
||||
case "passport":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z]{1}\w{5,17}$`, val)
|
||||
|
||||
// Universal password format rule1:
|
||||
// Containing any visible chars, length between 6 and 18.
|
||||
case "password":
|
||||
match = gregex.IsMatchString(`^[\w\S]{6,18}$`, val)
|
||||
|
||||
// Universal password format rule2:
|
||||
// Must meet password rule1, must contain lower and upper letters and numbers.
|
||||
case "password2":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, val) &&
|
||||
gregex.IsMatchString(`[a-z]+`, val) &&
|
||||
gregex.IsMatchString(`[A-Z]+`, val) &&
|
||||
gregex.IsMatchString(`\d+`, val) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Universal password format rule3:
|
||||
// Must meet password rule1, must contain lower and upper letters, numbers and special chars.
|
||||
case "password3":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, val) &&
|
||||
gregex.IsMatchString(`[a-z]+`, val) &&
|
||||
gregex.IsMatchString(`[A-Z]+`, val) &&
|
||||
gregex.IsMatchString(`\d+`, val) &&
|
||||
gregex.IsMatchString(`[^a-zA-Z0-9]+`, val) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Json.
|
||||
case "json":
|
||||
if _, err := gjson.Decode([]byte(val)); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Integer.
|
||||
case "integer":
|
||||
if _, err := strconv.Atoi(val); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Float.
|
||||
case "float":
|
||||
if _, err := strconv.ParseFloat(val, 10); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Boolean(1,true,on,yes:true | 0,false,off,no,"":false).
|
||||
case "boolean":
|
||||
match = false
|
||||
if _, ok := boolMap[strings.ToLower(val)]; ok {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Email.
|
||||
case "email":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)+$`, val)
|
||||
|
||||
// URL
|
||||
case "url":
|
||||
match = gregex.IsMatchString(`(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]`, val)
|
||||
|
||||
// Domain
|
||||
case "domain":
|
||||
match = gregex.IsMatchString(`^([0-9a-zA-Z][0-9a-zA-Z\-]{0,62}\.)+([a-zA-Z]{0,62})$`, val)
|
||||
|
||||
// IP(IPv4/IPv6).
|
||||
case "ip":
|
||||
match = gipv4.Validate(val) || gipv6.Validate(val)
|
||||
|
||||
// IPv4.
|
||||
case "ipv4":
|
||||
match = gipv4.Validate(val)
|
||||
|
||||
// IPv6.
|
||||
case "ipv6":
|
||||
match = gipv6.Validate(val)
|
||||
|
||||
// MAC.
|
||||
case "mac":
|
||||
match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}[\-:]){5}[0-9A-Fa-f]{2}$`, val)
|
||||
|
||||
default:
|
||||
if f, ok := customRuleFuncMap[ruleKey]; ok {
|
||||
var (
|
||||
dataMap map[string]interface{}
|
||||
message = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
)
|
||||
if len(params) > 0 {
|
||||
dataMap = gconv.Map(params[0])
|
||||
}
|
||||
if err := f(value, message, dataMap); err != nil {
|
||||
match = false
|
||||
errorMsgs[ruleKey] = err.Error()
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
} else {
|
||||
errorMsgs[ruleKey] = "Invalid rule name: " + ruleKey
|
||||
} else {
|
||||
// It checks build-in validation rules if there's no custom rule.
|
||||
match, err = doCheckBuildInRules(index, value, ruleKey, rulePattern, ruleItems, data, customMsgMap)
|
||||
if !match && err != nil {
|
||||
errorMsgArray[ruleKey] = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
@ -474,16 +208,303 @@ func doCheck(key string, value interface{}, rules string, messages interface{},
|
||||
if !match {
|
||||
// It does nothing if the error message for this rule
|
||||
// is already set in previous validation.
|
||||
if _, ok := errorMsgs[ruleKey]; !ok {
|
||||
errorMsgs[ruleKey] = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
if _, ok := errorMsgArray[ruleKey]; !ok {
|
||||
errorMsgArray[ruleKey] = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
}
|
||||
}
|
||||
index++
|
||||
}
|
||||
if len(errorMsgs) > 0 {
|
||||
if len(errorMsgArray) > 0 {
|
||||
return newError([]string{rules}, ErrorMap{
|
||||
key: errorMsgs,
|
||||
key: errorMsgArray,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func doCheckBuildInRules(
|
||||
index int,
|
||||
value interface{},
|
||||
ruleKey string,
|
||||
rulePattern string,
|
||||
ruleItems []string,
|
||||
dataMap map[string]string,
|
||||
customMsgMap map[string]string,
|
||||
) (match bool, err error) {
|
||||
valueStr := gconv.String(value)
|
||||
switch ruleKey {
|
||||
// Required rules.
|
||||
case
|
||||
"required",
|
||||
"required-if",
|
||||
"required-unless",
|
||||
"required-with",
|
||||
"required-with-all",
|
||||
"required-without",
|
||||
"required-without-all":
|
||||
match = checkRequired(valueStr, ruleKey, rulePattern, dataMap)
|
||||
|
||||
// Length rules.
|
||||
// It also supports length of unicode string.
|
||||
case
|
||||
"length",
|
||||
"min-length",
|
||||
"max-length":
|
||||
if msg := checkLength(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" {
|
||||
return match, errors.New(msg)
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Range rules.
|
||||
case
|
||||
"min",
|
||||
"max",
|
||||
"between":
|
||||
if msg := checkRange(valueStr, ruleKey, rulePattern, customMsgMap); msg != "" {
|
||||
return match, errors.New(msg)
|
||||
} else {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Custom regular expression.
|
||||
case "regex":
|
||||
// It here should check the rule as there might be special char '|' in it.
|
||||
for i := index + 1; i < len(ruleItems); i++ {
|
||||
if !gregex.IsMatchString(gSINGLE_RULE_PATTERN, ruleItems[i]) {
|
||||
rulePattern += "|" + ruleItems[i]
|
||||
index++
|
||||
}
|
||||
}
|
||||
match = gregex.IsMatchString(rulePattern, valueStr)
|
||||
|
||||
// Date rules.
|
||||
case "date":
|
||||
// Standard date string, which must contain char '-' or '.'.
|
||||
if _, err := gtime.StrToTime(valueStr); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
// Date that not contains char '-' or '.'.
|
||||
if _, err := gtime.StrToTime(valueStr, "Ymd"); err == nil {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
|
||||
// Date rule with specified format.
|
||||
case "date-format":
|
||||
if _, err := gtime.StrToTimeFormat(valueStr, rulePattern); err == nil {
|
||||
match = true
|
||||
} else {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":format", rulePattern, -1)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Values of two fields should be equal as string.
|
||||
case "same":
|
||||
if v, ok := dataMap[rulePattern]; ok {
|
||||
if strings.Compare(valueStr, v) == 0 {
|
||||
match = true
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":field", rulePattern, -1)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Values of two fields should not be equal as string.
|
||||
case "different":
|
||||
match = true
|
||||
if v, ok := dataMap[rulePattern]; ok {
|
||||
if strings.Compare(valueStr, v) == 0 {
|
||||
match = false
|
||||
}
|
||||
}
|
||||
if !match {
|
||||
var msg string
|
||||
msg = getErrorMessageByRule(ruleKey, customMsgMap)
|
||||
msg = strings.Replace(msg, ":field", rulePattern, -1)
|
||||
return match, errors.New(msg)
|
||||
}
|
||||
|
||||
// Field value should be in range of.
|
||||
case "in":
|
||||
array := strings.Split(rulePattern, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 {
|
||||
match = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Field value should not be in range of.
|
||||
case "not-in":
|
||||
match = true
|
||||
array := strings.Split(rulePattern, ",")
|
||||
for _, v := range array {
|
||||
if strings.Compare(valueStr, strings.TrimSpace(v)) == 0 {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Phone format validation.
|
||||
// 1. China Mobile:
|
||||
// 134, 135, 136, 137, 138, 139, 150, 151, 152, 157, 158, 159, 182, 183, 184, 187, 188,
|
||||
// 178(4G), 147(Net);
|
||||
//
|
||||
// 2. China Unicom:
|
||||
// 130, 131, 132, 155, 156, 185, 186 ,176(4G), 145(Net), 175
|
||||
//
|
||||
// 3. China Telecom:
|
||||
// 133, 153, 180, 181, 189, 177(4G)
|
||||
//
|
||||
// 4. Satelite:
|
||||
// 1349
|
||||
//
|
||||
// 5. Virtual:
|
||||
// 170, 173
|
||||
//
|
||||
// 6. 2018:
|
||||
// 16x, 19x
|
||||
case "phone":
|
||||
match = gregex.IsMatchString(`^13[\d]{9}$|^14[5,7]{1}\d{8}$|^15[^4]{1}\d{8}$|^16[\d]{9}$|^17[0,3,5,6,7,8]{1}\d{8}$|^18[\d]{9}$|^19[\d]{9}$`, valueStr)
|
||||
|
||||
// Telephone number:
|
||||
// "XXXX-XXXXXXX"
|
||||
// "XXXX-XXXXXXXX"
|
||||
// "XXX-XXXXXXX"
|
||||
// "XXX-XXXXXXXX"
|
||||
// "XXXXXXX"
|
||||
// "XXXXXXXX"
|
||||
case "telephone":
|
||||
match = gregex.IsMatchString(`^((\d{3,4})|\d{3,4}-)?\d{7,8}$`, valueStr)
|
||||
|
||||
// QQ number: from 10000.
|
||||
case "qq":
|
||||
match = gregex.IsMatchString(`^[1-9][0-9]{4,}$`, valueStr)
|
||||
|
||||
// Postcode number.
|
||||
case "postcode":
|
||||
match = gregex.IsMatchString(`^\d{6}$`, valueStr)
|
||||
|
||||
// China resident id number.
|
||||
//
|
||||
// xxxxxx yyyy MM dd 375 0 十八位
|
||||
// xxxxxx yy MM dd 75 0 十五位
|
||||
//
|
||||
// 地区: [1-9]\d{5}
|
||||
// 年的前两位:(18|19|([23]\d)) 1800-2399
|
||||
// 年的后两位:\d{2}
|
||||
// 月份: ((0[1-9])|(10|11|12))
|
||||
// 天数: (([0-2][1-9])|10|20|30|31) 闰年不能禁止29+
|
||||
//
|
||||
// 三位顺序码:\d{3}
|
||||
// 两位顺序码:\d{2}
|
||||
// 校验码: [0-9Xx]
|
||||
//
|
||||
// 十八位:^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$
|
||||
// 十五位:^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$
|
||||
//
|
||||
// 总:
|
||||
// (^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)
|
||||
case "resident-id":
|
||||
match = checkResidentId(valueStr)
|
||||
|
||||
// Bank card number using LUHN algorithm.
|
||||
case "bank-card":
|
||||
match = checkLuHn(valueStr)
|
||||
|
||||
// Universal passport format rule:
|
||||
// Starting with letter, containing only numbers or underscores, length between 6 and 18.
|
||||
case "passport":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z]{1}\w{5,17}$`, valueStr)
|
||||
|
||||
// Universal password format rule1:
|
||||
// Containing any visible chars, length between 6 and 18.
|
||||
case "password":
|
||||
match = gregex.IsMatchString(`^[\w\S]{6,18}$`, valueStr)
|
||||
|
||||
// Universal password format rule2:
|
||||
// Must meet password rule1, must contain lower and upper letters and numbers.
|
||||
case "password2":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, valueStr) &&
|
||||
gregex.IsMatchString(`[a-z]+`, valueStr) &&
|
||||
gregex.IsMatchString(`[A-Z]+`, valueStr) &&
|
||||
gregex.IsMatchString(`\d+`, valueStr) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Universal password format rule3:
|
||||
// Must meet password rule1, must contain lower and upper letters, numbers and special chars.
|
||||
case "password3":
|
||||
if gregex.IsMatchString(`^[\w\S]{6,18}$`, valueStr) &&
|
||||
gregex.IsMatchString(`[a-z]+`, valueStr) &&
|
||||
gregex.IsMatchString(`[A-Z]+`, valueStr) &&
|
||||
gregex.IsMatchString(`\d+`, valueStr) &&
|
||||
gregex.IsMatchString(`[^a-zA-Z0-9]+`, valueStr) {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Json.
|
||||
case "json":
|
||||
if _, err := gjson.Decode([]byte(valueStr)); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Integer.
|
||||
case "integer":
|
||||
if _, err := strconv.Atoi(valueStr); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Float.
|
||||
case "float":
|
||||
if _, err := strconv.ParseFloat(valueStr, 10); err == nil {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Boolean(1,true,on,yes:true | 0,false,off,no,"":false).
|
||||
case "boolean":
|
||||
match = false
|
||||
if _, ok := boolMap[strings.ToLower(valueStr)]; ok {
|
||||
match = true
|
||||
}
|
||||
|
||||
// Email.
|
||||
case "email":
|
||||
match = gregex.IsMatchString(`^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)+$`, valueStr)
|
||||
|
||||
// URL
|
||||
case "url":
|
||||
match = gregex.IsMatchString(`(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]`, valueStr)
|
||||
|
||||
// Domain
|
||||
case "domain":
|
||||
match = gregex.IsMatchString(`^([0-9a-zA-Z][0-9a-zA-Z\-]{0,62}\.)+([a-zA-Z]{0,62})$`, valueStr)
|
||||
|
||||
// IP(IPv4/IPv6).
|
||||
case "ip":
|
||||
match = gipv4.Validate(valueStr) || gipv6.Validate(valueStr)
|
||||
|
||||
// IPv4.
|
||||
case "ipv4":
|
||||
match = gipv4.Validate(valueStr)
|
||||
|
||||
// IPv6.
|
||||
case "ipv6":
|
||||
match = gipv6.Validate(valueStr)
|
||||
|
||||
// MAC.
|
||||
case "mac":
|
||||
match = gregex.IsMatchString(`^([0-9A-Fa-f]{2}[\-:]){5}[0-9A-Fa-f]{2}$`, valueStr)
|
||||
|
||||
default:
|
||||
return match, errors.New("Invalid rule name: " + ruleKey)
|
||||
}
|
||||
return match, nil
|
||||
}
|
||||
|
||||
@ -6,15 +6,13 @@
|
||||
|
||||
package gvalid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// RuleFunc is the custom function for data validation.
|
||||
// The parameter <rule> specifies the validation rule string, like "required", "between:1,100", etc.
|
||||
// The parameter <value> specifies the value for this rule to validate.
|
||||
// The parameter <message> specifies the custom error message or configured i18n message for this rule.
|
||||
// The parameter <params> specifies all the parameters that needs .
|
||||
type RuleFunc func(value interface{}, message string, params map[string]interface{}) error
|
||||
// The parameter <params> specifies all the parameters that needs. You can ignore parameter <params> if
|
||||
// you do not really need it in your custom validation rule.
|
||||
type RuleFunc func(rule string, value interface{}, message string, params map[string]interface{}) error
|
||||
|
||||
var (
|
||||
// customRuleFuncMap stores the custom rule functions.
|
||||
@ -24,10 +22,11 @@ var (
|
||||
// RegisterRule registers custom validation rule and function for package.
|
||||
// It returns error if there's already the same rule registered previously.
|
||||
func RegisterRule(rule string, f RuleFunc) error {
|
||||
if _, ok := allSupportedRules[rule]; ok {
|
||||
return fmt.Errorf(`validation rule "%s" is already registered`, rule)
|
||||
}
|
||||
allSupportedRules[rule] = struct{}{}
|
||||
customRuleFuncMap[rule] = f
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteRule deletes custom defined validation rule and its function from global package.
|
||||
func DeleteRule(rule string) {
|
||||
delete(customRuleFuncMap, rule)
|
||||
}
|
||||
|
||||
@ -49,17 +49,26 @@ func newErrorStr(key, err string) *Error {
|
||||
|
||||
// Map returns the first error message as map.
|
||||
func (e *Error) Map() map[string]string {
|
||||
if e == nil {
|
||||
return map[string]string{}
|
||||
}
|
||||
_, m := e.FirstItem()
|
||||
return m
|
||||
}
|
||||
|
||||
// Maps returns all error messages as map.
|
||||
func (e *Error) Maps() ErrorMap {
|
||||
if e == nil {
|
||||
return nil
|
||||
}
|
||||
return e.errors
|
||||
}
|
||||
|
||||
// FirstItem returns the field name and error messages for the first validation rule error.
|
||||
func (e *Error) FirstItem() (key string, messages map[string]string) {
|
||||
if e == nil {
|
||||
return "", map[string]string{}
|
||||
}
|
||||
if e.firstItem != nil {
|
||||
return e.firstKey, e.firstItem
|
||||
}
|
||||
@ -85,6 +94,9 @@ func (e *Error) FirstItem() (key string, messages map[string]string) {
|
||||
|
||||
// FirstRule returns the first error rule and message string.
|
||||
func (e *Error) FirstRule() (rule string, err string) {
|
||||
if e == nil {
|
||||
return "", ""
|
||||
}
|
||||
// By sequence.
|
||||
if len(e.rules) > 0 {
|
||||
for _, v := range e.rules {
|
||||
@ -112,22 +124,34 @@ func (e *Error) FirstRule() (rule string, err string) {
|
||||
// FirstString returns the first error message as string.
|
||||
// Note that the returned message might be different if it has no sequence.
|
||||
func (e *Error) FirstString() (err string) {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
_, err = e.FirstRule()
|
||||
return
|
||||
}
|
||||
|
||||
// String returns all error messages as string, multiple error messages joined using char ';'.
|
||||
func (e *Error) String() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(e.Strings(), "; ")
|
||||
}
|
||||
|
||||
// Error implements interface of error.Error.
|
||||
func (e *Error) Error() string {
|
||||
if e == nil {
|
||||
return ""
|
||||
}
|
||||
return e.String()
|
||||
}
|
||||
|
||||
// Strings returns all error messages as string array.
|
||||
func (e *Error) Strings() (errs []string) {
|
||||
if e == nil {
|
||||
return []string{}
|
||||
}
|
||||
errs = make([]string, 0)
|
||||
// By sequence.
|
||||
if len(e.rules) > 0 {
|
||||
|
||||
@ -57,6 +57,7 @@ var defaultMessages = map[string]string{
|
||||
"in": "The :attribute value is not in acceptable range",
|
||||
"not-in": "The :attribute value is not in acceptable range",
|
||||
"regex": "The :attribute value is invalid",
|
||||
"__default__": "The :attribute value is invalid",
|
||||
}
|
||||
|
||||
// getErrorMessageByRule retrieves and returns the error message for specified rule.
|
||||
@ -71,5 +72,12 @@ func getErrorMessageByRule(ruleKey string, customMsgMap map[string]string) strin
|
||||
if content == "" {
|
||||
content = defaultMessages[ruleKey]
|
||||
}
|
||||
// If there's no configured rule message, it uses default one.
|
||||
if content == "" {
|
||||
content = gi18n.GetContent(`gf.gvalid.rule.__default__`)
|
||||
if content == "" {
|
||||
content = defaultMessages["__default__"]
|
||||
}
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
@ -16,9 +16,9 @@ import (
|
||||
"github.com/gogf/gf/util/gvalid"
|
||||
)
|
||||
|
||||
func Test_CustomRule(t *testing.T) {
|
||||
func Test_CustomRule1(t *testing.T) {
|
||||
rule := "custom"
|
||||
err := gvalid.RegisterRule(rule, func(value interface{}, message string, params map[string]interface{}) error {
|
||||
err := gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error {
|
||||
pass := gconv.String(value)
|
||||
if len(pass) != 6 {
|
||||
return errors.New(message)
|
||||
@ -62,3 +62,47 @@ func Test_CustomRule(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_CustomRule2(t *testing.T) {
|
||||
rule := "required-map"
|
||||
err := gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error {
|
||||
m := gconv.Map(value)
|
||||
if len(m) == 0 {
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
// Check.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
errStr := "data map should not be empty"
|
||||
t.Assert(gvalid.Check(g.Map{}, rule, errStr).String(), errStr)
|
||||
t.Assert(gvalid.Check(g.Map{"k": "v"}, rule, errStr).String(), nil)
|
||||
})
|
||||
// Error with struct validation.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Value map[string]string `v:"uid@required-map#自定义错误"`
|
||||
Data string `p:"data"`
|
||||
}
|
||||
st := &T{
|
||||
Value: map[string]string{},
|
||||
Data: "123456",
|
||||
}
|
||||
err := gvalid.CheckStruct(st, nil)
|
||||
t.Assert(err.String(), "自定义错误")
|
||||
})
|
||||
// No error with struct validation.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Value map[string]string `v:"uid@required-map#自定义错误"`
|
||||
Data string `p:"data"`
|
||||
}
|
||||
st := &T{
|
||||
Value: map[string]string{"k": "v"},
|
||||
Data: "123456",
|
||||
}
|
||||
err := gvalid.CheckStruct(st, nil)
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
@ -7,9 +7,14 @@
|
||||
package gvalid_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gvalid"
|
||||
"math"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func ExampleCheckMap() {
|
||||
@ -68,9 +73,9 @@ func ExampleCheckStruct() {
|
||||
Size: 10,
|
||||
}
|
||||
err := gvalid.CheckStruct(obj, nil)
|
||||
fmt.Println(err)
|
||||
fmt.Println(err == nil)
|
||||
// Output:
|
||||
// <nil>
|
||||
// true
|
||||
}
|
||||
|
||||
// Empty pointer attribute.
|
||||
@ -85,9 +90,9 @@ func ExampleCheckStruct2() {
|
||||
Size: 10,
|
||||
}
|
||||
err := gvalid.CheckStruct(obj, nil)
|
||||
fmt.Println(err)
|
||||
fmt.Println(err == nil)
|
||||
// Output:
|
||||
// <nil>
|
||||
// true
|
||||
}
|
||||
|
||||
// Empty integer attribute.
|
||||
@ -106,3 +111,79 @@ func ExampleCheckStruct3() {
|
||||
// Output:
|
||||
// project id must between 1, 10000
|
||||
}
|
||||
|
||||
func ExampleRegisterRule() {
|
||||
rule := "unique-name"
|
||||
gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error {
|
||||
var (
|
||||
id = gconv.Int(params["Id"])
|
||||
name = gconv.String(value)
|
||||
)
|
||||
n, err := g.Table("user").Where("id != ? and name = ?", id, name).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `v:"required|unique-name # 请输入用户名称|用户名称已被占用"`
|
||||
Pass string `v:"required|length:6,18"`
|
||||
}
|
||||
user := &User{
|
||||
Id: 1,
|
||||
Name: "john",
|
||||
Pass: "123456",
|
||||
}
|
||||
err := gvalid.CheckStruct(user, nil)
|
||||
fmt.Println(err.Error())
|
||||
// May Output:
|
||||
// 用户名称已被占用
|
||||
}
|
||||
|
||||
func ExampleRegisterRule_OverwriteRequired() {
|
||||
rule := "required"
|
||||
gvalid.RegisterRule(rule, func(rule string, value interface{}, message string, params map[string]interface{}) error {
|
||||
reflectValue := reflect.ValueOf(value)
|
||||
if reflectValue.Kind() == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
}
|
||||
isEmpty := false
|
||||
switch reflectValue.Kind() {
|
||||
case reflect.Bool:
|
||||
isEmpty = !reflectValue.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
isEmpty = reflectValue.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
isEmpty = reflectValue.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
isEmpty = math.Float64bits(reflectValue.Float()) == 0
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
c := reflectValue.Complex()
|
||||
isEmpty = math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0
|
||||
case reflect.String, reflect.Map, reflect.Array, reflect.Slice:
|
||||
isEmpty = reflectValue.Len() == 0
|
||||
}
|
||||
if isEmpty {
|
||||
return errors.New(message)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
fmt.Println(gvalid.Check("", "required", "It's required"))
|
||||
fmt.Println(gvalid.Check([]string{}, "required", "It's required"))
|
||||
fmt.Println(gvalid.Check(map[string]int{}, "required", "It's required"))
|
||||
gvalid.DeleteRule(rule)
|
||||
fmt.Println("rule deleted")
|
||||
fmt.Println(gvalid.Check("", "required", "It's required"))
|
||||
fmt.Println(gvalid.Check([]string{}, "required", "It's required"))
|
||||
fmt.Println(gvalid.Check(map[string]int{}, "required", "It's required"))
|
||||
// Output:
|
||||
// It's required
|
||||
// It's required
|
||||
// It's required
|
||||
// rule deleted
|
||||
// It's required
|
||||
}
|
||||
|
||||
@ -40,4 +40,5 @@
|
||||
"gf.gvalid.rule.different" = ":attribute 字段值不能与:field相同"
|
||||
"gf.gvalid.rule.in" = ":attribute 字段值不合法"
|
||||
"gf.gvalid.rule.not-in" = ":attribute 字段值不合法"
|
||||
"gf.gvalid.rule.regex" = ":attribute 字段值不合法"
|
||||
"gf.gvalid.rule.regex" = ":attribute 字段值不合法"
|
||||
"gf.gvalid.rule.__default__" = ":attribute 字段值不合法"
|
||||
@ -40,4 +40,5 @@
|
||||
"gf.gvalid.rule.different" = "The :attribute value must be different from field :field"
|
||||
"gf.gvalid.rule.in" = "The :attribute value is not in acceptable range"
|
||||
"gf.gvalid.rule.not-in" = "The :attribute value is not in acceptable range"
|
||||
"gf.gvalid.rule.regex" = "The :attribute value is invalid"
|
||||
"gf.gvalid.rule.regex" = "The :attribute value is invalid"
|
||||
"gf.gvalid.rule.__default__" = "The :attribute value is invalid"
|
||||
@ -1,4 +1,4 @@
|
||||
package gf
|
||||
|
||||
const VERSION = "v1.13.6"
|
||||
const VERSION = "v1.13.7"
|
||||
const AUTHORS = "john<john@goframe.org>"
|
||||
|
||||
Reference in New Issue
Block a user