mirror of
https://gitee.com/johng/gf
synced 2026-06-09 11:03:59 +08:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bc8142974f | |||
| fadb7a8f8f | |||
| e1bfe90833 | |||
| 042dc0b33f | |||
| 8ca535dbf0 | |||
| e20183e7a1 | |||
| df86ffb61e | |||
| bfcf133c91 | |||
| 24a377d3a8 | |||
| baf51bc68f | |||
| 5f4b585164 | |||
| 17a11187b0 | |||
| 7caf7976cf | |||
| 1a31792c14 | |||
| d56eb49e41 | |||
| a1236b5e16 | |||
| 8f278be0dc | |||
| f8ab71e7f0 | |||
| 9cca3a3ec1 | |||
| 97bcf2a438 | |||
| 85dd2e9f4f | |||
| 23d62da87f | |||
| b4d947fecd | |||
| 650c95af31 | |||
| 943116d495 | |||
| 644df7c16e | |||
| 638773b216 | |||
| 889e7914e2 | |||
| 68cc85f2b2 | |||
| c273ce576b | |||
| f8d57096a8 | |||
| d7542e87ae | |||
| b178210a31 | |||
| 3e6a23b0e1 | |||
| ee8d2afe58 | |||
| 11e102e137 | |||
| e06b62ecf2 | |||
| d178102f82 | |||
| e1dd5cce7d | |||
| 1edc1f35fb | |||
| 4df47be521 | |||
| 9cb88bca5a | |||
| fa8cc1d3f4 | |||
| 9ae8a7ca33 | |||
| f4da179140 | |||
| 13330658cb | |||
| 9f04e46166 | |||
| 1072ea3fb0 | |||
| ea9e8055a4 | |||
| 784abf2a30 | |||
| ed4a70deff | |||
| fef20d10a2 | |||
| 176dcdc7cc | |||
| 12ed05f846 | |||
| 5999f22f76 | |||
| c056fd2a06 | |||
| cb422f043e | |||
| 4ae89dc9f6 | |||
| 4f6f07db1d | |||
| cd981c7294 | |||
| 557d2967fa | |||
| a7a70636dd | |||
| 2215661f89 | |||
| a22b590b43 | |||
| 1b0b209662 | |||
| 2a2761c54f | |||
| 1c83d72f39 | |||
| fcea774b59 |
@ -2,7 +2,7 @@
|
||||
# MySQL.
|
||||
[database]
|
||||
debug = true
|
||||
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true&loc=Local"
|
||||
link = "mysql:root:12345678@tcp(127.0.0.1:3306)/test?parseTime=true"
|
||||
MaxOpen = 100
|
||||
|
||||
# Redis.
|
||||
|
||||
@ -2,11 +2,17 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
db := g.DB()
|
||||
db.SetDebug(true)
|
||||
|
||||
db.Table("user").Data("num=num+1").Where("id", 8).Update()
|
||||
t1, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 19:03:32")
|
||||
t2, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 19:03:34")
|
||||
u, err := g.DB().Table("orders").Where("updated_at>? and updated_at<?", t1, t2).One()
|
||||
//u, err := g.DB().Table("orders").Where("updated_at>? and updated_at<?", gtime.New("2020-10-27 19:03:32"), gtime.New("2020-10-27 19:03:34")).One()
|
||||
//u, err := g.DB().Table("orders").Fields("id").Where("updated_at>'2020-10-27 19:03:32' and updated_at<'2020-10-27 19:03:34'").Value()
|
||||
g.Dump(u, err)
|
||||
}
|
||||
|
||||
22
DONATOR.MD
22
DONATOR.MD
@ -119,6 +119,28 @@ please note your github/gitee account in your payment bill. All the donations wi
|
||||
|金毛|wechat|¥100.00|
|
||||
|莫失莫忘|wechat|¥100.00|
|
||||
|**航|alipay|¥20.00|
|
||||
|阿康|wechat|¥100.00|
|
||||
|Tzp|wechat|¥10.00|
|
||||
|[hkxiaoyu118](https://github.com/hkxiaoyu118)|wechat|¥10.00|
|
||||
|辰|wechat|¥50.00|
|
||||
|LSJ|wechat|¥66.66|我想我是海:祝gf越来越好,统治后端
|
||||
|yu|wechat|¥100.00|感谢开源,加油!我是QQ群里的lah
|
||||
|雁字回时月满楼|wechat|¥20.00|感谢gf
|
||||
|Panda|wechat|¥20.00|支持一下!gf很棒👍
|
||||
|[Thunur](https://gitee.com/thunur)|wechat|¥100.00|
|
||||
|[Mr.奇淼](https://www.gin-vue-admin.com/)|wechat|¥18.88|强哥无敌,奇淼爱你
|
||||
|[SliverHorn](hhttps://github.com/sliverhorn)|wechat|¥17.77|强哥无敌,SliverHorn爱你
|
||||
|[fly的狐狸](https://github.com/zcool321)|wechat|¥50.00|
|
||||
|北漂生活|wechat|¥66.66|gf大展鸿图
|
||||
|YJ|wechat|¥10.00|YangJ-Eric祝愿越来越好
|
||||
|秋葵|wechat|¥20.00|之前强哥
|
||||
|陈诚|wechat|¥100.00|Loocor恭喜郭总发版🎉
|
||||
|**栋|alipay|¥100.00|
|
||||
|**浩|alipay|¥100.00|
|
||||
|RAGGA-TIME|alipay|¥50.00|
|
||||
|[ChArmy](https://gitee.com/charmy)|alipay|¥50.00|
|
||||
|[sanfenzui](https://gitee.com/sanfenzui)|alipay|¥88.00|
|
||||
|刘宇|wechat|¥30.00|请你喝咖啡
|
||||
|
||||
|
||||
|
||||
|
||||
@ -125,6 +125,7 @@ The concurrency starts from `100` to `10000`.
|
||||
- [LeYouJia](https://www.leyoujia.com/)
|
||||
- [IGG](https://igg.com)
|
||||
- [XiMaLaYa](https://www.ximalaya.com)
|
||||
- [ZYBang](https://www.zybang.com/)
|
||||
|
||||
> We list part of the users here, if your company or products are using `GoFrame`, please let us know [here](https://github.com/gogf/gf/issues/168).
|
||||
|
||||
|
||||
@ -145,6 +145,7 @@ ab -t 10 -c 100 http://127.0.0.1:3000/json
|
||||
- [乐有家](https://www.leyoujia.com/)
|
||||
- [IGG](https://igg.com)
|
||||
- [喜马拉雅](https://www.ximalaya.com)
|
||||
- [作业帮](https://www.zybang.com/)
|
||||
|
||||
> 在这里只列举了部分知名的用户,如果您的企业或者产品正在使用`GoFrame`,欢迎到 [这里](https://github.com/gogf/gf/issues/168) 留言。
|
||||
|
||||
|
||||
174
RELEASE.2.MD
174
RELEASE.2.MD
@ -1,3 +1,177 @@
|
||||
# `v1.14.2` (2020-10-27)
|
||||
|
||||
# GoFrame
|
||||
|
||||
`GF(Go Frame)`是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。并提供了Web服务开发的系列核心组件,如:`Router`、`Cookie`、`Session`、`Middleware`、服务注册、模板引擎等等,支持热重启、热更新、域名绑定、`TLS/HTTPS`、`Rewrite`等特性。
|
||||
|
||||
## 特点
|
||||
|
||||
* 模块化、松耦合设计;
|
||||
* 模块丰富、开箱即用;
|
||||
* 简便易用、易于维护;
|
||||
* 高代码质量、高单元测试覆盖率;
|
||||
* 社区活跃,大牛谦逊低调脾气好;
|
||||
* 详尽的开发文档及示例;
|
||||
* 完善的本地中文化支持;
|
||||
* 设计为团队及企业使用;
|
||||
|
||||
## 支持我们
|
||||
|
||||
OSC最佳开源项目评选开始了,如果您喜欢`GoFrame`,欢迎为`GoFrame`投上您宝贵的一票🙏 https://www.oschina.net/p/goframe
|
||||
|
||||
|
||||
# Change Log
|
||||
|
||||
由于`GoFrame`是模块化设计,因此每个版本的更新记录都会以模块的形式进行介绍。
|
||||
|
||||
重要更新:
|
||||
1. 将框架内所有的`json`操作从标准库替换为`json-iterator/go`,提高操作效率。
|
||||
1. 缓存模块重构底层设计,增加适配器设计模式,并增加内存及`Redis`适配器支持。其中内存适配器默认核心模块提供,`Redis`适配器由社区模块提供:https://goframe.org/os/gcache/adapter
|
||||
1. 增加可自定义的校验规则注册特性:https://goframe.org/util/gvalid/customrule
|
||||
1. `Web Server`增加所有配置项示例:https://goframe.org/net/ghttp/config/example
|
||||
1. `ORM`新增基于`Redis`的`SQL`缓存适配器:https://goframe.org/database/gdb/model/cache
|
||||
1. `ORM`新增模型关联实验特性:https://goframe.org/database/gdb/model/association
|
||||
1. `ORM`改进时间自动更新特性增加自定义时间字段:https://goframe.org/database/gdb/model/auto-time
|
||||
1. 错误处理模块新增`Current`及`Next`方法:https://goframe.org/errors/gerror/index
|
||||
|
||||
|
||||
|
||||
## `net`
|
||||
1. `ghttp`
|
||||
- `Client`
|
||||
- 增加`GetVar/PutVar/PostVar`等`*Var`请求方法,用于发起`HTTP`请求获取内容之后直接返回泛型对象,方便类型转换,特别是针对于返回`XML/JSON`的结果处理将会更加简便:https://goframe.org/net/ghttp/client/demo/index
|
||||
- 增加`SetProxy/Proxy`方法,用于设置客户端代理,支持`HTTP/Socket5`代理类型:https://goframe.org/net/ghttp/client/demo/proxy
|
||||
- 增加`SetRedirectLimit/RedirectLimit`方法,用于设置页面跳转数量限制。
|
||||
- `Request`
|
||||
- 增加`ParseQuery`, `ParseForm`方法,用于解析指定类型的参数,并绑定到给定的对象。
|
||||
- 增加`GetHeader`方法,用于获取指定`Header`参数。
|
||||
- 增加`GetRemoteIp`方法,用于获取请求客户端IP。在IP白名单限制时应当使用`GetRemoteIp`而不是`GetClientIp`进行判断,后者可以通过`Header`伪造。
|
||||
- 增加`ReloadParam`方法,往往用在中间件处理中,当中间件修改了请求参数,需要通过调用该方法重新解析一下请求参数。
|
||||
- 增加`GetRouterMap`方法,用于获得所有的路由参数返回为`map`。
|
||||
- `Response`
|
||||
- 将`Output`方法名称改为`Flush`,用于将缓冲区的数据写入到客户端数据流中。
|
||||
- `Server`
|
||||
- `Server`增加所有配置项示例:https://goframe.org/net/ghttp/config/example
|
||||
- 增加`SessionCookieOutput`配置,用于控制是否输出`SessionId`到`Cookie`中,默认为开启。
|
||||
- 改进路由解析,增加对`URI`带有重复的`/`符号的支持。
|
||||
- `Pprof`功能路由支持`Domain`绑定。
|
||||
- 其他一些细节改进。
|
||||
- `Cookie`
|
||||
- 增加`SetHttpCookie`方法,用于根据标准库`http.Cookie`对象设置`Cookie`。
|
||||
- 其他一些功能改进
|
||||
|
||||
|
||||
## `database`
|
||||
1. `gdb`
|
||||
- 新增模型关联实验特性:https://goframe.org/database/gdb/model/association
|
||||
- 改进时间自动更新特性增加自定义时间字段:https://goframe.org/database/gdb/model/auto-time
|
||||
- 新增基于`Redis`的`SQL`缓存适配器:https://goframe.org/database/gdb/model/cache
|
||||
- 新增对输入参数的键名-字段名自动识别映射特性:https://goframe.org/database/gdb/senior
|
||||
- 新增`DB.HasTable`方法,用于判断是否当前数据库存在指定数据表。
|
||||
- 新增`Model.HasField`方法,用于判断是否当前数据表存在指定字段。
|
||||
- 新增`Model.ScanList`方法,用于智能地将当前`struct`/`slice`绑定到指定的`list`对应属性上。
|
||||
- 新增`Result.MapKeyValue`方法,用于将当前`Result`转换为`map[string]Value`类型。
|
||||
- 新增`Result.IsEmpty/Len/Size/ScanList`方法。
|
||||
- 增加`ListItemValues`及`ListItemValuesUnique`方法,用于自动获取`list`中指定名称的键值或属性值,构成`slice`返回。
|
||||
- `SQL`日志内容增加分组名称打印。
|
||||
- 改进`DataToMapDeep`方法。
|
||||
- 其他一些细节改进工作。
|
||||
|
||||
1. `gredis`
|
||||
- 新增`TLS`特性支持,并支持配置文件配置,https://goframe.org/database/gredis/config
|
||||
|
||||
## `container`
|
||||
1. `gvar`
|
||||
- 增加`Scan`及`ScanDeep`方法,用于`struct`/`slice`自动识别转换。
|
||||
- 增加`ListItemValues`及`ListItemValuesUnique`方法,用于自动获取`list`中指定名称的键值或属性值,构成`slice`返回。
|
||||
- 增加`MapStrAny`接口实现方法。
|
||||
|
||||
## `os`
|
||||
1. `gcache`
|
||||
- 增加`GetVar`方法,用于获取缓存数据并返回为泛型对象。
|
||||
- 增加`Update`方法,用于仅修改缓存数值,不修改缓存过期时间。
|
||||
- 增加`UpdateExpire`方法,用于仅修改缓存过期时间,不修改缓存数值。
|
||||
- 重构底层设计,增加适配器设计模式,并增加内存及`Redis`适配器支持。其中内存适配器默认核心模块提供,`Redis`适配器由社区模块提供:https://goframe.org/os/gcache/adapter
|
||||
- 注意,本次模块的修改会有部分方法不兼容,部分方法增加了`error`参数返回,升级时请注意查看。编译时将不会通过。
|
||||
- 其他一些功能改进。
|
||||
1. `gfile`
|
||||
- 增加`ScanDirFileFunc`方法,用于自定义函数处理的递归目录文件遍历。
|
||||
- 改进`Scan*`方法,增加递归层级限制,默认层级限制为`100000`.
|
||||
|
||||
1. `gfsnotify`
|
||||
- 去掉模块初始化时的`Watcher`对象创建,调整为运行时按需创建,并且增加了并发安全控制。
|
||||
|
||||
1. `grpool`
|
||||
- 增加`AddWithRecover`方法,用于添加异步任务时给定一个`recover`处理方法,当任务`panic`时交由该`recover`方法处理,防止异步任务`panic`引起整个进程崩溃。
|
||||
> 这里解决的痛点是`recover`只能捕获到当前`goroutine`的`panic`,因此只能在创建异步任务的时候指定`recover`处理方法。
|
||||
|
||||
1. `gtime`
|
||||
- 增加`ParseDuration`方法,增加了对时间单位`d`的支持,表示天。
|
||||
- 改进`New`方法,支持通过字符串、时间戳、`time.Time`对象创建`gtime.Time`对象,https://goframe.org/os/gtime/time
|
||||
- 改进`Add/AddStr/ToLocation/ToZone/UTCLocal/AddDate/Truncate/Round`方法,这些方法调用时,不再修改当前对象本身,而是创建并返回一个新的`gtine.Time`对象,以便保证和标准库`time.Time`的逻辑一致,防止混淆。
|
||||
- 其他一些细节改进。
|
||||
1. `gtimer`
|
||||
- 增加`Reset`方法,用于重置定时任务的计时。
|
||||
1. `gfcache`
|
||||
- 去掉了该模块,该模块的功能作用不是特别大。
|
||||
|
||||
## `debug`
|
||||
1. `gdebug`
|
||||
- 新增`GoroutineId`方法,用于获取当前执行的`goroutine id`,仅作调试使用。
|
||||
|
||||
## `encoding`
|
||||
1. `gjson`
|
||||
- 新增`GetScan/GetScanDeep`方法。
|
||||
- 新增`ToScan/ToScanDeep`方法。
|
||||
- 新增`LoadContentType`方法,用于根据指定类型的内容创建`Json`操作对象。
|
||||
- 新增`IsValidDataType`方法,用于判断给定的数据类型是否支持解析。
|
||||
- 其他一些改进。
|
||||
- 单元测试完善。
|
||||
|
||||
1. `gcompress`
|
||||
- 新增`GzipFile/UnGzipFile`基于`gzip`压缩算法的文件压缩/解压。
|
||||
|
||||
## `i18n`
|
||||
1. `gi18n`
|
||||
- 新增`TranslateFormat/TranslateFormatLang/Tf/Tfl`方法: https://goframe.org/i18n/gi18n/index
|
||||
|
||||
## `text`
|
||||
1. `gstr`
|
||||
- 增加`SnakeFirstUpperCase`方法,用于在字母大写前增加连接符,并不会处理数字,例如:`SnakeFirstUpperCase("RGBCodeMd5")`将会返回`rgb_code_md5`。
|
||||
|
||||
## `util`
|
||||
1. `gconv`
|
||||
- 增加对指针基本类型的转换支持。
|
||||
- 增加`Scan/ScanDeep`方法,用于自动识别转换`Struct/[]Struct`。
|
||||
- 改进`MapDeep`方法的层级递归处理。
|
||||
- 其他一些细节改进,性能改进。
|
||||
|
||||
1. `gutil`
|
||||
- 增加`ListItemValues`及`ListItemValuesUnique`方法,用于自动获取`list`中指定名称的键值或属性值,构成`slice`返回。
|
||||
- 增加`ItemValue`方法,用于获取指定`map/*map/struct/*struct`类型的键值/属性值。
|
||||
- 增加`MapOmitEmpty`方法,用于过滤`map`中的空值。
|
||||
- 增加`SliceDelete`方法,用于数组项删除。
|
||||
- 增加`Try`方法,通过闭包执行给定的方法,如果方法产生`panic`则该方法返回`error`,否则返回`nil`。
|
||||
- 改进`TryCatch(try func(), catch ...func(exception interface{}))`为`TryCatch(try func(), catch ...func(exception error))`
|
||||
|
||||
1. `gvalid`
|
||||
- 增加自定义规则特性,开发者可注册自定义的校验规则:https://goframe.org/util/gvalid/customrule
|
||||
- 其他一些功能改进。
|
||||
|
||||
## `error`
|
||||
1. `gerror`
|
||||
- 新增`Current`方法,用于获取当前错误层级的`error`接口对象。
|
||||
- 新增`Next`方法,用于获取层级错误的下一级错误`error`接口对象。当下一层级不存在时,返回`nil`。
|
||||
- 文档更新:https://goframe.org/errors/gerror/index
|
||||
|
||||
## Bug Fix
|
||||
1. 修复`garray`模块的`Unique`方法问题。
|
||||
1. 修复`glog`中定时器懒初始化时的`goroutine`泄露问题。
|
||||
1. 修复`gstr`中名称`Case`转换相关方法在名称中带有数字+特殊字符时的名称转换问题。
|
||||
1. 修复`ghttp`模块中的`CORS`跨域设置`Header`细节问题。
|
||||
1. 其他BUG修复:https://github.com/gogf/gf/issues?q=is%3Aissue+label%3Abug+is%3Aclosed
|
||||
|
||||
|
||||
# `v1.13.1` (2020-06-10)
|
||||
|
||||
# GoFrame
|
||||
|
||||
@ -446,7 +446,7 @@ func (a *SortedArray) binSearch(value interface{}, lock bool) (index int, result
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for min <= max {
|
||||
mid = (min + max) / 2
|
||||
mid = min + int((max-min)/2)
|
||||
cmp = a.getComparator()(value, a.array[mid])
|
||||
switch {
|
||||
case cmp < 0:
|
||||
|
||||
@ -443,7 +443,7 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int)
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for min <= max {
|
||||
mid = (min + max) / 2
|
||||
mid = min + int((max-min)/2)
|
||||
cmp = a.getComparator()(value, a.array[mid])
|
||||
switch {
|
||||
case cmp < 0:
|
||||
|
||||
@ -445,7 +445,7 @@ func (a *SortedStrArray) binSearch(value string, lock bool) (index int, result i
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for min <= max {
|
||||
mid = (min + max) / 2
|
||||
mid = min + int((max-min)/2)
|
||||
cmp = a.getComparator()(value, a.array[mid])
|
||||
switch {
|
||||
case cmp < 0:
|
||||
|
||||
@ -620,7 +620,7 @@ func (tree *BTree) middle() int {
|
||||
func (tree *BTree) search(node *BTreeNode, key interface{}) (index int, found bool) {
|
||||
low, mid, high := 0, 0, len(node.Entries)-1
|
||||
for low <= high {
|
||||
mid = (high + low) / 2
|
||||
mid = low + int((high-low)/2)
|
||||
compare := tree.getComparator()(key, node.Entries[mid].Key)
|
||||
switch {
|
||||
case compare > 0:
|
||||
|
||||
@ -20,6 +20,7 @@ func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error {
|
||||
// Struct maps value of <v> to <pointer> recursively.
|
||||
// The parameter <pointer> should be a pointer to a struct instance.
|
||||
// The parameter <mapping> is used to specify the key-to-attribute mapping rules.
|
||||
// Deprecated, use Struct instead.
|
||||
func (v *Var) StructDeep(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructDeep(v.Val(), pointer, mapping...)
|
||||
}
|
||||
@ -30,6 +31,7 @@ func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) error {
|
||||
}
|
||||
|
||||
// StructsDeep converts and returns <v> as given struct slice recursively.
|
||||
// Deprecated, use Struct instead.
|
||||
func (v *Var) StructsDeep(pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructsDeep(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
@ -233,13 +233,14 @@ type (
|
||||
)
|
||||
|
||||
const (
|
||||
gINSERT_OPTION_DEFAULT = 0
|
||||
gINSERT_OPTION_REPLACE = 1
|
||||
gINSERT_OPTION_SAVE = 2
|
||||
gINSERT_OPTION_IGNORE = 3
|
||||
gDEFAULT_BATCH_NUM = 10 // Per count for batch insert/replace/save
|
||||
gDEFAULT_CONN_MAX_IDLE_COUNT = 10 // Max idle connection count in pool.
|
||||
gDEFAULT_CONN_MAX_LIFE_TIME = 30 // Max life time for per connection in pool in seconds.
|
||||
insertOptionDefault = 0
|
||||
insertOptionReplace = 1
|
||||
insertOptionSave = 2
|
||||
insertOptionIgnore = 3
|
||||
defaultBatchNumber = 10 // Per count for batch insert/replace/save.
|
||||
defaultMaxIdleConnCount = 10 // Max idle connection count in pool.
|
||||
defaultMaxOpenConnCount = 100 // Max open connection count in pool.
|
||||
defaultMaxConnLifeTime = 30 // Max life time for per connection in pool in seconds.
|
||||
)
|
||||
|
||||
var (
|
||||
@ -266,6 +267,9 @@ var (
|
||||
// which is a regular field name of table.
|
||||
regularFieldNameRegPattern = `^[\w\.\-]+$`
|
||||
|
||||
// internalCache is the memory cache for internal usage.
|
||||
internalCache = gcache.New()
|
||||
|
||||
// allDryRun sets dry-run feature for all database connections.
|
||||
// It is commonly used for command options for convenience.
|
||||
allDryRun = false
|
||||
@ -307,8 +311,9 @@ func New(group ...string) (db DB, err error) {
|
||||
logger: glog.New(),
|
||||
prefix: node.Prefix,
|
||||
config: node,
|
||||
maxIdleConnCount: gDEFAULT_CONN_MAX_IDLE_COUNT,
|
||||
maxConnLifetime: gDEFAULT_CONN_MAX_LIFE_TIME, // Default max connection life time if user does not configure.
|
||||
maxIdleConnCount: defaultMaxIdleConnCount,
|
||||
maxOpenConnCount: defaultMaxOpenConnCount,
|
||||
maxConnLifetime: defaultMaxConnLifeTime, // Default max connection life time if user does not configure.
|
||||
}
|
||||
if v, ok := driverMap[node.Type]; ok {
|
||||
c.DB, err = v.New(c, node)
|
||||
@ -441,7 +446,7 @@ func (c *Core) getSqlDb(master bool, schema ...string) (sqlDb *sql.DB, err error
|
||||
node = &n
|
||||
}
|
||||
// Cache the underlying connection pool object by node.
|
||||
v, _ := gcache.GetOrSetFuncLock(node.String(), func() (interface{}, error) {
|
||||
v, _ := internalCache.GetOrSetFuncLock(node.String(), func() (interface{}, error) {
|
||||
sqlDb, err = c.DB.Open(node)
|
||||
if err != nil {
|
||||
intlog.Printf("DB open failed: %v, %+v", err, node)
|
||||
|
||||
@ -452,7 +452,7 @@ func (c *Core) DoInsert(link Link, table string, data interface{}, option int, b
|
||||
values = append(values, "?")
|
||||
params = append(params, v)
|
||||
}
|
||||
if option == gINSERT_OPTION_SAVE {
|
||||
if option == insertOptionSave {
|
||||
for k, _ := range dataMap {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
@ -599,7 +599,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
operation = GetInsertOperationByOption(option)
|
||||
updateStr = ""
|
||||
)
|
||||
if option == gINSERT_OPTION_SAVE {
|
||||
if option == insertOptionSave {
|
||||
for _, k := range keys {
|
||||
// If it's SAVE operation,
|
||||
// do not automatically update the creating time.
|
||||
@ -617,7 +617,7 @@ func (c *Core) DoBatchInsert(link Link, table string, list interface{}, option i
|
||||
}
|
||||
updateStr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", updateStr)
|
||||
}
|
||||
batchNum := gDEFAULT_BATCH_NUM
|
||||
batchNum := defaultBatchNumber
|
||||
if len(batch) > 0 && batch[0] > 0 {
|
||||
batchNum = batch[0]
|
||||
}
|
||||
|
||||
@ -15,7 +15,6 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -199,8 +198,8 @@ func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[st
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mssql_table_fields_%s_%s`, table, checkSchema),
|
||||
v, _ := internalCache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mssql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
|
||||
@ -11,7 +11,6 @@ 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"
|
||||
|
||||
@ -42,7 +41,7 @@ func (d *DriverMysql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
} else {
|
||||
source = fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true&loc=Local",
|
||||
"%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true&parseTime=true",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset,
|
||||
)
|
||||
}
|
||||
@ -103,8 +102,8 @@ func (d *DriverMysql) TableFields(table string, schema ...string) (fields map[st
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mysql_table_fields_%s_%s`, table, checkSchema),
|
||||
v, _ := internalCache.GetOrSetFunc(
|
||||
fmt.Sprintf(`mysql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
|
||||
@ -16,7 +16,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@ -159,8 +158,8 @@ func (d *DriverOracle) TableFields(table string, schema ...string) (fields map[s
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`oracle_table_fields_%s_%s`, table, checkSchema),
|
||||
v, _ := internalCache.GetOrSetFunc(
|
||||
fmt.Sprintf(`oracle_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
|
||||
func() (interface{}, error) {
|
||||
result := (Result)(nil)
|
||||
structureSql := fmt.Sprintf(`
|
||||
@ -196,7 +195,7 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`,
|
||||
|
||||
func (d *DriverOracle) getTableUniqueIndex(table string) (fields map[string]map[string]string, err error) {
|
||||
table = strings.ToUpper(table)
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
v, _ := internalCache.GetOrSetFunc(
|
||||
"table_unique_index_"+table,
|
||||
func() (interface{}, error) {
|
||||
res := (Result)(nil)
|
||||
@ -249,7 +248,7 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
|
||||
indexs := make([]string, 0)
|
||||
indexMap := make(map[string]string)
|
||||
indexExists := false
|
||||
if option != gINSERT_OPTION_DEFAULT {
|
||||
if option != insertOptionDefault {
|
||||
index, err := d.getTableUniqueIndex(table)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -277,7 +276,7 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
|
||||
k = strings.ToUpper(k)
|
||||
|
||||
// 操作类型为REPLACE/SAVE时且存在唯一索引才使用merge,否则使用insert
|
||||
if (option == gINSERT_OPTION_REPLACE || option == gINSERT_OPTION_SAVE) && indexExists {
|
||||
if (option == insertOptionReplace || option == insertOptionSave) && indexExists {
|
||||
fields = append(fields, tableAlias1+"."+charL+k+charR)
|
||||
values = append(values, tableAlias2+"."+charL+k+charR)
|
||||
params = append(params, v)
|
||||
@ -303,18 +302,18 @@ func (d *DriverOracle) DoInsert(link Link, table string, data interface{}, optio
|
||||
}
|
||||
}
|
||||
|
||||
if indexExists && option != gINSERT_OPTION_DEFAULT {
|
||||
if indexExists && option != insertOptionDefault {
|
||||
switch option {
|
||||
case gINSERT_OPTION_REPLACE:
|
||||
case insertOptionReplace:
|
||||
fallthrough
|
||||
case gINSERT_OPTION_SAVE:
|
||||
case insertOptionSave:
|
||||
tmp := fmt.Sprintf(
|
||||
"MERGE INTO %s %s USING(SELECT %s FROM DUAL) %s ON(%s) WHEN MATCHED THEN UPDATE SET %s WHEN NOT MATCHED THEN INSERT (%s) VALUES(%s)",
|
||||
table, tableAlias1, strings.Join(subSqlStr, ","), tableAlias2,
|
||||
strings.Join(onStr, "AND"), strings.Join(updateStr, ","), strings.Join(fields, ","), strings.Join(values, ","),
|
||||
)
|
||||
return d.DB.DoExec(link, tmp, params...)
|
||||
case gINSERT_OPTION_IGNORE:
|
||||
case insertOptionIgnore:
|
||||
return d.DB.DoExec(link,
|
||||
fmt.Sprintf(
|
||||
"INSERT /*+ IGNORE_ROW_ON_DUPKEY_INDEX(%s(%s)) */ INTO %s(%s) VALUES(%s)",
|
||||
@ -392,7 +391,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
|
||||
valueHolderStr := strings.Join(holders, ",")
|
||||
|
||||
// 当操作类型非insert时调用单笔的insert功能
|
||||
if option != gINSERT_OPTION_DEFAULT {
|
||||
if option != insertOptionDefault {
|
||||
for _, v := range listMap {
|
||||
r, err := d.DB.DoInsert(link, table, v, option, 1)
|
||||
if err != nil {
|
||||
@ -410,7 +409,7 @@ func (d *DriverOracle) DoBatchInsert(link Link, table string, list interface{},
|
||||
}
|
||||
|
||||
// 构造批量写入数据格式(注意map的遍历是无序的)
|
||||
batchNum := gDEFAULT_BATCH_NUM
|
||||
batchNum := defaultBatchNumber
|
||||
if len(batch) > 0 {
|
||||
batchNum = batch[0]
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"strings"
|
||||
|
||||
@ -108,8 +107,8 @@ func (d *DriverPgsql) TableFields(table string, schema ...string) (fields map[st
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`pgsql_table_fields_%s_%s`, table, checkSchema),
|
||||
v, _ := internalCache.GetOrSetFunc(
|
||||
fmt.Sprintf(`pgsql_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
|
||||
@ -15,7 +15,6 @@ 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"
|
||||
@ -37,15 +36,14 @@ func (d *DriverSqlite) New(core *Core, node *ConfigNode) (DB, error) {
|
||||
// Open creates and returns a underlying sql.DB object for sqlite.
|
||||
func (d *DriverSqlite) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
var err error
|
||||
if config.LinkInfo != "" {
|
||||
source = config.LinkInfo
|
||||
} else {
|
||||
source = config.Name
|
||||
}
|
||||
source, err = gfile.Search(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// It searches the source file to locate its absolute path..
|
||||
if absolutePath, _ := gfile.Search(source); absolutePath != "" {
|
||||
source = absolutePath
|
||||
}
|
||||
intlog.Printf("Open: %s", source)
|
||||
if db, err := sql.Open("sqlite3", source); err == nil {
|
||||
@ -99,8 +97,8 @@ func (d *DriverSqlite) TableFields(table string, schema ...string) (fields map[s
|
||||
if len(schema) > 0 && schema[0] != "" {
|
||||
checkSchema = schema[0]
|
||||
}
|
||||
v, _ := gcache.GetOrSetFunc(
|
||||
fmt.Sprintf(`sqlite_table_fields_%s_%s`, table, checkSchema),
|
||||
v, _ := internalCache.GetOrSetFunc(
|
||||
fmt.Sprintf(`sqlite_table_fields_%s_%s@group:%s`, table, checkSchema, d.GetGroup()),
|
||||
func() (interface{}, error) {
|
||||
var (
|
||||
result Result
|
||||
|
||||
@ -88,9 +88,9 @@ func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) [
|
||||
func GetInsertOperationByOption(option int) string {
|
||||
var operator string
|
||||
switch option {
|
||||
case gINSERT_OPTION_REPLACE:
|
||||
case insertOptionReplace:
|
||||
operator = "REPLACE"
|
||||
case gINSERT_OPTION_IGNORE:
|
||||
case insertOptionIgnore:
|
||||
operator = "INSERT IGNORE"
|
||||
default:
|
||||
operator = "INSERT"
|
||||
@ -312,32 +312,40 @@ func doQuoteString(s, charLeft, charRight string) string {
|
||||
|
||||
// GetWhereConditionOfStruct returns the where condition sql and arguments by given struct pointer.
|
||||
// This function automatically retrieves primary or unique field and its attribute value as condition.
|
||||
func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}) {
|
||||
func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}, err error) {
|
||||
tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT})
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
array := ([]string)(nil)
|
||||
for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
|
||||
array = strings.Split(field.Tag, ",")
|
||||
for _, field := range tagField {
|
||||
array = strings.Split(field.TagValue, ",")
|
||||
if len(array) > 1 && gstr.InArray([]string{ORM_TAG_FOR_UNIQUE, ORM_TAG_FOR_PRIMARY}, array[1]) {
|
||||
return array[0], []interface{}{field.Value()}
|
||||
return array[0], []interface{}{field.Value()}, nil
|
||||
}
|
||||
if len(where) > 0 {
|
||||
where += " "
|
||||
}
|
||||
where += field.Tag + "=?"
|
||||
where += field.TagValue + "=?"
|
||||
args = append(args, field.Value())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetPrimaryKey retrieves and returns primary key field name from given struct.
|
||||
func GetPrimaryKey(pointer interface{}) string {
|
||||
func GetPrimaryKey(pointer interface{}) (string, error) {
|
||||
tagField, err := structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
array := ([]string)(nil)
|
||||
for _, field := range structs.TagFields(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
|
||||
array = strings.Split(field.Tag, ",")
|
||||
for _, field := range tagField {
|
||||
array = strings.Split(field.TagValue, ",")
|
||||
if len(array) > 1 && array[1] == ORM_TAG_FOR_PRIMARY {
|
||||
return array[0]
|
||||
return array[0], nil
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// GetPrimaryKeyCondition returns a new where condition by primary field name.
|
||||
@ -394,7 +402,6 @@ func formatSql(sql string, args []interface{}) (newSql string, newArgs []interfa
|
||||
}
|
||||
|
||||
// formatWhere formats where statement and its arguments.
|
||||
// TODO []interface{} type support for parameter <where> does not completed yet.
|
||||
func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (newWhere string, newArgs []interface{}) {
|
||||
var (
|
||||
buffer = bytes.NewBuffer(nil)
|
||||
@ -476,21 +483,23 @@ func formatWhere(db DB, where interface{}, args []interface{}, omitEmpty bool) (
|
||||
}
|
||||
|
||||
// formatWhereInterfaces formats <where> as []interface{}.
|
||||
// TODO supporting for parameter <where> with []interface{} type is not completed yet.
|
||||
func formatWhereInterfaces(db DB, where []interface{}, buffer *bytes.Buffer, newArgs []interface{}) []interface{} {
|
||||
if len(where) == 0 {
|
||||
return newArgs
|
||||
}
|
||||
if len(where)%2 != 0 {
|
||||
buffer.WriteString(gstr.Join(gconv.Strings(where), ""))
|
||||
return newArgs
|
||||
}
|
||||
var str string
|
||||
var array []interface{}
|
||||
var holderCount int
|
||||
for i := 0; i < len(where); {
|
||||
if holderCount > 0 {
|
||||
array = gconv.Interfaces(where[i])
|
||||
newArgs = append(newArgs, array...)
|
||||
holderCount -= len(array)
|
||||
for i := 0; i < len(where); i += 2 {
|
||||
str = gconv.String(where[i])
|
||||
if buffer.Len() > 0 {
|
||||
buffer.WriteString(" AND " + db.QuoteWord(str) + "=?")
|
||||
} else {
|
||||
str = gconv.String(where[i])
|
||||
holderCount = gstr.Count(str, "?")
|
||||
buffer.WriteString(str)
|
||||
buffer.WriteString(db.QuoteWord(str) + "=?")
|
||||
}
|
||||
newArgs = append(newArgs, where[i+1])
|
||||
}
|
||||
return newArgs
|
||||
}
|
||||
@ -721,10 +730,14 @@ func FormatSqlWithArgs(sql string, args []interface{}) string {
|
||||
// mapToStruct maps the <data> to given struct.
|
||||
// Note that the given parameter <pointer> should be a pointer to s struct.
|
||||
func mapToStruct(data map[string]interface{}, pointer interface{}) error {
|
||||
tagNameMap, err := structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// It retrieves and returns the mapping between orm tag and the struct attribute name.
|
||||
mapping := make(map[string]string)
|
||||
for tag, attr := range structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
|
||||
for tag, attr := range tagNameMap {
|
||||
mapping[strings.Split(tag, ",")[0]] = attr
|
||||
}
|
||||
return gconv.StructDeep(data, pointer, mapping)
|
||||
return gconv.Struct(data, pointer, mapping)
|
||||
}
|
||||
|
||||
@ -141,6 +141,7 @@ func (m *Model) DB(db DB) *Model {
|
||||
// TX sets/changes the transaction for current operation.
|
||||
func (m *Model) TX(tx *TX) *Model {
|
||||
model := m.getModel()
|
||||
model.db = tx.db
|
||||
model.tx = tx
|
||||
return model
|
||||
}
|
||||
@ -199,3 +200,10 @@ func (m *Model) Safe(safe ...bool) *Model {
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Args sets custom arguments for model operation.
|
||||
func (m *Model) Args(args ...interface{}) *Model {
|
||||
model := m.getModel()
|
||||
model.extraArgs = append(model.extraArgs, args)
|
||||
return model
|
||||
}
|
||||
|
||||
@ -9,7 +9,9 @@ package gdb
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
)
|
||||
|
||||
// Delete does "DELETE FROM ... " statement for the model.
|
||||
@ -26,7 +28,7 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
}()
|
||||
var (
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, false)
|
||||
)
|
||||
// Soft deleting.
|
||||
if !m.unscoped && fieldNameDelete != "" {
|
||||
@ -38,5 +40,9 @@ func (m *Model) Delete(where ...interface{}) (result sql.Result, err error) {
|
||||
append([]interface{}{gtime.Now().String()}, conditionArgs...),
|
||||
)
|
||||
}
|
||||
return m.db.DoDelete(m.getLink(true), m.tables, conditionWhere+conditionExtra, conditionArgs...)
|
||||
conditionStr := conditionWhere + conditionExtra
|
||||
if !gstr.ContainsI(conditionStr, " WHERE ") {
|
||||
return nil, gerror.New("there should be WHERE condition statement for DELETE operation")
|
||||
}
|
||||
return m.db.DoDelete(m.getLink(true), m.tables, conditionStr, conditionArgs...)
|
||||
}
|
||||
|
||||
@ -10,6 +10,8 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
)
|
||||
|
||||
// Filter marks filtering the fields which does not exist in the fields of the operated table.
|
||||
@ -24,10 +26,27 @@ 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 {
|
||||
if len(fields) > 0 {
|
||||
// The parameter <fieldNamesOrMapStruct> can be type of string/map/*map/struct/*struct.
|
||||
func (m *Model) Fields(fieldNamesOrMapStruct ...interface{}) *Model {
|
||||
length := len(fieldNamesOrMapStruct)
|
||||
if length == 0 {
|
||||
return m
|
||||
}
|
||||
switch {
|
||||
case length >= 2:
|
||||
model := m.getModel()
|
||||
model.fields = gstr.Join(fields, ",")
|
||||
model.fields = gstr.Join(m.mappingToTableFields(gconv.Strings(fieldNamesOrMapStruct)), ",")
|
||||
return model
|
||||
case length == 1:
|
||||
model := m.getModel()
|
||||
switch r := fieldNamesOrMapStruct[0].(type) {
|
||||
case string:
|
||||
model.fields = gstr.Join(m.mappingToTableFields([]string{r}), ",")
|
||||
case []string:
|
||||
model.fields = gstr.Join(m.mappingToTableFields(r), ",")
|
||||
default:
|
||||
model.fields = gstr.Join(m.mappingToTableFields(gutil.Keys(r)), ",")
|
||||
}
|
||||
return model
|
||||
}
|
||||
return m
|
||||
@ -35,10 +54,24 @@ func (m *Model) Fields(fields ...string) *Model {
|
||||
|
||||
// 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 {
|
||||
if len(fields) > 0 {
|
||||
model := m.getModel()
|
||||
model.fieldsEx = gstr.Join(fields, ",")
|
||||
// The parameter <fieldNamesOrMapStruct> can be type of string/map/*map/struct/*struct.
|
||||
func (m *Model) FieldsEx(fieldNamesOrMapStruct ...interface{}) *Model {
|
||||
length := len(fieldNamesOrMapStruct)
|
||||
if length == 0 {
|
||||
return m
|
||||
}
|
||||
model := m.getModel()
|
||||
switch {
|
||||
case length >= 2:
|
||||
model.fieldsEx = gstr.Join(m.mappingToTableFields(gconv.Strings(fieldNamesOrMapStruct)), ",")
|
||||
return model
|
||||
case length == 1:
|
||||
switch r := fieldNamesOrMapStruct[0].(type) {
|
||||
case string:
|
||||
model.fieldsEx = gstr.Join(m.mappingToTableFields([]string{r}), ",")
|
||||
default:
|
||||
model.fieldsEx = gstr.Join(m.mappingToTableFields(gutil.Keys(r)), ",")
|
||||
}
|
||||
return model
|
||||
}
|
||||
return m
|
||||
|
||||
@ -101,7 +101,7 @@ func (m *Model) Insert(data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Insert()
|
||||
}
|
||||
return m.doInsertWithOption(gINSERT_OPTION_DEFAULT, data...)
|
||||
return m.doInsertWithOption(insertOptionDefault, data...)
|
||||
}
|
||||
|
||||
// InsertIgnore does "INSERT IGNORE INTO ..." statement for the model.
|
||||
@ -111,7 +111,7 @@ func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error)
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Insert()
|
||||
}
|
||||
return m.doInsertWithOption(gINSERT_OPTION_IGNORE, data...)
|
||||
return m.doInsertWithOption(insertOptionIgnore, data...)
|
||||
}
|
||||
|
||||
// Replace does "REPLACE INTO ..." statement for the model.
|
||||
@ -121,7 +121,7 @@ func (m *Model) Replace(data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Replace()
|
||||
}
|
||||
return m.doInsertWithOption(gINSERT_OPTION_REPLACE, data...)
|
||||
return m.doInsertWithOption(insertOptionReplace, data...)
|
||||
}
|
||||
|
||||
// Save does "INSERT INTO ... ON DUPLICATE KEY UPDATE..." statement for the model.
|
||||
@ -134,7 +134,7 @@ func (m *Model) Save(data ...interface{}) (result sql.Result, err error) {
|
||||
if len(data) > 0 {
|
||||
return m.Data(data...).Save()
|
||||
}
|
||||
return m.doInsertWithOption(gINSERT_OPTION_SAVE, data...)
|
||||
return m.doInsertWithOption(insertOptionSave, data...)
|
||||
}
|
||||
|
||||
// doInsertWithOption inserts data with option parameter.
|
||||
@ -155,7 +155,7 @@ func (m *Model) doInsertWithOption(option int, data ...interface{}) (result sql.
|
||||
)
|
||||
// Batch operation.
|
||||
if list, ok := m.data.(List); ok {
|
||||
batch := gDEFAULT_BATCH_NUM
|
||||
batch := defaultBatchNumber
|
||||
if m.batch > 0 {
|
||||
batch = m.batch
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
|
||||
}
|
||||
var (
|
||||
softDeletingCondition = m.getConditionForSoftDeleting()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(limit1)
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(limit1, false)
|
||||
)
|
||||
if !m.unscoped && softDeletingCondition != "" {
|
||||
if conditionWhere == "" {
|
||||
@ -75,6 +75,9 @@ func (m *Model) doGetAll(limit1 bool, where ...interface{}) (Result, error) {
|
||||
func (m *Model) getFieldsFiltered() string {
|
||||
if m.fieldsEx == "" {
|
||||
// No filtering.
|
||||
if !gstr.Contains(m.fields, ".") && !gstr.Contains(m.fields, " ") {
|
||||
return m.db.QuoteString(m.fields)
|
||||
}
|
||||
return m.fields
|
||||
}
|
||||
var (
|
||||
@ -112,7 +115,7 @@ func (m *Model) getFieldsFiltered() string {
|
||||
if len(newFields) > 0 {
|
||||
newFields += ","
|
||||
}
|
||||
newFields += k
|
||||
newFields += m.db.QuoteWord(k)
|
||||
}
|
||||
return newFields
|
||||
}
|
||||
@ -341,7 +344,7 @@ func (m *Model) Count(where ...interface{}) (int, error) {
|
||||
}
|
||||
var (
|
||||
softDeletingCondition = m.getConditionForSoftDeleting()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, true)
|
||||
)
|
||||
if !m.unscoped && softDeletingCondition != "" {
|
||||
if conditionWhere == "" {
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
@ -45,7 +46,7 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
fieldNameCreate = m.getSoftFieldNameCreated()
|
||||
fieldNameUpdate = m.getSoftFieldNameUpdated()
|
||||
fieldNameDelete = m.getSoftFieldNameDeleted()
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false)
|
||||
conditionWhere, conditionExtra, conditionArgs = m.formatCondition(false, false)
|
||||
)
|
||||
// Automatically update the record updating time.
|
||||
if !m.unscoped && fieldNameUpdate != "" {
|
||||
@ -77,11 +78,15 @@ func (m *Model) Update(dataAndWhere ...interface{}) (result sql.Result, err erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conditionStr := conditionWhere + conditionExtra
|
||||
if !gstr.ContainsI(conditionStr, " WHERE ") {
|
||||
return nil, gerror.New("there should be WHERE condition statement for UPDATE operation")
|
||||
}
|
||||
return m.db.DoUpdate(
|
||||
m.getLink(true),
|
||||
m.tables,
|
||||
newData,
|
||||
conditionWhere+conditionExtra,
|
||||
conditionStr,
|
||||
m.mergeArguments(conditionArgs)...,
|
||||
)
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ import (
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -26,6 +27,33 @@ func (m *Model) getModel() *Model {
|
||||
}
|
||||
}
|
||||
|
||||
// mappingToTableFields mappings and changes given field name to really table field name.
|
||||
func (m *Model) mappingToTableFields(fields []string) []string {
|
||||
var (
|
||||
foundKey = ""
|
||||
fieldsArray = gstr.SplitAndTrim(gstr.Join(fields, ","), ",")
|
||||
)
|
||||
|
||||
if fieldsMap, err := m.db.TableFields(m.tables); err == nil {
|
||||
fieldsKeyMap := make(map[string]interface{}, len(fieldsMap))
|
||||
for k, _ := range fieldsMap {
|
||||
fieldsKeyMap[k] = nil
|
||||
}
|
||||
for i, v := range fieldsArray {
|
||||
if _, ok := fieldsKeyMap[v]; !ok {
|
||||
if gstr.Contains(v, " ") || gstr.Contains(v, ".") {
|
||||
continue
|
||||
}
|
||||
foundKey, _ = gutil.MapPossibleItemByKey(fieldsKeyMap, v)
|
||||
if foundKey != "" {
|
||||
fieldsArray[i] = foundKey
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return fieldsArray
|
||||
}
|
||||
|
||||
// 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{}, error) {
|
||||
@ -164,7 +192,7 @@ func (m *Model) getPrimaryKey() string {
|
||||
// Note that this function does not change any attribute value of the <m>.
|
||||
//
|
||||
// The parameter <limit1> specifies whether limits querying only one record if m.limit is not set.
|
||||
func (m *Model) formatCondition(limit1 bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
|
||||
func (m *Model) formatCondition(limit1 bool, isCountStatement bool) (conditionWhere string, conditionExtra string, conditionArgs []interface{}) {
|
||||
if len(m.whereHolder) > 0 {
|
||||
for _, v := range m.whereHolder {
|
||||
switch v.operator {
|
||||
@ -231,18 +259,22 @@ func (m *Model) formatCondition(limit1 bool) (conditionWhere string, conditionEx
|
||||
conditionArgs = append(conditionArgs, havingArgs...)
|
||||
}
|
||||
}
|
||||
if m.limit != 0 {
|
||||
if m.start >= 0 {
|
||||
conditionExtra += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
|
||||
} else {
|
||||
conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit)
|
||||
if !isCountStatement {
|
||||
if m.limit != 0 {
|
||||
if m.start >= 0 {
|
||||
conditionExtra += fmt.Sprintf(" LIMIT %d,%d", m.start, m.limit)
|
||||
} else {
|
||||
conditionExtra += fmt.Sprintf(" LIMIT %d", m.limit)
|
||||
}
|
||||
} else if limit1 {
|
||||
conditionExtra += " LIMIT 1"
|
||||
}
|
||||
|
||||
if m.offset >= 0 {
|
||||
conditionExtra += fmt.Sprintf(" OFFSET %d", m.offset)
|
||||
}
|
||||
} else if limit1 {
|
||||
conditionExtra += " LIMIT 1"
|
||||
}
|
||||
if m.offset >= 0 {
|
||||
conditionExtra += fmt.Sprintf(" OFFSET %d", m.offset)
|
||||
}
|
||||
|
||||
if m.lockInfo != "" {
|
||||
conditionExtra += " " + m.lockInfo
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ package gdb
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/os/gcmd"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
@ -312,3 +313,26 @@ func Test_isSubQuery(t *testing.T) {
|
||||
t.Assert(isSubQuery("select 1"), true)
|
||||
})
|
||||
}
|
||||
|
||||
func TestResult_Structs1(t *testing.T) {
|
||||
type A struct {
|
||||
Id int `orm:"id"`
|
||||
}
|
||||
type B struct {
|
||||
*A
|
||||
Name string
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
r := Result{
|
||||
Record{"id": gvar.New(nil), "name": gvar.New("john")},
|
||||
Record{"id": gvar.New(nil), "name": gvar.New("smith")},
|
||||
}
|
||||
array := make([]*B, 2)
|
||||
err := r.Structs(&array)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(array[0].Id, 0)
|
||||
t.Assert(array[1].Id, 0)
|
||||
t.Assert(array[0].Name, "john")
|
||||
t.Assert(array[1].Name, "smith")
|
||||
})
|
||||
}
|
||||
|
||||
@ -719,7 +719,7 @@ func Test_DB_Delete(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Delete(table, nil)
|
||||
result, err := db.Delete(table, 1)
|
||||
t.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, SIZE)
|
||||
@ -768,7 +768,7 @@ func Test_DB_Time(t *testing.T) {
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Delete(table, nil)
|
||||
result, err := db.Delete(table, 1)
|
||||
t.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 2)
|
||||
|
||||
@ -11,7 +11,9 @@ import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/encoding/gparser"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"testing"
|
||||
"time"
|
||||
@ -387,7 +389,7 @@ func Test_Model_Update(t *testing.T) {
|
||||
defer dropTable(table)
|
||||
// UPDATE...LIMIT
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).Data("nickname", "T100").Order("id desc").Limit(2).Update()
|
||||
result, err := db.Table(table).Data("nickname", "T100").Where(1).Order("id desc").Limit(2).Update()
|
||||
t.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 2)
|
||||
@ -763,6 +765,32 @@ func Test_Model_Count(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
t.Assert(count, SIZE)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Table(table).FieldsEx("id").Where("id>8").Count()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(count, 2)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Table(table).Fields("distinct id,nickname").Where("id>8").Count()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(count, 2)
|
||||
})
|
||||
// COUNT...LIMIT...
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
count, err := db.Table(table).Page(1, 2).Count()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(count, SIZE)
|
||||
})
|
||||
//gtest.C(t, func(t *gtest.T) {
|
||||
// count, err := db.Table(table).Fields("id myid").Where("id>8").Count()
|
||||
// t.Assert(err, nil)
|
||||
// t.Assert(count, 2)
|
||||
//})
|
||||
//gtest.C(t, func(t *gtest.T) {
|
||||
// count, err := db.Table(table).As("u1").LeftJoin(table, "u2", "u2.id=u1.id").Fields("u2.id u2id").Where("u1.id>8").Count()
|
||||
// t.Assert(err, nil)
|
||||
// t.Assert(count, 2)
|
||||
//})
|
||||
}
|
||||
|
||||
func Test_Model_FindCount(t *testing.T) {
|
||||
@ -1182,6 +1210,21 @@ func Test_Model_Where(t *testing.T) {
|
||||
t.AssertGT(len(result), 0)
|
||||
t.Assert(result["id"].Int(), 3)
|
||||
})
|
||||
|
||||
// slice
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).Where(g.Slice{"id", 3}).One()
|
||||
t.Assert(err, nil)
|
||||
t.AssertGT(len(result), 0)
|
||||
t.Assert(result["id"].Int(), 3)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).Where(g.Slice{"id", 3, "nickname", "name_3"}).One()
|
||||
t.Assert(err, nil)
|
||||
t.AssertGT(len(result), 0)
|
||||
t.Assert(result["id"].Int(), 3)
|
||||
})
|
||||
|
||||
// slice parameter
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).Where("id=? and nickname=?", g.Slice{3, "name_3"}).One()
|
||||
@ -1747,14 +1790,14 @@ func Test_Model_Delete(t *testing.T) {
|
||||
|
||||
// DELETE...LIMIT
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).Limit(2).Delete()
|
||||
result, err := db.Table(table).Where(1).Limit(2).Delete()
|
||||
t.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, 2)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
result, err := db.Table(table).Delete()
|
||||
result, err := db.Table(table).Where(1).Delete()
|
||||
t.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
t.Assert(n, SIZE-2)
|
||||
@ -2014,7 +2057,7 @@ func Test_Model_Option_Where(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
r, err := db.Table(table).OmitEmpty().Data("nickname", 1).Where(g.Map{"id": 0, "passport": ""}).Update()
|
||||
r, err := db.Table(table).OmitEmpty().Data("nickname", 1).Where(g.Map{"id": 0, "passport": ""}).And(1).Update()
|
||||
t.Assert(err, nil)
|
||||
n, _ := r.RowsAffected()
|
||||
t.Assert(n, SIZE)
|
||||
@ -2093,6 +2136,19 @@ func Test_Model_FieldsEx(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_FieldsEx_WithReservedWords(t *testing.T) {
|
||||
table := "fieldsex_test_table"
|
||||
sqlTpcPath := gdebug.TestDataPath("reservedwords_table_tpl.sql")
|
||||
if _, err := db.Exec(fmt.Sprintf(gfile.GetContents(sqlTpcPath), table)); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
defer dropTable(table)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
_, err := db.Table(table).FieldsEx("content").One()
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_FieldsStr(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
@ -2566,6 +2622,144 @@ func Test_Model_Min_Max(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Fields_AutoMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Table(table).Fields("ID").Where("id", 2).Value()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(value.Int(), 2)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Table(table).Fields("NICK_NAME").Where("id", 2).Value()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(value.String(), "name_2")
|
||||
})
|
||||
// Map
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Table(table).Fields(g.Map{
|
||||
"ID": 1,
|
||||
"NICK_NAME": 1,
|
||||
}).Where("id", 2).One()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(one), 2)
|
||||
t.Assert(one["id"], 2)
|
||||
t.Assert(one["nickname"], "name_2")
|
||||
})
|
||||
// Struct
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
ID int
|
||||
NICKNAME int
|
||||
}
|
||||
one, err := db.Table(table).Fields(&T{
|
||||
ID: 0,
|
||||
NICKNAME: 0,
|
||||
}).Where("id", 2).One()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(one), 2)
|
||||
t.Assert(one["id"], 2)
|
||||
t.Assert(one["nickname"], "name_2")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_FieldsEx_AutoMapping(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
// "id": i,
|
||||
// "passport": fmt.Sprintf(`user_%d`, i),
|
||||
// "password": fmt.Sprintf(`pass_%d`, i),
|
||||
// "nickname": fmt.Sprintf(`name_%d`, i),
|
||||
// "create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(),
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Table(table).FieldsEx("Passport, Password, NickName, CreateTime").Where("id", 2).Value()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(value.Int(), 2)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
value, err := db.Table(table).FieldsEx("ID, Passport, Password, CreateTime").Where("id", 2).Value()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(value.String(), "name_2")
|
||||
})
|
||||
// Map
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Table(table).FieldsEx(g.Map{
|
||||
"Passport": 1,
|
||||
"Password": 1,
|
||||
"CreateTime": 1,
|
||||
}).Where("id", 2).One()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(one), 2)
|
||||
t.Assert(one["id"], 2)
|
||||
t.Assert(one["nickname"], "name_2")
|
||||
})
|
||||
// Struct
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
Passport int
|
||||
Password int
|
||||
CreateTime int
|
||||
}
|
||||
one, err := db.Table(table).FieldsEx(&T{
|
||||
Passport: 0,
|
||||
Password: 0,
|
||||
CreateTime: 0,
|
||||
}).Where("id", 2).One()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(one), 2)
|
||||
t.Assert(one["id"], 2)
|
||||
t.Assert(one["nickname"], "name_2")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Model_Fields_Struct(t *testing.T) {
|
||||
table := createInitTable()
|
||||
defer dropTable(table)
|
||||
|
||||
type A struct {
|
||||
Passport string
|
||||
Password string
|
||||
}
|
||||
type B struct {
|
||||
A
|
||||
NickName string
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Table(table).Fields(A{}).Where("id", 2).One()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(one), 2)
|
||||
t.Assert(one["passport"], "user_2")
|
||||
t.Assert(one["password"], "pass_2")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Table(table).Fields(&A{}).Where("id", 2).One()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(one), 2)
|
||||
t.Assert(one["passport"], "user_2")
|
||||
t.Assert(one["password"], "pass_2")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Table(table).Fields(B{}).Where("id", 2).One()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(one), 3)
|
||||
t.Assert(one["passport"], "user_2")
|
||||
t.Assert(one["password"], "pass_2")
|
||||
t.Assert(one["nickname"], "name_2")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
one, err := db.Table(table).Fields(&B{}).Where("id", 2).One()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(one), 3)
|
||||
t.Assert(one["passport"], "user_2")
|
||||
t.Assert(one["password"], "pass_2")
|
||||
t.Assert(one["nickname"], "name_2")
|
||||
})
|
||||
}
|
||||
func Test_Model_NullField(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
@ -2642,3 +2836,87 @@ func Test_Model_HasField(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Issue: https://github.com/gogf/gf/issues/1002
|
||||
func Test_Model_Issue1002(t *testing.T) {
|
||||
table := createTable()
|
||||
defer dropTable(table)
|
||||
|
||||
result, err := db.Table(table).Data(g.Map{
|
||||
"id": 1,
|
||||
"passport": "port_1",
|
||||
"password": "pass_1",
|
||||
"nickname": "name_2",
|
||||
"create_time": "2020-10-27 19:03:33",
|
||||
}).Insert()
|
||||
gtest.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 1)
|
||||
|
||||
// where + string.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, err := db.Table(table).Fields("id").Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").Value()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.Int(), 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, err := db.Table(table).Fields("id").Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").FindValue()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.Int(), 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, err := db.Table(table).Where("create_time>'2020-10-27 19:03:32' and create_time<'2020-10-27 19:03:34'").FindValue("id")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.Int(), 1)
|
||||
})
|
||||
// where + string arguments.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", "2020-10-27 19:03:32", "2020-10-27 19:03:34").Value()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.Int(), 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", "2020-10-27 19:03:32", "2020-10-27 19:03:34").FindValue()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.Int(), 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, err := db.Table(table).Where("create_time>? and create_time<?", "2020-10-27 19:03:32", "2020-10-27 19:03:34").FindValue("id")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.Int(), 1)
|
||||
})
|
||||
// where + gtime.Time arguments.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", gtime.New("2020-10-27 19:03:32"), gtime.New("2020-10-27 19:03:34")).Value()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.Int(), 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", gtime.New("2020-10-27 19:03:32"), gtime.New("2020-10-27 19:03:34")).FindValue()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.Int(), 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, err := db.Table(table).Where("create_time>? and create_time<?", gtime.New("2020-10-27 19:03:32"), gtime.New("2020-10-27 19:03:34")).FindValue("id")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.Int(), 1)
|
||||
})
|
||||
// where + time.Time arguments.
|
||||
t1, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 19:03:32")
|
||||
t2, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 19:03:34")
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", t1, t2).Value()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.Int(), 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, err := db.Table(table).Fields("id").Where("create_time>? and create_time<?", t1, t2).FindValue()
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.Int(), 1)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v, err := db.Table(table).Where("create_time>? and create_time<?", t1, t2).FindValue("id")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v.Int(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -676,7 +676,7 @@ func Test_TX_Delete(t *testing.T) {
|
||||
if err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if _, err := tx.Delete(table, nil); err != nil {
|
||||
if _, err := tx.Delete(table, 1); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
@ -696,7 +696,7 @@ func Test_TX_Delete(t *testing.T) {
|
||||
if err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if _, err := tx.Delete(table, nil); err != nil {
|
||||
if _, err := tx.Delete(table, 1); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
if n, err := tx.Table(table).Count(); err != nil {
|
||||
|
||||
20
database/gdb/testdata/reservedwords_table_tpl.sql
vendored
Normal file
20
database/gdb/testdata/reservedwords_table_tpl.sql
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
CREATE TABLE %s (
|
||||
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`key` varchar(45) DEFAULT NULL,
|
||||
`category_id` int(10) unsigned NOT NULL,
|
||||
`user_id` int(10) unsigned NOT NULL,
|
||||
`title` varchar(255) NOT NULL,
|
||||
`content` mediumtext NOT NULL,
|
||||
`sort` int(10) unsigned DEFAULT '0',
|
||||
`brief` varchar(255) DEFAULT NULL,
|
||||
`thumb` varchar(255) DEFAULT NULL,
|
||||
`tags` varchar(900) DEFAULT NULL,
|
||||
`referer` varchar(255) DEFAULT NULL,
|
||||
`status` smallint(5) unsigned DEFAULT '0',
|
||||
`view_count` int(10) unsigned DEFAULT '0',
|
||||
`zan_count` int(10) unsigned DEFAULT NULL,
|
||||
`cai_count` int(10) unsigned DEFAULT NULL,
|
||||
`created_at` datetime DEFAULT NULL,
|
||||
`updated_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
@ -9,32 +9,33 @@ package ghtml
|
||||
|
||||
import (
|
||||
"html"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
strip "github.com/grokify/html-strip-tags-go"
|
||||
)
|
||||
|
||||
// 过滤掉HTML标签,只返回text内容
|
||||
// 参考:http://php.net/manual/zh/function.strip-tags.php
|
||||
// StripTags strips HTML tags from content, and returns only text.
|
||||
// Referer: http://php.net/manual/zh/function.strip-tags.php
|
||||
func StripTags(s string) string {
|
||||
return strip.StripTags(s)
|
||||
}
|
||||
|
||||
// 本函数各方面都和SpecialChars一样,
|
||||
// 除了Entities会转换所有具有 HTML 实体的字符。
|
||||
// 参考:http://php.net/manual/zh/function.htmlentities.php
|
||||
// Entities encodes all HTML chars for content.
|
||||
// Referer: http://php.net/manual/zh/function.htmlentities.php
|
||||
func Entities(s string) string {
|
||||
return html.EscapeString(s)
|
||||
}
|
||||
|
||||
// Entities 的相反操作
|
||||
// 参考:http://php.net/manual/zh/function.html-entity-decode.php
|
||||
// EntitiesDecode decodes all HTML chars for content.
|
||||
// Referer: http://php.net/manual/zh/function.html-entity-decode.php
|
||||
func EntitiesDecode(s string) string {
|
||||
return html.UnescapeString(s)
|
||||
}
|
||||
|
||||
// 将html中的部分特殊标签转换为html转义标签
|
||||
// 参考:http://php.net/manual/zh/function.htmlspecialchars.php
|
||||
// SpecialChars encodes some special chars for content, these special chars are:
|
||||
// "&", "<", ">", `"`, "'".
|
||||
// Referer: http://php.net/manual/zh/function.htmlspecialchars.php
|
||||
func SpecialChars(s string) string {
|
||||
return strings.NewReplacer(
|
||||
"&", "&",
|
||||
@ -45,8 +46,9 @@ func SpecialChars(s string) string {
|
||||
).Replace(s)
|
||||
}
|
||||
|
||||
// 将html部分转义标签还原为html特殊标签
|
||||
// 参考:http://php.net/manual/zh/function.htmlspecialchars-decode.php
|
||||
// SpecialCharsDecode decodes some special chars for content, these special chars are:
|
||||
// "&", "<", ">", `"`, "'".
|
||||
// Referer: http://php.net/manual/zh/function.htmlspecialchars-decode.php
|
||||
func SpecialCharsDecode(s string) string {
|
||||
return strings.NewReplacer(
|
||||
"&", "&",
|
||||
@ -56,3 +58,46 @@ func SpecialCharsDecode(s string) string {
|
||||
"'", "'",
|
||||
).Replace(s)
|
||||
}
|
||||
|
||||
// SpecialCharsMapOrStruct automatically encodes string values/attributes for map/struct.
|
||||
func SpecialCharsMapOrStruct(mapOrStruct interface{}) error {
|
||||
var (
|
||||
reflectValue = reflect.ValueOf(mapOrStruct)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
for reflectValue.IsValid() && (reflectKind == reflect.Ptr || reflectKind == reflect.Interface) {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.Map:
|
||||
var (
|
||||
mapKeys = reflectValue.MapKeys()
|
||||
mapValue reflect.Value
|
||||
)
|
||||
for _, key := range mapKeys {
|
||||
mapValue = reflectValue.MapIndex(key)
|
||||
switch mapValue.Kind() {
|
||||
case reflect.String:
|
||||
reflectValue.SetMapIndex(key, reflect.ValueOf(SpecialChars(mapValue.String())))
|
||||
case reflect.Interface:
|
||||
if mapValue.Elem().Kind() == reflect.String {
|
||||
reflectValue.SetMapIndex(key, reflect.ValueOf(SpecialChars(mapValue.Elem().String())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
var (
|
||||
fieldValue reflect.Value
|
||||
)
|
||||
for i := 0; i < reflectValue.NumField(); i++ {
|
||||
fieldValue = reflectValue.Field(i)
|
||||
switch fieldValue.Kind() {
|
||||
case reflect.String:
|
||||
fieldValue.Set(reflect.ValueOf(SpecialChars(fieldValue.String())))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -3,16 +3,18 @@
|
||||
// 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 ghtml_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/encoding/ghtml"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func TestStripTags(t *testing.T) {
|
||||
func Test_StripTags(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
src := `<p>Test paragraph.</p><!-- Comment --> <a href="#fragment">Other text</a>`
|
||||
dst := `Test paragraph. Other text`
|
||||
@ -20,7 +22,7 @@ func TestStripTags(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestEntities(t *testing.T) {
|
||||
func Test_Entities(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
src := `A 'quote' "is" <b>bold</b>`
|
||||
dst := `A 'quote' "is" <b>bold</b>`
|
||||
@ -29,7 +31,7 @@ func TestEntities(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSpecialChars(t *testing.T) {
|
||||
func Test_SpecialChars(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
src := `A 'quote' "is" <b>bold</b>`
|
||||
dst := `A 'quote' "is" <b>bold</b>`
|
||||
@ -37,3 +39,43 @@ func TestSpecialChars(t *testing.T) {
|
||||
t.Assert(ghtml.SpecialCharsDecode(dst), src)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SpecialCharsMapOrStruct_Map(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a := g.Map{
|
||||
"Title": "<h1>T</h1>",
|
||||
"Content": "<div>C</div>",
|
||||
}
|
||||
err := ghtml.SpecialCharsMapOrStruct(a)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a["Title"], `<h1>T</h1>`)
|
||||
t.Assert(a["Content"], `<div>C</div>`)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a := g.MapStrStr{
|
||||
"Title": "<h1>T</h1>",
|
||||
"Content": "<div>C</div>",
|
||||
}
|
||||
err := ghtml.SpecialCharsMapOrStruct(a)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a["Title"], `<h1>T</h1>`)
|
||||
t.Assert(a["Content"], `<div>C</div>`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SpecialCharsMapOrStruct_Struct(t *testing.T) {
|
||||
type A struct {
|
||||
Title string
|
||||
Content string
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
a := &A{
|
||||
Title: "<h1>T</h1>",
|
||||
Content: "<div>C</div>",
|
||||
}
|
||||
err := ghtml.SpecialCharsMapOrStruct(a)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(a.Title, `<h1>T</h1>`)
|
||||
t.Assert(a.Content, `<div>C</div>`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -315,6 +315,7 @@ func (j *Json) GetStruct(pattern string, pointer interface{}, mapping ...map[str
|
||||
}
|
||||
|
||||
// GetStructDeep does GetStruct recursively.
|
||||
// Deprecated, use GetStruct instead.
|
||||
func (j *Json) GetStructDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructDeep(j.Get(pattern), pointer, mapping...)
|
||||
}
|
||||
@ -325,6 +326,7 @@ func (j *Json) GetStructs(pattern string, pointer interface{}, mapping ...map[st
|
||||
}
|
||||
|
||||
// GetStructsDeep converts any slice to given struct slice recursively.
|
||||
// Deprecated, use GetStructs instead.
|
||||
func (j *Json) GetStructsDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructsDeep(j.Get(pattern), pointer, mapping...)
|
||||
}
|
||||
@ -394,6 +396,7 @@ func (j *Json) ToStruct(pointer interface{}, mapping ...map[string]string) error
|
||||
|
||||
// ToStructDeep converts current Json object to specified object recursively.
|
||||
// The <pointer> should be a pointer type of *struct.
|
||||
// Deprecated, use ToStruct instead.
|
||||
func (j *Json) ToStructDeep(pointer interface{}, mapping ...map[string]string) error {
|
||||
j.mu.RLock()
|
||||
defer j.mu.RUnlock()
|
||||
|
||||
@ -228,7 +228,7 @@ func IsValidDataType(dataType string) bool {
|
||||
func checkDataType(content []byte) string {
|
||||
if json.Valid(content) {
|
||||
return "json"
|
||||
} else if gregex.IsMatch(`^<.+>[\S\s]+<.+>$`, content) {
|
||||
} else if gregex.IsMatch(`^<.+>[\S\s]+<.+>\s*$`, content) {
|
||||
return "xml"
|
||||
} 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)) ||
|
||||
|
||||
@ -203,7 +203,7 @@ func Test_ToStruct1(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ToStructDeep(t *testing.T) {
|
||||
func Test_ToStruct(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Item struct {
|
||||
Title string `json:"title"`
|
||||
@ -231,10 +231,73 @@ func Test_ToStructDeep(t *testing.T) {
|
||||
t.Assert(j.GetBool("items"), false)
|
||||
t.Assert(j.GetArray("items"), nil)
|
||||
m := new(M)
|
||||
err = j.ToStructDeep(m)
|
||||
err = j.ToStruct(m)
|
||||
t.Assert(err, nil)
|
||||
t.AssertNE(m.Me, nil)
|
||||
t.Assert(m.Me["day"], "20009")
|
||||
t.Assert(m.Items, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_ToStruct_Complicated(t *testing.T) {
|
||||
type CertInfo struct {
|
||||
UserRealName string `json:"userRealname,omitempty"`
|
||||
IdentType string `json:"identType,omitempty"`
|
||||
IdentNo string `json:"identNo,omitempty"`
|
||||
CompanyName string `json:"companyName,omitempty"`
|
||||
Website string `json:"website,omitempty"`
|
||||
RegisterNo string `json:"registerNo,omitempty"`
|
||||
AreaCode string `json:"areaCode,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
CommunityCreditCode string `json:"communityCreditCode,omitempty"`
|
||||
PhoneNumber string `json:"phoneNumber,omitempty"`
|
||||
AreaName string `json:"areaName,omitempty"`
|
||||
PhoneAreaCode string `json:"phoneAreaCode,omitempty"`
|
||||
OperateRange string `json:"operateRange,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
LegalPersonName string `json:"legalPersonName,omitempty"`
|
||||
OrgCode string `json:"orgCode,omitempty"`
|
||||
BusinessLicense string `json:"businessLicense,omitempty"`
|
||||
FilePath1 string `json:"filePath1,omitempty"`
|
||||
MobileNo string `json:"mobileNo,omitempty"`
|
||||
CardName string `json:"cardName,omitempty"`
|
||||
BankMobileNo string `json:"bankMobileNo,omitempty"`
|
||||
BankCode string `json:"bankCode,omitempty"`
|
||||
BankCard string `json:"bankCard,omitempty"`
|
||||
}
|
||||
|
||||
type CertList struct {
|
||||
StatusCode uint `json:"statusCode,string"`
|
||||
SrcType uint `json:"srcType,string"`
|
||||
CertID string `json:"certId"`
|
||||
CardType string `json:"cardType,omitempty"`
|
||||
CertInfo CertInfo `json:"certInfo"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
UserLevel uint `json:"userLevel,string,omitempty"`
|
||||
CertList []CertList `json:"certList"`
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
jsonContent := `{
|
||||
"certList":[
|
||||
{"certId":"2023313","certInfo":"{\"address\":\"xxxxxxx\",\"phoneNumber\":\"15084890\",\"companyName\":\"dddd\",\"communityCreditCode\":\"91110111MBE1G2B\",\"operateRange\":\"fff\",\"registerNo\":\"91110111MA00G2B\",\"legalPersonName\":\"rrr\"}","srcType":"1","statusCode":"2"},
|
||||
{"certId":"2023314","certInfo":"{\"identNo\":\"342224196507051\",\"userRealname\":\"xxxx\",\"identType\":\"01\"}","srcType":"8","statusCode":"0"},
|
||||
{"certId":"2023322","certInfo":"{\"businessLicense\":\"91110111MA00BE1G\",\"companyName\":\"sssss\",\"communityCreditCode\":\"91110111MA00BE1\"}","srcType":"2","statusCode":"0"}
|
||||
]
|
||||
}`
|
||||
j, err := gjson.LoadContent(jsonContent)
|
||||
t.Assert(err, nil)
|
||||
var response = new(Response)
|
||||
err = j.ToStruct(response)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(response.CertList), 3)
|
||||
t.Assert(response.CertList[0].CertID, 2023313)
|
||||
t.Assert(response.CertList[1].CertID, 2023314)
|
||||
t.Assert(response.CertList[2].CertID, 2023322)
|
||||
t.Assert(response.CertList[0].CertInfo.PhoneNumber, "15084890")
|
||||
t.Assert(response.CertList[1].CertInfo.IdentNo, "342224196507051")
|
||||
t.Assert(response.CertList[2].CertInfo.BusinessLicense, "91110111MA00BE1G")
|
||||
})
|
||||
}
|
||||
|
||||
1
go.mod
1
go.mod
@ -9,7 +9,6 @@ require (
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/gomodule/redigo v2.0.0+incompatible
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/gqcn/structs v1.1.1
|
||||
github.com/grokify/html-strip-tags-go v0.0.0-20190921062105-daaa06bf1aaf
|
||||
github.com/json-iterator/go v1.1.10
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
|
||||
@ -33,6 +33,7 @@ func IsEmpty(value interface{}) bool {
|
||||
if value == nil {
|
||||
return true
|
||||
}
|
||||
// It firstly checks the variable as common types using assertion, and then reflection.
|
||||
switch value := value.(type) {
|
||||
case int:
|
||||
return value == 0
|
||||
@ -66,6 +67,16 @@ func IsEmpty(value interface{}) bool {
|
||||
return len(value) == 0
|
||||
case []rune:
|
||||
return len(value) == 0
|
||||
case []int:
|
||||
return len(value) == 0
|
||||
case []string:
|
||||
return len(value) == 0
|
||||
case []float32:
|
||||
return len(value) == 0
|
||||
case []float64:
|
||||
return len(value) == 0
|
||||
case map[string]interface{}:
|
||||
return len(value) == 0
|
||||
default:
|
||||
// Common interfaces checks.
|
||||
if f, ok := value.(apiString); ok {
|
||||
|
||||
@ -5,14 +5,46 @@
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package structs provides functions for struct conversion.
|
||||
//
|
||||
// Inspired and improved from: https://github.com/fatih/structs
|
||||
package structs
|
||||
|
||||
import "github.com/gqcn/structs"
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Field is alias of structs.Field.
|
||||
// Field contains information of a struct field .
|
||||
type Field struct {
|
||||
*structs.Field
|
||||
// Retrieved tag name. There might be more than one tags in the field,
|
||||
value reflect.Value
|
||||
field reflect.StructField
|
||||
// Retrieved tag value. There might be more than one tags in the field,
|
||||
// but only one can be retrieved according to calling function rules.
|
||||
Tag string
|
||||
TagValue string
|
||||
}
|
||||
|
||||
// Tag returns the value associated with key in the tag string. If there is no
|
||||
// such key in the tag, Tag returns the empty string.
|
||||
func (f *Field) Tag(key string) string {
|
||||
return f.field.Tag.Get(key)
|
||||
}
|
||||
|
||||
// Value returns the underlying value of the field. It panics if the field
|
||||
// is not exported.
|
||||
func (f *Field) Value() interface{} {
|
||||
return f.value.Interface()
|
||||
}
|
||||
|
||||
// IsEmbedded returns true if the given field is an anonymous field (embedded)
|
||||
func (f *Field) IsEmbedded() bool {
|
||||
return f.field.Anonymous
|
||||
}
|
||||
|
||||
// IsExported returns true if the given field is exported.
|
||||
func (f *Field) IsExported() bool {
|
||||
return f.field.PkgPath == ""
|
||||
}
|
||||
|
||||
// Name returns the name of the given field
|
||||
func (f *Field) Name() string {
|
||||
return f.field.Name
|
||||
}
|
||||
|
||||
@ -6,90 +6,54 @@
|
||||
|
||||
package structs
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/gqcn/structs"
|
||||
)
|
||||
|
||||
// MapField retrieves struct field as map[name/tag]*Field from <pointer>, and returns the map.
|
||||
//
|
||||
// The parameter <pointer> should be type of struct/*struct.
|
||||
//
|
||||
// The parameter <priority> specifies the priority tag array for retrieving from high to low.
|
||||
//
|
||||
// The parameter <recursive> specifies whether retrieving the struct field recursively.
|
||||
//
|
||||
// Note that it only retrieves the exported attributes with first letter up-case from struct.
|
||||
func MapField(pointer interface{}, priority []string, recursive bool) map[string]*Field {
|
||||
// If <pointer> points to an invalid address, for example a nil variable,
|
||||
// it here creates an empty struct using reflect feature.
|
||||
var (
|
||||
tempValue reflect.Value
|
||||
pointerValue = reflect.ValueOf(pointer)
|
||||
)
|
||||
for pointerValue.Kind() == reflect.Ptr {
|
||||
tempValue = pointerValue.Elem()
|
||||
if !tempValue.IsValid() {
|
||||
pointer = reflect.New(pointerValue.Type().Elem()).Elem()
|
||||
break
|
||||
} else {
|
||||
pointerValue = tempValue
|
||||
}
|
||||
func MapField(pointer interface{}, priority []string) (map[string]*Field, error) {
|
||||
fields, err := getFieldValues(pointer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
fields []*structs.Field
|
||||
fieldMap = make(map[string]*Field)
|
||||
)
|
||||
if v, ok := pointer.(reflect.Value); ok {
|
||||
fields = structs.Fields(v.Interface())
|
||||
} else {
|
||||
fields = structs.Fields(pointer)
|
||||
}
|
||||
var (
|
||||
tag = ""
|
||||
name = ""
|
||||
tagValue = ""
|
||||
mapField = make(map[string]*Field)
|
||||
)
|
||||
for _, field := range fields {
|
||||
name = field.Name()
|
||||
// Only retrieve exported attributes.
|
||||
if name[0] < byte('A') || name[0] > byte('Z') {
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
fieldMap[name] = &Field{
|
||||
Field: field,
|
||||
Tag: tag,
|
||||
}
|
||||
tag = ""
|
||||
tagValue = ""
|
||||
for _, p := range priority {
|
||||
tag = field.Tag(p)
|
||||
if tag != "" {
|
||||
tagValue = field.Tag(p)
|
||||
if tagValue != "" && tagValue != "-" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if tag != "" {
|
||||
fieldMap[tag] = &Field{
|
||||
Field: field,
|
||||
Tag: tag,
|
||||
}
|
||||
}
|
||||
if recursive {
|
||||
var (
|
||||
rv = reflect.ValueOf(field.Value())
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
if kind == reflect.Struct {
|
||||
for k, v := range MapField(rv, priority, true) {
|
||||
if _, ok := fieldMap[k]; !ok {
|
||||
fieldMap[k] = v
|
||||
tempField := field
|
||||
tempField.TagValue = tagValue
|
||||
if tagValue != "" {
|
||||
mapField[tagValue] = tempField
|
||||
} else {
|
||||
if field.IsEmbedded() {
|
||||
m, err := MapField(field.value, priority)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, v := range m {
|
||||
if _, ok := mapField[k]; !ok {
|
||||
tempV := v
|
||||
mapField[k] = tempV
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mapField[field.Name()] = tempField
|
||||
}
|
||||
}
|
||||
}
|
||||
return fieldMap
|
||||
return mapField, nil
|
||||
}
|
||||
|
||||
@ -7,134 +7,147 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"github.com/gqcn/structs"
|
||||
)
|
||||
|
||||
// TagFields retrieves struct tags as []*Field from <pointer>, and returns it.
|
||||
//
|
||||
// The parameter <pointer> should be type of struct/*struct.
|
||||
//
|
||||
// The parameter <recursive> specifies whether retrieving the struct field recursively.
|
||||
//
|
||||
// Note that it only retrieves the exported attributes with first letter up-case from struct.
|
||||
func TagFields(pointer interface{}, priority []string, recursive bool) []*Field {
|
||||
return doTagFields(pointer, priority, recursive, map[string]struct{}{})
|
||||
}
|
||||
|
||||
// doTagFields retrieves the tag and corresponding attribute name from <pointer>. It also filters repeated
|
||||
// tag internally.
|
||||
// The parameter <pointer> should be type of struct/*struct.
|
||||
func doTagFields(pointer interface{}, priority []string, recursive bool, tagMap map[string]struct{}) []*Field {
|
||||
// If <pointer> points to an invalid address, for example a nil variable,
|
||||
// it here creates an empty struct using reflect feature.
|
||||
var (
|
||||
tempValue reflect.Value
|
||||
pointerValue = reflect.ValueOf(pointer)
|
||||
)
|
||||
for pointerValue.Kind() == reflect.Ptr {
|
||||
tempValue = pointerValue.Elem()
|
||||
if !tempValue.IsValid() {
|
||||
pointer = reflect.New(pointerValue.Type().Elem()).Elem()
|
||||
break
|
||||
} else {
|
||||
pointerValue = tempValue
|
||||
}
|
||||
}
|
||||
var fields []*structs.Field
|
||||
if v, ok := pointer.(reflect.Value); ok {
|
||||
fields = structs.Fields(v.Interface())
|
||||
} else {
|
||||
var (
|
||||
rv = reflect.ValueOf(pointer)
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
// If pointer is type of **struct and nil, then automatically create a temporary struct,
|
||||
// which is used for structs.Fields.
|
||||
if kind == reflect.Ptr && (!rv.IsValid() || rv.IsNil()) {
|
||||
fields = structs.Fields(reflect.New(rv.Type().Elem()).Elem().Interface())
|
||||
} else {
|
||||
fields = structs.Fields(pointer)
|
||||
}
|
||||
}
|
||||
var (
|
||||
tag = ""
|
||||
name = ""
|
||||
)
|
||||
tagFields := make([]*Field, 0)
|
||||
for _, field := range fields {
|
||||
name = field.Name()
|
||||
// Only retrieve exported attributes.
|
||||
if name[0] < byte('A') || name[0] > byte('Z') {
|
||||
continue
|
||||
}
|
||||
tag = ""
|
||||
for _, p := range priority {
|
||||
tag = field.Tag(p)
|
||||
if tag != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if tag != "" {
|
||||
// Filter repeated tag.
|
||||
if _, ok := tagMap[tag]; ok {
|
||||
continue
|
||||
}
|
||||
tagFields = append(tagFields, &Field{
|
||||
Field: field,
|
||||
Tag: tag,
|
||||
})
|
||||
}
|
||||
if recursive {
|
||||
var (
|
||||
rv = reflect.ValueOf(field.Value())
|
||||
kind = rv.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
if kind == reflect.Struct {
|
||||
tagFields = append(tagFields, doTagFields(rv, priority, recursive, tagMap)...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tagFields
|
||||
func TagFields(pointer interface{}, priority []string) ([]*Field, error) {
|
||||
return getFieldValuesByTagPriority(pointer, priority, map[string]struct{}{})
|
||||
}
|
||||
|
||||
// TagMapName retrieves struct tags as map[tag]attribute from <pointer>, and returns it.
|
||||
//
|
||||
// The parameter <pointer> should be type of struct/*struct.
|
||||
//
|
||||
// The parameter <recursive> specifies whether retrieving the struct field recursively.
|
||||
//
|
||||
// Note that it only retrieves the exported attributes with first letter up-case from struct.
|
||||
func TagMapName(pointer interface{}, priority []string, recursive bool) map[string]string {
|
||||
fields := TagFields(pointer, priority, recursive)
|
||||
tagMap := make(map[string]string, len(fields))
|
||||
for _, v := range fields {
|
||||
tagMap[v.Tag] = v.Name()
|
||||
func TagMapName(pointer interface{}, priority []string) (map[string]string, error) {
|
||||
fields, err := TagFields(pointer, priority)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tagMap
|
||||
tagMap := make(map[string]string, len(fields))
|
||||
for _, field := range fields {
|
||||
tagMap[field.TagValue] = field.Name()
|
||||
}
|
||||
return tagMap, nil
|
||||
}
|
||||
|
||||
// TagMapField retrieves struct tags as map[tag]*Field from <pointer>, and returns it.
|
||||
//
|
||||
// The parameter <pointer> should be type of struct/*struct.
|
||||
//
|
||||
// The parameter <recursive> specifies whether retrieving the struct field recursively.
|
||||
//
|
||||
// Note that it only retrieves the exported attributes with first letter up-case from struct.
|
||||
func TagMapField(pointer interface{}, priority []string, recursive bool) map[string]*Field {
|
||||
fields := TagFields(pointer, priority, recursive)
|
||||
tagMap := make(map[string]*Field, len(fields))
|
||||
for _, v := range fields {
|
||||
tagMap[v.Tag] = v
|
||||
func TagMapField(pointer interface{}, priority []string) (map[string]*Field, error) {
|
||||
fields, err := TagFields(pointer, priority)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tagMap
|
||||
tagMap := make(map[string]*Field, len(fields))
|
||||
for _, field := range fields {
|
||||
tagField := field
|
||||
tagMap[field.TagValue] = tagField
|
||||
}
|
||||
return tagMap, nil
|
||||
}
|
||||
|
||||
func getFieldValues(value interface{}) ([]*Field, error) {
|
||||
var (
|
||||
reflectValue reflect.Value
|
||||
reflectKind reflect.Kind
|
||||
)
|
||||
if v, ok := value.(reflect.Value); ok {
|
||||
reflectValue = v
|
||||
reflectKind = reflectValue.Kind()
|
||||
} else {
|
||||
reflectValue = reflect.ValueOf(value)
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
|
||||
if reflectKind == reflect.Ptr {
|
||||
if !reflectValue.IsValid() || reflectValue.IsNil() {
|
||||
// If pointer is type of *struct and nil, then automatically create a temporary struct.
|
||||
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
} else {
|
||||
// If pointer is type of **struct and nil, then automatically create a temporary struct.
|
||||
var (
|
||||
pointedValue = reflectValue.Elem()
|
||||
pointedValueKind = pointedValue.Kind()
|
||||
)
|
||||
if pointedValueKind == reflect.Ptr && (!pointedValue.IsValid() || pointedValue.IsNil()) {
|
||||
reflectValue = reflect.New(pointedValue.Type().Elem()).Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
} else {
|
||||
reflectValue = pointedValue
|
||||
reflectKind = pointedValueKind
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
if reflectKind != reflect.Struct {
|
||||
return nil, errors.New("given value should be type of struct/*struct")
|
||||
}
|
||||
var (
|
||||
structType = reflectValue.Type()
|
||||
length = reflectValue.NumField()
|
||||
fields = make([]*Field, length)
|
||||
)
|
||||
for i := 0; i < length; i++ {
|
||||
fields[i] = &Field{
|
||||
value: reflectValue.Field(i),
|
||||
field: structType.Field(i),
|
||||
}
|
||||
}
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func getFieldValuesByTagPriority(pointer interface{}, priority []string, tagMap map[string]struct{}) ([]*Field, error) {
|
||||
fields, err := getFieldValues(pointer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
tagValue = ""
|
||||
tagFields = make([]*Field, 0)
|
||||
)
|
||||
for _, field := range fields {
|
||||
// Only retrieve exported attributes.
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
tagValue = ""
|
||||
for _, p := range priority {
|
||||
tagValue = field.Tag(p)
|
||||
if tagValue != "" && tagValue != "-" {
|
||||
break
|
||||
}
|
||||
}
|
||||
if tagValue != "" {
|
||||
// Filter repeated tag.
|
||||
if _, ok := tagMap[tagValue]; ok {
|
||||
continue
|
||||
}
|
||||
tagField := field
|
||||
tagField.TagValue = tagValue
|
||||
tagFields = append(tagFields, tagField)
|
||||
}
|
||||
// If this is an embedded attribute, it retrieves the tags recursively.
|
||||
if field.IsEmbedded() {
|
||||
if subTagFields, err := getFieldValuesByTagPriority(field.value, priority, tagMap); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
tagFields = append(tagFields, subTagFields...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tagFields, nil
|
||||
}
|
||||
|
||||
@ -1,90 +0,0 @@
|
||||
// Copyright 2019 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 structs_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/internal/structs"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Basic(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `params:"name"`
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
var user User
|
||||
t.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
t.Assert(structs.TagMapName(&user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
|
||||
t.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
t.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}, true), g.Map{"name": "Name", "pass1": "Pass"})
|
||||
t.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}, true), g.Map{"name": "Name", "pass2": "Pass"})
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Base struct {
|
||||
Pass1 string `params:"password1"`
|
||||
Pass2 string `params:"password2"`
|
||||
}
|
||||
type UserWithBase struct {
|
||||
Id int
|
||||
Name string
|
||||
Base `params:"base"`
|
||||
}
|
||||
user := new(UserWithBase)
|
||||
t.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{
|
||||
"base": "Base",
|
||||
"password1": "Pass1",
|
||||
"password2": "Pass2",
|
||||
})
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Base struct {
|
||||
Pass1 string `params:"password1"`
|
||||
Pass2 string `params:"password2"`
|
||||
}
|
||||
type UserWithBase1 struct {
|
||||
Id int
|
||||
Name string
|
||||
Base
|
||||
}
|
||||
type UserWithBase2 struct {
|
||||
Id int
|
||||
Name string
|
||||
Pass Base
|
||||
}
|
||||
user1 := new(UserWithBase1)
|
||||
user2 := new(UserWithBase2)
|
||||
t.Assert(structs.TagMapName(user1, []string{"params"}, true), g.Map{"password1": "Pass1", "password2": "Pass2"})
|
||||
t.Assert(structs.TagMapName(user2, []string{"params"}, true), g.Map{"password1": "Pass1", "password2": "Pass2"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_StructOfNilPointer(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `params:"name"`
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
var user *User
|
||||
t.Assert(structs.TagMapName(user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
t.Assert(structs.TagMapName(&user, []string{"params"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
|
||||
t.Assert(structs.TagMapName(&user, []string{"params", "my-tag1"}, true), g.Map{"name": "Name", "pass": "Pass"})
|
||||
t.Assert(structs.TagMapName(&user, []string{"my-tag1", "params"}, true), g.Map{"name": "Name", "pass1": "Pass"})
|
||||
t.Assert(structs.TagMapName(&user, []string{"my-tag2", "params"}, true), g.Map{"name": "Name", "pass2": "Pass"})
|
||||
})
|
||||
}
|
||||
35
internal/structs/structs_z_bench_test.go
Normal file
35
internal/structs/structs_z_bench_test.go
Normal file
@ -0,0 +1,35 @@
|
||||
// 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 structs_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/internal/structs"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `params:"name"`
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
|
||||
var (
|
||||
user = new(User)
|
||||
userNilPointer *User
|
||||
)
|
||||
|
||||
func Benchmark_TagFields(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
structs.TagFields(user, []string{"params", "my-tag1"})
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_TagFields_NilPointer(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
structs.TagFields(&userNilPointer, []string{"params", "my-tag1"})
|
||||
}
|
||||
}
|
||||
126
internal/structs/structs_z_unit_test.go
Normal file
126
internal/structs/structs_z_unit_test.go
Normal file
@ -0,0 +1,126 @@
|
||||
// Copyright 2019 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 structs_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/internal/structs"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Basic(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `params:"name"`
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
var user User
|
||||
m, _ := structs.TagMapName(user, []string{"params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
|
||||
m, _ = structs.TagMapName(&user, []string{"params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
|
||||
|
||||
m, _ = structs.TagMapName(&user, []string{"params", "my-tag1"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
|
||||
m, _ = structs.TagMapName(&user, []string{"my-tag1", "params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass1": "Pass"})
|
||||
m, _ = structs.TagMapName(&user, []string{"my-tag2", "params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass2": "Pass"})
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Base struct {
|
||||
Pass1 string `params:"password1"`
|
||||
Pass2 string `params:"password2"`
|
||||
}
|
||||
type UserWithBase struct {
|
||||
Id int
|
||||
Name string
|
||||
Base `params:"base"`
|
||||
}
|
||||
user := new(UserWithBase)
|
||||
m, _ := structs.TagMapName(user, []string{"params"})
|
||||
t.Assert(m, g.Map{
|
||||
"base": "Base",
|
||||
"password1": "Pass1",
|
||||
"password2": "Pass2",
|
||||
})
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Base struct {
|
||||
Pass1 string `params:"password1"`
|
||||
Pass2 string `params:"password2"`
|
||||
}
|
||||
type UserWithEmbeddedAttribute struct {
|
||||
Id int
|
||||
Name string
|
||||
Base
|
||||
}
|
||||
type UserWithoutEmbeddedAttribute struct {
|
||||
Id int
|
||||
Name string
|
||||
Pass Base
|
||||
}
|
||||
user1 := new(UserWithEmbeddedAttribute)
|
||||
user2 := new(UserWithoutEmbeddedAttribute)
|
||||
m, _ := structs.TagMapName(user1, []string{"params"})
|
||||
t.Assert(m, g.Map{"password1": "Pass1", "password2": "Pass2"})
|
||||
m, _ = structs.TagMapName(user2, []string{"params"})
|
||||
t.Assert(m, g.Map{})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_StructOfNilPointer(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `params:"name"`
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
var user *User
|
||||
m, _ := structs.TagMapName(user, []string{"params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
|
||||
m, _ = structs.TagMapName(&user, []string{"params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
|
||||
|
||||
m, _ = structs.TagMapName(&user, []string{"params", "my-tag1"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass": "Pass"})
|
||||
m, _ = structs.TagMapName(&user, []string{"my-tag1", "params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass1": "Pass"})
|
||||
m, _ = structs.TagMapName(&user, []string{"my-tag2", "params"})
|
||||
t.Assert(m, g.Map{"name": "Name", "pass2": "Pass"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_MapField(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string `params:"name"`
|
||||
Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"`
|
||||
}
|
||||
var user *User
|
||||
m, _ := structs.MapField(user, []string{"params"})
|
||||
t.Assert(len(m), 3)
|
||||
_, ok := m["Id"]
|
||||
t.Assert(ok, true)
|
||||
_, ok = m["Name"]
|
||||
t.Assert(ok, false)
|
||||
_, ok = m["name"]
|
||||
t.Assert(ok, true)
|
||||
_, ok = m["Pass"]
|
||||
t.Assert(ok, false)
|
||||
_, ok = m["pass"]
|
||||
t.Assert(ok, true)
|
||||
})
|
||||
}
|
||||
@ -7,16 +7,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// replaceCharReg is the regular expression object for replacing chars in key.
|
||||
// It is used for function EqualFoldWithoutChars.
|
||||
replaceCharReg, _ = regexp.Compile(`[\-\.\_\s]+`)
|
||||
)
|
||||
|
||||
// IsLetterUpper checks whether the given byte b is in upper case.
|
||||
func IsLetterUpper(b byte) bool {
|
||||
if b >= byte('A') && b <= byte('Z') {
|
||||
@ -83,11 +76,19 @@ func ReplaceByMap(origin string, replaces map[string]string) string {
|
||||
return origin
|
||||
}
|
||||
|
||||
// RemoveSymbols removes all symbols from string and lefts only numbers and letters.
|
||||
func RemoveSymbols(s string) string {
|
||||
var b []byte
|
||||
for _, c := range s {
|
||||
if (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') {
|
||||
b = append(b, byte(c))
|
||||
}
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// EqualFoldWithoutChars checks string <s1> and <s2> equal case-insensitively,
|
||||
// with/without chars '-'/'_'/'.'/' '.
|
||||
func EqualFoldWithoutChars(s1, s2 string) bool {
|
||||
return strings.EqualFold(
|
||||
replaceCharReg.ReplaceAllString(s1, ""),
|
||||
replaceCharReg.ReplaceAllString(s2, ""),
|
||||
)
|
||||
return strings.EqualFold(RemoveSymbols(s1), RemoveSymbols(s2))
|
||||
}
|
||||
|
||||
29
internal/utils/utils_z_bench_test.go
Normal file
29
internal/utils/utils_z_bench_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
// 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 utils_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
replaceCharReg, _ = regexp.Compile(`[\-\.\_\s]+`)
|
||||
)
|
||||
|
||||
func Benchmark_RemoveSymbols(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
utils.RemoveSymbols(`-a-b._a c1!@#$%^&*()_+:";'.,'01`)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_RegularReplaceChars(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
replaceCharReg.ReplaceAllString(`-a-b._a c1!@#$%^&*()_+:";'.,'01`, "")
|
||||
}
|
||||
}
|
||||
@ -63,3 +63,9 @@ func Test_ReadCloser(t *testing.T) {
|
||||
t.Assert(r, []byte{1, 2, 3, 4})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_RemoveSymbols(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(utils.RemoveSymbols(`-a-b._a c1!@#$%^&*()_+:";'.,'01`), `abac101`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -6,3 +6,153 @@
|
||||
|
||||
// Package ghttp provides powerful http server and simple client implements.
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/os/gsession"
|
||||
"github.com/gorilla/websocket"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// Server wraps the http.Server and provides more feature.
|
||||
Server struct {
|
||||
name string // Unique name for instance management.
|
||||
config ServerConfig // Configuration.
|
||||
plugins []Plugin // Plugin array.
|
||||
servers []*gracefulServer // Underlying http.Server array.
|
||||
serverCount *gtype.Int // Underlying http.Server count.
|
||||
closeChan chan struct{} // Used for underlying server closing event notification.
|
||||
serveTree map[string]interface{} // The route map tree.
|
||||
serveCache *gcache.Cache // Server cache for internal usage.
|
||||
routesMap map[string][]registeredRouteItem // Route map mainly for route dumps and repeated route checks.
|
||||
statusHandlerMap map[string]HandlerFunc // Custom status handler map.
|
||||
sessionManager *gsession.Manager // Session manager.
|
||||
}
|
||||
|
||||
// Router object.
|
||||
Router struct {
|
||||
Uri string // URI.
|
||||
Method string // HTTP method
|
||||
Domain string // Bound domain.
|
||||
RegRule string // Parsed regular expression for route matching.
|
||||
RegNames []string // Parsed router parameter names.
|
||||
Priority int // Just for reference.
|
||||
}
|
||||
|
||||
// Router item just for route dumps.
|
||||
RouterItem struct {
|
||||
Server string // Server name.
|
||||
Address string // Listening address.
|
||||
Domain string // Bound domain.
|
||||
Type int // Router type.
|
||||
Middleware string // Bound middleware.
|
||||
Method string // Handler method name.
|
||||
Route string // Route URI.
|
||||
Priority int // Just for reference.
|
||||
IsServiceHandler bool // Is service handler.
|
||||
handler *handlerItem // The handler.
|
||||
}
|
||||
|
||||
// handlerItem is the registered handler for route handling,
|
||||
// including middleware and hook functions.
|
||||
handlerItem struct {
|
||||
itemId int // Unique handler item id mark.
|
||||
itemName string // Handler name, which is automatically retrieved from runtime stack when registered.
|
||||
itemType int // Handler type: object/handler/controller/middleware/hook.
|
||||
itemFunc HandlerFunc // Handler address.
|
||||
initFunc HandlerFunc // Initialization function when request enters the object(only available for object register type).
|
||||
shutFunc HandlerFunc // Shutdown function when request leaves out the object(only available for object register type).
|
||||
middleware []HandlerFunc // Bound middleware array.
|
||||
ctrlInfo *handlerController // Controller information for reflect usage.
|
||||
hookName string // Hook type name.
|
||||
router *Router // Router object.
|
||||
source string // Source file path:line when registering.
|
||||
}
|
||||
|
||||
// handlerParsedItem is the item parsed from URL.Path.
|
||||
handlerParsedItem struct {
|
||||
handler *handlerItem // Handler information.
|
||||
values map[string]string // Router values parsed from URL.Path.
|
||||
}
|
||||
|
||||
// handlerController is the controller information used for reflect.
|
||||
handlerController struct {
|
||||
name string // Handler method name.
|
||||
reflect reflect.Type // Reflect type of the controller.
|
||||
}
|
||||
|
||||
// registeredRouteItem stores the information of the router and is used for route map.
|
||||
registeredRouteItem struct {
|
||||
source string // Source file path and its line number.
|
||||
handler *handlerItem // Handler object.
|
||||
}
|
||||
|
||||
// Request handler function.
|
||||
HandlerFunc = func(r *Request)
|
||||
|
||||
// Listening file descriptor mapping.
|
||||
// The key is either "http" or "https" and the value is its FD.
|
||||
listenerFdMap = map[string]string
|
||||
)
|
||||
|
||||
const (
|
||||
SERVER_STATUS_STOPPED = 0
|
||||
SERVER_STATUS_RUNNING = 1
|
||||
HOOK_BEFORE_SERVE = "HOOK_BEFORE_SERVE"
|
||||
HOOK_AFTER_SERVE = "HOOK_AFTER_SERVE"
|
||||
HOOK_BEFORE_OUTPUT = "HOOK_BEFORE_OUTPUT"
|
||||
HOOK_AFTER_OUTPUT = "HOOK_AFTER_OUTPUT"
|
||||
HTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
|
||||
gDEFAULT_SERVER = "default"
|
||||
gDEFAULT_DOMAIN = "default"
|
||||
gDEFAULT_METHOD = "ALL"
|
||||
gHANDLER_TYPE_HANDLER = 1
|
||||
gHANDLER_TYPE_OBJECT = 2
|
||||
gHANDLER_TYPE_CONTROLLER = 3
|
||||
gHANDLER_TYPE_MIDDLEWARE = 4
|
||||
gHANDLER_TYPE_HOOK = 5
|
||||
gEXCEPTION_EXIT = "exit"
|
||||
gEXCEPTION_EXIT_ALL = "exit_all"
|
||||
gEXCEPTION_EXIT_HOOK = "exit_hook"
|
||||
gROUTE_CACHE_DURATION = time.Hour
|
||||
)
|
||||
|
||||
var (
|
||||
// methodsMap stores all supported HTTP method,
|
||||
// it is used for quick HTTP method searching using map.
|
||||
methodsMap = make(map[string]struct{})
|
||||
|
||||
// serverMapping stores more than one server instances for current process.
|
||||
// The key is the name of the server, and the value is its instance.
|
||||
serverMapping = gmap.NewStrAnyMap(true)
|
||||
|
||||
// serverRunning marks the running server count.
|
||||
// If there no successful server running or all servers shutdown, this value is 0.
|
||||
serverRunning = gtype.NewInt()
|
||||
|
||||
// wsUpGrader is the default up-grader configuration for websocket.
|
||||
wsUpGrader = websocket.Upgrader{
|
||||
// It does not check the origin in default, the application can do it itself.
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
// allDoneChan is the event for all server have done its serving and exit.
|
||||
// It is used for process blocking purpose.
|
||||
allDoneChan = make(chan struct{}, 1000)
|
||||
|
||||
// serverProcessInitialized is used for lazy initialization for server.
|
||||
// The process can only be initialized once.
|
||||
serverProcessInitialized = gtype.NewBool()
|
||||
|
||||
// gracefulEnabled is used for graceful reload feature, which is false in default.
|
||||
gracefulEnabled = false
|
||||
|
||||
// defaultValueTags is the struct tag names for default value storing.
|
||||
defaultValueTags = []string{"d", "default"}
|
||||
)
|
||||
|
||||
@ -18,7 +18,7 @@ import (
|
||||
|
||||
// dumpTextFormat is the format of the dumped raw string
|
||||
const dumpTextFormat = `+---------------------------------------------+
|
||||
| %s |
|
||||
| %s |
|
||||
+---------------------------------------------+
|
||||
%s
|
||||
%s
|
||||
@ -50,7 +50,7 @@ func (r *ClientResponse) RawRequest() string {
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
dumpTextFormat,
|
||||
"REQUEST",
|
||||
"REQUEST ",
|
||||
gconv.UnsafeBytesToStr(bs),
|
||||
r.requestBody,
|
||||
)
|
||||
|
||||
@ -124,67 +124,82 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien
|
||||
}
|
||||
}
|
||||
var req *http.Request
|
||||
if strings.Contains(param, "@file:") {
|
||||
// File uploading request.
|
||||
buffer := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(buffer)
|
||||
for _, item := range strings.Split(param, "&") {
|
||||
array := strings.Split(item, "=")
|
||||
if len(array[1]) > 6 && strings.Compare(array[1][0:6], "@file:") == 0 {
|
||||
path := array[1][6:]
|
||||
if !gfile.Exists(path) {
|
||||
return nil, errors.New(fmt.Sprintf(`"%s" does not exist`, path))
|
||||
}
|
||||
if file, err := writer.CreateFormFile(array[0], gfile.Basename(path)); err == nil {
|
||||
if f, err := os.Open(path); err == nil {
|
||||
if _, err = io.Copy(file, f); err != nil {
|
||||
if method == "GET" {
|
||||
// It appends the parameters to the url if http method is GET.
|
||||
if param != "" {
|
||||
if gstr.Contains(url, "?") {
|
||||
url = url + "&" + param
|
||||
} else {
|
||||
url = url + "?" + param
|
||||
}
|
||||
}
|
||||
if req, err = http.NewRequest(method, url, bytes.NewBuffer(nil)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(param, "@file:") {
|
||||
// File uploading request.
|
||||
buffer := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(buffer)
|
||||
for _, item := range strings.Split(param, "&") {
|
||||
array := strings.Split(item, "=")
|
||||
if len(array[1]) > 6 && strings.Compare(array[1][0:6], "@file:") == 0 {
|
||||
path := array[1][6:]
|
||||
if !gfile.Exists(path) {
|
||||
return nil, errors.New(fmt.Sprintf(`"%s" does not exist`, path))
|
||||
}
|
||||
if file, err := writer.CreateFormFile(array[0], gfile.Basename(path)); err == nil {
|
||||
if f, err := os.Open(path); err == nil {
|
||||
if _, err = io.Copy(file, f); err != nil {
|
||||
f.Close()
|
||||
return nil, err
|
||||
}
|
||||
f.Close()
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
f.Close()
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err = writer.WriteField(array[0], array[1]); err != nil {
|
||||
return nil, err
|
||||
if err = writer.WriteField(array[0], array[1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Close finishes the multipart message and writes the trailing
|
||||
// boundary end line to the output.
|
||||
if err = writer.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Close finishes the multipart message and writes the trailing
|
||||
// boundary end line to the output.
|
||||
if err = writer.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req, err = http.NewRequest(method, url, buffer); err != nil {
|
||||
return nil, err
|
||||
if req, err = http.NewRequest(method, url, buffer); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
}
|
||||
} else {
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
}
|
||||
} else {
|
||||
// Normal request.
|
||||
paramBytes := []byte(param)
|
||||
if req, err = http.NewRequest(method, url, bytes.NewReader(paramBytes)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if v, ok := c.header["Content-Type"]; ok {
|
||||
// Custom Content-Type.
|
||||
req.Header.Set("Content-Type", v)
|
||||
} else if len(paramBytes) > 0 {
|
||||
if (paramBytes[0] == '[' || paramBytes[0] == '{') && json.Valid(paramBytes) {
|
||||
// Auto detecting and setting the post content format: JSON.
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else if gregex.IsMatchString(`^[\w\[\]]+=.+`, param) {
|
||||
// If the parameters passed like "name=value", it then uses form type.
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
// Normal request.
|
||||
paramBytes := []byte(param)
|
||||
if req, err = http.NewRequest(method, url, bytes.NewReader(paramBytes)); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if v, ok := c.header["Content-Type"]; ok {
|
||||
// Custom Content-Type.
|
||||
req.Header.Set("Content-Type", v)
|
||||
} else if len(paramBytes) > 0 {
|
||||
if (paramBytes[0] == '[' || paramBytes[0] == '{') && json.Valid(paramBytes) {
|
||||
// Auto detecting and setting the post content format: JSON.
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
} else if gregex.IsMatchString(`^[\w\[\]]+=.+`, param) {
|
||||
// If the parameters passed like "name=value", it then uses form type.
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Context.
|
||||
if c.ctx != nil {
|
||||
req = req.WithContext(c.ctx)
|
||||
@ -246,7 +261,7 @@ func (c *Client) DoRequest(method, url string, data ...interface{}) (resp *Clien
|
||||
if c.browserMode {
|
||||
now := time.Now()
|
||||
for _, v := range resp.Response.Cookies() {
|
||||
if v.Expires.UnixNano() < now.UnixNano() {
|
||||
if !v.Expires.IsZero() && v.Expires.UnixNano() < now.UnixNano() {
|
||||
delete(c.cookies, v.Name)
|
||||
} else {
|
||||
c.cookies[v.Name] = v.Value
|
||||
|
||||
@ -97,7 +97,7 @@ func newRequest(s *Server, r *http.Request, w http.ResponseWriter) *Request {
|
||||
// It returns a new WebSocket object if success, or the error if failure.
|
||||
// Note that the request should be a websocket request, or it will surely fail upgrading.
|
||||
func (r *Request) WebSocket() (*WebSocket, error) {
|
||||
if conn, err := wsUpgrader.Upgrade(r.Response.Writer, r.Request, nil); err == nil {
|
||||
if conn, err := wsUpGrader.Upgrade(r.Response.Writer, r.Request, nil); err == nil {
|
||||
return &WebSocket{
|
||||
conn,
|
||||
}, nil
|
||||
|
||||
@ -281,13 +281,6 @@ func (r *Request) GetStruct(pointer interface{}, mapping ...map[string]string) e
|
||||
return r.GetRequestStruct(pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetToStruct is an alias and convenient function for GetRequestStruct.
|
||||
// See GetRequestToStruct.
|
||||
// Deprecated.
|
||||
func (r *Request) GetToStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
return r.GetRequestStruct(pointer, mapping...)
|
||||
}
|
||||
|
||||
// parseQuery parses query string into r.queryMap.
|
||||
func (r *Request) parseQuery() {
|
||||
if r.parsedQuery {
|
||||
|
||||
@ -189,15 +189,12 @@ func (r *Request) GetFormMapStrVar(kvMap ...map[string]interface{}) map[string]*
|
||||
// The optional parameter <mapping> is used to specify the key to attribute mapping.
|
||||
func (r *Request) GetFormStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
r.parseForm()
|
||||
m := r.formMap
|
||||
if m == nil {
|
||||
m = map[string]interface{}{}
|
||||
data := r.formMap
|
||||
if data == nil {
|
||||
data = map[string]interface{}{}
|
||||
}
|
||||
return gconv.Struct(m, pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetFormToStruct is alias of GetFormStruct. See GetFormStruct.
|
||||
// Deprecated.
|
||||
func (r *Request) GetFormToStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
return r.GetFormStruct(pointer, mapping...)
|
||||
if err := r.mergeDefaultStructValue(data, pointer); err != nil {
|
||||
return nil
|
||||
}
|
||||
return gconv.Struct(data, pointer, mapping...)
|
||||
}
|
||||
|
||||
@ -197,15 +197,12 @@ func (r *Request) GetQueryMapStrVar(kvMap ...map[string]interface{}) map[string]
|
||||
// attribute mapping.
|
||||
func (r *Request) GetQueryStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
r.parseQuery()
|
||||
m := r.GetQueryMap()
|
||||
if m == nil {
|
||||
m = map[string]interface{}{}
|
||||
data := r.GetQueryMap()
|
||||
if data == nil {
|
||||
data = map[string]interface{}{}
|
||||
}
|
||||
return gconv.Struct(m, pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetQueryToStruct is alias of GetQueryStruct. See GetQueryStruct.
|
||||
// Deprecated.
|
||||
func (r *Request) GetQueryToStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
return r.GetQueryStruct(pointer, mapping...)
|
||||
if err := r.mergeDefaultStructValue(data, pointer); err != nil {
|
||||
return nil
|
||||
}
|
||||
return gconv.Struct(data, pointer, mapping...)
|
||||
}
|
||||
|
||||
@ -8,7 +8,10 @@ package ghttp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gvar"
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/internal/structs"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
)
|
||||
|
||||
// GetRequest retrieves and returns the parameter named <key> passed from client and
|
||||
@ -267,15 +270,37 @@ func (r *Request) GetRequestMapStrVar(kvMap ...map[string]interface{}) map[strin
|
||||
// the parameter <pointer> is a pointer to the struct object.
|
||||
// The optional parameter <mapping> is used to specify the key to attribute mapping.
|
||||
func (r *Request) GetRequestStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
m := r.GetRequestMap()
|
||||
if m == nil {
|
||||
m = map[string]interface{}{}
|
||||
data := r.GetRequestMap()
|
||||
if data == nil {
|
||||
data = map[string]interface{}{}
|
||||
}
|
||||
return gconv.Struct(m, pointer, mapping...)
|
||||
if err := r.mergeDefaultStructValue(data, pointer); err != nil {
|
||||
return nil
|
||||
}
|
||||
return gconv.Struct(data, pointer, mapping...)
|
||||
}
|
||||
|
||||
// GetRequestToStruct is alias of GetRequestStruct. See GetRequestStruct.
|
||||
// Deprecated.
|
||||
func (r *Request) GetRequestToStruct(pointer interface{}, mapping ...map[string]string) error {
|
||||
return r.GetRequestStruct(pointer, mapping...)
|
||||
// mergeDefaultStructValue merges the request parameters with default values from struct tag definition.
|
||||
func (r *Request) mergeDefaultStructValue(data map[string]interface{}, pointer interface{}) error {
|
||||
tagFields, err := structs.TagFields(pointer, defaultValueTags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(tagFields) > 0 {
|
||||
var (
|
||||
foundKey string
|
||||
foundValue interface{}
|
||||
)
|
||||
for _, field := range tagFields {
|
||||
foundKey, foundValue = gutil.MapPossibleItemByKey(data, field.Name())
|
||||
if foundKey == "" {
|
||||
data[field.Name()] = field.TagValue
|
||||
} else {
|
||||
if empty.IsEmpty(foundValue) {
|
||||
data[foundKey] = field.TagValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -74,13 +74,16 @@ func (r *Response) ParseTplContent(content string, params ...gview.Params) (stri
|
||||
}
|
||||
|
||||
// buildInVars merges build-in variables into <params> and returns the new template variables.
|
||||
// TODO performance improving.
|
||||
func (r *Response) buildInVars(params ...map[string]interface{}) map[string]interface{} {
|
||||
m := gutil.MapMergeCopy(params...)
|
||||
m := gutil.MapMergeCopy(r.Request.viewParams)
|
||||
if len(params) > 0 {
|
||||
gutil.MapMerge(m, params[0])
|
||||
}
|
||||
// Retrieve custom template variables from request object.
|
||||
gutil.MapMerge(m, r.Request.viewParams, map[string]interface{}{
|
||||
gutil.MapMerge(m, map[string]interface{}{
|
||||
"Form": r.Request.GetFormMap(),
|
||||
"Query": r.Request.GetQueryMap(),
|
||||
"Request": r.Request.GetMap(),
|
||||
"Cookie": r.Request.Cookie.Map(),
|
||||
"Session": r.Request.Session.Map(),
|
||||
})
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@ -22,7 +21,6 @@ import (
|
||||
"github.com/gogf/gf/os/gsession"
|
||||
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
"github.com/gogf/gf/os/genv"
|
||||
@ -32,146 +30,9 @@ import (
|
||||
"github.com/gogf/gf/os/gtimer"
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
)
|
||||
|
||||
type (
|
||||
// Server wraps the http.Server and provides more feature.
|
||||
Server struct {
|
||||
name string // Unique name for instance management.
|
||||
config ServerConfig // Configuration.
|
||||
plugins []Plugin // Plugin array.
|
||||
servers []*gracefulServer // Underlying http.Server array.
|
||||
serverCount *gtype.Int // Underlying http.Server count.
|
||||
closeChan chan struct{} // Used for underlying server closing event notification.
|
||||
serveTree map[string]interface{} // The route map tree.
|
||||
serveCache *gcache.Cache // Server cache for internal usage.
|
||||
routesMap map[string][]registeredRouteItem // Route map mainly for route dumps and repeated route checks.
|
||||
statusHandlerMap map[string]HandlerFunc // Custom status handler map.
|
||||
sessionManager *gsession.Manager // Session manager.
|
||||
}
|
||||
|
||||
// Router object.
|
||||
Router struct {
|
||||
Uri string // URI.
|
||||
Method string // HTTP method
|
||||
Domain string // Bound domain.
|
||||
RegRule string // Parsed regular expression for route matching.
|
||||
RegNames []string // Parsed router parameter names.
|
||||
Priority int // Just for reference.
|
||||
}
|
||||
|
||||
// Router item just for route dumps.
|
||||
RouterItem struct {
|
||||
Server string // Server name.
|
||||
Address string // Listening address.
|
||||
Domain string // Bound domain.
|
||||
Type int // Router type.
|
||||
Middleware string // Bound middleware.
|
||||
Method string // Handler method name.
|
||||
Route string // Route URI.
|
||||
Priority int // Just for reference.
|
||||
IsServiceHandler bool // Is service handler.
|
||||
handler *handlerItem // The handler.
|
||||
}
|
||||
|
||||
// handlerItem is the registered handler for route handling,
|
||||
// including middleware and hook functions.
|
||||
handlerItem struct {
|
||||
itemId int // Unique handler item id mark.
|
||||
itemName string // Handler name, which is automatically retrieved from runtime stack when registered.
|
||||
itemType int // Handler type: object/handler/controller/middleware/hook.
|
||||
itemFunc HandlerFunc // Handler address.
|
||||
initFunc HandlerFunc // Initialization function when request enters the object(only available for object register type).
|
||||
shutFunc HandlerFunc // Shutdown function when request leaves out the object(only available for object register type).
|
||||
middleware []HandlerFunc // Bound middleware array.
|
||||
ctrlInfo *handlerController // Controller information for reflect usage.
|
||||
hookName string // Hook type name.
|
||||
router *Router // Router object.
|
||||
source string // Source file path:line when registering.
|
||||
}
|
||||
|
||||
// handlerParsedItem is the item parsed from URL.Path.
|
||||
handlerParsedItem struct {
|
||||
handler *handlerItem // Handler information.
|
||||
values map[string]string // Router values parsed from URL.Path.
|
||||
}
|
||||
|
||||
// handlerController is the controller information used for reflect.
|
||||
handlerController struct {
|
||||
name string // Handler method name.
|
||||
reflect reflect.Type // Reflect type of the controller.
|
||||
}
|
||||
|
||||
// registeredRouteItem stores the information of the router and is used for route map.
|
||||
registeredRouteItem struct {
|
||||
source string // Source file path and its line number.
|
||||
handler *handlerItem // Handler object.
|
||||
}
|
||||
|
||||
// Request handler function.
|
||||
HandlerFunc = func(r *Request)
|
||||
|
||||
// Listening file descriptor mapping.
|
||||
// The key is either "http" or "https" and the value is its FD.
|
||||
listenerFdMap = map[string]string
|
||||
)
|
||||
|
||||
const (
|
||||
SERVER_STATUS_STOPPED = 0
|
||||
SERVER_STATUS_RUNNING = 1
|
||||
HOOK_BEFORE_SERVE = "HOOK_BEFORE_SERVE"
|
||||
HOOK_AFTER_SERVE = "HOOK_AFTER_SERVE"
|
||||
HOOK_BEFORE_OUTPUT = "HOOK_BEFORE_OUTPUT"
|
||||
HOOK_AFTER_OUTPUT = "HOOK_AFTER_OUTPUT"
|
||||
HTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
|
||||
gDEFAULT_SERVER = "default"
|
||||
gDEFAULT_DOMAIN = "default"
|
||||
gDEFAULT_METHOD = "ALL"
|
||||
gHANDLER_TYPE_HANDLER = 1
|
||||
gHANDLER_TYPE_OBJECT = 2
|
||||
gHANDLER_TYPE_CONTROLLER = 3
|
||||
gHANDLER_TYPE_MIDDLEWARE = 4
|
||||
gHANDLER_TYPE_HOOK = 5
|
||||
gEXCEPTION_EXIT = "exit"
|
||||
gEXCEPTION_EXIT_ALL = "exit_all"
|
||||
gEXCEPTION_EXIT_HOOK = "exit_hook"
|
||||
gROUTE_CACHE_DURATION = time.Hour
|
||||
)
|
||||
|
||||
var (
|
||||
// methodsMap stores all supported HTTP method,
|
||||
// it is used for quick HTTP method searching using map.
|
||||
methodsMap = make(map[string]struct{})
|
||||
|
||||
// serverMapping stores more than one server instances for current process.
|
||||
// The key is the name of the server, and the value is its instance.
|
||||
serverMapping = gmap.NewStrAnyMap(true)
|
||||
|
||||
// serverRunning marks the running server count.
|
||||
// If there no successful server running or all servers shutdown, this value is 0.
|
||||
serverRunning = gtype.NewInt()
|
||||
|
||||
// wsUpgrader is the default up-grader configuration for websocket.
|
||||
wsUpgrader = websocket.Upgrader{
|
||||
// It does not check the origin in default, the application can do it itself.
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
// allDoneChan is the event for all server have done its serving and exit.
|
||||
// It is used for process blocking purpose.
|
||||
allDoneChan = make(chan struct{}, 1000)
|
||||
|
||||
// serverProcessInited is used for lazy initialization for server.
|
||||
// The process can only be initialized once.
|
||||
serverProcessInited = gtype.NewBool()
|
||||
|
||||
// gracefulEnabled is used for graceful reload feature, which is false in default.
|
||||
gracefulEnabled = false
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Initialize the methods map.
|
||||
for _, v := range strings.Split(HTTP_METHODS, ",") {
|
||||
@ -190,7 +51,7 @@ func SetGraceful(enabled bool) {
|
||||
|
||||
// serverProcessInit initializes some process configurations, which can only be done once.
|
||||
func serverProcessInit() {
|
||||
if !serverProcessInited.Cas(false, true) {
|
||||
if !serverProcessInitialized.Cas(false, true) {
|
||||
return
|
||||
}
|
||||
// This means it is a restart server, it should kill its parent before starting its listening,
|
||||
|
||||
@ -13,13 +13,19 @@ import (
|
||||
|
||||
// Cookie for HTTP COOKIE management.
|
||||
type Cookie struct {
|
||||
data map[string]*http.Cookie // Underlying cookie items.
|
||||
path string // The default cookie path.
|
||||
domain string // The default cookie domain
|
||||
maxAge time.Duration // The default cookie max age.
|
||||
server *Server // Belonged HTTP server
|
||||
request *Request // Belonged HTTP request.
|
||||
response *Response // Belonged HTTP response.
|
||||
data map[string]*cookieItem // Underlying cookie items.
|
||||
path string // The default cookie path.
|
||||
domain string // The default cookie domain
|
||||
maxAge time.Duration // The default cookie max age.
|
||||
server *Server // Belonged HTTP server
|
||||
request *Request // Belonged HTTP request.
|
||||
response *Response // Belonged HTTP response.
|
||||
}
|
||||
|
||||
// cookieItem is the item stored in Cookie.
|
||||
type cookieItem struct {
|
||||
*http.Cookie // Underlying cookie items.
|
||||
FromClient bool // Mark this cookie received from client.
|
||||
}
|
||||
|
||||
// GetCookie creates or retrieves a cookie object with given request.
|
||||
@ -40,7 +46,7 @@ func (c *Cookie) init() {
|
||||
if c.data != nil {
|
||||
return
|
||||
}
|
||||
c.data = make(map[string]*http.Cookie)
|
||||
c.data = make(map[string]*cookieItem)
|
||||
c.path = c.request.Server.GetCookiePath()
|
||||
c.domain = c.request.Server.GetCookieDomain()
|
||||
c.maxAge = c.request.Server.GetCookieMaxAge()
|
||||
@ -50,7 +56,10 @@ func (c *Cookie) init() {
|
||||
// c.domain = c.request.GetHost()
|
||||
//}
|
||||
for _, v := range c.request.Cookies() {
|
||||
c.data[v.Name] = v
|
||||
c.data[v.Name] = &cookieItem{
|
||||
Cookie: v,
|
||||
FromClient: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,23 +98,27 @@ func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration
|
||||
if len(httpOnly) > 0 {
|
||||
isHttpOnly = httpOnly[0]
|
||||
}
|
||||
c.data[key] = &http.Cookie{
|
||||
httpCookie := &http.Cookie{
|
||||
Name: key,
|
||||
Value: value,
|
||||
Path: path,
|
||||
Domain: domain,
|
||||
Expires: time.Now().Add(maxAge),
|
||||
HttpOnly: isHttpOnly,
|
||||
}
|
||||
if maxAge != 0 {
|
||||
httpCookie.Expires = time.Now().Add(maxAge)
|
||||
}
|
||||
c.data[key] = &cookieItem{
|
||||
Cookie: httpCookie,
|
||||
}
|
||||
}
|
||||
|
||||
// SetHttpCookie sets cookie with *http.Cookie.
|
||||
func (c *Cookie) SetHttpCookie(cookie *http.Cookie) {
|
||||
func (c *Cookie) SetHttpCookie(httpCookie *http.Cookie) {
|
||||
c.init()
|
||||
if cookie.Expires.IsZero() {
|
||||
cookie.Expires = time.Now().Add(c.maxAge)
|
||||
c.data[httpCookie.Name] = &cookieItem{
|
||||
Cookie: httpCookie,
|
||||
}
|
||||
c.data[cookie.Name] = cookie
|
||||
}
|
||||
|
||||
// GetSessionId retrieves and returns the session id from cookie.
|
||||
@ -151,11 +164,9 @@ func (c *Cookie) Flush() {
|
||||
return
|
||||
}
|
||||
for _, v := range c.data {
|
||||
// If cookie item is v.Expires.IsZero() means it is set in this request,
|
||||
// which should be outputted to client.
|
||||
if v.Expires.IsZero() {
|
||||
if v.FromClient {
|
||||
continue
|
||||
}
|
||||
http.SetCookie(c.response.Writer, v)
|
||||
http.SetCookie(c.response.Writer, v.Cookie)
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,8 +175,8 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han
|
||||
parsedItemList.PushBack(parsedItem)
|
||||
|
||||
// The middleware is inserted before the serving handler.
|
||||
// If there're multiple middlewares, they're inserted into the result list by their registering order.
|
||||
// The middlewares are also executed by their registering order.
|
||||
// If there're multiple middleware, they're inserted into the result list by their registering order.
|
||||
// The middleware are also executed by their registered order.
|
||||
case gHANDLER_TYPE_MIDDLEWARE:
|
||||
if lastMiddlewareElem == nil {
|
||||
lastMiddlewareElem = parsedItemList.PushFront(parsedItem)
|
||||
|
||||
@ -42,6 +42,7 @@ func Test_Client_Request_13_Dump(t *testing.T) {
|
||||
dumpedText := r.RawRequest()
|
||||
t.Assert(gstr.Contains(dumpedText, "test_for_request_body"), true)
|
||||
dumpedText2 := r.RawResponse()
|
||||
fmt.Println(dumpedText2)
|
||||
t.Assert(gstr.Contains(dumpedText2, "test_for_response_body"), true)
|
||||
|
||||
client2 := ghttp.NewClient().SetPrefix(url).ContentType("text/html")
|
||||
|
||||
@ -95,12 +95,12 @@ func Test_SetHttpCookie(t *testing.T) {
|
||||
t.Assert(client.GetContent("/set?k=key2&v=200"), "")
|
||||
|
||||
t.Assert(client.GetContent("/get?k=key1"), "100")
|
||||
t.Assert(client.GetContent("/get?k=key2"), "200")
|
||||
t.Assert(client.GetContent("/get?k=key3"), "")
|
||||
t.Assert(client.GetContent("/remove?k=key1"), "")
|
||||
t.Assert(client.GetContent("/remove?k=key3"), "")
|
||||
t.Assert(client.GetContent("/remove?k=key4"), "")
|
||||
t.Assert(client.GetContent("/get?k=key1"), "")
|
||||
t.Assert(client.GetContent("/get?k=key2"), "200")
|
||||
//t.Assert(client.GetContent("/get?k=key2"), "200")
|
||||
//t.Assert(client.GetContent("/get?k=key3"), "")
|
||||
//t.Assert(client.GetContent("/remove?k=key1"), "")
|
||||
//t.Assert(client.GetContent("/remove?k=key3"), "")
|
||||
//t.Assert(client.GetContent("/remove?k=key4"), "")
|
||||
//t.Assert(client.GetContent("/get?k=key1"), "")
|
||||
//t.Assert(client.GetContent("/get?k=key2"), "200")
|
||||
})
|
||||
}
|
||||
|
||||
@ -54,8 +54,8 @@ func Test_Params_Json_Request(t *testing.T) {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
|
||||
t.Assert(client.GetContent("/get", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john`)
|
||||
t.Assert(client.GetContent("/map", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`)
|
||||
t.Assert(client.GetContent("/get", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), ``)
|
||||
t.Assert(client.GetContent("/map", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), ``)
|
||||
t.Assert(client.PostContent("/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`)
|
||||
t.Assert(client.PostContent("/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123"}`), `密码强度不足; 两次密码不一致`)
|
||||
})
|
||||
|
||||
@ -40,7 +40,7 @@ func Test_Params_Parse(t *testing.T) {
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
client := ghttp.NewClient()
|
||||
client := g.Client()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
t.Assert(client.PostContent("/parse", `{"id":1,"name":"john","map":{"id":1,"score":100}}`), `1100`)
|
||||
})
|
||||
@ -454,8 +454,6 @@ func Test_Params_Struct(t *testing.T) {
|
||||
t.Assert(client.PostContent("/struct2", ``), ``)
|
||||
t.Assert(client.PostContent("/struct-valid", `id=1&name=john&password1=123&password2=0`), `The passwd1 value length must be between 2 and 20; 密码强度不足`)
|
||||
t.Assert(client.PostContent("/parse", `id=1&name=john&password1=123&password2=0`), `The passwd1 value length must be between 2 and 20; 密码强度不足`)
|
||||
t.Assert(client.GetContent("/parse", `id=1&name=john&password1=123&password2=456`), `密码强度不足`)
|
||||
t.Assert(client.GetContent("/parse", `id=1&name=john&password1=123Abc!@#&password2=123Abc!@#`), `1john123Abc!@#123Abc!@#`)
|
||||
t.Assert(client.PostContent("/parse", `{"id":1,"name":"john","password1":"123Abc!@#","password2":"123Abc!@#"}`), `1john123Abc!@#123Abc!@#`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -484,7 +484,7 @@ func Test_Params_Priority(t *testing.T) {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.GetContent("/query?a=1", "a=100"), "1")
|
||||
t.Assert(client.GetContent("/query?a=1", "a=100"), "100")
|
||||
t.Assert(client.PostContent("/post?a=1", "a=100"), "100")
|
||||
t.Assert(client.PostContent("/form?a=1", "a=100"), "100")
|
||||
t.Assert(client.PutContent("/form?a=1", "a=100"), "100")
|
||||
@ -555,3 +555,34 @@ func Test_Params_Modify(t *testing.T) {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Params_Parse_DefaultValueTag(t *testing.T) {
|
||||
type T struct {
|
||||
Name string `d:"john"`
|
||||
Score float32 `d:"60"`
|
||||
}
|
||||
p, _ := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/parse", func(r *ghttp.Request) {
|
||||
var t *T
|
||||
if err := r.Parse(&t); err != nil {
|
||||
r.Response.WriteExit(err)
|
||||
}
|
||||
r.Response.WriteExit(t)
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
prefix := fmt.Sprintf("http://127.0.0.1:%d", p)
|
||||
client := g.Client()
|
||||
client.SetPrefix(prefix)
|
||||
|
||||
t.Assert(client.PostContent("/parse"), `{"Name":"john","Score":60}`)
|
||||
t.Assert(client.PostContent("/parse", `{"name":"smith"}`), `{"Name":"smith","Score":60}`)
|
||||
t.Assert(client.PostContent("/parse", `{"name":"smith", "score":100}`), `{"Name":"smith","Score":100}`)
|
||||
})
|
||||
}
|
||||
|
||||
@ -55,9 +55,9 @@ func Test_Params_Xml_Request(t *testing.T) {
|
||||
|
||||
content1 := `<doc><id>1</id><name>john</name><password1>123Abc!@#</password1><password2>123Abc!@#</password2></doc>`
|
||||
content2 := `<doc><id>1</id><name>john</name><password1>123Abc!@#</password1><password2>123</password2></doc>`
|
||||
t.Assert(client.GetContent("/get", content1), `1john`)
|
||||
t.Assert(client.GetContent("/get", content1), ``)
|
||||
t.Assert(client.PostContent("/get", content1), `1john`)
|
||||
t.Assert(client.GetContent("/map", content1), `1john123Abc!@#123Abc!@#`)
|
||||
t.Assert(client.GetContent("/map", content1), ``)
|
||||
t.Assert(client.PostContent("/map", content1), `1john123Abc!@#123Abc!@#`)
|
||||
t.Assert(client.PostContent("/parse", content1), `1john123Abc!@#123Abc!@#`)
|
||||
t.Assert(client.PostContent("/parse", content2), `密码强度不足; 两次密码不一致`)
|
||||
|
||||
@ -299,6 +299,7 @@ func (c *Config) GetStruct(pattern string, pointer interface{}, mapping ...map[s
|
||||
}
|
||||
|
||||
// GetStructDeep does GetStruct recursively.
|
||||
// Deprecated, use GetStruct instead.
|
||||
func (c *Config) GetStructDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
|
||||
if j := c.getJson(); j != nil {
|
||||
return j.GetStructDeep(pattern, pointer, mapping...)
|
||||
@ -315,6 +316,7 @@ func (c *Config) GetStructs(pattern string, pointer interface{}, mapping ...map[
|
||||
}
|
||||
|
||||
// GetStructsDeep converts any slice to given struct slice recursively.
|
||||
// Deprecated, use GetStructs instead.
|
||||
func (c *Config) GetStructsDeep(pattern string, pointer interface{}, mapping ...map[string]string) error {
|
||||
if j := c.getJson(); j != nil {
|
||||
return j.GetStructsDeep(pattern, pointer, mapping...)
|
||||
|
||||
@ -21,6 +21,9 @@ const (
|
||||
var (
|
||||
// Default expire time for file content caching.
|
||||
cacheExpire = cmdenv.Get("gf.gfile.cache", gDEFAULT_CACHE_EXPIRE).Duration()
|
||||
|
||||
// internalCache is the memory cache for internal usage.
|
||||
internalCache = gcache.New()
|
||||
)
|
||||
|
||||
// GetContents returns string content of given file by <path> from cache.
|
||||
@ -39,13 +42,13 @@ func GetBytesWithCache(path string, duration ...time.Duration) []byte {
|
||||
if len(duration) > 0 {
|
||||
expire = duration[0]
|
||||
}
|
||||
r, _ := gcache.GetOrSetFuncLock(key, func() (interface{}, error) {
|
||||
r, _ := internalCache.GetOrSetFuncLock(key, func() (interface{}, error) {
|
||||
b := GetBytes(path)
|
||||
if b != nil {
|
||||
// Adding this <path> to gfsnotify,
|
||||
// it will clear its cache if there's any changes of the file.
|
||||
_, _ = gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
gcache.Remove(key)
|
||||
internalCache.Remove(key)
|
||||
gfsnotify.Exit()
|
||||
})
|
||||
}
|
||||
|
||||
@ -39,11 +39,11 @@ type Logger struct {
|
||||
}
|
||||
|
||||
const (
|
||||
gDEFAULT_FILE_FORMAT = `{Y-m-d}.log`
|
||||
gDEFAULT_FILE_FLAGS = os.O_CREATE | os.O_WRONLY | os.O_APPEND
|
||||
gDEFAULT_FILE_PERM = os.FileMode(0666)
|
||||
gDEFAULT_FILE_EXPIRE = time.Minute
|
||||
gPATH_FILTER_KEY = "/os/glog/glog"
|
||||
defaultFileFormat = `{Y-m-d}.log`
|
||||
defaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND
|
||||
defaultFilePerm = os.FileMode(0666)
|
||||
defaultFileExpire = time.Minute
|
||||
pathFilterKey = "/os/glog/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -91,9 +91,6 @@ func (l *Logger) getFilePath(now time.Time) string {
|
||||
return gtime.New(now).Format(strings.Trim(s, "{}"))
|
||||
})
|
||||
file = gfile.Join(l.config.Path, file)
|
||||
if gfile.ExtName(file) != "log" {
|
||||
file += ".log"
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
@ -143,7 +140,7 @@ func (l *Logger) print(std io.Writer, lead string, values ...interface{}) {
|
||||
// Caller path and Fn name.
|
||||
if l.config.Flags&(F_FILE_LONG|F_FILE_SHORT|F_CALLER_FN) > 0 {
|
||||
callerPath := ""
|
||||
callerFnName, path, line := gdebug.CallerWithFilter(gPATH_FILTER_KEY, l.config.StSkip)
|
||||
callerFnName, path, line := gdebug.CallerWithFilter(pathFilterKey, l.config.StSkip)
|
||||
if l.config.Flags&F_CALLER_FN > 0 {
|
||||
buffer.WriteString(fmt.Sprintf(`[%s] `, callerFnName))
|
||||
}
|
||||
@ -245,6 +242,9 @@ func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) {
|
||||
gmlock.Lock(memoryLockKey)
|
||||
defer gmlock.Unlock(memoryLockKey)
|
||||
file := l.getFilePointer(logFilePath)
|
||||
if file == nil {
|
||||
return
|
||||
}
|
||||
// Rotation file size checks.
|
||||
if l.config.RotateSize > 0 {
|
||||
stat, err := file.Stat()
|
||||
@ -272,9 +272,9 @@ func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) {
|
||||
func (l *Logger) getFilePointer(path string) *gfpool.File {
|
||||
file, err := gfpool.Open(
|
||||
path,
|
||||
gDEFAULT_FILE_FLAGS,
|
||||
gDEFAULT_FILE_PERM,
|
||||
gDEFAULT_FILE_EXPIRE,
|
||||
defaultFileFlags,
|
||||
defaultFilePerm,
|
||||
defaultFileExpire,
|
||||
)
|
||||
if err != nil {
|
||||
// panic(err)
|
||||
@ -321,7 +321,7 @@ func (l *Logger) GetStack(skip ...int) string {
|
||||
if len(skip) > 0 {
|
||||
stackSkip += skip[0]
|
||||
}
|
||||
filters := []string{gPATH_FILTER_KEY}
|
||||
filters := []string{pathFilterKey}
|
||||
if l.config.StFilter != "" {
|
||||
filters = append(filters, l.config.StFilter)
|
||||
}
|
||||
|
||||
@ -44,7 +44,7 @@ type Config struct {
|
||||
// DefaultConfig returns the default configuration for logger.
|
||||
func DefaultConfig() Config {
|
||||
c := Config{
|
||||
File: gDEFAULT_FILE_FORMAT,
|
||||
File: defaultFileFormat,
|
||||
Flags: F_TIME_STD,
|
||||
Level: LEVEL_ALL,
|
||||
StStatus: 1,
|
||||
|
||||
2
os/gres/testdata/data/data.go
vendored
2
os/gres/testdata/data/data.go
vendored
File diff suppressed because one or more lines are too long
2
os/gres/testdata/testdata.go
vendored
2
os/gres/testdata/testdata.go
vendored
File diff suppressed because one or more lines are too long
@ -355,6 +355,7 @@ func (s *Session) GetStruct(key string, pointer interface{}, mapping ...map[stri
|
||||
return gconv.Struct(s.Get(key), pointer, mapping...)
|
||||
}
|
||||
|
||||
// Deprecated, use GetStruct instead.
|
||||
func (s *Session) GetStructDeep(key string, pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructDeep(s.Get(key), pointer, mapping...)
|
||||
}
|
||||
@ -363,6 +364,7 @@ func (s *Session) GetStructs(key string, pointer interface{}, mapping ...map[str
|
||||
return gconv.Structs(s.Get(key), pointer, mapping...)
|
||||
}
|
||||
|
||||
// Deprecated, use GetStructs instead.
|
||||
func (s *Session) GetStructsDeep(key string, pointer interface{}, mapping ...map[string]string) error {
|
||||
return gconv.StructsDeep(s.Get(key), pointer, mapping...)
|
||||
}
|
||||
|
||||
@ -233,7 +233,7 @@ func (t *Timer) binSearchIndex(n int64) (index int, result int) {
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for min <= max {
|
||||
mid = int((min + max) / 2)
|
||||
mid = min + int((max-min)/2)
|
||||
switch {
|
||||
case t.wheels[mid].intervalMs == n:
|
||||
cmp = 0
|
||||
|
||||
@ -107,34 +107,36 @@ func New(path ...string) *View {
|
||||
}
|
||||
// default build-in functions.
|
||||
view.BindFuncMap(FuncMap{
|
||||
"eq": view.funcEq,
|
||||
"ne": view.funcNe,
|
||||
"lt": view.funcLt,
|
||||
"le": view.funcLe,
|
||||
"gt": view.funcGt,
|
||||
"ge": view.funcGe,
|
||||
"text": view.funcText,
|
||||
"html": view.funcHtmlEncode,
|
||||
"htmlencode": view.funcHtmlEncode,
|
||||
"htmldecode": view.funcHtmlDecode,
|
||||
"encode": view.funcHtmlEncode,
|
||||
"decode": view.funcHtmlDecode,
|
||||
"url": view.funcUrlEncode,
|
||||
"urlencode": view.funcUrlEncode,
|
||||
"urldecode": view.funcUrlDecode,
|
||||
"date": view.funcDate,
|
||||
"substr": view.funcSubStr,
|
||||
"strlimit": view.funcStrLimit,
|
||||
"concat": view.funcConcat,
|
||||
"replace": view.funcReplace,
|
||||
"compare": view.funcCompare,
|
||||
"hidestr": view.funcHideStr,
|
||||
"highlight": view.funcHighlight,
|
||||
"toupper": view.funcToUpper,
|
||||
"tolower": view.funcToLower,
|
||||
"nl2br": view.funcNl2Br,
|
||||
"include": view.funcInclude,
|
||||
"dump": view.funcDump,
|
||||
"eq": view.buildInFuncEq,
|
||||
"ne": view.buildInFuncNe,
|
||||
"lt": view.buildInFuncLt,
|
||||
"le": view.buildInFuncLe,
|
||||
"gt": view.buildInFuncGt,
|
||||
"ge": view.buildInFuncGe,
|
||||
"text": view.buildInFuncText,
|
||||
"html": view.buildInFuncHtmlEncode,
|
||||
"htmlencode": view.buildInFuncHtmlEncode,
|
||||
"htmldecode": view.buildInFuncHtmlDecode,
|
||||
"encode": view.buildInFuncHtmlEncode,
|
||||
"decode": view.buildInFuncHtmlDecode,
|
||||
"url": view.buildInFuncUrlEncode,
|
||||
"urlencode": view.buildInFuncUrlEncode,
|
||||
"urldecode": view.buildInFuncUrlDecode,
|
||||
"date": view.buildInFuncDate,
|
||||
"substr": view.buildInFuncSubStr,
|
||||
"strlimit": view.buildInFuncStrLimit,
|
||||
"concat": view.buildInFuncConcat,
|
||||
"replace": view.buildInFuncReplace,
|
||||
"compare": view.buildInFuncCompare,
|
||||
"hidestr": view.buildInFuncHideStr,
|
||||
"highlight": view.buildInFuncHighlight,
|
||||
"toupper": view.buildInFuncToUpper,
|
||||
"tolower": view.buildInFuncToLower,
|
||||
"nl2br": view.buildInFuncNl2Br,
|
||||
"include": view.buildInFuncInclude,
|
||||
"dump": view.buildInFuncDump,
|
||||
"map": view.buildInFuncMap,
|
||||
"maps": view.buildInFuncMaps,
|
||||
})
|
||||
|
||||
return view
|
||||
|
||||
@ -20,8 +20,8 @@ import (
|
||||
htmltpl "html/template"
|
||||
)
|
||||
|
||||
// funcDump implements build-in template function: dump
|
||||
func (view *View) funcDump(values ...interface{}) (result string) {
|
||||
// buildInFuncDump implements build-in template function: dump
|
||||
func (view *View) buildInFuncDump(values ...interface{}) (result string) {
|
||||
result += "<!--\n"
|
||||
for _, v := range values {
|
||||
result += gutil.Export(v) + "\n"
|
||||
@ -30,8 +30,24 @@ func (view *View) funcDump(values ...interface{}) (result string) {
|
||||
return result
|
||||
}
|
||||
|
||||
// funcEq implements build-in template function: eq
|
||||
func (view *View) funcEq(value interface{}, others ...interface{}) bool {
|
||||
// buildInFuncMap implements build-in template function: map
|
||||
func (view *View) buildInFuncMap(value ...interface{}) map[string]interface{} {
|
||||
if len(value) > 0 {
|
||||
return gconv.Map(value[0])
|
||||
}
|
||||
return map[string]interface{}{}
|
||||
}
|
||||
|
||||
// buildInFuncMaps implements build-in template function: maps
|
||||
func (view *View) buildInFuncMaps(value ...interface{}) []map[string]interface{} {
|
||||
if len(value) > 0 {
|
||||
return gconv.Maps(value[0])
|
||||
}
|
||||
return []map[string]interface{}{}
|
||||
}
|
||||
|
||||
// buildInFuncEq implements build-in template function: eq
|
||||
func (view *View) buildInFuncEq(value interface{}, others ...interface{}) bool {
|
||||
s := gconv.String(value)
|
||||
for _, v := range others {
|
||||
if strings.Compare(s, gconv.String(v)) != 0 {
|
||||
@ -41,13 +57,13 @@ func (view *View) funcEq(value interface{}, others ...interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// funcNe implements build-in template function: ne
|
||||
func (view *View) funcNe(value, other interface{}) bool {
|
||||
// buildInFuncNe implements build-in template function: ne
|
||||
func (view *View) buildInFuncNe(value, other interface{}) bool {
|
||||
return strings.Compare(gconv.String(value), gconv.String(other)) != 0
|
||||
}
|
||||
|
||||
// funcLt implements build-in template function: lt
|
||||
func (view *View) funcLt(value, other interface{}) bool {
|
||||
// buildInFuncLt implements build-in template function: lt
|
||||
func (view *View) buildInFuncLt(value, other interface{}) bool {
|
||||
s1 := gconv.String(value)
|
||||
s2 := gconv.String(other)
|
||||
if gstr.IsNumeric(s1) && gstr.IsNumeric(s2) {
|
||||
@ -56,8 +72,8 @@ func (view *View) funcLt(value, other interface{}) bool {
|
||||
return strings.Compare(s1, s2) < 0
|
||||
}
|
||||
|
||||
// funcLe implements build-in template function: le
|
||||
func (view *View) funcLe(value, other interface{}) bool {
|
||||
// buildInFuncLe implements build-in template function: le
|
||||
func (view *View) buildInFuncLe(value, other interface{}) bool {
|
||||
s1 := gconv.String(value)
|
||||
s2 := gconv.String(other)
|
||||
if gstr.IsNumeric(s1) && gstr.IsNumeric(s2) {
|
||||
@ -66,8 +82,8 @@ func (view *View) funcLe(value, other interface{}) bool {
|
||||
return strings.Compare(s1, s2) <= 0
|
||||
}
|
||||
|
||||
// funcGt implements build-in template function: gt
|
||||
func (view *View) funcGt(value, other interface{}) bool {
|
||||
// buildInFuncGt implements build-in template function: gt
|
||||
func (view *View) buildInFuncGt(value, other interface{}) bool {
|
||||
s1 := gconv.String(value)
|
||||
s2 := gconv.String(other)
|
||||
if gstr.IsNumeric(s1) && gstr.IsNumeric(s2) {
|
||||
@ -76,8 +92,8 @@ func (view *View) funcGt(value, other interface{}) bool {
|
||||
return strings.Compare(s1, s2) > 0
|
||||
}
|
||||
|
||||
// funcGe implements build-in template function: ge
|
||||
func (view *View) funcGe(value, other interface{}) bool {
|
||||
// buildInFuncGe implements build-in template function: ge
|
||||
func (view *View) buildInFuncGe(value, other interface{}) bool {
|
||||
s1 := gconv.String(value)
|
||||
s2 := gconv.String(other)
|
||||
if gstr.IsNumeric(s1) && gstr.IsNumeric(s2) {
|
||||
@ -86,9 +102,9 @@ func (view *View) funcGe(value, other interface{}) bool {
|
||||
return strings.Compare(s1, s2) >= 0
|
||||
}
|
||||
|
||||
// funcInclude implements build-in template function: include
|
||||
// buildInFuncInclude implements build-in template function: include
|
||||
// Note that configuration AutoEncode does not affect the output of this function.
|
||||
func (view *View) funcInclude(file interface{}, data ...map[string]interface{}) htmltpl.HTML {
|
||||
func (view *View) buildInFuncInclude(file interface{}, data ...map[string]interface{}) htmltpl.HTML {
|
||||
var m map[string]interface{} = nil
|
||||
if len(data) > 0 {
|
||||
m = data[0]
|
||||
@ -105,28 +121,28 @@ func (view *View) funcInclude(file interface{}, data ...map[string]interface{})
|
||||
return htmltpl.HTML(content)
|
||||
}
|
||||
|
||||
// funcText implements build-in template function: text
|
||||
func (view *View) funcText(html interface{}) string {
|
||||
// buildInFuncText implements build-in template function: text
|
||||
func (view *View) buildInFuncText(html interface{}) string {
|
||||
return ghtml.StripTags(gconv.String(html))
|
||||
}
|
||||
|
||||
// funcHtmlEncode implements build-in template function: html
|
||||
func (view *View) funcHtmlEncode(html interface{}) string {
|
||||
// buildInFuncHtmlEncode implements build-in template function: html
|
||||
func (view *View) buildInFuncHtmlEncode(html interface{}) string {
|
||||
return ghtml.Entities(gconv.String(html))
|
||||
}
|
||||
|
||||
// funcHtmlDecode implements build-in template function: htmldecode
|
||||
func (view *View) funcHtmlDecode(html interface{}) string {
|
||||
// buildInFuncHtmlDecode implements build-in template function: htmldecode
|
||||
func (view *View) buildInFuncHtmlDecode(html interface{}) string {
|
||||
return ghtml.EntitiesDecode(gconv.String(html))
|
||||
}
|
||||
|
||||
// funcUrlEncode implements build-in template function: url
|
||||
func (view *View) funcUrlEncode(url interface{}) string {
|
||||
// buildInFuncUrlEncode implements build-in template function: url
|
||||
func (view *View) buildInFuncUrlEncode(url interface{}) string {
|
||||
return gurl.Encode(gconv.String(url))
|
||||
}
|
||||
|
||||
// funcUrlDecode implements build-in template function: urldecode
|
||||
func (view *View) funcUrlDecode(url interface{}) string {
|
||||
// buildInFuncUrlDecode implements build-in template function: urldecode
|
||||
func (view *View) buildInFuncUrlDecode(url interface{}) string {
|
||||
if content, err := gurl.Decode(gconv.String(url)); err == nil {
|
||||
return content
|
||||
} else {
|
||||
@ -134,8 +150,8 @@ func (view *View) funcUrlDecode(url interface{}) string {
|
||||
}
|
||||
}
|
||||
|
||||
// funcDate implements build-in template function: date
|
||||
func (view *View) funcDate(format interface{}, timestamp ...interface{}) string {
|
||||
// buildInFuncDate implements build-in template function: date
|
||||
func (view *View) buildInFuncDate(format interface{}, timestamp ...interface{}) string {
|
||||
t := int64(0)
|
||||
if len(timestamp) > 0 {
|
||||
t = gconv.Int64(timestamp[0])
|
||||
@ -146,23 +162,23 @@ func (view *View) funcDate(format interface{}, timestamp ...interface{}) string
|
||||
return gtime.NewFromTimeStamp(t).Format(gconv.String(format))
|
||||
}
|
||||
|
||||
// funcCompare implements build-in template function: compare
|
||||
func (view *View) funcCompare(value1, value2 interface{}) int {
|
||||
// buildInFuncCompare implements build-in template function: compare
|
||||
func (view *View) buildInFuncCompare(value1, value2 interface{}) int {
|
||||
return strings.Compare(gconv.String(value1), gconv.String(value2))
|
||||
}
|
||||
|
||||
// funcSubStr implements build-in template function: substr
|
||||
func (view *View) funcSubStr(start, end, str interface{}) string {
|
||||
// buildInFuncSubStr implements build-in template function: substr
|
||||
func (view *View) buildInFuncSubStr(start, end, str interface{}) string {
|
||||
return gstr.SubStrRune(gconv.String(str), gconv.Int(start), gconv.Int(end))
|
||||
}
|
||||
|
||||
// funcStrLimit implements build-in template function: strlimit
|
||||
func (view *View) funcStrLimit(length, suffix, str interface{}) string {
|
||||
// buildInFuncStrLimit implements build-in template function: strlimit
|
||||
func (view *View) buildInFuncStrLimit(length, suffix, str interface{}) string {
|
||||
return gstr.StrLimitRune(gconv.String(str), gconv.Int(length), gconv.String(suffix))
|
||||
}
|
||||
|
||||
// funcConcat implements build-in template function: concat
|
||||
func (view *View) funcConcat(str ...interface{}) string {
|
||||
// buildInFuncConcat implements build-in template function: concat
|
||||
func (view *View) buildInFuncConcat(str ...interface{}) string {
|
||||
var s string
|
||||
for _, v := range str {
|
||||
s += gconv.String(v)
|
||||
@ -170,32 +186,32 @@ func (view *View) funcConcat(str ...interface{}) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// funcReplace implements build-in template function: replace
|
||||
func (view *View) funcReplace(search, replace, str interface{}) string {
|
||||
// buildInFuncReplace implements build-in template function: replace
|
||||
func (view *View) buildInFuncReplace(search, replace, str interface{}) string {
|
||||
return gstr.Replace(gconv.String(str), gconv.String(search), gconv.String(replace), -1)
|
||||
}
|
||||
|
||||
// funcHighlight implements build-in template function: highlight
|
||||
func (view *View) funcHighlight(key, color, str interface{}) string {
|
||||
// buildInFuncHighlight implements build-in template function: highlight
|
||||
func (view *View) buildInFuncHighlight(key, color, str interface{}) string {
|
||||
return gstr.Replace(gconv.String(str), gconv.String(key), fmt.Sprintf(`<span style="color:%v;">%v</span>`, color, key))
|
||||
}
|
||||
|
||||
// funcHideStr implements build-in template function: hidestr
|
||||
func (view *View) funcHideStr(percent, hide, str interface{}) string {
|
||||
// buildInFuncHideStr implements build-in template function: hidestr
|
||||
func (view *View) buildInFuncHideStr(percent, hide, str interface{}) string {
|
||||
return gstr.HideStr(gconv.String(str), gconv.Int(percent), gconv.String(hide))
|
||||
}
|
||||
|
||||
// funcToUpper implements build-in template function: toupper
|
||||
func (view *View) funcToUpper(str interface{}) string {
|
||||
// buildInFuncToUpper implements build-in template function: toupper
|
||||
func (view *View) buildInFuncToUpper(str interface{}) string {
|
||||
return gstr.ToUpper(gconv.String(str))
|
||||
}
|
||||
|
||||
// funcToLower implements build-in template function: toupper
|
||||
func (view *View) funcToLower(str interface{}) string {
|
||||
// buildInFuncToLower implements build-in template function: toupper
|
||||
func (view *View) buildInFuncToLower(str interface{}) string {
|
||||
return gstr.ToLower(gconv.String(str))
|
||||
}
|
||||
|
||||
// funcNl2Br implements build-in template function: nl2br
|
||||
func (view *View) funcNl2Br(str interface{}) string {
|
||||
// buildInFuncNl2Br implements build-in template function: nl2br
|
||||
func (view *View) buildInFuncNl2Br(str interface{}) string {
|
||||
return gstr.Nl2Br(gconv.String(str))
|
||||
}
|
||||
|
||||
@ -344,3 +344,59 @@ func Test_XSS(t *testing.T) {
|
||||
t.Assert(r, ghtml.Entities(s))
|
||||
})
|
||||
}
|
||||
|
||||
type TypeForBuildInFuncMap struct {
|
||||
Name string
|
||||
Score float32
|
||||
}
|
||||
|
||||
func (t *TypeForBuildInFuncMap) Test() (*TypeForBuildInFuncMap, error) {
|
||||
return &TypeForBuildInFuncMap{"john", 99.9}, nil
|
||||
}
|
||||
|
||||
func Test_BuildInFuncMap(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New()
|
||||
v.Assign("v", new(TypeForBuildInFuncMap))
|
||||
r, err := v.ParseContent("{{range $k, $v := map .v.Test}} {{$k}}:{{$v}} {{end}}")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(gstr.Contains(r, "Name:john"), true)
|
||||
t.Assert(gstr.Contains(r, "Score:99.9"), true)
|
||||
})
|
||||
}
|
||||
|
||||
type TypeForBuildInFuncMaps struct {
|
||||
Name string
|
||||
Score float32
|
||||
}
|
||||
|
||||
func (t *TypeForBuildInFuncMaps) Test() ([]*TypeForBuildInFuncMaps, error) {
|
||||
return []*TypeForBuildInFuncMaps{
|
||||
{"john", 99.9},
|
||||
{"smith", 100},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func Test_BuildInFuncMaps(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New()
|
||||
v.Assign("v", new(TypeForBuildInFuncMaps))
|
||||
r, err := v.ParseContent("{{range $k, $v := maps .v.Test}} {{$k}}:{{$v.Name}} {{$v.Score}} {{end}}")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(r, ` 0:john 99.9 1:smith 100 `)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_BuildInFuncDump(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v := gview.New()
|
||||
v.Assign("v", g.Map{
|
||||
"name": "john",
|
||||
"score": 100,
|
||||
})
|
||||
r, err := v.ParseContent("{{dump .}}")
|
||||
t.Assert(err, nil)
|
||||
t.Assert(gstr.Contains(r, `"name": "john"`), true)
|
||||
t.Assert(gstr.Contains(r, `"score": 100`), true)
|
||||
})
|
||||
}
|
||||
|
||||
@ -271,7 +271,7 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
|
||||
}
|
||||
}
|
||||
}
|
||||
if recursive {
|
||||
if recursive || rtField.Anonymous {
|
||||
// Do map converting recursively.
|
||||
var (
|
||||
rvAttrField = rvField
|
||||
@ -290,7 +290,7 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
|
||||
if hasNoTag && rtField.Anonymous {
|
||||
// It means this attribute field has no tag.
|
||||
// Overwrite the attribute with sub-struct attribute fields.
|
||||
anonymousValue := doMapConvertForMapOrStructValue(false, rvAttrInterface, recursive, tags...)
|
||||
anonymousValue := doMapConvertForMapOrStructValue(false, rvAttrInterface, true, tags...)
|
||||
if m, ok := anonymousValue.(map[string]interface{}); ok {
|
||||
for k, v := range m {
|
||||
dataMap[k] = v
|
||||
@ -298,9 +298,11 @@ func doMapConvertForMapOrStructValue(isRoot bool, value interface{}, recursive b
|
||||
} else {
|
||||
dataMap[name] = rvAttrInterface
|
||||
}
|
||||
} else {
|
||||
} else if !hasNoTag && rtField.Anonymous {
|
||||
// It means this attribute field has desired tag.
|
||||
dataMap[name] = doMapConvertForMapOrStructValue(false, rvAttrInterface, recursive, tags...)
|
||||
dataMap[name] = doMapConvertForMapOrStructValue(false, rvAttrInterface, true, tags...)
|
||||
} else {
|
||||
dataMap[name] = doMapConvertForMapOrStructValue(false, rvAttrInterface, false, tags...)
|
||||
}
|
||||
|
||||
// The struct attribute is type of slice.
|
||||
@ -390,14 +392,14 @@ func MapStrStrDeep(value interface{}, tags ...string) map[string]string {
|
||||
// using reflect.
|
||||
// See doMapToMap.
|
||||
func MapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) error {
|
||||
return doMapToMap(params, pointer, false, mapping...)
|
||||
return doMapToMap(params, pointer, mapping...)
|
||||
}
|
||||
|
||||
// MapToMapDeep converts any map type variable <params> to another map type variable <pointer>
|
||||
// using reflect recursively.
|
||||
// See doMapToMap.
|
||||
// Deprecated, use MapToMap instead.
|
||||
func MapToMapDeep(params interface{}, pointer interface{}, mapping ...map[string]string) error {
|
||||
return doMapToMap(params, pointer, true, mapping...)
|
||||
return doMapToMap(params, pointer, mapping...)
|
||||
}
|
||||
|
||||
// doMapToMap converts any map type variable <params> to another map type variable <pointer>.
|
||||
@ -410,7 +412,7 @@ func MapToMapDeep(params interface{}, pointer interface{}, mapping ...map[string
|
||||
//
|
||||
// The optional parameter <mapping> is used for struct attribute to map key mapping, which makes
|
||||
// sense only if the items of original map <params> is type struct.
|
||||
func doMapToMap(params interface{}, pointer interface{}, deep bool, mapping ...map[string]string) (err error) {
|
||||
func doMapToMap(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
var (
|
||||
paramsRv = reflect.ValueOf(params)
|
||||
paramsKind = paramsRv.Kind()
|
||||
@ -461,14 +463,8 @@ func doMapToMap(params interface{}, pointer interface{}, deep bool, mapping ...m
|
||||
e := reflect.New(pointerValueType).Elem()
|
||||
switch pointerValueKind {
|
||||
case reflect.Map, reflect.Struct:
|
||||
if deep {
|
||||
if err = StructDeep(paramsRv.MapIndex(key).Interface(), e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Struct(paramsRv.MapIndex(key).Interface(), e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = Struct(paramsRv.MapIndex(key).Interface(), e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
e.Set(
|
||||
@ -497,14 +493,14 @@ func doMapToMap(params interface{}, pointer interface{}, deep bool, mapping ...m
|
||||
// MapToMaps converts any map type variable <params> to another map type variable <pointer>.
|
||||
// See doMapToMaps.
|
||||
func MapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) error {
|
||||
return doMapToMaps(params, pointer, false, mapping...)
|
||||
return doMapToMaps(params, pointer, mapping...)
|
||||
}
|
||||
|
||||
// MapToMapsDeep converts any map type variable <params> to another map type variable
|
||||
// <pointer> recursively.
|
||||
// See doMapToMaps.
|
||||
// Deprecated, use MapToMaps instead.
|
||||
func MapToMapsDeep(params interface{}, pointer interface{}, mapping ...map[string]string) error {
|
||||
return doMapToMaps(params, pointer, true, mapping...)
|
||||
return doMapToMaps(params, pointer, mapping...)
|
||||
}
|
||||
|
||||
// doMapToMaps converts any map type variable <params> to another map type variable <pointer>.
|
||||
@ -519,7 +515,7 @@ func MapToMapsDeep(params interface{}, pointer interface{}, mapping ...map[strin
|
||||
// sense only if the items of original map is type struct.
|
||||
//
|
||||
// TODO it's supposed supporting target type <pointer> like: map[int][]map, map[string][]map.
|
||||
func doMapToMaps(params interface{}, pointer interface{}, deep bool, mapping ...map[string]string) (err error) {
|
||||
func doMapToMaps(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
var (
|
||||
paramsRv = reflect.ValueOf(params)
|
||||
paramsKind = paramsRv.Kind()
|
||||
@ -560,14 +556,8 @@ func doMapToMaps(params interface{}, pointer interface{}, deep bool, mapping ...
|
||||
)
|
||||
for _, key := range paramsKeys {
|
||||
e := reflect.New(pointerValueType).Elem()
|
||||
if deep {
|
||||
if err = StructsDeep(paramsRv.MapIndex(key).Interface(), e.Addr(), mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = Structs(paramsRv.MapIndex(key).Interface(), e.Addr(), mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = Structs(paramsRv.MapIndex(key).Interface(), e.Addr(), mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
dataMap.SetMapIndex(
|
||||
reflect.ValueOf(
|
||||
|
||||
@ -33,6 +33,7 @@ func Scan(params interface{}, pointer interface{}, mapping ...map[string]string)
|
||||
// parameter <pointer> to implement the converting..
|
||||
// It calls function StructDeep if <pointer> is type of *struct/**struct to do the converting.
|
||||
// It calls function StructsDeep if <pointer> is type of *[]struct/*[]*struct to do the converting.
|
||||
// Deprecated, use Scan instead.
|
||||
func ScanDeep(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
t := reflect.TypeOf(pointer)
|
||||
k := t.Kind()
|
||||
|
||||
@ -26,6 +26,7 @@ func SliceStruct(params interface{}, pointer interface{}, mapping ...map[string]
|
||||
}
|
||||
|
||||
// SliceStructDeep is alias of StructsDeep.
|
||||
// Deprecated, use SliceStruct instead.
|
||||
func SliceStructDeep(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
return StructsDeep(params, pointer, mapping...)
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
package gconv
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
@ -113,16 +112,16 @@ func Interfaces(i interface{}) []interface{} {
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
array[i] = rv.Index(i).Interface()
|
||||
}
|
||||
case reflect.Struct:
|
||||
rt := rv.Type()
|
||||
array = make([]interface{}, 0)
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
// Only public attributes.
|
||||
if !utils.IsLetterUpper(rt.Field(i).Name[0]) {
|
||||
continue
|
||||
}
|
||||
array = append(array, rv.Field(i).Interface())
|
||||
}
|
||||
//case reflect.Struct:
|
||||
// rt := rv.Type()
|
||||
// array = make([]interface{}, 0)
|
||||
// for i := 0; i < rv.NumField(); i++ {
|
||||
// // Only public attributes.
|
||||
// if !utils.IsLetterUpper(rt.Field(i).Name[0]) {
|
||||
// continue
|
||||
// }
|
||||
// array = append(array, rv.Field(i).Interface())
|
||||
// }
|
||||
default:
|
||||
return []interface{}{i}
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
|
||||
package gconv
|
||||
|
||||
import "reflect"
|
||||
|
||||
// SliceFloat is alias of Floats.
|
||||
func SliceFloat(i interface{}) []float64 {
|
||||
return Floats(i)
|
||||
@ -111,7 +113,18 @@ func Float32s(i interface{}) []float32 {
|
||||
if v, ok := i.(apiInterfaces); ok {
|
||||
return Float32s(v.Interfaces())
|
||||
}
|
||||
return []float32{Float32(i)}
|
||||
// Use reflect feature at last.
|
||||
rv := reflect.ValueOf(i)
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
length := rv.Len()
|
||||
array = make([]float32, length)
|
||||
for n := 0; n < length; n++ {
|
||||
array[n] = Float32(rv.Index(n).Interface())
|
||||
}
|
||||
default:
|
||||
return []float32{Float32(i)}
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
@ -201,7 +214,18 @@ func Float64s(i interface{}) []float64 {
|
||||
if v, ok := i.(apiInterfaces); ok {
|
||||
return Floats(v.Interfaces())
|
||||
}
|
||||
return []float64{Float64(i)}
|
||||
// Use reflect feature at last.
|
||||
rv := reflect.ValueOf(i)
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
length := rv.Len()
|
||||
array = make([]float64, length)
|
||||
for n := 0; n < length; n++ {
|
||||
array[n] = Float64(rv.Index(n).Interface())
|
||||
}
|
||||
default:
|
||||
return []float64{Float64(i)}
|
||||
}
|
||||
}
|
||||
return array
|
||||
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
|
||||
package gconv
|
||||
|
||||
import "reflect"
|
||||
|
||||
// SliceInt is alias of Ints.
|
||||
func SliceInt(i interface{}) []int {
|
||||
return Ints(i)
|
||||
@ -116,7 +118,18 @@ func Ints(i interface{}) []int {
|
||||
if v, ok := i.(apiInterfaces); ok {
|
||||
return Ints(v.Interfaces())
|
||||
}
|
||||
return []int{Int(i)}
|
||||
// Use reflect feature at last.
|
||||
rv := reflect.ValueOf(i)
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
length := rv.Len()
|
||||
array = make([]int, length)
|
||||
for n := 0; n < length; n++ {
|
||||
array[n] = Int(rv.Index(n).Interface())
|
||||
}
|
||||
default:
|
||||
return []int{Int(i)}
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
|
||||
package gconv
|
||||
|
||||
import "reflect"
|
||||
|
||||
// SliceStr is alias of Strings.
|
||||
func SliceStr(i interface{}) []string {
|
||||
return Strings(i)
|
||||
@ -102,7 +104,18 @@ func Strings(i interface{}) []string {
|
||||
if v, ok := i.(apiInterfaces); ok {
|
||||
return Strings(v.Interfaces())
|
||||
}
|
||||
return []string{String(i)}
|
||||
// Use reflect feature at last.
|
||||
rv := reflect.ValueOf(i)
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
length := rv.Len()
|
||||
array = make([]string, length)
|
||||
for n := 0; n < length; n++ {
|
||||
array[n] = String(rv.Index(n).Interface())
|
||||
}
|
||||
default:
|
||||
return []string{String(i)}
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@
|
||||
|
||||
package gconv
|
||||
|
||||
import "reflect"
|
||||
|
||||
// SliceUint is alias of Uints.
|
||||
func SliceUint(i interface{}) []uint {
|
||||
return Uints(i)
|
||||
@ -112,7 +114,18 @@ func Uints(i interface{}) []uint {
|
||||
if v, ok := i.(apiInterfaces); ok {
|
||||
return Uints(v.Interfaces())
|
||||
}
|
||||
return []uint{Uint(i)}
|
||||
// Use reflect feature at last.
|
||||
rv := reflect.ValueOf(i)
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
length := rv.Len()
|
||||
array = make([]uint, length)
|
||||
for n := 0; n < length; n++ {
|
||||
array[n] = Uint(rv.Index(n).Interface())
|
||||
}
|
||||
default:
|
||||
return []uint{Uint(i)}
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
@ -207,7 +220,18 @@ func Uint32s(i interface{}) []uint32 {
|
||||
if v, ok := i.(apiInterfaces); ok {
|
||||
return Uint32s(v.Interfaces())
|
||||
}
|
||||
return []uint32{Uint32(i)}
|
||||
// Use reflect feature at last.
|
||||
rv := reflect.ValueOf(i)
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
length := rv.Len()
|
||||
array = make([]uint32, length)
|
||||
for n := 0; n < length; n++ {
|
||||
array[n] = Uint32(rv.Index(n).Interface())
|
||||
}
|
||||
default:
|
||||
return []uint32{Uint32(i)}
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
@ -302,7 +326,18 @@ func Uint64s(i interface{}) []uint64 {
|
||||
if v, ok := i.(apiInterfaces); ok {
|
||||
return Uint64s(v.Interfaces())
|
||||
}
|
||||
return []uint64{Uint64(i)}
|
||||
// Use reflect feature at last.
|
||||
rv := reflect.ValueOf(i)
|
||||
switch rv.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
length := rv.Len()
|
||||
array = make([]uint64, length)
|
||||
for n := 0; n < length; n++ {
|
||||
array[n] = Uint64(rv.Index(n).Interface())
|
||||
}
|
||||
default:
|
||||
return []uint64{Uint64(i)}
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
@ -11,20 +11,13 @@ import (
|
||||
"github.com/gogf/gf/errors/gerror"
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/internal/json"
|
||||
"github.com/gogf/gf/internal/structs"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/internal/structs"
|
||||
"github.com/gogf/gf/internal/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
// replaceCharReg is the regular expression object for replacing chars
|
||||
// in map keys and attribute names.
|
||||
replaceCharReg, _ = regexp.Compile(`[\-\.\_\s]+`)
|
||||
)
|
||||
|
||||
// Struct maps the params key-value pairs to the corresponding struct object's attributes.
|
||||
// The third parameter <mapping> is unnecessary, indicating the mapping rules between the
|
||||
// custom key name and the attribute name(case sensitive).
|
||||
@ -39,17 +32,17 @@ var (
|
||||
// in mapping procedure to do the matching.
|
||||
// It ignores the map key, if it does not match.
|
||||
func Struct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
return doStruct(params, pointer, false, mapping...)
|
||||
return doStruct(params, pointer, mapping...)
|
||||
}
|
||||
|
||||
// StructDeep do Struct function recursively.
|
||||
// See Struct.
|
||||
// Deprecated, use Struct instead.
|
||||
func StructDeep(params interface{}, pointer interface{}, mapping ...map[string]string) error {
|
||||
return doStruct(params, pointer, true, mapping...)
|
||||
return doStruct(params, pointer, mapping...)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// doStruct is the core internal converting function for any data to struct.
|
||||
func doStruct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
if params == nil {
|
||||
// If <params> is nil, no conversion.
|
||||
return nil
|
||||
@ -57,6 +50,11 @@ func doStruct(params interface{}, pointer interface{}, recursive bool, mapping .
|
||||
if pointer == nil {
|
||||
return gerror.New("object pointer cannot be nil")
|
||||
}
|
||||
|
||||
if doStructByDirectReflectSet(params, pointer) {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// Catch the panic, especially the reflect operation panics.
|
||||
if e := recover(); e != nil {
|
||||
@ -68,11 +66,23 @@ func doStruct(params interface{}, pointer interface{}, recursive bool, mapping .
|
||||
switch r := params.(type) {
|
||||
case []byte:
|
||||
if json.Valid(r) {
|
||||
return json.Unmarshal(r, pointer)
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
return json.Unmarshal(r, rv.Interface())
|
||||
}
|
||||
} else {
|
||||
return json.Unmarshal(r, pointer)
|
||||
}
|
||||
}
|
||||
case string:
|
||||
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
|
||||
return json.Unmarshal(paramsBytes, pointer)
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
return json.Unmarshal(paramsBytes, rv.Interface())
|
||||
}
|
||||
} else {
|
||||
return json.Unmarshal(paramsBytes, pointer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,18 +148,6 @@ func doStruct(params interface{}, pointer interface{}, recursive bool, mapping .
|
||||
// doneMap is used to check repeated converting, its key is the real attribute name
|
||||
// of the struct.
|
||||
doneMap := make(map[string]struct{})
|
||||
// It first checks the passed mapping rules.
|
||||
if len(mapping) > 0 && len(mapping[0]) > 0 {
|
||||
for mapK, mapV := range mapping[0] {
|
||||
// mapV is the the attribute name of the struct.
|
||||
if paramV, ok := paramsMap[mapK]; ok {
|
||||
doneMap[mapV] = struct{}{}
|
||||
if err := bindVarToStructAttr(elem, mapV, paramV, recursive, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The key of the attrMap is the attribute name of the struct,
|
||||
// and the value is its replaced name for later comparison to improve performance.
|
||||
@ -166,8 +164,8 @@ func doStruct(params interface{}, pointer interface{}, recursive bool, mapping .
|
||||
if !utils.IsLetterUpper(elemFieldType.Name[0]) {
|
||||
continue
|
||||
}
|
||||
// Maybe it's struct/*struct.
|
||||
if recursive && elemFieldType.Anonymous {
|
||||
// Maybe it's struct/*struct embedded.
|
||||
if elemFieldType.Anonymous {
|
||||
elemFieldValue = elem.Field(i)
|
||||
// Ignore the interface attribute if it's nil.
|
||||
if elemFieldValue.Kind() == reflect.Interface {
|
||||
@ -176,12 +174,12 @@ func doStruct(params interface{}, pointer interface{}, recursive bool, mapping .
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err = doStruct(paramsMap, elemFieldValue, recursive, mapping...); err != nil {
|
||||
if err = doStruct(paramsMap, elemFieldValue, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
tempName = elemFieldType.Name
|
||||
attrMap[tempName] = replaceCharReg.ReplaceAllString(tempName, "")
|
||||
attrMap[tempName] = utils.RemoveSymbols(tempName)
|
||||
}
|
||||
}
|
||||
if len(attrMap) == 0 {
|
||||
@ -191,8 +189,12 @@ func doStruct(params interface{}, pointer interface{}, recursive bool, mapping .
|
||||
// The key of the tagMap is the attribute name of the struct,
|
||||
// and the value is its replaced tag name for later comparison to improve performance.
|
||||
tagMap := make(map[string]string)
|
||||
for k, v := range structs.TagMapName(pointer, StructTagPriority, true) {
|
||||
tagMap[v] = replaceCharReg.ReplaceAllString(k, "")
|
||||
tagToNameMap, err := structs.TagMapName(elem, StructTagPriority)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range tagToNameMap {
|
||||
tagMap[v] = utils.RemoveSymbols(k)
|
||||
}
|
||||
|
||||
var (
|
||||
@ -201,35 +203,43 @@ func doStruct(params interface{}, pointer interface{}, recursive bool, mapping .
|
||||
)
|
||||
for mapK, mapV := range paramsMap {
|
||||
attrName = ""
|
||||
checkName = replaceCharReg.ReplaceAllString(mapK, "")
|
||||
// Loop to find the matched attribute name with or without
|
||||
// string cases and chars like '-'/'_'/'.'/' '.
|
||||
|
||||
// Matching the parameters to struct tag names.
|
||||
// The <tagV> is the attribute name of the struct.
|
||||
for attrKey, cmpKey := range tagMap {
|
||||
if strings.EqualFold(checkName, cmpKey) {
|
||||
attrName = attrKey
|
||||
break
|
||||
// It firstly checks the passed mapping rules.
|
||||
if len(mapping) > 0 && len(mapping[0]) > 0 {
|
||||
if passedAttrKey, ok := mapping[0][mapK]; ok {
|
||||
attrName = passedAttrKey
|
||||
}
|
||||
}
|
||||
|
||||
// Matching the parameters to struct attributes.
|
||||
// It secondly checks the predefined tags and matching rules.
|
||||
if attrName == "" {
|
||||
for attrKey, cmpKey := range attrMap {
|
||||
// Eg:
|
||||
// UserName eq user_name
|
||||
// User-Name eq username
|
||||
// username eq userName
|
||||
// etc.
|
||||
checkName = utils.RemoveSymbols(mapK)
|
||||
// Loop to find the matched attribute name with or without
|
||||
// string cases and chars like '-'/'_'/'.'/' '.
|
||||
|
||||
// Matching the parameters to struct tag names.
|
||||
// The <tagV> is the attribute name of the struct.
|
||||
for attrKey, cmpKey := range tagMap {
|
||||
if strings.EqualFold(checkName, cmpKey) {
|
||||
attrName = attrKey
|
||||
break
|
||||
}
|
||||
}
|
||||
// Matching the parameters to struct attributes.
|
||||
if attrName == "" {
|
||||
for attrKey, cmpKey := range attrMap {
|
||||
// Eg:
|
||||
// UserName eq user_name
|
||||
// User-Name eq username
|
||||
// username eq userName
|
||||
// etc.
|
||||
if strings.EqualFold(checkName, cmpKey) {
|
||||
attrName = attrKey
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No matching, give up this attribute converting.
|
||||
// No matching, it gives up this attribute converting.
|
||||
if attrName == "" {
|
||||
continue
|
||||
}
|
||||
@ -239,30 +249,29 @@ func doStruct(params interface{}, pointer interface{}, recursive bool, mapping .
|
||||
}
|
||||
// Mark it done.
|
||||
doneMap[attrName] = struct{}{}
|
||||
if err := bindVarToStructAttr(elem, attrName, mapV, recursive, mapping...); err != nil {
|
||||
if err := bindVarToStructAttr(elem, attrName, mapV, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Recursively concerting for struct attributes with the same params map.
|
||||
if recursive && elem.Kind() == reflect.Struct {
|
||||
for i := 0; i < elemType.NumField(); i++ {
|
||||
// Only do converting to public attributes.
|
||||
if !utils.IsLetterUpper(elemType.Field(i).Name[0]) {
|
||||
continue
|
||||
}
|
||||
fieldValue := elem.Field(i)
|
||||
if fieldValue.Kind() == reflect.Struct {
|
||||
if err := doStruct(paramsMap, fieldValue, recursive, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// doStructByDirectReflectSet do the converting directly using reflect Set.
|
||||
// It returns true if success, or else false.
|
||||
func doStructByDirectReflectSet(params interface{}, pointer interface{}) (ok bool) {
|
||||
v1 := reflect.ValueOf(pointer)
|
||||
v2 := reflect.ValueOf(params)
|
||||
if v1.Kind() == reflect.Ptr {
|
||||
if elem := v1.Elem(); elem.IsValid() && elem.Type() == v2.Type() {
|
||||
elem.Set(v2)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// bindVarToStructAttr sets value to struct object attribute by name.
|
||||
func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, recursive bool, mapping ...map[string]string) (err error) {
|
||||
func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, mapping ...map[string]string) (err error) {
|
||||
structFieldValue := elem.FieldByName(name)
|
||||
if !structFieldValue.IsValid() {
|
||||
return nil
|
||||
@ -273,8 +282,7 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, rec
|
||||
}
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = bindVarToReflectValue(structFieldValue, value, recursive, mapping...)
|
||||
if err != nil {
|
||||
if err = bindVarToReflectValue(structFieldValue, value, mapping...); err != nil {
|
||||
err = gerror.Wrapf(err, `error binding value to attribute "%s"`, name)
|
||||
}
|
||||
}
|
||||
@ -288,7 +296,7 @@ func bindVarToStructAttr(elem reflect.Value, name string, value interface{}, rec
|
||||
}
|
||||
|
||||
// bindVarToReflectValue sets <value> to reflect value object <structFieldValue>.
|
||||
func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, recursive bool, mapping ...map[string]string) (err error) {
|
||||
func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, mapping ...map[string]string) (err error) {
|
||||
kind := structFieldValue.Kind()
|
||||
|
||||
// Converting using interface, for some kinds.
|
||||
@ -315,7 +323,7 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, re
|
||||
}
|
||||
|
||||
// Recursively converting for struct attribute.
|
||||
if err := doStruct(value, structFieldValue, recursive); err != nil {
|
||||
if err := doStruct(value, structFieldValue); err != nil {
|
||||
// Note there's reflect conversion mechanism here.
|
||||
structFieldValue.Set(reflect.ValueOf(value).Convert(structFieldValue.Type()))
|
||||
}
|
||||
@ -332,14 +340,14 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, re
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if t.Kind() == reflect.Ptr {
|
||||
e := reflect.New(t.Elem()).Elem()
|
||||
if err := doStruct(v.Index(i).Interface(), e, recursive); err != nil {
|
||||
if err := doStruct(v.Index(i).Interface(), e); err != nil {
|
||||
// Note there's reflect conversion mechanism here.
|
||||
e.Set(reflect.ValueOf(v.Index(i).Interface()).Convert(t))
|
||||
}
|
||||
a.Index(i).Set(e.Addr())
|
||||
} else {
|
||||
e := reflect.New(t).Elem()
|
||||
if err := doStruct(v.Index(i).Interface(), e, recursive); err != nil {
|
||||
if err := doStruct(v.Index(i).Interface(), e); err != nil {
|
||||
// Note there's reflect conversion mechanism here.
|
||||
e.Set(reflect.ValueOf(v.Index(i).Interface()).Convert(t))
|
||||
}
|
||||
@ -352,14 +360,14 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, re
|
||||
t := a.Index(0).Type()
|
||||
if t.Kind() == reflect.Ptr {
|
||||
e := reflect.New(t.Elem()).Elem()
|
||||
if err := doStruct(value, e, recursive); err != nil {
|
||||
if err := doStruct(value, e); err != nil {
|
||||
// Note there's reflect conversion mechanism here.
|
||||
e.Set(reflect.ValueOf(value).Convert(t))
|
||||
}
|
||||
a.Index(0).Set(e.Addr())
|
||||
} else {
|
||||
e := reflect.New(t).Elem()
|
||||
if err := doStruct(value, e, recursive); err != nil {
|
||||
if err := doStruct(value, e); err != nil {
|
||||
// Note there's reflect conversion mechanism here.
|
||||
e.Set(reflect.ValueOf(value).Convert(t))
|
||||
}
|
||||
@ -378,7 +386,7 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}, re
|
||||
return err
|
||||
}
|
||||
elem := item.Elem()
|
||||
if err = bindVarToReflectValue(elem, value, recursive, mapping...); err == nil {
|
||||
if err = bindVarToReflectValue(elem, value, mapping...); err == nil {
|
||||
structFieldValue.Set(elem.Addr())
|
||||
}
|
||||
|
||||
|
||||
@ -14,12 +14,13 @@ import (
|
||||
|
||||
// Structs converts any slice to given struct slice.
|
||||
func Structs(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
return doStructs(params, pointer, false, mapping...)
|
||||
return doStructs(params, pointer, mapping...)
|
||||
}
|
||||
|
||||
// StructsDeep converts any slice to given struct slice recursively.
|
||||
// Deprecated, use Structs instead.
|
||||
func StructsDeep(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
return doStructs(params, pointer, true, mapping...)
|
||||
return doStructs(params, pointer, mapping...)
|
||||
}
|
||||
|
||||
// doStructs converts any slice to given struct slice.
|
||||
@ -29,7 +30,7 @@ func StructsDeep(params interface{}, pointer interface{}, mapping ...map[string]
|
||||
// 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) {
|
||||
func doStructs(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) {
|
||||
if params == nil {
|
||||
// If <params> is nil, no conversion.
|
||||
return nil
|
||||
@ -37,6 +38,11 @@ func doStructs(params interface{}, pointer interface{}, deep bool, mapping ...ma
|
||||
if pointer == nil {
|
||||
return gerror.New("object pointer cannot be nil")
|
||||
}
|
||||
|
||||
if doStructsByDirectReflectSet(params, pointer) {
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// Catch the panic, especially the reflect operation panics.
|
||||
if e := recover(); e != nil {
|
||||
@ -47,11 +53,23 @@ func doStructs(params interface{}, pointer interface{}, deep bool, mapping ...ma
|
||||
switch r := params.(type) {
|
||||
case []byte:
|
||||
if json.Valid(r) {
|
||||
return json.Unmarshal(r, pointer)
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
return json.Unmarshal(r, rv.Interface())
|
||||
}
|
||||
} else {
|
||||
return json.Unmarshal(r, pointer)
|
||||
}
|
||||
}
|
||||
case string:
|
||||
if paramsBytes := []byte(r); json.Valid(paramsBytes) {
|
||||
return json.Unmarshal(paramsBytes, pointer)
|
||||
if rv, ok := pointer.(reflect.Value); ok {
|
||||
if rv.Kind() == reflect.Ptr {
|
||||
return json.Unmarshal(paramsBytes, rv.Interface())
|
||||
}
|
||||
} else {
|
||||
return json.Unmarshal(paramsBytes, pointer)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Pointer type check.
|
||||
@ -76,32 +94,33 @@ func doStructs(params interface{}, pointer interface{}, deep bool, mapping ...ma
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
if err = Struct(paramsMaps[i], e, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
array.Index(i).Set(e)
|
||||
}
|
||||
}
|
||||
pointerRv.Elem().Set(array)
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// doStructsByDirectReflectSet do the converting directly using reflect Set.
|
||||
// It returns true if success, or else false.
|
||||
func doStructsByDirectReflectSet(params interface{}, pointer interface{}) (ok bool) {
|
||||
v1 := reflect.ValueOf(pointer)
|
||||
v2 := reflect.ValueOf(params)
|
||||
if v1.Kind() == reflect.Ptr {
|
||||
if elem := v1.Elem(); elem.IsValid() && elem.Type() == v2.Type() {
|
||||
elem.Set(v2)
|
||||
ok = true
|
||||
}
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
133
util/gconv/gconv_z_bench_struct_test.go
Normal file
133
util/gconv/gconv_z_bench_struct_test.go
Normal file
@ -0,0 +1,133 @@
|
||||
// Copyright 2017-2018 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.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gconv
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type structType struct {
|
||||
Name string
|
||||
Score int
|
||||
}
|
||||
|
||||
var (
|
||||
structMap = map[string]interface{}{
|
||||
"name": "gf",
|
||||
"score": 100,
|
||||
}
|
||||
structObj = structType{
|
||||
Name: "john",
|
||||
Score: 60,
|
||||
}
|
||||
structPointer = &structType{
|
||||
Name: "john",
|
||||
Score: 60,
|
||||
}
|
||||
structPointerNil *structType
|
||||
// struct slice
|
||||
structSliceNil []structType
|
||||
structSlice = []structType{
|
||||
{Name: "john", Score: 60},
|
||||
{Name: "smith", Score: 100},
|
||||
}
|
||||
// struct pointer slice
|
||||
structPointerSliceNil []*structType
|
||||
structPointerSlice = []*structType{
|
||||
{Name: "john", Score: 60},
|
||||
{Name: "smith", Score: 100},
|
||||
}
|
||||
)
|
||||
|
||||
func Benchmark_Struct_Basic(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Struct(structMap, structPointer)
|
||||
}
|
||||
}
|
||||
|
||||
// *struct -> **struct
|
||||
func Benchmark_Reflect_PPStruct_PStruct(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
v1 := reflect.ValueOf(&structPointerNil)
|
||||
v2 := reflect.ValueOf(structPointer)
|
||||
//if v1.Kind() == reflect.Ptr {
|
||||
// if elem := v1.Elem(); elem.Type() == v2.Type() {
|
||||
// elem.Set(v2)
|
||||
// }
|
||||
//}
|
||||
v1.Elem().Set(v2)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Struct_PPStruct_PStruct(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Struct(structPointer, &structPointerNil)
|
||||
}
|
||||
}
|
||||
|
||||
// struct -> *struct
|
||||
func Benchmark_Reflect_PStruct_Struct(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
v1 := reflect.ValueOf(structPointer)
|
||||
v2 := reflect.ValueOf(structObj)
|
||||
//if v1.Kind() == reflect.Ptr {
|
||||
// if elem := v1.Elem(); elem.Type() == v2.Type() {
|
||||
// elem.Set(v2)
|
||||
// }
|
||||
//}
|
||||
v1.Elem().Set(v2)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Struct_PStruct_Struct(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Struct(structObj, structPointer)
|
||||
}
|
||||
}
|
||||
|
||||
// []struct -> *[]struct
|
||||
func Benchmark_Reflect_PStructs_Structs(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
v1 := reflect.ValueOf(&structSliceNil)
|
||||
v2 := reflect.ValueOf(structSlice)
|
||||
//if v1.Kind() == reflect.Ptr {
|
||||
// if elem := v1.Elem(); elem.Type() == v2.Type() {
|
||||
// elem.Set(v2)
|
||||
// }
|
||||
//}
|
||||
v1.Elem().Set(v2)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Structs_PStructs_Structs(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Structs(structSlice, &structSliceNil)
|
||||
}
|
||||
}
|
||||
|
||||
// []*struct -> *[]*struct
|
||||
func Benchmark_Reflect_PPStructs_PStructs(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
v1 := reflect.ValueOf(&structPointerSliceNil)
|
||||
v2 := reflect.ValueOf(structPointerSlice)
|
||||
//if v1.Kind() == reflect.Ptr {
|
||||
// if elem := v1.Elem(); elem.Type() == v2.Type() {
|
||||
// elem.Set(v2)
|
||||
// }
|
||||
//}
|
||||
v1.Elem().Set(v2)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Structs_PPStructs_PStructs(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Structs(structPointerSlice, &structPointerSliceNil)
|
||||
}
|
||||
}
|
||||
@ -633,7 +633,7 @@ func Test_Convert_All(t *testing.T) {
|
||||
t.AssertEQ(gconv.Convert(true, "bool"), true)
|
||||
t.AssertEQ(gconv.Convert([]byte{}, "[]byte"), []uint8{})
|
||||
t.AssertEQ(gconv.Convert([]string{}, "[]string"), []string{})
|
||||
t.AssertEQ(gconv.Convert([2]int{1, 2}, "[]int"), []int{0})
|
||||
t.AssertEQ(gconv.Convert([2]int{1, 2}, "[]int"), []int{1, 2})
|
||||
t.AssertEQ(gconv.Convert("1989-01-02", "Time", "Y-m-d"), gconv.Time("1989-01-02", "Y-m-d"))
|
||||
t.AssertEQ(gconv.Convert(1989, "Time"), gconv.Time("1970-01-01 08:33:09 +0800 CST"))
|
||||
t.AssertEQ(gconv.Convert(gtime.Now(), "gtime.Time", 1), *gtime.New())
|
||||
@ -753,7 +753,8 @@ func Test_Slice_PrivateAttribute_All(t *testing.T) {
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := &User{1, "john", []interface{}{2}}
|
||||
t.Assert(gconv.Interfaces(user), g.Slice{1, []interface{}{2}})
|
||||
//t.Assert(gconv.Interfaces(user), g.Slice{1, []interface{}{2}})
|
||||
t.Assert(gconv.Interfaces(user), g.Slice{user})
|
||||
})
|
||||
}
|
||||
|
||||
@ -1278,7 +1279,7 @@ func Test_Struct_PrivateAttribute_All(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Struct_Deep_All(t *testing.T) {
|
||||
func Test_Struct_Embedded_All(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Ids struct {
|
||||
Id int `json:"id"`
|
||||
@ -1303,7 +1304,7 @@ func Test_Struct_Deep_All(t *testing.T) {
|
||||
"create_time": "2019",
|
||||
}
|
||||
user := new(User)
|
||||
gconv.StructDeep(data, user)
|
||||
gconv.Struct(data, user)
|
||||
t.Assert(user.Id, 100)
|
||||
t.Assert(user.Uid, 101)
|
||||
t.Assert(user.Nickname, "T1")
|
||||
|
||||
@ -203,7 +203,43 @@ func Test_Map_PrivateAttribute(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_MapDeep1(t *testing.T) {
|
||||
func Test_Map_Embedded(t *testing.T) {
|
||||
type Base struct {
|
||||
Id int
|
||||
}
|
||||
type User struct {
|
||||
Base
|
||||
Name string
|
||||
}
|
||||
type UserDetail struct {
|
||||
User
|
||||
Brief string
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := &User{}
|
||||
user.Id = 1
|
||||
user.Name = "john"
|
||||
|
||||
m := gconv.Map(user)
|
||||
t.Assert(len(m), 2)
|
||||
t.Assert(m["Id"], user.Id)
|
||||
t.Assert(m["Name"], user.Name)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := &UserDetail{}
|
||||
user.Id = 1
|
||||
user.Name = "john"
|
||||
user.Brief = "john guo"
|
||||
|
||||
m := gconv.Map(user)
|
||||
t.Assert(len(m), 3)
|
||||
t.Assert(m["Id"], user.Id)
|
||||
t.Assert(m["Name"], user.Name)
|
||||
t.Assert(m["Brief"], user.Brief)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Map_Embedded2(t *testing.T) {
|
||||
type Ids struct {
|
||||
Id int `c:"id"`
|
||||
Uid int `c:"uid"`
|
||||
@ -224,9 +260,9 @@ func Test_MapDeep1(t *testing.T) {
|
||||
user.Nickname = "john"
|
||||
user.CreateTime = "2019"
|
||||
m := gconv.Map(user)
|
||||
t.Assert(m["id"], "")
|
||||
t.Assert(m["id"], "100")
|
||||
t.Assert(m["nickname"], user.Nickname)
|
||||
t.Assert(m["create_time"], "")
|
||||
t.Assert(m["create_time"], "2019")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := new(User)
|
||||
|
||||
@ -138,14 +138,6 @@ func Test_MapToMapDeep(t *testing.T) {
|
||||
err := gconv.MapToMap(params, &m)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(m), 1)
|
||||
t.Assert(m["key"].Id, 0)
|
||||
t.Assert(m["key"].Name, "john")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := (map[string]*User)(nil)
|
||||
err := gconv.MapToMapDeep(params, &m)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(m), 1)
|
||||
t.Assert(m["key"].Id, 1)
|
||||
t.Assert(m["key"].Name, "john")
|
||||
})
|
||||
@ -255,7 +247,7 @@ func Test_MapToMaps2(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_MapToMapsDeep(t *testing.T) {
|
||||
func Test_MapToMaps3(t *testing.T) {
|
||||
type Ids struct {
|
||||
Id int
|
||||
Uid int
|
||||
@ -283,20 +275,6 @@ func Test_MapToMapsDeep(t *testing.T) {
|
||||
err := gconv.MapToMaps(params, &m)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(m), 2)
|
||||
t.Assert(m["100"][0].Id, 0)
|
||||
t.Assert(m["100"][1].Id, 0)
|
||||
t.Assert(m["100"][0].Name, "john")
|
||||
t.Assert(m["100"][1].Name, "smith")
|
||||
t.Assert(m["200"][0].Id, 0)
|
||||
t.Assert(m["200"][1].Id, 0)
|
||||
t.Assert(m["200"][0].Name, "green")
|
||||
t.Assert(m["200"][1].Name, "jim")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := make(map[string][]*User)
|
||||
err := gconv.MapToMapsDeep(params, &m)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(m), 2)
|
||||
t.Assert(m["100"][0].Id, 1)
|
||||
t.Assert(m["100"][1].Id, 2)
|
||||
t.Assert(m["100"][0].Name, "john")
|
||||
@ -308,7 +286,7 @@ func Test_MapToMapsDeep(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_MapToMapsDeepWithTag(t *testing.T) {
|
||||
func Test_MapToMapsWithTag(t *testing.T) {
|
||||
type Ids struct {
|
||||
Id int
|
||||
Uid int
|
||||
@ -336,20 +314,6 @@ func Test_MapToMapsDeepWithTag(t *testing.T) {
|
||||
err := gconv.MapToMaps(params, &m)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(m), 2)
|
||||
t.Assert(m["100"][0].Id, 0)
|
||||
t.Assert(m["100"][1].Id, 0)
|
||||
t.Assert(m["100"][0].Name, "john")
|
||||
t.Assert(m["100"][1].Name, "smith")
|
||||
t.Assert(m["200"][0].Id, 0)
|
||||
t.Assert(m["200"][1].Id, 0)
|
||||
t.Assert(m["200"][0].Name, "green")
|
||||
t.Assert(m["200"][1].Name, "jim")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
m := make(map[string][]*User)
|
||||
err := gconv.MapToMapsDeep(params, &m)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(m), 2)
|
||||
t.Assert(m["100"][0].Id, 1)
|
||||
t.Assert(m["100"][1].Id, 2)
|
||||
t.Assert(m["100"][0].Name, "john")
|
||||
|
||||
@ -25,6 +25,17 @@ func Test_Slice(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Strings(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := []*g.Var{
|
||||
g.NewVar(1),
|
||||
g.NewVar(2),
|
||||
g.NewVar(3),
|
||||
}
|
||||
t.AssertEQ(gconv.Strings(array), []string{"1", "2", "3"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Slice_PrivateAttribute(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
@ -32,7 +43,8 @@ func Test_Slice_PrivateAttribute(t *testing.T) {
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := &User{1, "john"}
|
||||
t.Assert(gconv.Interfaces(user), g.Slice{1})
|
||||
//t.Assert(gconv.Interfaces(user), g.Slice{1})
|
||||
t.Assert(gconv.Interfaces(user), g.Slice{user})
|
||||
})
|
||||
}
|
||||
|
||||
@ -57,28 +69,10 @@ func Test_Slice_Structs(t *testing.T) {
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(users[0].Id, params[0]["id"])
|
||||
t.Assert(users[0].Name, params[0]["name"])
|
||||
t.Assert(users[0].Age, 0)
|
||||
t.Assert(users[0].Age, 18)
|
||||
|
||||
t.Assert(users[1].Id, params[1]["id"])
|
||||
t.Assert(users[1].Name, params[1]["name"])
|
||||
t.Assert(users[1].Age, 0)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
users := make([]User, 0)
|
||||
params := []g.Map{
|
||||
{"id": 1, "name": "john", "age": 18},
|
||||
{"id": 2, "name": "smith", "age": 20},
|
||||
}
|
||||
err := gconv.StructsDeep(params, &users)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(users), 2)
|
||||
t.Assert(users[0].Id, params[0]["id"])
|
||||
t.Assert(users[0].Name, params[0]["name"])
|
||||
t.Assert(users[0].Age, params[0]["age"])
|
||||
|
||||
t.Assert(users[1].Id, params[1]["id"])
|
||||
t.Assert(users[1].Name, params[1]["name"])
|
||||
t.Assert(users[1].Age, params[1]["age"])
|
||||
t.Assert(users[1].Age, 20)
|
||||
})
|
||||
}
|
||||
|
||||
@ -143,3 +143,57 @@ func Test_Struct_SliceWithTag(t *testing.T) {
|
||||
t.Assert(users[1].NickName, "name2")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Structs_DirectReflectSet(t *testing.T) {
|
||||
type A struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
a = []*A{
|
||||
{Id: 1, Name: "john"},
|
||||
{Id: 2, Name: "smith"},
|
||||
}
|
||||
b []*A
|
||||
)
|
||||
err := gconv.Structs(a, &b)
|
||||
t.Assert(err, nil)
|
||||
t.AssertEQ(a, b)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
a = []A{
|
||||
{Id: 1, Name: "john"},
|
||||
{Id: 2, Name: "smith"},
|
||||
}
|
||||
b []A
|
||||
)
|
||||
err := gconv.Structs(a, &b)
|
||||
t.Assert(err, nil)
|
||||
t.AssertEQ(a, b)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Structs_SliceIntAttribute(t *testing.T) {
|
||||
type A struct {
|
||||
Id []int
|
||||
}
|
||||
type B struct {
|
||||
*A
|
||||
Name string
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
array []*B
|
||||
)
|
||||
err := gconv.Structs(g.Slice{
|
||||
g.Map{"id": nil, "name": "john"},
|
||||
g.Map{"id": nil, "name": "smith"},
|
||||
}, &array)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(len(array), 2)
|
||||
t.Assert(array[0].Name, "john")
|
||||
t.Assert(array[1].Name, "smith")
|
||||
})
|
||||
}
|
||||
|
||||
@ -374,7 +374,7 @@ func Test_Struct_PrivateAttribute(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_StructDeep1(t *testing.T) {
|
||||
func Test_StructEmbedded1(t *testing.T) {
|
||||
type Base struct {
|
||||
Age int
|
||||
}
|
||||
@ -394,25 +394,11 @@ func Test_StructDeep1(t *testing.T) {
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Id, params["id"])
|
||||
t.Assert(user.Name, params["name"])
|
||||
t.Assert(user.Age, 0)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := new(User)
|
||||
params := g.Map{
|
||||
"id": 1,
|
||||
"name": "john",
|
||||
"age": 18,
|
||||
}
|
||||
err := gconv.StructDeep(params, user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Id, params["id"])
|
||||
t.Assert(user.Name, params["name"])
|
||||
t.Assert(user.Age, params["age"])
|
||||
t.Assert(user.Age, 18)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_StructDeep2(t *testing.T) {
|
||||
func Test_StructEmbedded2(t *testing.T) {
|
||||
type Ids struct {
|
||||
Id int
|
||||
Uid int
|
||||
@ -434,30 +420,13 @@ func Test_StructDeep2(t *testing.T) {
|
||||
user := new(User)
|
||||
err := gconv.Struct(params, user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Id, 0)
|
||||
t.Assert(user.Uid, 0)
|
||||
t.Assert(user.Name, "john")
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := new(User)
|
||||
err := gconv.StructDeep(params, user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Id, 1)
|
||||
t.Assert(user.Uid, 10)
|
||||
t.Assert(user.Name, "john")
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
user := (*User)(nil)
|
||||
err := gconv.StructDeep(params, &user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Id, 1)
|
||||
t.Assert(user.Uid, 10)
|
||||
t.Assert(user.Name, "john")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_StructDeep3(t *testing.T) {
|
||||
func Test_StructEmbedded3(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Ids struct {
|
||||
Id int `json:"id"`
|
||||
@ -482,7 +451,7 @@ func Test_StructDeep3(t *testing.T) {
|
||||
"create_time": "2019",
|
||||
}
|
||||
user := new(User)
|
||||
err := gconv.StructDeep(data, user)
|
||||
err := gconv.Struct(data, user)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user.Id, 100)
|
||||
t.Assert(user.Uid, 101)
|
||||
@ -492,7 +461,7 @@ func Test_StructDeep3(t *testing.T) {
|
||||
}
|
||||
|
||||
// https://github.com/gogf/gf/issues/775
|
||||
func Test_StructDeep4(t *testing.T) {
|
||||
func Test_StructEmbedded4(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Sub2 struct {
|
||||
SubName string
|
||||
@ -521,14 +490,14 @@ func Test_StructDeep4(t *testing.T) {
|
||||
},
|
||||
}
|
||||
tx := new(Test)
|
||||
if err := gconv.StructDeep(data, &tx); err != nil {
|
||||
if err := gconv.Struct(data, &tx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
t.Assert(tx, expect)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_StructDeep5(t *testing.T) {
|
||||
func Test_StructEmbedded5(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type Base struct {
|
||||
Pass1 string `params:"password1"`
|
||||
@ -554,13 +523,18 @@ func Test_StructDeep5(t *testing.T) {
|
||||
var err error
|
||||
user1 := new(UserWithBase1)
|
||||
user2 := new(UserWithBase2)
|
||||
err = gconv.StructDeep(data, user1)
|
||||
err = gconv.Struct(data, user1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user1, &UserWithBase1{1, "john", Base{"123", "456"}})
|
||||
|
||||
err = gconv.StructDeep(data, user2)
|
||||
err = gconv.Struct(data, user2)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user2, &UserWithBase2{1, "john", Base{"123", "456"}})
|
||||
t.Assert(user2, &UserWithBase2{1, "john", Base{"", ""}})
|
||||
|
||||
var user3 *UserWithBase1
|
||||
err = gconv.Struct(user1, &user3)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(user3, user1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -867,7 +841,7 @@ func Test_Struct_CatchPanic(t *testing.T) {
|
||||
Result int
|
||||
}
|
||||
type User struct {
|
||||
Score
|
||||
Score Score
|
||||
}
|
||||
|
||||
user := new(User)
|
||||
@ -927,14 +901,14 @@ type TestStruct struct {
|
||||
TestInterface
|
||||
}
|
||||
|
||||
func Test_Struct_WithInterfaceAttr(t *testing.T) {
|
||||
func Test_Struct_Embedded(t *testing.T) {
|
||||
// Implemented interface attribute.
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
v1 := TestStruct{
|
||||
TestInterface: &T{"john"},
|
||||
}
|
||||
v2 := g.Map{}
|
||||
err := gconv.StructDeep(v2, &v1)
|
||||
err := gconv.Struct(v2, &v1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v1.Test(), "john")
|
||||
})
|
||||
@ -946,7 +920,7 @@ func Test_Struct_WithInterfaceAttr(t *testing.T) {
|
||||
v2 := g.Map{
|
||||
"name": "test",
|
||||
}
|
||||
err := gconv.StructDeep(v2, &v1)
|
||||
err := gconv.Struct(v2, &v1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v1.Test(), "test")
|
||||
})
|
||||
@ -956,7 +930,7 @@ func Test_Struct_WithInterfaceAttr(t *testing.T) {
|
||||
v2 := g.Map{
|
||||
"name": "test",
|
||||
}
|
||||
err := gconv.StructDeep(v2, &v1)
|
||||
err := gconv.Struct(v2, &v1)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(v1.TestInterface, nil)
|
||||
})
|
||||
@ -1001,3 +975,85 @@ func Test_Struct_WithJson(t *testing.T) {
|
||||
t.Assert(b2, b1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Struct_AttrStructHasTheSameTag(t *testing.T) {
|
||||
type Product struct {
|
||||
Id int `json:"id"`
|
||||
UpdatedAt time.Time `json:"-" `
|
||||
UpdatedAtFormat string `json:"updated_at" `
|
||||
}
|
||||
|
||||
type Order struct {
|
||||
Id int `json:"id"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Product Product `json:"products"`
|
||||
}
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
data := g.Map{
|
||||
"id": 1,
|
||||
"updated_at": time.Now(),
|
||||
}
|
||||
order := new(Order)
|
||||
err := gconv.Struct(data, order)
|
||||
t.Assert(err, nil)
|
||||
t.Assert(order.Id, data["id"])
|
||||
t.Assert(order.UpdatedAt, data["updated_at"])
|
||||
t.Assert(order.Product.Id, 0)
|
||||
t.Assert(order.Product.UpdatedAt.IsZero(), true)
|
||||
t.Assert(order.Product.UpdatedAtFormat, "")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Struct_DirectReflectSet(t *testing.T) {
|
||||
type A struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
a = &A{
|
||||
Id: 1,
|
||||
Name: "john",
|
||||
}
|
||||
b *A
|
||||
)
|
||||
err := gconv.Struct(a, &b)
|
||||
t.Assert(err, nil)
|
||||
t.AssertEQ(a, b)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
a = A{
|
||||
Id: 1,
|
||||
Name: "john",
|
||||
}
|
||||
b A
|
||||
)
|
||||
err := gconv.Struct(a, &b)
|
||||
t.Assert(err, nil)
|
||||
t.AssertEQ(a, b)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Struct_NilEmbeddedStructAttribute(t *testing.T) {
|
||||
type A struct {
|
||||
Name string
|
||||
}
|
||||
type B struct {
|
||||
*A
|
||||
Id int
|
||||
}
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
b *B
|
||||
)
|
||||
err := gconv.Struct(g.Map{
|
||||
"id": 1,
|
||||
"name": nil,
|
||||
}, &b)
|
||||
t.Assert(err, nil)
|
||||
g.Dump(b)
|
||||
})
|
||||
}
|
||||
|
||||
@ -6,128 +6,9 @@
|
||||
|
||||
// Package guid provides simple and high performance unique id generation functionality.
|
||||
//
|
||||
// Unique String ID:
|
||||
// PLEASE VERY NOTE:
|
||||
// This package only provides unique number generation for simple, convenient and most common
|
||||
// usage purpose, but does not provide strict global unique number generation. Please refer
|
||||
// to UUID algorithm for global unique number generation if necessary.
|
||||
package guid
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/encoding/ghash"
|
||||
"github.com/gogf/gf/net/gipv4"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/grand"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
sequence gtype.Uint32 // Sequence for unique purpose of current process.
|
||||
sequenceMax = uint32(46655) // Sequence max("zzz").
|
||||
randomStrBase = "0123456789abcdefghijklmnopqrstuvwxyz" // Random chars string(36 bytes).
|
||||
macAddrStr = "0000000" // MAC addresses hash result in 7 bytes.
|
||||
processIdStr = "0000" // Process id in 4 bytes.
|
||||
)
|
||||
|
||||
// init initializes several fixed local variable.
|
||||
func init() {
|
||||
// MAC addresses hash result in 7 bytes.
|
||||
macs, _ := gipv4.GetMacArray()
|
||||
if len(macs) > 0 {
|
||||
var macAddrBytes []byte
|
||||
for _, mac := range macs {
|
||||
macAddrBytes = append(macAddrBytes, []byte(mac)...)
|
||||
}
|
||||
b := []byte{'0', '0', '0', '0', '0', '0', '0'}
|
||||
s := strconv.FormatUint(uint64(ghash.DJBHash(macAddrBytes)), 36)
|
||||
copy(b, s)
|
||||
macAddrStr = string(b)
|
||||
}
|
||||
// Process id in 4 bytes.
|
||||
{
|
||||
b := []byte{'0', '0', '0', '0'}
|
||||
s := strconv.FormatInt(int64(os.Getpid()), 36)
|
||||
copy(b, s)
|
||||
processIdStr = string(b)
|
||||
}
|
||||
}
|
||||
|
||||
// S creates and returns a global unique string in 32 bytes that meets most common
|
||||
// usages without strict UUID algorithm. It returns an unique string using default
|
||||
// unique algorithm if no <data> is given.
|
||||
//
|
||||
// The specified <data> can be no more than 2 count. No matter how long each of the
|
||||
// <data> size is, each of them will be hashed into 7 bytes as part of the result.
|
||||
// If given <data> count is less than 2, the leftover size of the result bytes will
|
||||
// be token by random string.
|
||||
//
|
||||
// The returned string is composed with:
|
||||
// 1. Default: MAC(7) + PID(4) + TimestampNano(12) + Sequence(3) + RandomString(6)
|
||||
// 2. CustomData: Data(7/14) + TimestampNano(12) + Sequence(3) + RandomString(3/10)
|
||||
//
|
||||
// Note that:
|
||||
// 1. The returned length is fixed to 32 bytes for performance purpose.
|
||||
// 2. The custom parameter <data> composed should have unique attribute in your
|
||||
// business situation.
|
||||
func S(data ...[]byte) string {
|
||||
var (
|
||||
b = make([]byte, 32)
|
||||
nanoStr = strconv.FormatInt(time.Now().UnixNano(), 36)
|
||||
)
|
||||
if len(data) == 0 {
|
||||
copy(b, macAddrStr)
|
||||
copy(b[7:], processIdStr)
|
||||
copy(b[11:], nanoStr)
|
||||
copy(b[23:], getSequence())
|
||||
copy(b[26:], getRandomStr(6))
|
||||
} else if len(data) <= 2 {
|
||||
n := 0
|
||||
for i, v := range data {
|
||||
// Ignore empty data item bytes.
|
||||
if len(v) > 0 {
|
||||
copy(b[i*7:], getDataHashStr(v))
|
||||
n += 7
|
||||
}
|
||||
}
|
||||
copy(b[n:], nanoStr)
|
||||
copy(b[n+12:], getSequence())
|
||||
copy(b[n+12+3:], getRandomStr(32-n-12-3))
|
||||
} else {
|
||||
panic("data count too long, no more than 2")
|
||||
}
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// getSequence increases and returns the sequence string in 3 bytes.
|
||||
// The sequence is less than "zzz"(46655).
|
||||
func getSequence() []byte {
|
||||
b := []byte{'0', '0', '0'}
|
||||
s := strconv.FormatUint(uint64(sequence.Add(1)%sequenceMax), 36)
|
||||
copy(b, s)
|
||||
return b
|
||||
}
|
||||
|
||||
// getRandomStr randomly picks and returns <n> count of chars from randomStrBase.
|
||||
func getRandomStr(n int) []byte {
|
||||
if n <= 0 {
|
||||
return []byte{}
|
||||
}
|
||||
var (
|
||||
b = make([]byte, n)
|
||||
numberBytes = grand.B(n)
|
||||
)
|
||||
for i := range b {
|
||||
b[i] = randomStrBase[numberBytes[i]%36]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// getDataHashStr creates and returns hash bytes in 7 bytes with given data bytes.
|
||||
func getDataHashStr(data []byte) []byte {
|
||||
b := []byte{'0', '0', '0', '0', '0', '0', '0'}
|
||||
s := strconv.FormatUint(uint64(ghash.DJBHash(data)), 36)
|
||||
copy(b, s)
|
||||
return b
|
||||
}
|
||||
|
||||
127
util/guid/guid_string.go
Normal file
127
util/guid/guid_string.go
Normal file
@ -0,0 +1,127 @@
|
||||
// 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 guid
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/encoding/ghash"
|
||||
"github.com/gogf/gf/net/gipv4"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"github.com/gogf/gf/util/grand"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
sequence gtype.Uint32 // Sequence for unique purpose of current process.
|
||||
sequenceMax = uint32(46655) // Sequence max("zzz").
|
||||
randomStrBase = "0123456789abcdefghijklmnopqrstuvwxyz" // Random chars string(36 bytes).
|
||||
macAddrStr = "0000000" // MAC addresses hash result in 7 bytes.
|
||||
processIdStr = "0000" // Process id in 4 bytes.
|
||||
)
|
||||
|
||||
// init initializes several fixed local variable.
|
||||
func init() {
|
||||
// MAC addresses hash result in 7 bytes.
|
||||
macs, _ := gipv4.GetMacArray()
|
||||
if len(macs) > 0 {
|
||||
var macAddrBytes []byte
|
||||
for _, mac := range macs {
|
||||
macAddrBytes = append(macAddrBytes, []byte(mac)...)
|
||||
}
|
||||
b := []byte{'0', '0', '0', '0', '0', '0', '0'}
|
||||
s := strconv.FormatUint(uint64(ghash.DJBHash(macAddrBytes)), 36)
|
||||
copy(b, s)
|
||||
macAddrStr = string(b)
|
||||
}
|
||||
// Process id in 4 bytes.
|
||||
{
|
||||
b := []byte{'0', '0', '0', '0'}
|
||||
s := strconv.FormatInt(int64(os.Getpid()), 36)
|
||||
copy(b, s)
|
||||
processIdStr = string(b)
|
||||
}
|
||||
}
|
||||
|
||||
// S creates and returns a global unique string in 32 bytes that meets most common
|
||||
// usages without strict UUID algorithm. It returns an unique string using default
|
||||
// unique algorithm if no <data> is given.
|
||||
//
|
||||
// The specified <data> can be no more than 2 parts. No matter how long each of the
|
||||
// <data> size is, each of them will be hashed into 7 bytes as part of the result.
|
||||
// If given <data> parts is less than 2, the leftover size of the result bytes will
|
||||
// be token by random string.
|
||||
//
|
||||
// The returned string is composed with:
|
||||
// 1. Default: MAC(7) + PID(4) + TimestampNano(12) + Sequence(3) + RandomString(6)
|
||||
// 2. CustomData: Data(7/14) + TimestampNano(12) + Sequence(3) + RandomString(3/10)
|
||||
//
|
||||
// Note that:
|
||||
// 1. The returned length is fixed to 32 bytes for performance purpose.
|
||||
// 2. The custom parameter <data> composed should have unique attribute in your
|
||||
// business situation.
|
||||
func S(data ...[]byte) string {
|
||||
var (
|
||||
b = make([]byte, 32)
|
||||
nanoStr = strconv.FormatInt(time.Now().UnixNano(), 36)
|
||||
)
|
||||
if len(data) == 0 {
|
||||
copy(b, macAddrStr)
|
||||
copy(b[7:], processIdStr)
|
||||
copy(b[11:], nanoStr)
|
||||
copy(b[23:], getSequence())
|
||||
copy(b[26:], getRandomStr(6))
|
||||
} else if len(data) <= 2 {
|
||||
n := 0
|
||||
for i, v := range data {
|
||||
// Ignore empty data item bytes.
|
||||
if len(v) > 0 {
|
||||
copy(b[i*7:], getDataHashStr(v))
|
||||
n += 7
|
||||
}
|
||||
}
|
||||
copy(b[n:], nanoStr)
|
||||
copy(b[n+12:], getSequence())
|
||||
copy(b[n+12+3:], getRandomStr(32-n-12-3))
|
||||
} else {
|
||||
panic("too many data parts, it should be no more than 2 parts")
|
||||
}
|
||||
return gconv.UnsafeBytesToStr(b)
|
||||
}
|
||||
|
||||
// getSequence increases and returns the sequence string in 3 bytes.
|
||||
// The sequence is less than "zzz"(46655).
|
||||
func getSequence() []byte {
|
||||
b := []byte{'0', '0', '0'}
|
||||
s := strconv.FormatUint(uint64(sequence.Add(1)%sequenceMax), 36)
|
||||
copy(b, s)
|
||||
return b
|
||||
}
|
||||
|
||||
// getRandomStr randomly picks and returns <n> count of chars from randomStrBase.
|
||||
func getRandomStr(n int) []byte {
|
||||
if n <= 0 {
|
||||
return []byte{}
|
||||
}
|
||||
var (
|
||||
b = make([]byte, n)
|
||||
numberBytes = grand.B(n)
|
||||
)
|
||||
for i := range b {
|
||||
b[i] = randomStrBase[numberBytes[i]%36]
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// getDataHashStr creates and returns hash bytes in 7 bytes with given data bytes.
|
||||
func getDataHashStr(data []byte) []byte {
|
||||
b := []byte{'0', '0', '0', '0', '0', '0', '0'}
|
||||
s := strconv.FormatUint(uint64(ghash.DJBHash(data)), 36)
|
||||
copy(b, s)
|
||||
return b
|
||||
}
|
||||
@ -14,19 +14,25 @@ import (
|
||||
)
|
||||
|
||||
func Benchmark_S(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
guid.S()
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
guid.S()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_S_Data_1(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
guid.S([]byte("123"))
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
guid.S([]byte("123"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Benchmark_S_Data_2(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
guid.S([]byte("123"), []byte("456"))
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
guid.S([]byte("123"), []byte("456"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -10,6 +10,8 @@ package gutil
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/empty"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Throw throws out an exception, which can be caught be TryCatch or recover.
|
||||
@ -46,3 +48,99 @@ func TryCatch(try func(), catch ...func(exception error)) {
|
||||
func IsEmpty(value interface{}) bool {
|
||||
return empty.IsEmpty(value)
|
||||
}
|
||||
|
||||
// Keys retrieves and returns the keys from given map or struct.
|
||||
func Keys(mapOrStruct interface{}) (keysOrAttrs []string) {
|
||||
keysOrAttrs = make([]string, 0)
|
||||
if m, ok := mapOrStruct.(map[string]interface{}); ok {
|
||||
for k, _ := range m {
|
||||
keysOrAttrs = append(keysOrAttrs, k)
|
||||
}
|
||||
return
|
||||
}
|
||||
var (
|
||||
reflectValue reflect.Value
|
||||
reflectKind reflect.Kind
|
||||
)
|
||||
if v, ok := mapOrStruct.(reflect.Value); ok {
|
||||
reflectValue = v
|
||||
} else {
|
||||
reflectValue = reflect.ValueOf(mapOrStruct)
|
||||
}
|
||||
reflectKind = reflectValue.Kind()
|
||||
if reflectKind == reflect.Ptr {
|
||||
if !reflectValue.IsValid() || reflectValue.IsNil() {
|
||||
reflectValue = reflect.New(reflectValue.Type().Elem()).Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
}
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.Map:
|
||||
for _, k := range reflectValue.MapKeys() {
|
||||
keysOrAttrs = append(keysOrAttrs, gconv.String(k.Interface()))
|
||||
}
|
||||
case reflect.Struct:
|
||||
var (
|
||||
fieldType reflect.StructField
|
||||
reflectType = reflectValue.Type()
|
||||
)
|
||||
for i := 0; i < reflectValue.NumField(); i++ {
|
||||
fieldType = reflectType.Field(i)
|
||||
if fieldType.Anonymous {
|
||||
keysOrAttrs = append(keysOrAttrs, Keys(reflectValue.Field(i))...)
|
||||
} else {
|
||||
keysOrAttrs = append(keysOrAttrs, fieldType.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Values retrieves and returns the values from given map or struct.
|
||||
func Values(mapOrStruct interface{}) (values []interface{}) {
|
||||
values = make([]interface{}, 0)
|
||||
if m, ok := mapOrStruct.(map[string]interface{}); ok {
|
||||
for _, v := range m {
|
||||
values = append(values, v)
|
||||
}
|
||||
return
|
||||
}
|
||||
var (
|
||||
reflectValue reflect.Value
|
||||
reflectKind reflect.Kind
|
||||
)
|
||||
if v, ok := mapOrStruct.(reflect.Value); ok {
|
||||
reflectValue = v
|
||||
} else {
|
||||
reflectValue = reflect.ValueOf(mapOrStruct)
|
||||
}
|
||||
reflectKind = reflectValue.Kind()
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch reflectKind {
|
||||
case reflect.Map:
|
||||
for _, k := range reflectValue.MapKeys() {
|
||||
values = append(values, reflectValue.MapIndex(k).Interface())
|
||||
}
|
||||
case reflect.Struct:
|
||||
var (
|
||||
fieldType reflect.StructField
|
||||
reflectType = reflectValue.Type()
|
||||
)
|
||||
for i := 0; i < reflectValue.NumField(); i++ {
|
||||
fieldType = reflectType.Field(i)
|
||||
if fieldType.Anonymous {
|
||||
values = append(values, Values(reflectValue.Field(i))...)
|
||||
} else {
|
||||
values = append(values, reflectValue.Field(i).Interface())
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -15,6 +15,11 @@ import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// apiVal is used for type assert api for Val().
|
||||
type apiVal interface {
|
||||
Val() interface{}
|
||||
}
|
||||
|
||||
// apiString is used for type assert api for String().
|
||||
type apiString interface {
|
||||
String() string
|
||||
@ -36,37 +41,52 @@ func Dump(i ...interface{}) {
|
||||
// Export returns variables <i...> as a string with more manually readable.
|
||||
func Export(i ...interface{}) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for _, v := range i {
|
||||
switch r := v.(type) {
|
||||
for _, value := range i {
|
||||
switch r := value.(type) {
|
||||
case []byte:
|
||||
buffer.Write(r)
|
||||
case string:
|
||||
buffer.WriteString(r)
|
||||
default:
|
||||
var (
|
||||
rv = reflect.ValueOf(v)
|
||||
kind = rv.Kind()
|
||||
reflectValue = reflect.ValueOf(value)
|
||||
reflectKind = reflectValue.Kind()
|
||||
)
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
for reflectKind == reflect.Ptr {
|
||||
reflectValue = reflectValue.Elem()
|
||||
reflectKind = reflectValue.Kind()
|
||||
}
|
||||
switch kind {
|
||||
switch reflectKind {
|
||||
case reflect.Slice, reflect.Array:
|
||||
v = gconv.Interfaces(v)
|
||||
value = gconv.Interfaces(value)
|
||||
case reflect.Map:
|
||||
v = gconv.Map(v)
|
||||
value = gconv.Map(value)
|
||||
case reflect.Struct:
|
||||
if r, ok := v.(apiMapStrAny); ok {
|
||||
v = r.MapStrAny()
|
||||
} else if r, ok := v.(apiString); ok {
|
||||
v = r.String()
|
||||
converted := false
|
||||
if r, ok := value.(apiVal); ok {
|
||||
if result := r.Val(); result != nil {
|
||||
value = result
|
||||
converted = true
|
||||
}
|
||||
}
|
||||
if !converted {
|
||||
if r, ok := value.(apiMapStrAny); ok {
|
||||
if result := r.MapStrAny(); result != nil {
|
||||
value = result
|
||||
converted = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !converted {
|
||||
if r, ok := value.(apiString); ok {
|
||||
value = r.String()
|
||||
}
|
||||
}
|
||||
}
|
||||
encoder := json.NewEncoder(buffer)
|
||||
encoder.SetEscapeHTML(false)
|
||||
encoder.SetIndent("", "\t")
|
||||
if err := encoder.Encode(v); err != nil {
|
||||
if err := encoder.Encode(value); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,13 +22,16 @@ func MapCopy(data map[string]interface{}) (copy map[string]interface{}) {
|
||||
|
||||
// MapContains checks whether map <data> contains <key>.
|
||||
func MapContains(data map[string]interface{}, key string) (ok bool) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
_, ok = data[key]
|
||||
return
|
||||
}
|
||||
|
||||
// MapDelete deletes all <keys> from map <data>.
|
||||
func MapDelete(data map[string]interface{}, keys ...string) {
|
||||
if data == nil {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
for _, key := range keys {
|
||||
@ -59,11 +62,13 @@ func MapMergeCopy(src ...map[string]interface{}) (copy map[string]interface{}) {
|
||||
return
|
||||
}
|
||||
|
||||
// MapPossibleItemByKey tries to find the possible key-value pair for given key with or without
|
||||
// cases or chars '-'/'_'/'.'/' '.
|
||||
// MapPossibleItemByKey tries to find the possible key-value pair for given key ignoring cases and symbols.
|
||||
//
|
||||
// Note that this function might be of low performance.
|
||||
func MapPossibleItemByKey(data map[string]interface{}, key string) (foundKey string, foundValue interface{}) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
if v, ok := data[key]; ok {
|
||||
return key, v
|
||||
}
|
||||
@ -77,7 +82,7 @@ func MapPossibleItemByKey(data map[string]interface{}, key string) (foundKey str
|
||||
}
|
||||
|
||||
// MapContainsPossibleKey checks if the given <key> is contained in given map <data>.
|
||||
// It checks the key with or without cases or chars '-'/'_'/'.'/' '.
|
||||
// It checks the key ignoring cases and symbols.
|
||||
//
|
||||
// Note that this function might be of low performance.
|
||||
func MapContainsPossibleKey(data map[string]interface{}, key string) bool {
|
||||
@ -87,8 +92,11 @@ func MapContainsPossibleKey(data map[string]interface{}, key string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// MapOmitEmpty deletes all empty values from guven map.
|
||||
// MapOmitEmpty deletes all empty values from given map.
|
||||
func MapOmitEmpty(data map[string]interface{}) {
|
||||
if len(data) == 0 {
|
||||
return
|
||||
}
|
||||
for k, v := range data {
|
||||
if IsEmpty(v) {
|
||||
delete(data, k)
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package gutil_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
@ -62,3 +63,56 @@ func Test_Throw(t *testing.T) {
|
||||
gutil.Throw("gutil Throw test")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Keys(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
keys := gutil.Keys(map[int]int{
|
||||
1: 10,
|
||||
2: 20,
|
||||
})
|
||||
t.AssertIN("1", keys)
|
||||
t.AssertIN("2", keys)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
A string
|
||||
B int
|
||||
}
|
||||
keys := gutil.Keys(new(T))
|
||||
t.Assert(keys, g.SliceStr{"A", "B"})
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
A string
|
||||
B int
|
||||
}
|
||||
var pointer *T
|
||||
keys := gutil.Keys(pointer)
|
||||
t.Assert(keys, g.SliceStr{"A", "B"})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Values(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
values := gutil.Keys(map[int]int{
|
||||
1: 10,
|
||||
2: 20,
|
||||
})
|
||||
t.AssertIN("1", values)
|
||||
t.AssertIN("2", values)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
type T struct {
|
||||
A string
|
||||
B int
|
||||
}
|
||||
keys := gutil.Values(T{
|
||||
A: "1",
|
||||
B: 2,
|
||||
})
|
||||
t.Assert(keys, g.Slice{"1", 2})
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user