mirror of
https://gitee.com/johng/gf
synced 2026-06-12 12:13:22 +08:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc0a385c22 | |||
| 0b8c9713e6 | |||
| e400a94ffb | |||
| 136ad3b0b5 | |||
| f9826104d8 | |||
| c0c97b76fb | |||
| a908e4d4b1 | |||
| 055074246e | |||
| 4bbe51fb4b | |||
| 841224372b | |||
| d1f0fa1a47 | |||
| 6fecf8bb01 | |||
| 6d44f02a38 | |||
| 1458e486d7 | |||
| dc29822e69 | |||
| dae7722da1 | |||
| 16d978dc58 | |||
| 5d3c154b45 | |||
| 00a8ef63b6 | |||
| 6ac437a3a5 | |||
| 33b24eba01 | |||
| 3a99c6e5f5 | |||
| 4c5d2839bd | |||
| 85b104bafa | |||
| 74e5d03a78 | |||
| cc43324ede | |||
| e4f9e1000d | |||
| 62829b0698 | |||
| 2551e990cb | |||
| 45c34319b5 | |||
| 75dcc566b3 | |||
| 4f1047e853 | |||
| 79f765c961 | |||
| eead2fad2c | |||
| 455c9e09ab | |||
| 4665c3565c | |||
| 7456b4b4ad | |||
| 32a6454065 | |||
| c52640c672 | |||
| d62ef17290 | |||
| 216af6a662 | |||
| 35d860427e | |||
| 9206574bae | |||
| 9c6f54131f | |||
| d67b95c593 | |||
| 2bf2f1b822 | |||
| 9ad94eccad | |||
| 6e8a900f25 | |||
| d9aa9e4480 | |||
| 7335126064 | |||
| 945dd71251 | |||
| 652aa29370 | |||
| 7034e2015e | |||
| 0a890ad871 | |||
| b52bb1124e | |||
| 41f33af51b | |||
| 6b4763c7da | |||
| be07889a45 | |||
| 1b3243c09c | |||
| fee1c9eccf | |||
| 084f6c31cb | |||
| 592bf76eb0 | |||
| 417ce4b470 | |||
| 48deaa5f57 | |||
| 635d228c86 | |||
| 303d03d43c | |||
| 50f561dbd2 |
21
DONATOR.MD
Normal file
21
DONATOR.MD
Normal file
@ -0,0 +1,21 @@
|
||||
# Donators
|
||||
|
||||
|
||||
| Name | Channel | Amount
|
||||
|---|---|---
|
||||
|[hailaz](https://gitee.com/hailaz)|gitee|¥20.00
|
||||
|[ireadx](https://github.com/ireadx)|alipay|¥201.00
|
||||
|[mg91](https://gitee.com/mg91)|gitee|¥10.00
|
||||
|[pibigstar](https://github.com/pibigstar)|alipay|¥10.00
|
||||
|[tiangenglan](https://gitee.com/tiangenglan)|gitee|¥30.00
|
||||
|[wxkj](https://gitee.com/wxkj)|wechat|¥10.00
|
||||
|[zhuhuan12](https://gitee.com/zhuhuan12)|gitee|¥50.00
|
||||
|[zfan_codes](https://gitee.com/zfan_codes)|gitee|¥10.00
|
||||
|[arden](https://github.com/arden)|alipay|¥10.00
|
||||
|潘兄|wechat|¥100.00
|
||||
|土豆相公|alipay|¥66.60
|
||||
|上海金保证网络科技|bank|¥2000.00
|
||||
|
||||
|
||||
|
||||
<img src="https://goframe.org/images/donate.png"/>
|
||||
42
README.MD
42
README.MD
@ -68,50 +68,18 @@ func main() {
|
||||
|
||||
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
|
||||
|
||||
# Contributors
|
||||
|
||||
- [aloncn](https://github.com/aloncn)
|
||||
- [chenyang351](https://github.com/chenyang351)
|
||||
- [garfieldkwong](https://gitee.com/garfieldkwong)
|
||||
- [hailaz](https://gitee.com/hailaz)
|
||||
- [johng](https://johng.cn)
|
||||
- [jroam](https://github.com/jroam)
|
||||
- [pibigstar](https://github.com/pibigstar)
|
||||
- [qq1054000800](https://gitee.com/qq1054000800)
|
||||
- [qq976739120](https://github.com/qq976739120)
|
||||
- [touzijiao](https://github.com/touzijiao)
|
||||
- [wenzi1](https://gitee.com/wenzi1)
|
||||
- [wxkj001](https://github.com/wxkj001)
|
||||
- [ymrjqyy](https://gitee.com/ymrjqyy)
|
||||
- [youyixiao](https://github.com/youyixiao)
|
||||
- [zhangjinfu](https://gitee.com/zhangjinfu)
|
||||
- [zhaopengme](https://github.com/zhaopengme)
|
||||
- [zseeker](https://gitee.com/zseeker)
|
||||
|
||||
# Donators
|
||||
|
||||
We currently accept donation by Alipay/WechatPay, please note your github/gitee account in your payment bill. If you like `GF`, why not [buy developer a cup of coffee](https://goframe.org/images/donate.png)?
|
||||
|
||||
- [flyke-xu](https://gitee.com/flyke-xu)
|
||||
- [hailaz](https://gitee.com/hailaz)
|
||||
- [ireadx](https://github.com/ireadx)
|
||||
- [mg91](https://gitee.com/mg91)
|
||||
- [pibigstar](https://github.com/pibigstar)
|
||||
- [tiangenglan](https://gitee.com/tiangenglan)
|
||||
- [wxkj](https://gitee.com/wxkj)
|
||||
- [zhuhuan12](https://gitee.com/zhuhuan12)
|
||||
- [zfan_codes](https://gitee.com/zfan_codes)
|
||||
|
||||
|
||||
|
||||
|
||||
We currently accept donation by Alipay/WechatPay, please note your github/gitee account in your payment bill. If you like `GF`, why not [buy developer a cup of coffee](DONATOR.MD)?
|
||||
|
||||
# Thanks
|
||||
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/images/jetbrains.png" width="100" alt="JetBrains"/></a>
|
||||
|
||||
|
||||
<!--
|
||||
# Sponsor
|
||||
|
||||
We appreciate any kind of sponsorship for `GF` development. If you've got some interested, please contact john@goframe.org.
|
||||
|
||||
-->
|
||||
|
||||
|
||||
|
||||
|
||||
42
README_ZH.MD
42
README_ZH.MD
@ -17,7 +17,7 @@
|
||||
|
||||
# 特点
|
||||
* 模块化、松耦合设计;
|
||||
* 丰富实用的开发模块;
|
||||
* 模块丰富,开箱即用;
|
||||
* 详尽的开发文档及示例;
|
||||
* 完善的本地中文化支持;
|
||||
* 致力于项目的通用方案;
|
||||
@ -79,42 +79,8 @@ func main() {
|
||||
|
||||
# 捐赠
|
||||
|
||||
如果您喜欢`GF`,要不[给开发者来杯咖啡吧](https://goframe.org/images/donate.png)!
|
||||
如果您喜欢`GF`,要不[给开发者来杯咖啡吧](DONATOR.MD)!
|
||||
请在捐赠时备注您的`github`/`gitee`账号名称。
|
||||
|
||||
# 赞助
|
||||
|
||||
赞助支持`GF`框架的快速研发,如果您感兴趣,请联系 john@goframe.org 。
|
||||
|
||||
# 贡献者
|
||||
|
||||
- [aloncn](https://github.com/aloncn)
|
||||
- [chenyang351](https://github.com/chenyang351)
|
||||
- [garfieldkwong](https://gitee.com/garfieldkwong)
|
||||
- [hailaz](https://gitee.com/hailaz)
|
||||
- [johng](https://johng.cn)
|
||||
- [jroam](https://github.com/jroam)
|
||||
- [pibigstar](https://github.com/pibigstar)
|
||||
- [qq1054000800](https://gitee.com/qq1054000800)
|
||||
- [qq976739120](https://github.com/qq976739120)
|
||||
- [touzijiao](https://github.com/touzijiao)
|
||||
- [wenzi1](https://gitee.com/wenzi1)
|
||||
- [wxkj001](https://github.com/wxkj001)
|
||||
- [ymrjqyy](https://gitee.com/ymrjqyy)
|
||||
- [youyixiao](https://github.com/youyixiao)
|
||||
- [zhangjinfu](https://gitee.com/zhangjinfu)
|
||||
- [zhaopengme](https://github.com/zhaopengme)
|
||||
- [zseeker](https://gitee.com/zseeker)
|
||||
|
||||
# 捐赠者
|
||||
|
||||
- [flyke-xu](https://gitee.com/flyke-xu)
|
||||
- [hailaz](https://gitee.com/hailaz)
|
||||
- [ireadx](https://github.com/ireadx)
|
||||
- [mg91](https://gitee.com/mg91)
|
||||
- [pibigstar](https://github.com/pibigstar)
|
||||
- [tiangenglan](https://gitee.com/tiangenglan)
|
||||
- [wxkj](https://gitee.com/wxkj)
|
||||
- [zhuhuan12](https://gitee.com/zhuhuan12)
|
||||
- [zfan_codes](https://gitee.com/zfan_codes)
|
||||
|
||||
# 感谢
|
||||
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/images/jetbrains.png" width="100" alt="JetBrains"/></a>
|
||||
60
RELEASE.MD
60
RELEASE.MD
@ -1,3 +1,63 @@
|
||||
# `v1.7.0`
|
||||
## 新功能/改进
|
||||
1. 重构改进`glog`模块:
|
||||
- 去掉日志模块所有的锁机制,改为无锁设计,执行性能更加高效
|
||||
- 增加日志内容的异步输出特性:https://goframe.org/os/glog/async
|
||||
- 增加日志输出内容的`Json`格式支持:https://goframe.org/os/glog/json
|
||||
- 增加`Flags`额外特性支持,包括文件行号打印、自定义时间格式、异步输出等特性控制:https://goframe.org/os/glog/flags
|
||||
- 增加`Writer`接口支持,便于开发者进行自定义的日志功能扩展,或者与第三方服务/模块对接集成:https://goframe.org/os/glog/writer
|
||||
- 修改`SetStdPrint`方法名为`SetStdoutPrint`
|
||||
- 修改链式方法`StdPrint`方法名为`Stdout`
|
||||
- 标记淘汰`*fln`日志输出方法,`*f`方法支持自动的换行输出
|
||||
- 新增更多的链式方法支持:https://goframe.org/os/glog/chain
|
||||
1. 重构改进`gmap`模块:
|
||||
- 增加更多数据格式支持:`HashMap`/`ListMap`/`TreeMap`
|
||||
- 简化类型名称,如`gmap.StringInterfaceMap`简化为`gmap.StrAnyMap`
|
||||
- 改进`Map/Keys/Values`方法以提高性能
|
||||
- 修改`BatchSet`/`BatchRemove`方法名为`Sets`/`Removes`
|
||||
- 新增更多功能方法支持:https://goframe.org/container/gmap/index
|
||||
1. 改进`gtime`时间模块:
|
||||
- 增加并完善更多的类`PHP`时间格式支持
|
||||
- 新增更多功能方法,如`FormatTo`/`LayoutTo`等等
|
||||
- 详见开发文档:https://goframe.org/os/gtime/index
|
||||
1. 改进`gdb`数据库模块:
|
||||
- 增加对继承结构体的数据转换支持:https://goframe.org/database/gdb/senior
|
||||
- 新增`GetLastSql`方法,用以在调试模式下获取最近一条执行的SQL语句
|
||||
- 其他的细节处理改进
|
||||
1. 改进`gtcp`通信模块:
|
||||
- 完善处理细节,提高通信性能;
|
||||
- 增加`TLS`服务端/客户端通信支持:https://goframe.org/net/gtcp/tls
|
||||
- 增加简单协议支持,便于开发者封包/解包,并解决粘包/半包问题:https://goframe.org/net/gtcp/conn/pkg
|
||||
- TCP服务端增加`Close`方法
|
||||
- 更多细节查看开发文档:https://goframe.org/net/gtcp/index
|
||||
1. 改进`gconv`类型转换模块
|
||||
- 修改`gconv.TimeDuration`转换方法名称为`gconv.Duration`
|
||||
- 新增`gconv.StructDeep`及`gconv.MapDeep`方法,支持递归转换
|
||||
- 详见开发文档:https://goframe.org/util/gconv/struct
|
||||
1. 改进`ghttp`模块:
|
||||
- 日志输出增加`http/https`字段:https://goframe.org/net/ghttp/logs
|
||||
- 新增`ghttp.Server.SetKeepAlive`设置方法,用以开启/关闭`KeepAlive`特性
|
||||
- 增加`ghttp.Request.GetUrl`方法,用以获取当前完整的URL请求地址
|
||||
- `ghttp.Client`客户端支持开发者自定义`Transport`属性,`ghttp.Client.Post`方法支持`浏览器模式`:https://goframe.org/net/ghttp/client
|
||||
1. 新增`gtree`树形数据结构容器支持:https://goframe.org/container/gtree/index
|
||||
1. 改进`gudp`通信模块,具体请参考开发文档:https://goframe.org/net/gudp/index
|
||||
1. 改进`gcfg`配置管理模块,所有`Get*`方法增加默认值支持:https://goframe.org/os/gcfg/index
|
||||
1. `gredis`模块新增`DoVar`/`ReceiveVar`方法以便于开发者对执行结果进行灵活的数据格式转换:https://goframe.org/database/gredis/index
|
||||
1. `gcache`模块`BatchSet`/`BatchRemove`方法名修改为`Sets`/`Removes`
|
||||
1. 改进`gjson`/`gparser`模块,增加更多方法:https://goframe.org/encoding/gjson/index
|
||||
1. 改进`gfile.MainPkgPath`方法,以支持不同平台的开发环境;
|
||||
1. 改进`grpool`协程池模块,提高执行性能:https://goframe.org/os/grpool/index
|
||||
1. 改进`TryCatch`方法,当开发者不传递`Catch`参数时,默认抑制并忽略错误的处理
|
||||
1. 改进`gmlock`模块,增加`TryLockFunc`/`TryRLockFunc`方法,并且为`gmlock.Mutex`高级互斥锁对象增加`TryLockFunc`/`TryRLockFunc`方法
|
||||
1. 去除`gvar.VarRead`接口类型支持
|
||||
|
||||
## Bug Fix
|
||||
1. 解决`gdb`模块与其他第三方`ORM`模块同时使用的冲突;
|
||||
1. 修复`gcron.AddOnce`方法的细节逻辑问题;
|
||||
1. 修复内部`empty`模块的`IsEmpty`方法对结构体属性的空校验错误;
|
||||
1. 修复`gview`模板引擎的并发安全问题;
|
||||
1. 修复`ghttp.Server`的SESSION初始化过期时间问题;
|
||||
|
||||
# `v1.6.0` (2019-04-09)
|
||||
|
||||
## 新功能/改进
|
||||
|
||||
9
TODO.MD
9
TODO.MD
@ -6,7 +6,6 @@
|
||||
1. orm增加sqlite对Save方法的支持(去掉触发器语句);
|
||||
1. ghttp.Server增加Ip访问控制功能(DenyIps&AllowIps);
|
||||
1. ghttp增加返回数据压缩机制;
|
||||
1. gview中的template标签失效问题;
|
||||
1. ghttp.Server增加proxy功能特性,本地proxy和远程proxy,本地即将路由规则映射;远程即反向代理;
|
||||
1. gjson对大json数据的解析效率问题;
|
||||
1. ghttp增加route name特性,并同时支持backend和template(提供内置函数)引用,可以通过RedirectRoute方法给定route name和路由参数跳转到指定的路由地址上;
|
||||
@ -46,6 +45,11 @@
|
||||
1. gset.Add/Remove/Contains方法增加批量操作支持;
|
||||
1. gmlock增加手动清理机制:当内存锁不再使用时,由调用端决定是否清理内存锁;
|
||||
1. gtimer增加DelayAdd*方法返回Entry对象,以便DelayAdd*的定时任务也能进行状态控制;gcron同理需要改进;
|
||||
1. 改进gdb对pgsql/mssql/oracle的支持,使用方法覆盖的方式改进操作,而不是完全依靠正则替换的方式;
|
||||
1. gdb的Cache缓存功能增加可自定义缓存接口,以便支持外部缓存功能,缓存接口可以通过io.ReadWriter接口实现;
|
||||
1. grpool增加支持阻塞添加任务接口;
|
||||
|
||||
|
||||
|
||||
# DONE
|
||||
1. gconv完善针对不同类型的判断,例如:尽量减少sprintf("%v", xxx)来执行string类型的转换;
|
||||
@ -119,4 +123,5 @@
|
||||
1. gfile对于文件的读写强行使用了gfpool,在某些场景下不合适,需要考虑剥离开,并为开发者提供单独的指针池文件操作特性;
|
||||
1. ghttp.Client自动Close机制;
|
||||
1. ghttp路由功能增加分组路由特性;
|
||||
1. 增加可选择性的orm tag特性,用以数据表记录与struct对象转换的键名属性映射;
|
||||
1. 增加可选择性的orm tag特性,用以数据表记录与struct对象转换的键名属性映射;
|
||||
1. gview中的template标签失效问题;
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gchan provides graceful channel for safe operations.
|
||||
// Package gchan provides graceful channel for no panic operations.
|
||||
//
|
||||
// It's safe to call Chan.Push/Close functions repeatedly.
|
||||
package gchan
|
||||
@ -14,12 +14,13 @@ import (
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
)
|
||||
|
||||
// Graceful channel.
|
||||
type Chan struct {
|
||||
channel chan interface{}
|
||||
closed *gtype.Bool
|
||||
}
|
||||
|
||||
// New creates a graceful channel with given limit.
|
||||
// New creates a graceful channel with given <limit>.
|
||||
func New(limit int) *Chan {
|
||||
return &Chan {
|
||||
channel : make(chan interface{}, limit),
|
||||
@ -31,7 +32,7 @@ func New(limit int) *Chan {
|
||||
// It is safe to be called repeatedly.
|
||||
func (c *Chan) Push(value interface{}) error {
|
||||
if c.closed.Val() {
|
||||
return errors.New("closed")
|
||||
return errors.New("channel is closed")
|
||||
}
|
||||
c.channel <- value
|
||||
return nil
|
||||
@ -39,6 +40,7 @@ func (c *Chan) Push(value interface{}) error {
|
||||
|
||||
// Pop pops value from channel.
|
||||
// If there's no value in channel, it would block to wait.
|
||||
// If the channel is closed, it will return a nil value immediately.
|
||||
func (c *Chan) Pop() interface{} {
|
||||
return <- c.channel
|
||||
}
|
||||
|
||||
@ -209,6 +209,11 @@ func (l *List) Len() (length int) {
|
||||
return
|
||||
}
|
||||
|
||||
// Alias of Len.
|
||||
func (l *List) Size() int {
|
||||
return l.Len()
|
||||
}
|
||||
|
||||
// MoveBefore moves element <e> to its new position before <p>.
|
||||
// If <e> or <p> is not an element of <l>, or <e> == <p>, the list is not modified.
|
||||
// The element and <p> must not be nil.
|
||||
|
||||
@ -43,10 +43,11 @@ type ExpireFunc func(interface{})
|
||||
|
||||
// New returns a new object pool.
|
||||
// To ensure execution efficiency, the expiration time cannot be modified once it is set.
|
||||
// Expire:
|
||||
//
|
||||
// Expiration logistics:
|
||||
// expire = 0 : not expired;
|
||||
// expire < 0 : immediate recovery after use;
|
||||
// expire > 0 : timeout recovery;
|
||||
// expire < 0 : immediate expired after use;
|
||||
// expire > 0 : timeout expired;
|
||||
// Note that the expiration time unit is ** milliseconds **.
|
||||
func New(expire int, newFunc NewFunc, expireFunc...ExpireFunc) *Pool {
|
||||
r := &Pool {
|
||||
@ -103,14 +104,26 @@ func (p *Pool) Size() int {
|
||||
return p.list.Len()
|
||||
}
|
||||
|
||||
// Close closes the pool.
|
||||
// Close closes the pool. If <p> has ExpireFunc,
|
||||
// then it automatically closes all items using this function before it's closed.
|
||||
func (p *Pool) Close() {
|
||||
p.closed.Set(true)
|
||||
}
|
||||
|
||||
// checkExpire secondly removes expired items from pool.
|
||||
// checkExpire removes expired items from pool every second.
|
||||
func (p *Pool) checkExpire() {
|
||||
if p.closed.Val() {
|
||||
// If p has ExpireFunc,
|
||||
// then it must close all items using this function.
|
||||
if p.ExpireFunc != nil {
|
||||
for {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
p.ExpireFunc(r.(*poolItem).value)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
gtimer.Exit()
|
||||
}
|
||||
for {
|
||||
|
||||
@ -6,14 +6,15 @@
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gpool
|
||||
package gpool_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"github.com/gogf/gf/g/container/gpool"
|
||||
"testing"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var pool = New(99999999, nil)
|
||||
var pool = gpool.New(99999999, nil)
|
||||
var syncp = sync.Pool{}
|
||||
|
||||
func BenchmarkGPoolPut(b *testing.B) {
|
||||
@ -37,8 +37,8 @@ const (
|
||||
)
|
||||
|
||||
// New returns an empty queue object.
|
||||
// Optional parameter <limit> is used to limit the size of the queue, which is unlimited by default.
|
||||
// When <limit> is given, the queue will be static and high performance which is comparable with stdlib chan.
|
||||
// Optional parameter <limit> is used to limit the size of the queue, which is unlimited in default.
|
||||
// When <limit> is given, the queue will be static and high performance which is comparable with stdlib channel.
|
||||
func New(limit...int) *Queue {
|
||||
q := &Queue {
|
||||
closed : make(chan struct{}, 0),
|
||||
@ -103,7 +103,7 @@ func (q *Queue) Pop() interface{} {
|
||||
|
||||
// Close closes the queue.
|
||||
// Notice: It would notify all goroutines return immediately,
|
||||
// which are being blocked reading by Pop method.
|
||||
// which are being blocked reading using Pop method.
|
||||
func (q *Queue) Close() {
|
||||
close(q.C)
|
||||
close(q.events)
|
||||
|
||||
@ -80,9 +80,9 @@ func (v *Var) GTime(format...string) *gtime.Time {
|
||||
|
||||
// Struct maps value of <v> to <objPointer>.
|
||||
// The param <objPointer> should be a pointer to a struct instance.
|
||||
// The param <attrMapping> is used to specify the key-to-attribute mapping rules.
|
||||
func (v *Var) Struct(objPointer interface{}, attrMapping...map[string]string) error {
|
||||
return gconv.Struct(v.Val(), objPointer, attrMapping...)
|
||||
// The param <mapping> is used to specify the key-to-attribute mapping rules.
|
||||
func (v *Var) Struct(pointer interface{}, mapping...map[string]string) error {
|
||||
return gconv.Struct(v.Val(), pointer, mapping...)
|
||||
}
|
||||
|
||||
func (v *Var) IsNil() bool { return v.Val() == nil }
|
||||
|
||||
@ -25,18 +25,18 @@ func Encrypt(plainText []byte, key []byte, iv...[]byte) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
blockSize := block.BlockSize()
|
||||
plainText = PKCS5Padding(plainText, blockSize)
|
||||
ivValue := ([]byte)(nil)
|
||||
plainText = PKCS5Padding(plainText, blockSize)
|
||||
ivValue := ([]byte)(nil)
|
||||
if len(iv) > 0 {
|
||||
ivValue = iv[0]
|
||||
} else {
|
||||
ivValue = []byte(ivDefValue)
|
||||
}
|
||||
blockMode := cipher.NewCBCEncrypter(block, ivValue)
|
||||
ciphertext := make([]byte, len(plainText))
|
||||
blockMode.CryptBlocks(ciphertext, plainText)
|
||||
cipherText := make([]byte, len(plainText))
|
||||
blockMode.CryptBlocks(cipherText, plainText)
|
||||
|
||||
return ciphertext, nil
|
||||
return cipherText, nil
|
||||
}
|
||||
|
||||
// AES解密, 使用CBC模式,注意key必须为16/24/32位长度,iv初始化向量为非必需参数
|
||||
@ -65,7 +65,6 @@ func Decrypt(cipherText []byte, key []byte, iv...[]byte) ([]byte, error) {
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return plainText, nil
|
||||
}
|
||||
|
||||
|
||||
@ -4,17 +4,26 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gcrc32 provides useful API for CRC32 encryption/decryption algorithms.
|
||||
// Package gcrc32 provides useful API for CRC32 encryption algorithms.
|
||||
package gcrc32
|
||||
|
||||
import (
|
||||
"hash/crc32"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"hash/crc32"
|
||||
)
|
||||
|
||||
// Encrypt encrypts any type of variable using CRC32 algorithms.
|
||||
// It uses gconv package to convert <v> to its bytes type.
|
||||
func Encrypt(v interface{}) uint32 {
|
||||
return crc32.ChecksumIEEE(gconv.Bytes(v))
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func EncryptString(v string) uint32 {
|
||||
return crc32.ChecksumIEEE([]byte(v))
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
func EncryptBytes(v []byte) uint32 {
|
||||
return crc32.ChecksumIEEE(v)
|
||||
}
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
// 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.
|
||||
// @author: wenzi1<liyz23@qq.com>
|
||||
//
|
||||
// @author wenzi1<liyz23@qq.com>
|
||||
|
||||
// Package gdes provides useful API for DES encryption/decryption algorithms.
|
||||
package gdes
|
||||
@ -16,11 +17,11 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
NOPADDING = iota
|
||||
NOPADDING = iota
|
||||
PKCS5PADDING
|
||||
)
|
||||
|
||||
//ECB模式DES加密
|
||||
// ECB模式DES加密
|
||||
func DesECBEncrypt(key []byte, clearText []byte, padding int) ([]byte, error) {
|
||||
text, err := Padding(clearText, padding)
|
||||
if err != nil {
|
||||
@ -42,7 +43,7 @@ func DesECBEncrypt(key []byte, clearText []byte, padding int) ([]byte, error) {
|
||||
return cipherText, nil
|
||||
}
|
||||
|
||||
//ECB模式DES解密
|
||||
// ECB模式DES解密
|
||||
func DesECBDecrypt(key []byte, cipherText []byte, padding int) ([]byte, error) {
|
||||
text := make([]byte, len(cipherText))
|
||||
block, err := des.NewCipher(key)
|
||||
@ -63,7 +64,7 @@ func DesECBDecrypt(key []byte, cipherText []byte, padding int) ([]byte, error) {
|
||||
return clearText, nil
|
||||
}
|
||||
|
||||
//ECB模式3DES加密,密钥长度可以是16或24位长
|
||||
// ECB模式3DES加密,密钥长度可以是16或24位长
|
||||
func TripleDesECBEncrypt(key []byte, clearText []byte, padding int) ( []byte, error) {
|
||||
if len(key) != 16 && len(key) != 24 {
|
||||
return nil, errors.New("key length error")
|
||||
@ -96,7 +97,7 @@ func TripleDesECBEncrypt(key []byte, clearText []byte, padding int) ( []byte, er
|
||||
return cipherText, nil
|
||||
}
|
||||
|
||||
//ECB模式3DES解密,密钥长度可以是16或24位长
|
||||
// ECB模式3DES解密,密钥长度可以是16或24位长
|
||||
func TripleDesECBDecrypt(key []byte, cipherText []byte, padding int) ([]byte, error) {
|
||||
if len(key) != 16 && len(key) != 24 {
|
||||
return nil, errors.New("key length error")
|
||||
@ -129,7 +130,7 @@ func TripleDesECBDecrypt(key []byte, cipherText []byte, padding int) ([]byte, e
|
||||
return clearText, nil
|
||||
}
|
||||
|
||||
//CBC模式DES加密
|
||||
// CBC模式DES加密
|
||||
func DesCBCEncrypt(key []byte, clearText []byte, iv []byte, padding int) ([]byte, error) {
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
@ -152,7 +153,7 @@ func DesCBCEncrypt(key []byte, clearText []byte, iv []byte, padding int) ([]byte
|
||||
return cipherText, nil
|
||||
}
|
||||
|
||||
//CBC模式DES解密
|
||||
// CBC模式DES解密
|
||||
func DesCBCDecrypt(key []byte, cipherText []byte, iv []byte, padding int) ([]byte, error) {
|
||||
block, err := des.NewCipher(key)
|
||||
if err != nil {
|
||||
@ -175,7 +176,7 @@ func DesCBCDecrypt(key []byte, cipherText []byte, iv []byte, padding int) ([]byt
|
||||
return clearText, nil
|
||||
}
|
||||
|
||||
//CBC模式3DES加密
|
||||
// CBC模式3DES加密
|
||||
func TripleDesCBCEncrypt(key []byte, clearText []byte, iv []byte, padding int) ([]byte, error) {
|
||||
if len(key) != 16 && len(key) != 24 {
|
||||
return nil, errors.New("key length invalid")
|
||||
@ -210,7 +211,7 @@ func TripleDesCBCEncrypt(key []byte, clearText []byte, iv []byte, padding int) (
|
||||
return cipherText, nil
|
||||
}
|
||||
|
||||
//CBC模式3DES解密
|
||||
// CBC模式3DES解密
|
||||
func TripleDesCBCDecrypt(key []byte, cipherText []byte, iv []byte, padding int) ( []byte, error) {
|
||||
if len(key) != 16 && len(key) != 24 {
|
||||
return nil, errors.New("key length invalid")
|
||||
@ -245,21 +246,21 @@ func TripleDesCBCDecrypt(key []byte, cipherText []byte, iv []byte, padding int)
|
||||
return clearText, nil
|
||||
}
|
||||
|
||||
//PKCS5补位
|
||||
// PKCS5补位
|
||||
func PKCS5Padding(text []byte, blockSize int) []byte {
|
||||
padding := blockSize - len(text) % blockSize
|
||||
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
|
||||
return append(text, padtext...)
|
||||
}
|
||||
|
||||
//去除PKCS5补位
|
||||
// 去除PKCS5补位
|
||||
func PKCS5Unpadding(text []byte) []byte{
|
||||
length := len(text)
|
||||
padtext := int(text[length - 1])
|
||||
return text[:(length - padtext)]
|
||||
}
|
||||
|
||||
//补位方法
|
||||
// 补位方法
|
||||
func Padding(text []byte, padding int)([]byte, error) {
|
||||
switch padding {
|
||||
case NOPADDING:
|
||||
@ -275,7 +276,7 @@ func Padding(text []byte, padding int)([]byte, error) {
|
||||
return text, nil
|
||||
}
|
||||
|
||||
//去除补位方法
|
||||
// 去除补位方法
|
||||
func UnPadding(text []byte, padding int)([]byte, error) {
|
||||
switch padding {
|
||||
case NOPADDING:
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gmd5 provides useful API for MD5 encryption/decryption algorithms.
|
||||
// Package gmd5 provides useful API for MD5 encryption algorithms.
|
||||
package gmd5
|
||||
|
||||
import (
|
||||
@ -15,28 +15,31 @@ import (
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
// 将任意类型的变量进行md5摘要(注意map等非排序变量造成的不同结果)
|
||||
// Encrypt encrypts any type of variable using MD5 algorithms.
|
||||
// It uses gconv package to convert <v> to its bytes type.
|
||||
func Encrypt(v interface{}) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(gconv.Bytes(v)))
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
// 将字符串进行MD5哈希摘要计算
|
||||
|
||||
// Deprecated.
|
||||
func EncryptString(v string) string {
|
||||
h := md5.New()
|
||||
h.Write([]byte(v))
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
h := md5.New()
|
||||
h.Write([]byte(v))
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
// 将文件内容进行MD5哈希摘要计算
|
||||
|
||||
// EncryptFile encrypts file content of <path> using MD5 algorithms.
|
||||
func EncryptFile(path string) string {
|
||||
f, e := os.Open(path)
|
||||
if e != nil {
|
||||
return ""
|
||||
}
|
||||
defer f.Close()
|
||||
h := md5.New()
|
||||
h := md5.New()
|
||||
_, e = io.Copy(h, f)
|
||||
if e != nil {
|
||||
return ""
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gsha1 provides useful API for SHA1 encryption/decryption algorithms.
|
||||
// Package gsha1 provides useful API for SHA1 encryption algorithms.
|
||||
package gsha1
|
||||
|
||||
import (
|
||||
@ -15,20 +15,20 @@ import (
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
// 将任意类型的变量进行SHA摘要(注意map等非排序变量造成的不同结果)
|
||||
// 内部使用了md5计算,因此效率会稍微差一些,更多情况请使用 EncodeString
|
||||
// Encrypt encrypts any type of variable using SHA1 algorithms.
|
||||
// It uses gconv package to convert <v> to its bytes type.
|
||||
func Encrypt(v interface{}) string {
|
||||
r := sha1.Sum(gconv.Bytes(v))
|
||||
return hex.EncodeToString(r[:])
|
||||
}
|
||||
|
||||
// 对字符串行SHA1摘要计算
|
||||
// Deprecated.
|
||||
func EncryptString(s string) string {
|
||||
r := sha1.Sum([]byte(s))
|
||||
return hex.EncodeToString(r[:])
|
||||
r := sha1.Sum([]byte(s))
|
||||
return hex.EncodeToString(r[:])
|
||||
}
|
||||
|
||||
// 对文件内容进行SHA1摘要计算
|
||||
// EncryptFile encrypts file content of <path> using SHA1 algorithms.
|
||||
func EncryptFile(path string) string {
|
||||
f, e := os.Open(path)
|
||||
if e != nil {
|
||||
|
||||
@ -84,6 +84,7 @@ type DB interface {
|
||||
SetDebug(debug bool)
|
||||
SetSchema(schema string)
|
||||
GetQueriedSqls() []*Sql
|
||||
GetLastSql() *Sql
|
||||
PrintQueriedSqls()
|
||||
SetMaxIdleConns(n int)
|
||||
SetMaxOpenConns(n int)
|
||||
|
||||
@ -24,6 +24,17 @@ const (
|
||||
gDEFAULT_DEBUG_SQL_LENGTH = 1000 // 默认调试模式下记录的SQL条数
|
||||
)
|
||||
|
||||
// 获取最近一条执行的sql
|
||||
func (bs *dbBase) GetLastSql() *Sql {
|
||||
if bs.sqls == nil {
|
||||
return nil
|
||||
}
|
||||
if v := bs.sqls.Val(); v != nil {
|
||||
return v.(*Sql)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取已经执行的SQL列表(仅在debug=true时有效)
|
||||
func (bs *dbBase) GetQueriedSqls() []*Sql {
|
||||
if bs.sqls == nil {
|
||||
@ -312,7 +323,7 @@ func (bs *dbBase) doInsert(link dbLink, table string, data interface{}, option i
|
||||
return bs.db.doBatchInsert(link, table, data, option, batch...)
|
||||
case reflect.Map: fallthrough
|
||||
case reflect.Struct:
|
||||
dataMap = gconv.Map(data)
|
||||
dataMap = structToMap(data)
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
|
||||
}
|
||||
@ -390,11 +401,11 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt
|
||||
case reflect.Array:
|
||||
listMap = make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
listMap[i] = gconv.Map(rv.Index(i).Interface())
|
||||
listMap[i] = structToMap(rv.Index(i).Interface())
|
||||
}
|
||||
case reflect.Map: fallthrough
|
||||
case reflect.Struct:
|
||||
listMap = List{Map(gconv.Map(list))}
|
||||
listMap = List{Map(structToMap(list))}
|
||||
default:
|
||||
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
|
||||
}
|
||||
@ -504,7 +515,7 @@ func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, conditio
|
||||
case reflect.Map: fallthrough
|
||||
case reflect.Struct:
|
||||
var fields []string
|
||||
for k, v := range gconv.Map(data) {
|
||||
for k, v := range structToMap(data) {
|
||||
fields = append(fields, fmt.Sprintf("%s%s%s=?", charL, k, charR))
|
||||
params = append(params, convertParam(v))
|
||||
}
|
||||
|
||||
@ -7,19 +7,24 @@
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"reflect"
|
||||
"strings"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Type assert api for String().
|
||||
type apiString interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
// 格式化SQL查询条件
|
||||
func formatCondition(where interface{}, args []interface{}) (newWhere string, newArgs []interface{}) {
|
||||
// 条件字符串处理
|
||||
@ -36,7 +41,7 @@ func formatCondition(where interface{}, args []interface{}) (newWhere string, ne
|
||||
// map/struct类型
|
||||
case reflect.Map: fallthrough
|
||||
case reflect.Struct:
|
||||
for key, value := range gconv.Map(where) {
|
||||
for key, value := range structToMap(where) {
|
||||
if buffer.Len() > 0 {
|
||||
buffer.WriteString(" AND ")
|
||||
}
|
||||
@ -137,10 +142,19 @@ func convertParam(value interface{}) interface{} {
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
// 底层数据库引擎支持 time.Time 类型
|
||||
if _, ok := value.(time.Time); ok {
|
||||
// 底层数据库引擎支持 time.Time/*time.Time 类型
|
||||
if v, ok := value.(time.Time); ok {
|
||||
if v.IsZero() {
|
||||
return "null"
|
||||
}
|
||||
return value
|
||||
}
|
||||
if v, ok := value.(*time.Time); ok {
|
||||
if v.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return value
|
||||
}
|
||||
return gconv.String(value)
|
||||
}
|
||||
return value
|
||||
@ -165,12 +179,12 @@ func printSql(v *Sql) {
|
||||
// 格式化错误信息
|
||||
func formatError(err error, query string, args ...interface{}) error {
|
||||
if err != nil {
|
||||
errstr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
|
||||
errstr += fmt.Sprintf("DB QUERY: %s\n", query)
|
||||
errStr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
|
||||
errStr += fmt.Sprintf("DB QUERY: %s\n", query)
|
||||
if len(args) > 0 {
|
||||
errstr += fmt.Sprintf("DB PARAM: %v\n", args)
|
||||
errStr += fmt.Sprintf("DB PARAM: %v\n", args)
|
||||
}
|
||||
err = errors.New(errstr)
|
||||
err = errors.New(errStr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@ -187,3 +201,42 @@ func getInsertOperationByOption(option int) string {
|
||||
}
|
||||
return operator
|
||||
}
|
||||
|
||||
// 将对象转换为map,如果对象带有继承对象,那么执行递归转换。
|
||||
// 该方法用于将变量传递给数据库执行之前。
|
||||
func structToMap(obj interface{}) map[string]interface{} {
|
||||
data := gconv.Map(obj)
|
||||
for key, value := range data {
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
// 底层数据库引擎支持 time.Time/*time.Time 类型
|
||||
if _, ok := value.(time.Time); ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := value.(*time.Time); ok {
|
||||
continue
|
||||
}
|
||||
// 如果执行String方法,那么执行字符串转换
|
||||
if s, ok := value.(apiString); ok {
|
||||
data[key] = s.String()
|
||||
continue
|
||||
}
|
||||
delete(data, key)
|
||||
for k, v := range structToMap(value) {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// 使用递归的方式将map键值对映射到struct对象上,注意参数<pointer>是一个指向struct的指针。
|
||||
func mapToStruct(data map[string]interface{}, pointer interface{}) error {
|
||||
return gconv.StructDeep(data, pointer)
|
||||
}
|
||||
@ -268,12 +268,12 @@ func (md *Model) Data(data ...interface{}) *Model {
|
||||
case reflect.Array:
|
||||
list := make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
list[i] = gconv.Map(rv.Index(i).Interface())
|
||||
list[i] = structToMap(rv.Index(i).Interface())
|
||||
}
|
||||
model.data = list
|
||||
case reflect.Map: fallthrough
|
||||
case reflect.Struct:
|
||||
model.data = Map(gconv.Map(data[0]))
|
||||
model.data = Map(structToMap(data[0]))
|
||||
default:
|
||||
model.data = data[0]
|
||||
}
|
||||
|
||||
@ -67,13 +67,13 @@ func (db *dbMssql) parseSql(sql string) string {
|
||||
//下面的正则表达式匹配出SELECT和INSERT的关键字后分别做不同的处理,如有LIMIT则将LIMIT的关键字也匹配出
|
||||
patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
|
||||
if gregex.IsMatchString(patten, sql) == false {
|
||||
fmt.Println("not matched..")
|
||||
//fmt.Println("not matched..")
|
||||
return sql
|
||||
}
|
||||
|
||||
res, err := gregex.MatchAllString(patten, sql)
|
||||
if err != nil {
|
||||
fmt.Println("MatchString error.", err)
|
||||
//fmt.Println("MatchString error.", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -83,69 +83,69 @@ func (db *dbMssql) parseSql(sql string) string {
|
||||
|
||||
index++
|
||||
switch keyword {
|
||||
case "SELECT":
|
||||
//不含LIMIT关键字则不处理
|
||||
if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false && strings.HasPrefix(res[index][0], "limit") == false) {
|
||||
break
|
||||
}
|
||||
|
||||
//不含LIMIT则不处理
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
|
||||
break
|
||||
}
|
||||
|
||||
//判断SQL中是否含有order by
|
||||
selectStr := ""
|
||||
orderbyStr := ""
|
||||
haveOrderby := gregex.IsMatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
|
||||
if haveOrderby {
|
||||
//取order by 前面的字符串
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
|
||||
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "ORDER BY") == false {
|
||||
case "SELECT":
|
||||
//不含LIMIT关键字则不处理
|
||||
if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false && strings.HasPrefix(res[index][0], "limit") == false) {
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
|
||||
//取order by表达式的值
|
||||
orderbyExpr, _ := gregex.MatchString("((?i)ORDER BY)(.+)((?i)LIMIT)", sql)
|
||||
if len(orderbyExpr) != 4 || strings.EqualFold(orderbyExpr[1], "ORDER BY") == false || strings.EqualFold(orderbyExpr[3], "LIMIT") == false {
|
||||
//不含LIMIT则不处理
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
|
||||
break
|
||||
}
|
||||
orderbyStr = orderbyExpr[2]
|
||||
} else {
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
}
|
||||
|
||||
//取limit后面的取值范围
|
||||
first, limit := 0, 0
|
||||
for i := 1; i < len(res[index]); i++ {
|
||||
if len(strings.TrimSpace(res[index][i])) == 0 {
|
||||
continue
|
||||
}
|
||||
//判断SQL中是否含有order by
|
||||
selectStr := ""
|
||||
orderbyStr := ""
|
||||
haveOrderby := gregex.IsMatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
|
||||
if haveOrderby {
|
||||
//取order by 前面的字符串
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
|
||||
|
||||
if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
|
||||
first, _ = strconv.Atoi(res[index][i+1])
|
||||
limit, _ = strconv.Atoi(res[index][i+2])
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "ORDER BY") == false {
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
|
||||
if haveOrderby {
|
||||
sql = fmt.Sprintf("SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROWNUMBER_, %s ) as TMP_ WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d", orderbyStr, selectStr, first, limit)
|
||||
} else {
|
||||
if first == 0 {
|
||||
first = limit
|
||||
//取order by表达式的值
|
||||
orderbyExpr, _ := gregex.MatchString("((?i)ORDER BY)(.+)((?i)LIMIT)", sql)
|
||||
if len(orderbyExpr) != 4 || strings.EqualFold(orderbyExpr[1], "ORDER BY") == false || strings.EqualFold(orderbyExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
orderbyStr = orderbyExpr[2]
|
||||
} else {
|
||||
first = limit - first
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
}
|
||||
sql = fmt.Sprintf("SELECT * FROM (SELECT TOP %d * FROM (SELECT TOP %d %s) as TMP1_ ) as TMP2_ ", first, limit, selectStr)
|
||||
}
|
||||
default:
|
||||
|
||||
//取limit后面的取值范围
|
||||
first, limit := 0, 0
|
||||
for i := 1; i < len(res[index]); i++ {
|
||||
if len(strings.TrimSpace(res[index][i])) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
|
||||
first, _ = strconv.Atoi(res[index][i+1])
|
||||
limit, _ = strconv.Atoi(res[index][i+2])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if haveOrderby {
|
||||
sql = fmt.Sprintf("SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROWNUMBER_, %s ) as TMP_ WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d", orderbyStr, selectStr, first, limit)
|
||||
} else {
|
||||
if first == 0 {
|
||||
first = limit
|
||||
} else {
|
||||
first = limit - first
|
||||
}
|
||||
sql = fmt.Sprintf("SELECT * FROM (SELECT TOP %d * FROM (SELECT TOP %d %s) as TMP1_ ) as TMP2_ ", first, limit, selectStr)
|
||||
}
|
||||
default:
|
||||
}
|
||||
return sql
|
||||
}
|
||||
|
||||
@ -65,80 +65,80 @@ func (db *dbOracle) parseSql(sql string) string {
|
||||
//下面的正则表达式匹配出SELECT和INSERT的关键字后分别做不同的处理,如有LIMIT则将LIMIT的关键字也匹配出
|
||||
patten := `^\s*(?i)(SELECT)|(INSERT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
|
||||
if gregex.IsMatchString(patten, sql) == false {
|
||||
fmt.Println("not matched..")
|
||||
//fmt.Println("not matched..")
|
||||
return sql
|
||||
}
|
||||
|
||||
res, err := gregex.MatchAllString(patten, sql)
|
||||
if err != nil {
|
||||
fmt.Println("MatchString error.", err)
|
||||
//fmt.Println("MatchString error.", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
index := 0
|
||||
index := 0
|
||||
keyword := strings.TrimSpace(res[index][0])
|
||||
keyword = strings.ToUpper(keyword)
|
||||
keyword = strings.ToUpper(keyword)
|
||||
|
||||
index++
|
||||
switch keyword {
|
||||
case "SELECT":
|
||||
//不含LIMIT关键字则不处理
|
||||
if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false && strings.HasPrefix(res[index][0], "limit") == false) {
|
||||
break
|
||||
}
|
||||
|
||||
//取limit前面的字符串
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
|
||||
break
|
||||
}
|
||||
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
|
||||
//取limit后面的取值范围
|
||||
first, limit := 0, 0
|
||||
for i := 1; i < len(res[index]); i++ {
|
||||
if len(strings.TrimSpace(res[index][i])) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
|
||||
first, _ = strconv.Atoi(res[index][i+1])
|
||||
limit, _ = strconv.Atoi(res[index][i+2])
|
||||
case "SELECT":
|
||||
//不含LIMIT关键字则不处理
|
||||
if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false && strings.HasPrefix(res[index][0], "limit") == false) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//也可以使用between,据说这种写法的性能会比between好点,里层SQL中的ROWNUM_ >= limit可以缩小查询后的数据集规模
|
||||
sql = fmt.Sprintf("SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d", queryExpr[1], queryExpr[2], limit, first)
|
||||
case "INSERT":
|
||||
//获取VALUE的值,匹配所有带括号的值,会将INSERT INTO后的值匹配到,所以下面的判断语句会判断数组长度是否小于3
|
||||
valueExpr, err := gregex.MatchAllString(`(\s*\(([^\(\)]*)\))`, sql)
|
||||
if err != nil {
|
||||
return sql
|
||||
}
|
||||
//取limit前面的字符串
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
|
||||
break
|
||||
}
|
||||
|
||||
//判断VALUE后的值是否有多个,只有在批量插入的时候才需要做转换,如只有1个VALUE则不需要做转换
|
||||
if len(valueExpr) < 3 {
|
||||
break
|
||||
}
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
|
||||
//获取INTO后面的值
|
||||
tableExpr, err := gregex.MatchString(`(?i)\s*(INTO\s+\w+\(([^\(\)]*)\))`, sql)
|
||||
if err != nil {
|
||||
return sql
|
||||
}
|
||||
tableExpr[0] = strings.TrimSpace(tableExpr[0])
|
||||
//取limit后面的取值范围
|
||||
first, limit := 0, 0
|
||||
for i := 1; i < len(res[index]); i++ {
|
||||
if len(strings.TrimSpace(res[index][i])) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
sql = "INSERT ALL"
|
||||
for i := 1; i < len(valueExpr); i++ {
|
||||
sql += fmt.Sprintf(" %s VALUES%s", tableExpr[0], strings.TrimSpace(valueExpr[i][0]))
|
||||
}
|
||||
sql += " SELECT 1 FROM DUAL"
|
||||
if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
|
||||
first, _ = strconv.Atoi(res[index][i+1])
|
||||
limit, _ = strconv.Atoi(res[index][i+2])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
//也可以使用between,据说这种写法的性能会比between好点,里层SQL中的ROWNUM_ >= limit可以缩小查询后的数据集规模
|
||||
sql = fmt.Sprintf("SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d", queryExpr[1], queryExpr[2], limit, first)
|
||||
case "INSERT":
|
||||
//获取VALUE的值,匹配所有带括号的值,会将INSERT INTO后的值匹配到,所以下面的判断语句会判断数组长度是否小于3
|
||||
valueExpr, err := gregex.MatchAllString(`(\s*\(([^\(\)]*)\))`, sql)
|
||||
if err != nil {
|
||||
return sql
|
||||
}
|
||||
|
||||
//判断VALUE后的值是否有多个,只有在批量插入的时候才需要做转换,如只有1个VALUE则不需要做转换
|
||||
if len(valueExpr) < 3 {
|
||||
break
|
||||
}
|
||||
|
||||
//获取INTO后面的值
|
||||
tableExpr, err := gregex.MatchString(`(?i)\s*(INTO\s+\w+\(([^\(\)]*)\))`, sql)
|
||||
if err != nil {
|
||||
return sql
|
||||
}
|
||||
tableExpr[0] = strings.TrimSpace(tableExpr[0])
|
||||
|
||||
sql = "INSERT ALL"
|
||||
for i := 1; i < len(valueExpr); i++ {
|
||||
sql += fmt.Sprintf(" %s VALUES%s", tableExpr[0], strings.TrimSpace(valueExpr[i][0]))
|
||||
}
|
||||
sql += " SELECT 1 FROM DUAL"
|
||||
|
||||
default:
|
||||
}
|
||||
return sql
|
||||
}
|
||||
|
||||
@ -28,42 +28,42 @@ func (bs *dbBase) convertValue(fieldValue interface{}, fieldType string) interfa
|
||||
t, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
|
||||
t = strings.ToLower(t)
|
||||
switch t {
|
||||
case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob":
|
||||
return gconv.Bytes(fieldValue)
|
||||
case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob":
|
||||
return gconv.Bytes(fieldValue)
|
||||
|
||||
case "bit", "int", "tinyint", "small_int", "medium_int":
|
||||
return gconv.Int(fieldValue)
|
||||
case "bit", "int", "tinyint", "small_int", "medium_int":
|
||||
return gconv.Int(fieldValue)
|
||||
|
||||
case "big_int":
|
||||
return gconv.Int64(fieldValue)
|
||||
case "big_int":
|
||||
return gconv.Int64(fieldValue)
|
||||
|
||||
case "float", "double", "decimal":
|
||||
return gconv.Float64(fieldValue)
|
||||
case "float", "double", "decimal":
|
||||
return gconv.Float64(fieldValue)
|
||||
|
||||
case "bool":
|
||||
return gconv.Bool(fieldValue)
|
||||
case "bool":
|
||||
return gconv.Bool(fieldValue)
|
||||
|
||||
default:
|
||||
// 自动识别类型, 以便默认支持更多数据库类型
|
||||
switch {
|
||||
case strings.Contains(t, "int"):
|
||||
return gconv.Int(fieldValue)
|
||||
default:
|
||||
// 自动识别类型, 以便默认支持更多数据库类型
|
||||
switch {
|
||||
case strings.Contains(t, "int"):
|
||||
return gconv.Int(fieldValue)
|
||||
|
||||
case strings.Contains(t, "text") || strings.Contains(t, "char"):
|
||||
return gconv.String(fieldValue)
|
||||
case strings.Contains(t, "text") || strings.Contains(t, "char"):
|
||||
return gconv.String(fieldValue)
|
||||
|
||||
case strings.Contains(t, "float") || strings.Contains(t, "double"):
|
||||
return gconv.Float64(fieldValue)
|
||||
case strings.Contains(t, "float") || strings.Contains(t, "double"):
|
||||
return gconv.Float64(fieldValue)
|
||||
|
||||
case strings.Contains(t, "bool"):
|
||||
return gconv.Bool(fieldValue)
|
||||
case strings.Contains(t, "bool"):
|
||||
return gconv.Bool(fieldValue)
|
||||
|
||||
case strings.Contains(t, "binary") || strings.Contains(t, "blob"):
|
||||
return gconv.Bytes(fieldValue)
|
||||
case strings.Contains(t, "binary") || strings.Contains(t, "blob"):
|
||||
return gconv.Bytes(fieldValue)
|
||||
|
||||
default:
|
||||
return gconv.String(fieldValue)
|
||||
}
|
||||
default:
|
||||
return gconv.String(fieldValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,8 +7,7 @@
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/encoding/gparser"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/encoding/gparser"
|
||||
)
|
||||
|
||||
// 将记录结果转换为JSON字符串
|
||||
@ -33,6 +32,6 @@ func (r Record) ToMap() Map {
|
||||
}
|
||||
|
||||
// 将Map变量映射到指定的struct对象中,注意参数应当是一个对象的指针
|
||||
func (r Record) ToStruct(objPointer interface{}) error {
|
||||
return gconv.Struct(r.ToMap(), objPointer)
|
||||
func (r Record) ToStruct(pointer interface{}) error {
|
||||
return mapToStruct(r.ToMap(), pointer)
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDbBase_Ping(t *testing.T) {
|
||||
@ -488,3 +489,51 @@ func TestDbBase_Delete(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_Time(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
result, err := db.Insert("user", g.Map{
|
||||
"id" : 200,
|
||||
"passport" : "t200",
|
||||
"password" : "123456",
|
||||
"nickname" : "T200",
|
||||
"create_time" : time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 1)
|
||||
value, err := db.GetValue("select `passport` from `user` where id=?", 200)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(value.String(), "t200")
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
t := time.Now()
|
||||
result, err := db.Insert("user", g.Map{
|
||||
"id" : 300,
|
||||
"passport" : "t300",
|
||||
"password" : "123456",
|
||||
"nickname" : "T300",
|
||||
"create_time" : &t,
|
||||
})
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 1)
|
||||
value, err := db.GetValue("select `passport` from `user` where id=?", 300)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(value.String(), "t300")
|
||||
})
|
||||
|
||||
if result, err := db.Delete("user", nil); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -7,10 +7,10 @@
|
||||
package gdb_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 基本测试
|
||||
|
||||
99
g/database/gdb/gdb_unit_struct_inherit_test.go
Normal file
99
g/database/gdb/gdb_unit_struct_inherit_test.go
Normal file
@ -0,0 +1,99 @@
|
||||
// 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 gdb_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestModel_Inherit_Insert(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
type Base struct {
|
||||
Id int `json:"id"`
|
||||
Uid int `json:"uid"`
|
||||
CreateTime string `json:"create_time"`
|
||||
}
|
||||
type User struct {
|
||||
Base
|
||||
Passport string `json:"passport"`
|
||||
Password string `json:"password"`
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
result, err := db.Table("user").Filter().Data(User{
|
||||
Passport : "john-test",
|
||||
Password : "123456",
|
||||
Nickname : "John",
|
||||
Base : Base {
|
||||
Id : 100,
|
||||
Uid : 100,
|
||||
CreateTime : gtime.Now().String(),
|
||||
},
|
||||
}).Insert()
|
||||
gtest.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 1)
|
||||
value, err := db.Table("user").Fields("passport").Where("id=100").Value()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(value.String(), "john-test")
|
||||
// Delete this test data.
|
||||
_, err = db.Table("user").Where("id", 100).Delete()
|
||||
gtest.Assert(err, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestModel_Inherit_MapToStruct(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
type Ids struct {
|
||||
Id int `json:"id"`
|
||||
Uid int `json:"uid"`
|
||||
}
|
||||
type Base struct {
|
||||
Ids
|
||||
CreateTime string `json:"create_time"`
|
||||
}
|
||||
type User struct {
|
||||
Base
|
||||
Passport string `json:"passport"`
|
||||
Password string `json:"password"`
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
data := g.Map{
|
||||
"id" : 100,
|
||||
"uid" : 101,
|
||||
"passport" : "t1",
|
||||
"password" : "123456",
|
||||
"nickname" : "T1",
|
||||
"create_time" : gtime.Now().String(),
|
||||
}
|
||||
result, err := db.Table("user").Filter().Data(data).Insert()
|
||||
gtest.Assert(err, nil)
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 1)
|
||||
|
||||
one, err := db.Table("user").Where("id=100").One()
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
user := new(User)
|
||||
|
||||
gtest.Assert(one.ToStruct(user), nil)
|
||||
gtest.Assert(user.Id, data["id"])
|
||||
gtest.Assert(user.Passport, data["passport"])
|
||||
gtest.Assert(user.Password, data["password"])
|
||||
gtest.Assert(user.Nickname, data["nickname"])
|
||||
gtest.Assert(user.CreateTime, data["create_time"])
|
||||
|
||||
// Delete this test data.
|
||||
_, err = db.Table("user").Where("id", 100).Delete()
|
||||
gtest.Assert(err, nil)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -43,6 +43,10 @@ func New(data interface{}, unsafe...bool) *Json {
|
||||
default:
|
||||
rv := reflect.ValueOf(data)
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Slice: fallthrough
|
||||
case reflect.Array:
|
||||
@ -56,7 +60,7 @@ func New(data interface{}, unsafe...bool) *Json {
|
||||
case reflect.Map: fallthrough
|
||||
case reflect.Struct:
|
||||
i := interface{}(nil)
|
||||
i = gconv.Map(data)
|
||||
i = gconv.Map(data, "json")
|
||||
j = &Json {
|
||||
p : &i,
|
||||
c : byte(gDEFAULT_SPLIT_CHAR),
|
||||
@ -132,11 +136,14 @@ func LoadContent(data interface{}, unsafe...bool) (*Json, error) {
|
||||
var err error
|
||||
var result interface{}
|
||||
b := gconv.Bytes(data)
|
||||
t := "json"
|
||||
t := ""
|
||||
if len(b) == 0 {
|
||||
return New(nil, unsafe...), nil
|
||||
}
|
||||
// auto check data type
|
||||
if json.Valid(b) {
|
||||
t = "json"
|
||||
} else if gregex.IsMatch(`^<.+>.*</.+>$`, b) {
|
||||
} else if gregex.IsMatch(`^<.+>[\S\s]+<.+>$`, b) {
|
||||
t = "xml"
|
||||
} else if gregex.IsMatch(`^[\s\t]*\w+\s*:\s*.+`, b) || gregex.IsMatch(`\n[\s\t]*\w+\s*:\s*.+`, b) {
|
||||
t = "yml"
|
||||
|
||||
@ -67,6 +67,24 @@ func Test_Load_XML(t *testing.T) {
|
||||
gtest.Assert(j.Get("doc.a"), g.Slice{1, 2, 3})
|
||||
gtest.Assert(j.Get("doc.a.1"), 2)
|
||||
})
|
||||
|
||||
// XML
|
||||
gtest.Case(t, func() {
|
||||
xml := `<?xml version="1.0"?>
|
||||
|
||||
<Output type="o">
|
||||
<itotalSize>0</itotalSize>
|
||||
<ipageSize>1</ipageSize>
|
||||
<ipageIndex>2</ipageIndex>
|
||||
<itotalRecords>GF框架</itotalRecords>
|
||||
<nworkOrderDtos/>
|
||||
<nworkOrderFrontXML/>
|
||||
</Output>`
|
||||
j, err := gjson.LoadContent(xml)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(j.Get("Output.ipageIndex"), "2")
|
||||
gtest.Assert(j.Get("Output.itotalRecords"), "GF框架")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Load_YAML1(t *testing.T) {
|
||||
|
||||
@ -40,7 +40,7 @@ func Load(path string, unsafe...bool) (*Parser, error) {
|
||||
// LoadContent creates a Parser object from given content,
|
||||
// it checks the data type of <content> automatically,
|
||||
// supporting JSON, XML, YAML and TOML types of data.
|
||||
func LoadContent(data []byte, unsafe...bool) (*Parser, error) {
|
||||
func LoadContent(data interface{}, unsafe...bool) (*Parser, error) {
|
||||
if j, e := gjson.LoadContent(data, unsafe...); e == nil {
|
||||
return &Parser{j}, nil
|
||||
} else {
|
||||
|
||||
@ -7,11 +7,11 @@
|
||||
package gparser_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/encoding/gparser"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/encoding/gparser"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
@ -67,6 +67,24 @@ func Test_Load_XML(t *testing.T) {
|
||||
gtest.Assert(j.Get("doc.a"), g.Slice{1, 2, 3})
|
||||
gtest.Assert(j.Get("doc.a.1"), 2)
|
||||
})
|
||||
|
||||
// XML
|
||||
gtest.Case(t, func() {
|
||||
xml := `<?xml version="1.0"?>
|
||||
|
||||
<Output type="o">
|
||||
<itotalSize>0</itotalSize>
|
||||
<ipageSize>1</ipageSize>
|
||||
<ipageIndex>2</ipageIndex>
|
||||
<itotalRecords>GF框架</itotalRecords>
|
||||
<nworkOrderDtos/>
|
||||
<nworkOrderFrontXML/>
|
||||
</Output>`
|
||||
j, err := gparser.LoadContent(xml)
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(j.Get("Output.ipageIndex"), "2")
|
||||
gtest.Assert(j.Get("Output.itotalRecords"), "GF框架")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Load_YAML1(t *testing.T) {
|
||||
|
||||
@ -5,8 +5,6 @@
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gxml provides accessing and converting for XML content.
|
||||
//
|
||||
// XML数据格式解析。
|
||||
package gxml
|
||||
|
||||
import (
|
||||
@ -52,33 +50,26 @@ func ToJson(content []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
// XML字符集预处理
|
||||
// @author wenzi1
|
||||
// @date 20180604 修复并发安全问题,改为如果非UTF8字符集则先做字符集转换
|
||||
func convert(xmlbyte []byte) (res []byte, err error) {
|
||||
func convert(xml []byte) (res []byte, err error) {
|
||||
patten := `<\?xml.*encoding\s*=\s*['|"](.*?)['|"].*\?>`
|
||||
matchStr, err := gregex.MatchString(patten, string(xmlbyte))
|
||||
matchStr, err := gregex.MatchString(patten, string(xml))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
xmlEncode := "UTF-8"
|
||||
if len(matchStr) == 2 {
|
||||
xmlEncode = matchStr[1]
|
||||
}
|
||||
|
||||
s := mahonia.GetCharset(xmlEncode)
|
||||
if s == nil {
|
||||
return nil, fmt.Errorf("not support charset:%s\n", xmlEncode)
|
||||
}
|
||||
|
||||
res, err = gregex.Replace(patten, []byte(""), []byte(xmlbyte))
|
||||
res, err = gregex.Replace(patten, []byte(""), xml)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !strings.EqualFold(s.Name, "UTF-8") {
|
||||
res = []byte(s.NewDecoder().ConvertString(string(res)))
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@ -219,13 +219,13 @@ func Redis(name...string) *gredis.Redis {
|
||||
Pass : array[4],
|
||||
})
|
||||
} else {
|
||||
glog.Errorfln(`invalid redis node configuration: "%s"`, line)
|
||||
glog.Errorf(`invalid redis node configuration: "%s"`, line)
|
||||
}
|
||||
} else {
|
||||
glog.Errorfln(`configuration for redis not found for group "%s"`, group)
|
||||
glog.Errorf(`configuration for redis not found for group "%s"`, group)
|
||||
}
|
||||
} else {
|
||||
glog.Errorfln(`incomplete configuration for redis: "redis" node not found in config file "%s"`, config.FilePath())
|
||||
glog.Errorf(`incomplete configuration for redis: "redis" node not found in config file "%s"`, config.FilePath())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
package empty
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// 判断给定的变量是否为空。
|
||||
@ -35,17 +35,22 @@ func IsEmpty(value interface{}) bool {
|
||||
case string: return value == ""
|
||||
case []byte: return len(value) == 0
|
||||
default:
|
||||
// 最后通过反射来判断
|
||||
// Finally using reflect.
|
||||
rv := reflect.ValueOf(value)
|
||||
if rv.IsNil() {
|
||||
return true
|
||||
}
|
||||
kind := rv.Kind()
|
||||
switch kind {
|
||||
case reflect.Map: fallthrough
|
||||
case reflect.Slice: fallthrough
|
||||
case reflect.Array:
|
||||
switch rv.Kind() {
|
||||
case reflect.Chan,
|
||||
reflect.Map,
|
||||
reflect.Slice,
|
||||
reflect.Array:
|
||||
return rv.Len() == 0
|
||||
|
||||
case reflect.Func,
|
||||
reflect.Ptr,
|
||||
reflect.Interface,
|
||||
reflect.UnsafePointer:
|
||||
if rv.IsNil() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
@ -180,6 +180,17 @@ func (c *Client) Post(url string, data...interface{}) (*ClientResponse, error) {
|
||||
cookies : make(map[string]string),
|
||||
}
|
||||
r.Response = resp
|
||||
// 浏览器模式
|
||||
if c.browserMode {
|
||||
now := time.Now()
|
||||
for _, v := range r.Cookies() {
|
||||
if v.Expires.UnixNano() < now.UnixNano() {
|
||||
delete(c.cookies, v.Name)
|
||||
} else {
|
||||
c.cookies[v.Name] = v.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@ -258,7 +269,7 @@ func (c *Client) DoRequestContent(method string, url string, data...interface{})
|
||||
return string(response.ReadAll())
|
||||
}
|
||||
|
||||
// 请求并返回response对象,该方法支持二进制提交数据
|
||||
// 请求并返回response对象
|
||||
func (c *Client) DoRequest(method, url string, data...interface{}) (*ClientResponse, error) {
|
||||
if strings.EqualFold("POST", method) {
|
||||
return c.Post(url, data...)
|
||||
|
||||
@ -142,16 +142,16 @@ func (r *Request) GetPostMap(def...map[string]string) map[string]string {
|
||||
}
|
||||
|
||||
// 将所有的request参数映射到struct属性上,参数object应当为一个struct对象的指针, mapping为非必需参数,自定义参数与属性的映射关系
|
||||
func (r *Request) GetPostToStruct(object interface{}, mapping...map[string]string) error {
|
||||
tagmap := r.getStructParamsTagMap(object)
|
||||
func (r *Request) GetPostToStruct(pointer interface{}, mapping...map[string]string) error {
|
||||
tagMap := r.getStructParamsTagMap(pointer)
|
||||
if len(mapping) > 0 {
|
||||
for k, v := range mapping[0] {
|
||||
tagmap[k] = v
|
||||
tagMap[k] = v
|
||||
}
|
||||
}
|
||||
params := make(map[string]interface{})
|
||||
for k, v := range r.GetPostMap() {
|
||||
params[k] = v
|
||||
}
|
||||
return gconv.Struct(params, object, tagmap)
|
||||
return gconv.Struct(params, pointer, tagMap)
|
||||
}
|
||||
@ -150,8 +150,8 @@ func (r *Request) GetQueryMap(def... map[string]string) map[string]string {
|
||||
}
|
||||
|
||||
// 将所有的get参数映射到struct属性上,参数object应当为一个struct对象的指针, mapping为非必需参数,自定义参数与属性的映射关系
|
||||
func (r *Request) GetQueryToStruct(object interface{}, mapping...map[string]string) error {
|
||||
tagmap := r.getStructParamsTagMap(object)
|
||||
func (r *Request) GetQueryToStruct(pointer interface{}, mapping...map[string]string) error {
|
||||
tagmap := r.getStructParamsTagMap(pointer)
|
||||
if len(mapping) > 0 {
|
||||
for k, v := range mapping[0] {
|
||||
tagmap[k] = v
|
||||
@ -161,5 +161,5 @@ func (r *Request) GetQueryToStruct(object interface{}, mapping...map[string]stri
|
||||
for k, v := range r.GetQueryMap() {
|
||||
params[k] = v
|
||||
}
|
||||
return gconv.Struct(params, object, tagmap)
|
||||
return gconv.Struct(params, pointer, tagmap)
|
||||
}
|
||||
@ -133,10 +133,10 @@ func (r *Request) GetRequestMap(def...map[string]string) map[string]string {
|
||||
|
||||
// 将所有的request参数映射到struct属性上,参数object应当为一个struct对象的指针, mapping为非必需参数,自定义参数与属性的映射关系
|
||||
func (r *Request) GetRequestToStruct(pointer interface{}, mapping...map[string]string) error {
|
||||
tagmap := r.getStructParamsTagMap(pointer)
|
||||
tagMap := r.getStructParamsTagMap(pointer)
|
||||
if len(mapping) > 0 {
|
||||
for k, v := range mapping[0] {
|
||||
tagmap[k] = v
|
||||
tagMap[k] = v
|
||||
}
|
||||
}
|
||||
params := make(map[string]interface{})
|
||||
@ -148,6 +148,6 @@ func (r *Request) GetRequestToStruct(pointer interface{}, mapping...map[string]s
|
||||
params = j.ToMap()
|
||||
}
|
||||
}
|
||||
return gconv.Struct(params, pointer, tagmap)
|
||||
return gconv.Struct(params, pointer, tagMap)
|
||||
}
|
||||
|
||||
|
||||
@ -134,7 +134,9 @@ func (r *Response) WriteStatus(status int, content...string) {
|
||||
// 状态码注册回调函数处理
|
||||
if status != http.StatusOK {
|
||||
if f := r.request.Server.getStatusHandler(status, r.request); f != nil {
|
||||
f(r.request)
|
||||
r.Server.niceCallFunc(func() {
|
||||
f(r.request)
|
||||
})
|
||||
// 防止多次设置(http: multiple response.WriteHeader calls)
|
||||
if r.Status == 0 {
|
||||
r.WriteHeader(status)
|
||||
|
||||
@ -52,7 +52,10 @@ func (r *Response) buildInVars(params...map[string]interface{}) map[string]inter
|
||||
} else {
|
||||
vars = make(map[string]interface{})
|
||||
}
|
||||
vars["Config"] = gins.Config().GetMap("")
|
||||
// 当配置文件不存在时就不赋值该模板变量,不然会报错
|
||||
if c := gins.Config(); c.FilePath() != "" {
|
||||
vars["Config"] = c.GetMap("")
|
||||
}
|
||||
vars["Cookie"] = r.request.Cookie.Map()
|
||||
vars["Session"] = r.request.Session.Map()
|
||||
vars["Get"] = r.request.GetQueryMap()
|
||||
|
||||
@ -389,7 +389,7 @@ func (s *Server) Run() error {
|
||||
// 阻塞等待服务执行完成
|
||||
<- s.closeChan
|
||||
|
||||
glog.Printfln("%d: all servers shutdown", gproc.Pid())
|
||||
glog.Printf("%d: all servers shutdown", gproc.Pid())
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -400,7 +400,7 @@ func Wait() {
|
||||
// 阻塞等待服务执行完成
|
||||
<- allDoneChan
|
||||
|
||||
glog.Printfln("%d: all servers shutdown", gproc.Pid())
|
||||
glog.Printf("%d: all servers shutdown", gproc.Pid())
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -129,7 +129,7 @@ func forkReloadProcess(newExeFilePath...string) error {
|
||||
buffer, _ := gjson.Encode(sfm)
|
||||
p.Env = append(p.Env, gADMIN_ACTION_RELOAD_ENVKEY + "=" + string(buffer))
|
||||
if _, err := p.Start(); err != nil {
|
||||
glog.Errorfln("%d: fork process failed, error:%s, %s", gproc.Pid(), err.Error(), string(buffer))
|
||||
glog.Errorf("%d: fork process failed, error:%s, %s", gproc.Pid(), err.Error(), string(buffer))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@ -147,7 +147,7 @@ func forkRestartProcess(newExeFilePath...string) error {
|
||||
env = append(env, gADMIN_ACTION_RESTART_ENVKEY + "=1")
|
||||
p := gproc.NewProcess(path, os.Args, env)
|
||||
if _, err := p.Start(); err != nil {
|
||||
glog.Errorfln("%d: fork process failed, error:%s", gproc.Pid(), err.Error())
|
||||
glog.Errorf("%d: fork process failed, error:%s", gproc.Pid(), err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@ -197,14 +197,14 @@ func restartWebServers(signal string, newExeFilePath...string) error {
|
||||
}
|
||||
} else {
|
||||
if err := forkReloadProcess(newExeFilePath...); err != nil {
|
||||
glog.Printfln("%d: server restarts failed", gproc.Pid())
|
||||
glog.Printf("%d: server restarts failed", gproc.Pid())
|
||||
serverProcessStatus.Set(gADMIN_ACTION_NONE)
|
||||
return err
|
||||
} else {
|
||||
if len(signal) > 0 {
|
||||
glog.Printfln("%d: server restarting by signal: %s", gproc.Pid(), signal)
|
||||
glog.Printf("%d: server restarting by signal: %s", gproc.Pid(), signal)
|
||||
} else {
|
||||
glog.Printfln("%d: server restarting by web admin", gproc.Pid())
|
||||
glog.Printf("%d: server restarting by web admin", gproc.Pid())
|
||||
}
|
||||
|
||||
}
|
||||
@ -216,12 +216,12 @@ func restartWebServers(signal string, newExeFilePath...string) error {
|
||||
func shutdownWebServers(signal...string) {
|
||||
serverProcessStatus.Set(gADMIN_ACTION_SHUTINGDOWN)
|
||||
if len(signal) > 0 {
|
||||
glog.Printfln("%d: server shutting down by signal: %s", gproc.Pid(), signal[0])
|
||||
glog.Printf("%d: server shutting down by signal: %s", gproc.Pid(), signal[0])
|
||||
// 在终端信号下,立即执行关闭操作
|
||||
forceCloseWebServers()
|
||||
allDoneChan <- struct{}{}
|
||||
} else {
|
||||
glog.Printfln("%d: server shutting down by api", gproc.Pid())
|
||||
glog.Printf("%d: server shutting down by api", gproc.Pid())
|
||||
// 非终端信号下,异步1秒后再执行关闭,
|
||||
// 目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了)
|
||||
gtimer.SetTimeout(time.Second, func() {
|
||||
|
||||
@ -46,6 +46,7 @@ type ServerConfig struct {
|
||||
IdleTimeout time.Duration // 等待超时
|
||||
MaxHeaderBytes int // 最大的header长度
|
||||
TLSConfig tls.Config
|
||||
KeepAlive bool
|
||||
|
||||
// 静态文件配置
|
||||
IndexFiles []string // 默认访问的文件列表
|
||||
@ -96,6 +97,7 @@ var defaultServerConfig = ServerConfig {
|
||||
WriteTimeout : 60 * time.Second,
|
||||
IdleTimeout : 60 * time.Second,
|
||||
MaxHeaderBytes : 1024,
|
||||
KeepAlive : true,
|
||||
|
||||
IndexFiles : []string{"index.html", "index.htm"},
|
||||
IndexFolder : false,
|
||||
@ -316,6 +318,15 @@ func (s *Server) SetRouterCacheExpire(expire int) {
|
||||
s.config.RouterCacheExpire = expire
|
||||
}
|
||||
|
||||
// 设置KeepAlive
|
||||
func (s *Server) SetKeepAlive(enabled bool) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.KeepAlive = enabled
|
||||
}
|
||||
|
||||
// 获取WebServer名称
|
||||
func (s *Server) GetName() string {
|
||||
return s.name
|
||||
|
||||
@ -21,9 +21,9 @@ import (
|
||||
|
||||
// 优雅的Web Server对象封装
|
||||
type gracefulServer struct {
|
||||
fd uintptr
|
||||
addr string
|
||||
httpServer *http.Server
|
||||
fd uintptr // 热重启时传递的socket监听文件句柄
|
||||
addr string // 监听地址信息
|
||||
httpServer *http.Server // 底层http.Server
|
||||
rawListener net.Listener // 原始listener
|
||||
listener net.Listener // 接口化封装的listener
|
||||
isHttps bool // 是否HTTPS
|
||||
@ -45,7 +45,7 @@ func (s *Server) newGracefulServer(addr string, fd...int) *gracefulServer {
|
||||
|
||||
// 生成一个底层的Web Server对象
|
||||
func (s *Server) newHttpServer(addr string) *http.Server {
|
||||
return &http.Server {
|
||||
server := &http.Server {
|
||||
Addr : addr,
|
||||
Handler : s.config.Handler,
|
||||
ReadTimeout : s.config.ReadTimeout,
|
||||
@ -53,6 +53,8 @@ func (s *Server) newHttpServer(addr string) *http.Server {
|
||||
IdleTimeout : s.config.IdleTimeout,
|
||||
MaxHeaderBytes : s.config.MaxHeaderBytes,
|
||||
}
|
||||
server.SetKeepAlivesEnabled(s.config.KeepAlive)
|
||||
return server
|
||||
}
|
||||
|
||||
// 执行HTTP监听
|
||||
@ -128,7 +130,7 @@ func (s *gracefulServer) doServe() error {
|
||||
if s.fd != 0 {
|
||||
action = "reloaded"
|
||||
}
|
||||
glog.Printfln("%d: %s server %s listening on [%s]", gproc.Pid(), s.getProto(), action, s.addr)
|
||||
glog.Printf("%d: %s server %s listening on [%s]", gproc.Pid(), s.getProto(), action, s.addr)
|
||||
s.status = SERVER_STATUS_RUNNING
|
||||
err := s.httpServer.Serve(s.listener)
|
||||
s.status = SERVER_STATUS_STOPPED
|
||||
@ -171,7 +173,7 @@ func (s *gracefulServer) shutdown() {
|
||||
return
|
||||
}
|
||||
if err := s.httpServer.Shutdown(context.Background()); err != nil {
|
||||
glog.Errorfln("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.addr, err)
|
||||
glog.Errorf("%d: %s server [%s] shutdown error: %v", gproc.Pid(), s.getProto(), s.addr, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,7 +183,7 @@ func (s *gracefulServer) close() {
|
||||
return
|
||||
}
|
||||
if err := s.httpServer.Close(); err != nil {
|
||||
glog.Errorfln("%d: %s server [%s] closed error: %v", gproc.Pid(), s.getProto(), s.addr, err)
|
||||
glog.Errorf("%d: %s server [%s] closed error: %v", gproc.Pid(), s.getProto(), s.addr, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -84,7 +84,7 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin
|
||||
caller := s.getHandlerRegisterCallerLine(handler)
|
||||
if len(hook) == 0 {
|
||||
if item, ok := s.routesMap[regkey]; ok {
|
||||
glog.Errorfln(`duplicated route registry "%s", already registered at %s`, pattern, item[0].file)
|
||||
glog.Errorf(`duplicated route registry "%s", already registered at %s`, pattern, item[0].file)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ func (d *Domain) Group(prefix...string) *RouterGroup {
|
||||
func (g *RouterGroup) Bind(items []GroupItem) {
|
||||
for _, item := range items {
|
||||
if len(item) < 3 {
|
||||
glog.Fatalfln("invalid router item: %s", item)
|
||||
glog.Fatalf("invalid router item: %s", item)
|
||||
}
|
||||
if strings.EqualFold(gconv.String(item[0]), "REST") {
|
||||
g.bind("REST", gconv.String(item[0]) + ":" + gconv.String(item[1]), item[2])
|
||||
@ -124,7 +124,7 @@ func (g *RouterGroup) bind(bindType string, pattern string, object interface{},
|
||||
if len(g.prefix) > 0 {
|
||||
domain, method, path, err := g.server.parsePattern(pattern)
|
||||
if err != nil {
|
||||
glog.Fatalfln("invalid pattern: %s", pattern)
|
||||
glog.Fatalf("invalid pattern: %s", pattern)
|
||||
}
|
||||
if bindType == "HANDLER" {
|
||||
pattern = g.server.serveHandlerKey(method, g.prefix + "/" + strings.TrimLeft(path, "/"), domain)
|
||||
@ -196,7 +196,7 @@ func (g *RouterGroup) bind(bindType string, pattern string, object interface{},
|
||||
g.domain.BindHookHandler(pattern, methods[0], h)
|
||||
}
|
||||
} else {
|
||||
glog.Fatalfln("invalid hook handler for pattern:%s", pattern)
|
||||
glog.Fatalf("invalid hook handler for pattern:%s", pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,11 +50,11 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) {
|
||||
if _, ok := v.Method(i).Interface().(func()); !ok {
|
||||
if len(methodMap) > 0 {
|
||||
// 指定的方法名称注册,那么需要使用错误提示
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
|
||||
glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
|
||||
pkgPath, ctlName, mname, v.Method(i).Type().String())
|
||||
} else {
|
||||
// 否则只是Debug提示
|
||||
glog.Debugfln(`ignore route method: %s.%s.%s defined as "%s", no match "func()"`,
|
||||
glog.Debugf(`ignore route method: %s.%s.%s defined as "%s", no match "func()"`,
|
||||
pkgPath, ctlName, mname, v.Method(i).Type().String())
|
||||
}
|
||||
continue
|
||||
@ -108,7 +108,7 @@ func (s *Server)BindControllerMethod(pattern string, c Controller, method string
|
||||
ctlName = fmt.Sprintf(`(%s)`, ctlName)
|
||||
}
|
||||
if _, ok := fval.Interface().(func()); !ok {
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
|
||||
glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
|
||||
pkgPath, ctlName, mname, fval.Type().String())
|
||||
return
|
||||
}
|
||||
@ -147,7 +147,7 @@ func (s *Server)BindControllerRest(pattern string, c Controller) {
|
||||
ctlName = fmt.Sprintf(`(%s)`, ctlName)
|
||||
}
|
||||
if _, ok := v.Method(i).Interface().(func()); !ok {
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
|
||||
glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
|
||||
pkgPath, ctlName, mname, v.Method(i).Type().String())
|
||||
return
|
||||
}
|
||||
|
||||
@ -57,11 +57,11 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) {
|
||||
if !ok {
|
||||
if len(methodMap) > 0 {
|
||||
// 指定的方法名称注册,那么需要使用错误提示
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
pkgPath, objName, mname, v.Method(i).Type().String())
|
||||
} else {
|
||||
// 否则只是Debug提示
|
||||
glog.Debugfln(`ignore route method: %s.%s.%s defined as "%s", no match "func(*ghttp.Request)"`,
|
||||
glog.Debugf(`ignore route method: %s.%s.%s defined as "%s", no match "func(*ghttp.Request)"`,
|
||||
pkgPath, objName, mname, v.Method(i).Type().String())
|
||||
}
|
||||
continue
|
||||
@ -127,7 +127,7 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string)
|
||||
}
|
||||
faddr, ok := fval.Interface().(func(*Request))
|
||||
if !ok {
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
pkgPath, objName, mname, fval.Type().String())
|
||||
return
|
||||
}
|
||||
@ -174,7 +174,7 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) {
|
||||
}
|
||||
faddr, ok := v.Method(i).Interface().(func(*Request))
|
||||
if !ok {
|
||||
glog.Errorfln(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
glog.Errorf(`invalid route method: %s.%s.%s defined as "%s", but "func(*ghttp.Request)" is required for object registry`,
|
||||
pkgPath, objName, mname, v.Method(i).Type().String())
|
||||
continue
|
||||
}
|
||||
|
||||
@ -20,10 +20,10 @@ import (
|
||||
|
||||
// SESSION对象
|
||||
type Session struct {
|
||||
id string // SessionId
|
||||
id string // SessionId
|
||||
data *gmap.StrAnyMap // Session数据
|
||||
server *Server // 所属Server
|
||||
request *Request // 关联的请求
|
||||
server *Server // 所属Server
|
||||
request *Request // 关联的请求
|
||||
}
|
||||
|
||||
// 生成一个唯一的SessionId字符串,长度18位。
|
||||
@ -58,7 +58,7 @@ func (s *Session) init() {
|
||||
// 否则执行初始化创建
|
||||
s.id = s.request.Cookie.MakeSessionId()
|
||||
s.data = gmap.NewStrAnyMap()
|
||||
s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge())
|
||||
s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -46,6 +46,48 @@ func Test_Params_Json(t *testing.T) {
|
||||
Pass2 : "456",
|
||||
})
|
||||
})
|
||||
s.BindHandler("/json3", func(r *ghttp.Request){
|
||||
type Message struct {
|
||||
Code int `json:"code"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
type ResponseJson struct {
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
ExtData interface{} `json:"ext_data,omitempty"`
|
||||
Paginate interface{} `json:"paginate,omitempty"`
|
||||
Message Message `json:"message,omitempty"`
|
||||
}
|
||||
responseJson := &ResponseJson{
|
||||
Success: true,
|
||||
Data: nil,
|
||||
ExtData: nil,
|
||||
Message: Message{3, "测试", "error"},
|
||||
}
|
||||
r.Response.WriteJson(responseJson)
|
||||
})
|
||||
s.BindHandler("/json4", func(r *ghttp.Request){
|
||||
type Message struct {
|
||||
Code int `json:"code"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
type ResponseJson struct {
|
||||
Success bool `json:"success"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
ExtData interface{} `json:"ext_data,omitempty"`
|
||||
Paginate interface{} `json:"paginate,omitempty"`
|
||||
Message *Message `json:"message,omitempty"`
|
||||
}
|
||||
responseJson := ResponseJson{
|
||||
Success: true,
|
||||
Data: nil,
|
||||
ExtData: nil,
|
||||
Message: &Message{3, "测试", "error"},
|
||||
}
|
||||
r.Response.WriteJson(responseJson)
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouteMap(false)
|
||||
s.Start()
|
||||
@ -67,7 +109,7 @@ func Test_Params_Json(t *testing.T) {
|
||||
gtest.Assert(map1["password2"], "456")
|
||||
|
||||
map2 := make(map[string]interface{})
|
||||
err2 := json.Unmarshal([]byte(client.GetContent("/json1")), &map2)
|
||||
err2 := json.Unmarshal([]byte(client.GetContent("/json2")), &map2)
|
||||
gtest.Assert(err2, nil)
|
||||
gtest.Assert(len(map2), 4)
|
||||
gtest.Assert(map2["Name"], "john")
|
||||
@ -75,5 +117,18 @@ func Test_Params_Json(t *testing.T) {
|
||||
gtest.Assert(map2["password1"], "123")
|
||||
gtest.Assert(map2["password2"], "456")
|
||||
|
||||
map3 := make(map[string]interface{})
|
||||
err3 := json.Unmarshal([]byte(client.GetContent("/json3")), &map3)
|
||||
gtest.Assert(err3, nil)
|
||||
gtest.Assert(len(map3), 2)
|
||||
gtest.Assert(map3["success"], "true")
|
||||
gtest.Assert(map3["message"], g.Map{"body":"测试", "code":3, "error":"error"})
|
||||
|
||||
map4 := make(map[string]interface{})
|
||||
err4 := json.Unmarshal([]byte(client.GetContent("/json4")), &map4)
|
||||
gtest.Assert(err4, nil)
|
||||
gtest.Assert(len(map4), 2)
|
||||
gtest.Assert(map4["success"], "true")
|
||||
gtest.Assert(map4["message"], g.Map{"body":"测试", "code":3, "error":"error"})
|
||||
})
|
||||
}
|
||||
|
||||
@ -85,3 +85,36 @@ func Test_Router_Hook_Priority(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_Hook_Multi(t *testing.T) {
|
||||
p := ports.PopRand()
|
||||
s := g.Server(p)
|
||||
s.BindHandler("/multi-hook", func(r *ghttp.Request) {
|
||||
r.Response.Write("show")
|
||||
})
|
||||
|
||||
s.BindHookHandlerByMap("/multi-hook", map[string]ghttp.HandlerFunc {
|
||||
"BeforeServe" : func(r *ghttp.Request) {
|
||||
r.Response.Write("1")
|
||||
},
|
||||
})
|
||||
s.BindHookHandlerByMap("/multi-hook", map[string]ghttp.HandlerFunc {
|
||||
"BeforeServe" : func(r *ghttp.Request) {
|
||||
r.Response.Write("2")
|
||||
},
|
||||
})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouteMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
// 等待启动完成
|
||||
time.Sleep(time.Second)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
|
||||
|
||||
gtest.Assert(client.GetContent("/"), "Not Found")
|
||||
gtest.Assert(client.GetContent("/multi-hook"), "12show")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -7,11 +7,12 @@
|
||||
package gtcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
"io"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 封装的链接对象
|
||||
@ -21,7 +22,7 @@ type Conn struct {
|
||||
buffer []byte // 读取缓冲区(用于数据读取时的缓冲区处理)
|
||||
recvDeadline time.Time // 读取超时时间
|
||||
sendDeadline time.Time // 写入超时时间
|
||||
recvBufferWait time.Duration // 读取全部缓冲区数据时,读取完毕后的写入等待间隔
|
||||
recvBufferWait time.Duration // 读取全部缓冲区数据时,读取缓冲区完毕后的等待间隔
|
||||
}
|
||||
|
||||
const (
|
||||
@ -38,6 +39,24 @@ func NewConn(addr string, timeout...int) (*Conn, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 创建支持TLS加密通信的TCP链接
|
||||
func NewConnTLS(addr string, tlsConfig *tls.Config) (*Conn, error) {
|
||||
if conn, err := NewNetConnTLS(addr, tlsConfig); err == nil {
|
||||
return NewConnByNetConn(conn), nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 根据证书和密钥文件创建支持TLS加密通信的TCP链接
|
||||
func NewConnKeyCrt(addr, crtFile, keyFile string) (*Conn, error) {
|
||||
if conn, err := NewNetConnKeyCrt(addr, crtFile, keyFile); err == nil {
|
||||
return NewConnByNetConn(conn), nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 将net.Conn接口对象转换为*gtcp.Conn对象
|
||||
func NewConnByNetConn(conn net.Conn) *Conn {
|
||||
return &Conn {
|
||||
@ -50,16 +69,14 @@ func NewConnByNetConn(conn net.Conn) *Conn {
|
||||
}
|
||||
|
||||
// 关闭连接
|
||||
func (c *Conn) Close() {
|
||||
c.conn.Close()
|
||||
func (c *Conn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// 发送数据
|
||||
func (c *Conn) Send(data []byte, retry...Retry) error {
|
||||
length := 0
|
||||
for {
|
||||
n, err := c.conn.Write(data)
|
||||
if err != nil {
|
||||
if _, err := c.conn.Write(data); err != nil {
|
||||
// 链接已关闭
|
||||
if err == io.EOF {
|
||||
return err
|
||||
@ -76,18 +93,17 @@ func (c *Conn) Send(data []byte, retry...Retry) error {
|
||||
time.Sleep(time.Duration(retry[0].Interval) * time.Millisecond)
|
||||
}
|
||||
} else {
|
||||
length += n
|
||||
if length == len(data) {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取数据,指定读取的数据长度(length < 1表示获取所有可读数据),以及重试策略(retry)
|
||||
// 阻塞等待获取指定读取的数据长度,并给定重试策略。
|
||||
//
|
||||
// 需要注意:
|
||||
// 1、往往在socket通信中需要指定固定的数据结构,并在设定对应的长度字段,并在读取数据时便于区分包大小;
|
||||
// 2、当length < 1时表示获取缓冲区所有的数据,但是可能会引起包解析问题(可能出现非完整的包情况),因此需要解析端注意解析策略;
|
||||
// 2、当length < 0时表示获取缓冲区所有的数据,但是可能会引起包解析问题(可能出现粘包/断包情况),因此需要解析端注意解析策略;
|
||||
// 3、当length = 0时表示获取一次缓冲区的数据后立即返回;
|
||||
func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) {
|
||||
var err error // 读取错误
|
||||
var size int // 读取长度
|
||||
@ -106,9 +122,11 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) {
|
||||
// 如果已经读取到数据(这点很关键,表明缓冲区已经有数据,剩下的操作就是将所有数据读取完毕),
|
||||
// 那么可以设置读取全部缓冲数据的超时时间;如果没有接收到任何数据,那么将会进入读取阻塞(或者自定义的超时阻塞);
|
||||
// 仅对读取全部缓冲区数据操作有效
|
||||
if length <= 0 && index > 0 {
|
||||
if length < 0 && index > 0 {
|
||||
bufferWait = true
|
||||
c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait))
|
||||
if err = c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
size, err = c.reader.Read(buffer[index:])
|
||||
if size > 0 {
|
||||
@ -137,7 +155,9 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) {
|
||||
}
|
||||
// 判断数据是否全部读取完毕(由于超时机制的存在,获取的数据完整性不可靠)
|
||||
if bufferWait && isTimeout(err) {
|
||||
c.conn.SetReadDeadline(c.recvDeadline)
|
||||
if err = c.conn.SetReadDeadline(c.recvDeadline); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
@ -155,6 +175,10 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) {
|
||||
}
|
||||
break
|
||||
}
|
||||
// 只获取一次数据
|
||||
if length == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return buffer[:index], err
|
||||
}
|
||||
@ -184,14 +208,18 @@ func (c *Conn) RecvLine(retry...Retry) ([]byte, error) {
|
||||
|
||||
// 带超时时间的数据获取
|
||||
func (c *Conn) RecvWithTimeout(length int, timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
c.SetRecvDeadline(time.Now().Add(timeout))
|
||||
if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer c.SetRecvDeadline(time.Time{})
|
||||
return c.Recv(length, retry...)
|
||||
}
|
||||
|
||||
// 带超时时间的数据发送
|
||||
func (c *Conn) SendWithTimeout(data []byte, timeout time.Duration, retry...Retry) error {
|
||||
c.SetSendDeadline(time.Now().Add(timeout))
|
||||
if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.SetSendDeadline(time.Time{})
|
||||
return c.Send(data, retry...)
|
||||
}
|
||||
|
||||
@ -14,88 +14,118 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// 允许最大的简单协议包大小(byte), 15MB
|
||||
PKG_MAX_SIZE = 0xFFFFFF
|
||||
// 消息包头大小: "总长度"3字节+"校验码"4字节
|
||||
PKG_HEADER_SIZE = 7
|
||||
// 默认允许最大的简单协议包大小(byte), 65535 byte
|
||||
gPKG_MAX_DATA_SIZE = 65535
|
||||
// 简单协议包头大小
|
||||
gPKG_HEADER_SIZE = 3
|
||||
)
|
||||
|
||||
// 根据简单协议发送数据包。
|
||||
// 简单协议数据格式:总长度(24bit)|校验码(32bit)|数据(变长)。
|
||||
// 注意:
|
||||
// 1. "总长度"包含自身3字节及"校验码"4字节。
|
||||
// 2. 由于"总长度"为3字节,并且使用的BigEndian字节序,因此最后返回的buffer使用了buffer[1:]。
|
||||
func (c *Conn) SendPkg(data []byte, retry...Retry) error {
|
||||
length := uint32(len(data))
|
||||
if length > PKG_MAX_SIZE - PKG_HEADER_SIZE {
|
||||
return errors.New(fmt.Sprintf(`data size %d exceeds max pkg size %d`, length, PKG_MAX_SIZE - PKG_HEADER_SIZE))
|
||||
// 数据读取选项
|
||||
type PkgOption struct {
|
||||
MaxSize int // (byte)数据读取的最大包大小,最大不能超过3字节(0xFFFFFF,15MB),默认为65535byte
|
||||
Retry Retry // 失败重试
|
||||
}
|
||||
|
||||
// getPkgOption wraps and returns the PkgOption.
|
||||
// If no option given, it returns a new option with default value.
|
||||
func getPkgOption(option...PkgOption) (*PkgOption, error) {
|
||||
pkgOption := PkgOption{}
|
||||
if len(option) > 0 {
|
||||
pkgOption = option[0]
|
||||
}
|
||||
if pkgOption.MaxSize == 0 {
|
||||
pkgOption.MaxSize = gPKG_MAX_DATA_SIZE
|
||||
} else if pkgOption.MaxSize > 0xFFFFFF {
|
||||
return nil, fmt.Errorf(`package size %d exceeds allowed max size %d`, pkgOption.MaxSize, 0xFFFFFF)
|
||||
}
|
||||
return &pkgOption, nil
|
||||
}
|
||||
|
||||
// 根据简单协议发送数据包。
|
||||
//
|
||||
// 简单协议数据格式:数据长度(24bit)|数据字段(变长)。
|
||||
//
|
||||
// 注意:
|
||||
// 1. "数据长度"仅为"数据字段"的长度,不包含头信息的长度字段3字节。
|
||||
// 2. 由于"数据长度"为3字节,并且使用的BigEndian字节序,因此这里最后返回的buffer使用了buffer[1:]。
|
||||
func (c *Conn) SendPkg(data []byte, option...PkgOption) error {
|
||||
pkgOption, err := getPkgOption(option...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
length := len(data)
|
||||
if length > pkgOption.MaxSize {
|
||||
return errors.New(fmt.Sprintf(`data size %d exceeds max pkg size %d`, length, gPKG_MAX_DATA_SIZE))
|
||||
}
|
||||
buffer := make([]byte, gPKG_HEADER_SIZE + 1 + len(data))
|
||||
binary.BigEndian.PutUint32(buffer[0 : ], uint32(length))
|
||||
copy(buffer[gPKG_HEADER_SIZE + 1 : ], data)
|
||||
if pkgOption.Retry.Count > 0 {
|
||||
return c.Send(buffer[1:], pkgOption.Retry)
|
||||
}
|
||||
buffer := make([]byte, PKG_HEADER_SIZE + 1 + len(data))
|
||||
copy(buffer[PKG_HEADER_SIZE + 1 : ], data)
|
||||
binary.BigEndian.PutUint32(buffer[0 : ], PKG_HEADER_SIZE + length)
|
||||
binary.BigEndian.PutUint32(buffer[4 : ], Checksum(data))
|
||||
//fmt.Println("SendPkg:", buffer[1:])
|
||||
return c.Send(buffer[1:], retry...)
|
||||
return c.Send(buffer[1:])
|
||||
}
|
||||
|
||||
// 简单协议: 带超时时间的数据发送
|
||||
func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) error {
|
||||
c.SetSendDeadline(time.Now().Add(timeout))
|
||||
func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) error {
|
||||
if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.SetSendDeadline(time.Time{})
|
||||
return c.SendPkg(data, retry...)
|
||||
return c.SendPkg(data, option...)
|
||||
}
|
||||
|
||||
// 简单协议: 发送数据并等待接收返回数据
|
||||
func (c *Conn) SendRecvPkg(data []byte, retry...Retry) ([]byte, error) {
|
||||
if err := c.SendPkg(data, retry...); err == nil {
|
||||
return c.RecvPkg(retry...)
|
||||
func (c *Conn) SendRecvPkg(data []byte, option...PkgOption) ([]byte, error) {
|
||||
if err := c.SendPkg(data, option...); err == nil {
|
||||
return c.RecvPkg(option...)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 简单协议: 发送数据并等待接收返回数据(带返回超时等待时间)
|
||||
func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
if err := c.SendPkg(data, retry...); err == nil {
|
||||
return c.RecvPkgWithTimeout(timeout, retry...)
|
||||
func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) ([]byte, error) {
|
||||
if err := c.SendPkg(data, option...); err == nil {
|
||||
return c.RecvPkgWithTimeout(timeout, option...)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 简单协议: 获取一个数据包。
|
||||
func (c *Conn) RecvPkg(retry...Retry) (result []byte, err error) {
|
||||
func (c *Conn) RecvPkg(option...PkgOption) (result []byte, err error) {
|
||||
var temp []byte
|
||||
var length uint32
|
||||
var length int
|
||||
pkgOption, err := getPkgOption(option...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for {
|
||||
// 先根据对象的缓冲区数据进行计算
|
||||
for {
|
||||
if len(c.buffer) >= PKG_HEADER_SIZE {
|
||||
// 注意"总长度"为3个字节,不满足4个字节的uint32类型,因此这里"低位"补0
|
||||
length = binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]})
|
||||
// 解析的大小是否符合规范
|
||||
if length == 0 || length + PKG_HEADER_SIZE > PKG_MAX_SIZE {
|
||||
c.buffer = c.buffer[1:]
|
||||
continue
|
||||
if len(c.buffer) >= gPKG_HEADER_SIZE {
|
||||
// 注意"数据长度"为3个字节,不满足4个字节的uint32类型,因此这里"低位"补0
|
||||
length = int(binary.BigEndian.Uint32([]byte{0, c.buffer[0], c.buffer[1], c.buffer[2]}))
|
||||
// 解析的大小是否符合规范,清空从该连接接收到的所有数据包
|
||||
if length < 0 || length > pkgOption.MaxSize {
|
||||
c.buffer = c.buffer[:0]
|
||||
return nil, fmt.Errorf(`invalid package size %d`, length)
|
||||
}
|
||||
// 不满足包大小,需要继续读取
|
||||
if uint32(len(c.buffer)) < length {
|
||||
if len(c.buffer) < length + gPKG_HEADER_SIZE {
|
||||
break
|
||||
}
|
||||
// 数据校验
|
||||
if binary.BigEndian.Uint32(c.buffer[3 : PKG_HEADER_SIZE]) != Checksum(c.buffer[PKG_HEADER_SIZE : length]) {
|
||||
c.buffer = c.buffer[1:]
|
||||
continue
|
||||
}
|
||||
result = c.buffer[PKG_HEADER_SIZE : length]
|
||||
c.buffer = c.buffer[length: ]
|
||||
result = c.buffer[gPKG_HEADER_SIZE : gPKG_HEADER_SIZE + length]
|
||||
c.buffer = c.buffer[gPKG_HEADER_SIZE + length: ]
|
||||
return
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// 读取系统socket缓冲区的完整数据
|
||||
temp, err = c.Recv(-1, retry...)
|
||||
temp, err = c.Recv(0, pkgOption.Retry)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@ -108,8 +138,10 @@ func (c *Conn) RecvPkg(retry...Retry) (result []byte, err error) {
|
||||
}
|
||||
|
||||
// 简单协议: 带超时时间的消息包获取
|
||||
func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
c.SetRecvDeadline(time.Now().Add(timeout))
|
||||
func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option...PkgOption) ([]byte, error) {
|
||||
if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer c.SetRecvDeadline(time.Time{})
|
||||
return c.RecvPkg(retry...)
|
||||
return c.RecvPkg(option...)
|
||||
}
|
||||
@ -7,13 +7,16 @@
|
||||
package gtcp
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
gDEFAULT_RETRY_INTERVAL = 100 // (毫秒)默认重试时间间隔
|
||||
gDEFAULT_READ_BUFFER_SIZE = 1024 // 默认数据读取缓冲区大小
|
||||
gDEFAULT_RETRY_INTERVAL = 100 // (毫秒)默认重试时间间隔
|
||||
gDEFAULT_READ_BUFFER_SIZE = 128 // (byte)默认数据读取缓冲区大小
|
||||
)
|
||||
|
||||
type Retry struct {
|
||||
@ -21,6 +24,7 @@ type Retry struct {
|
||||
Interval int // 重试间隔(毫秒)
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
// 常见的二进制数据校验方式,生成校验结果
|
||||
func Checksum(buffer []byte) uint32 {
|
||||
var checksum uint32
|
||||
@ -39,6 +43,20 @@ func NewNetConn(addr string, timeout...int) (net.Conn, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 创建支持TLS的原生TCP链接, addr地址格式形如:127.0.0.1:80
|
||||
func NewNetConnTLS(addr string, tlsConfig *tls.Config) (net.Conn, error) {
|
||||
return tls.Dial("tcp", addr, tlsConfig)
|
||||
}
|
||||
|
||||
// 根据给定的证书和密钥文件创建支持TLS的原生TCP链接, addr地址格式形如:127.0.0.1:80
|
||||
func NewNetConnKeyCrt(addr, crtFile, keyFile string) (net.Conn, error) {
|
||||
tlsConfig, err := LoadKeyCrt(crtFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewNetConnTLS(addr, tlsConfig)
|
||||
}
|
||||
|
||||
// (面向短链接)发送数据
|
||||
func Send(addr string, data []byte, retry...Retry) error {
|
||||
conn, err := NewConn(addr)
|
||||
@ -88,4 +106,25 @@ func isTimeout(err error) bool {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 根据证书和密钥生成TLS对象
|
||||
func LoadKeyCrt(crtFile, keyFile string) (*tls.Config, error) {
|
||||
crtPath, err := gfile.Search(crtFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keyPath, err := gfile.Search(keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
crt, err := tls.LoadX509KeyPair(crtPath, keyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig := &tls.Config{}
|
||||
tlsConfig.Certificates = []tls.Certificate{crt}
|
||||
tlsConfig.Time = time.Now
|
||||
tlsConfig.Rand = rand.Reader
|
||||
return tlsConfig, nil
|
||||
}
|
||||
@ -9,41 +9,41 @@ package gtcp
|
||||
import "time"
|
||||
|
||||
// 简单协议: (面向短链接)发送消息包
|
||||
func SendPkg(addr string, data []byte, retry...Retry) error {
|
||||
func SendPkg(addr string, data []byte, option...PkgOption) error {
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendPkg(data, retry...)
|
||||
return conn.SendPkg(data, option...)
|
||||
}
|
||||
|
||||
// 简单协议: (面向短链接)发送数据并等待接收返回数据
|
||||
func SendRecvPkg(addr string, data []byte, retry...Retry) ([]byte, error) {
|
||||
func SendRecvPkg(addr string, data []byte, option...PkgOption) ([]byte, error) {
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendRecvPkg(data, retry...)
|
||||
return conn.SendRecvPkg(data, option...)
|
||||
}
|
||||
|
||||
// 简单协议: (面向短链接)带超时时间的数据发送
|
||||
func SendPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) error {
|
||||
func SendPkgWithTimeout(addr string, data []byte, timeout time.Duration, option...PkgOption) error {
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendPkgWithTimeout(data, timeout, retry...)
|
||||
return conn.SendPkgWithTimeout(data, timeout, option...)
|
||||
}
|
||||
|
||||
// 简单协议: (面向短链接)发送数据并等待接收返回数据(带返回超时等待时间)
|
||||
func SendRecvPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
func SendRecvPkgWithTimeout(addr string, data []byte, timeout time.Duration, option...PkgOption) ([]byte, error) {
|
||||
conn, err := NewConn(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
return conn.SendRecvPkgWithTimeout(data, timeout, retry...)
|
||||
return conn.SendRecvPkgWithTimeout(data, timeout, option...)
|
||||
}
|
||||
@ -67,7 +67,7 @@ func (c *PoolConn) Close() error {
|
||||
c.status = gCONN_STATUS_UNKNOWN
|
||||
c.pool.Put(c)
|
||||
} else {
|
||||
c.Conn.Close()
|
||||
return c.Conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -114,15 +114,19 @@ func (c *PoolConn) RecvLine(retry...Retry) ([]byte, error) {
|
||||
}
|
||||
|
||||
// (方法覆盖)带超时时间的数据获取
|
||||
func (c *PoolConn) RecvWithTimeout(length int, timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
c.SetRecvDeadline(time.Now().Add(timeout))
|
||||
func (c *PoolConn) RecvWithTimeout(length int, timeout time.Duration, retry...Retry) (data []byte, err error) {
|
||||
if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer c.SetRecvDeadline(time.Time{})
|
||||
return c.Recv(length, retry...)
|
||||
}
|
||||
|
||||
// (方法覆盖)带超时时间的数据发送
|
||||
func (c *PoolConn) SendWithTimeout(data []byte, timeout time.Duration, retry...Retry) error {
|
||||
c.SetSendDeadline(time.Now().Add(timeout))
|
||||
if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.SetSendDeadline(time.Time{})
|
||||
return c.Send(data, retry...)
|
||||
}
|
||||
|
||||
@ -11,11 +11,11 @@ import (
|
||||
)
|
||||
|
||||
// 简单协议: (方法覆盖)发送数据
|
||||
func (c *PoolConn) SendPkg(data []byte, retry...Retry) (err error) {
|
||||
if err = c.Conn.SendPkg(data, retry...); err != nil && c.status == gCONN_STATUS_UNKNOWN {
|
||||
func (c *PoolConn) SendPkg(data []byte, option...PkgOption) (err error) {
|
||||
if err = c.Conn.SendPkg(data, option...); err != nil && c.status == gCONN_STATUS_UNKNOWN {
|
||||
if v, e := c.pool.NewFunc(); e == nil {
|
||||
c.Conn = v.(*PoolConn).Conn
|
||||
err = c.Conn.SendPkg(data, retry...)
|
||||
err = c.Conn.SendPkg(data, option...)
|
||||
} else {
|
||||
err = e
|
||||
}
|
||||
@ -29,8 +29,8 @@ func (c *PoolConn) SendPkg(data []byte, retry...Retry) (err error) {
|
||||
}
|
||||
|
||||
// 简单协议: (方法覆盖)接收数据
|
||||
func (c *PoolConn) RecvPkg(retry...Retry) ([]byte, error) {
|
||||
data, err := c.Conn.RecvPkg(retry...)
|
||||
func (c *PoolConn) RecvPkg(option...PkgOption) ([]byte, error) {
|
||||
data, err := c.Conn.RecvPkg(option...)
|
||||
if err != nil {
|
||||
c.status = gCONN_STATUS_ERROR
|
||||
} else {
|
||||
@ -40,32 +40,36 @@ func (c *PoolConn) RecvPkg(retry...Retry) ([]byte, error) {
|
||||
}
|
||||
|
||||
// 简单协议: (方法覆盖)带超时时间的数据获取
|
||||
func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
c.SetRecvDeadline(time.Now().Add(timeout))
|
||||
func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, option...PkgOption) ([]byte, error) {
|
||||
if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer c.SetRecvDeadline(time.Time{})
|
||||
return c.RecvPkg(retry...)
|
||||
return c.RecvPkg(option...)
|
||||
}
|
||||
|
||||
// 简单协议: (方法覆盖)带超时时间的数据发送
|
||||
func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) error {
|
||||
c.SetSendDeadline(time.Now().Add(timeout))
|
||||
func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) error {
|
||||
if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.SetSendDeadline(time.Time{})
|
||||
return c.SendPkg(data, retry...)
|
||||
return c.SendPkg(data, option...)
|
||||
}
|
||||
|
||||
// 简单协议: (方法覆盖)发送数据并等待接收返回数据
|
||||
func (c *PoolConn) SendRecvPkg(data []byte, retry...Retry) ([]byte, error) {
|
||||
if err := c.SendPkg(data, retry...); err == nil {
|
||||
return c.RecvPkg(retry...)
|
||||
func (c *PoolConn) SendRecvPkg(data []byte, option...PkgOption) ([]byte, error) {
|
||||
if err := c.SendPkg(data, option...); err == nil {
|
||||
return c.RecvPkg(option...)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 简单协议: (方法覆盖)发送数据并等待接收返回数据(带返回超时等待时间)
|
||||
func (c *PoolConn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
if err := c.SendPkg(data, retry...); err == nil {
|
||||
return c.RecvPkgWithTimeout(timeout, retry...)
|
||||
func (c *PoolConn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option...PkgOption) ([]byte, error) {
|
||||
if err := c.SendPkg(data, option...); err == nil {
|
||||
return c.RecvPkgWithTimeout(timeout, option...)
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -8,79 +8,132 @@
|
||||
package gtcp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"net"
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"github.com/gogf/gf/g/container/gmap"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"net"
|
||||
)
|
||||
|
||||
const (
|
||||
gDEFAULT_SERVER = "default"
|
||||
)
|
||||
|
||||
// tcp server结构体
|
||||
// TCP Server.
|
||||
type Server struct {
|
||||
listen net.Listener
|
||||
address string
|
||||
handler func (*Conn)
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
// Server表,用以存储和检索名称与Server对象之间的关联关系
|
||||
// Map for name to server, for singleton purpose.
|
||||
var serverMapping = gmap.NewStrAnyMap()
|
||||
|
||||
// 获取/创建一个空配置的TCP Server
|
||||
// 单例模式,请保证name的唯一性
|
||||
// GetServer returns the TCP server with specified <name>,
|
||||
// or it returns a new normal TCP server named <name> if it does not exist.
|
||||
// The parameter <name> is used to specify the TCP server
|
||||
func GetServer(name...interface{}) *Server {
|
||||
serverName := gDEFAULT_SERVER
|
||||
if len(name) > 0 {
|
||||
serverName = gconv.String(name[0])
|
||||
}
|
||||
if s := serverMapping.Get(serverName); s != nil {
|
||||
return s.(*Server)
|
||||
}
|
||||
s := NewServer("", nil)
|
||||
serverMapping.Set(serverName, s)
|
||||
return s
|
||||
return serverMapping.GetOrSetFuncLock(serverName, func() interface{} {
|
||||
return NewServer("", nil)
|
||||
}).(*Server)
|
||||
}
|
||||
|
||||
// 创建一个tcp server对象,并且可以选择指定一个单例名字
|
||||
func NewServer(address string, handler func (*Conn), names...string) *Server {
|
||||
s := &Server{address, handler}
|
||||
if len(names) > 0 {
|
||||
serverMapping.Set(names[0], s)
|
||||
// NewServer creates and returns a new normal TCP server.
|
||||
// The param <name> is optional, which is used to specify the instance name of the server.
|
||||
func NewServer(address string, handler func (*Conn), name...string) *Server {
|
||||
s := &Server{
|
||||
address : address,
|
||||
handler : handler,
|
||||
}
|
||||
if len(name) > 0 {
|
||||
serverMapping.Set(name[0], s)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// 设置参数 - address
|
||||
func (s *Server) SetAddress (address string) {
|
||||
// NewServerTLS creates and returns a new TCP server with TLS support.
|
||||
// The param <name> is optional, which is used to specify the instance name of the server.
|
||||
func NewServerTLS(address string, tlsConfig *tls.Config, handler func (*Conn), name...string) *Server {
|
||||
s := NewServer(address, handler, name...)
|
||||
s.SetTLSConfig(tlsConfig)
|
||||
return s
|
||||
}
|
||||
|
||||
// NewServerKeyCrt creates and returns a new TCP server with TLS support.
|
||||
// The param <name> is optional, which is used to specify the instance name of the server.
|
||||
func NewServerKeyCrt(address, crtFile, keyFile string, handler func (*Conn), name...string) *Server {
|
||||
s := NewServer(address, handler, name...)
|
||||
if err := s.SetTLSKeyCrt(crtFile, keyFile); err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// SetAddress sets the listening address for server.
|
||||
func (s *Server) SetAddress(address string) {
|
||||
s.address = address
|
||||
}
|
||||
|
||||
// 设置参数 - handler
|
||||
func (s *Server) SetHandler (handler func (*Conn)) {
|
||||
// SetHandler sets the connection handler for server.
|
||||
func (s *Server) SetHandler(handler func (*Conn)) {
|
||||
s.handler = handler
|
||||
}
|
||||
|
||||
// 执行监听
|
||||
func (s *Server) Run() error {
|
||||
// SetTlsKeyCrt sets the certificate and key file for TLS configuration of server.
|
||||
func (s *Server) SetTLSKeyCrt(crtFile, keyFile string) error {
|
||||
tlsConfig, err := LoadKeyCrt(crtFile, keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.tlsConfig = tlsConfig
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTlsConfig sets the TLS configuration of server.
|
||||
func (s *Server) SetTLSConfig(tlsConfig *tls.Config) {
|
||||
s.tlsConfig = tlsConfig
|
||||
}
|
||||
|
||||
// Close closes the listener and shutdowns the server.
|
||||
func (s *Server) Close() error {
|
||||
return s.listen.Close()
|
||||
}
|
||||
|
||||
// Run starts running the TCP Server.
|
||||
func (s *Server) Run() (err error) {
|
||||
if s.handler == nil {
|
||||
err := errors.New("start running failed: socket handler not defined")
|
||||
err = errors.New("start running failed: socket handler not defined")
|
||||
glog.Error(err)
|
||||
return err
|
||||
return
|
||||
}
|
||||
addr, err := net.ResolveTCPAddr("tcp", s.address)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return err
|
||||
if s.tlsConfig != nil {
|
||||
// TLS Server
|
||||
s.listen, err = tls.Listen("tcp", s.address, s.tlsConfig)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Normal Server
|
||||
addr, err := net.ResolveTCPAddr("tcp", s.address)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return err
|
||||
}
|
||||
s.listen, err = net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
listen, err := net.ListenTCP("tcp", addr)
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
return err
|
||||
}
|
||||
for {
|
||||
if conn, err := listen.Accept(); err != nil {
|
||||
for {
|
||||
if conn, err := s.listen.Accept(); err != nil {
|
||||
glog.Error(err)
|
||||
return err
|
||||
} else if conn != nil {
|
||||
|
||||
@ -12,7 +12,7 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// 封装的链接对象
|
||||
// 封装的UDP链接对象
|
||||
type Conn struct {
|
||||
conn *net.UDPConn // 底层链接对象
|
||||
raddr *net.UDPAddr // 远程地址
|
||||
@ -53,15 +53,12 @@ func NewConnByNetConn(udp *net.UDPConn) *Conn {
|
||||
}
|
||||
|
||||
// 发送数据
|
||||
func (c *Conn) Send(data []byte, retry...Retry) error {
|
||||
var err error
|
||||
var size int
|
||||
var length int
|
||||
func (c *Conn) Send(data []byte, retry...Retry) (err error) {
|
||||
for {
|
||||
if c.raddr != nil {
|
||||
size, err = c.conn.WriteToUDP(data, c.raddr)
|
||||
_, err = c.conn.WriteToUDP(data, c.raddr)
|
||||
} else {
|
||||
size, err = c.conn.Write(data)
|
||||
_, err = c.conn.Write(data)
|
||||
}
|
||||
if err != nil {
|
||||
// 链接已关闭
|
||||
@ -80,16 +77,16 @@ func (c *Conn) Send(data []byte, retry...Retry) error {
|
||||
time.Sleep(time.Duration(retry[0].Interval) * time.Millisecond)
|
||||
}
|
||||
} else {
|
||||
length += size
|
||||
if length == len(data) {
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 接收数据.
|
||||
// 注意:UDP协议存在消息边界,因此使用 length<=0 可以获取缓冲区所有消息包数据,即一个完整包。
|
||||
// 接收UDP协议数据.
|
||||
//
|
||||
// 注意事项:
|
||||
// 1、UDP协议存在消息边界,因此使用 length < 0 可以获取缓冲区所有消息包数据,即一个完整包;
|
||||
// 2、当length = 0时,表示获取当前的缓冲区数据,获取一次后立即返回;
|
||||
func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) {
|
||||
var err error // 读取错误
|
||||
var size int // 读取长度
|
||||
@ -105,9 +102,11 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) {
|
||||
}
|
||||
|
||||
for {
|
||||
if length <= 0 && index > 0 {
|
||||
if length < 0 && index > 0 {
|
||||
bufferWait = true
|
||||
c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait))
|
||||
if err = c.conn.SetReadDeadline(time.Now().Add(c.recvBufferWait)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
size, raddr, err = c.conn.ReadFromUDP(buffer[index:])
|
||||
if err == nil {
|
||||
@ -139,7 +138,9 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) {
|
||||
}
|
||||
// 判断数据是否全部读取完毕(由于超时机制的存在,获取的数据完整性不可靠)
|
||||
if bufferWait && isTimeout(err) {
|
||||
c.conn.SetReadDeadline(c.recvDeadline)
|
||||
if err = c.conn.SetReadDeadline(c.recvDeadline); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = nil
|
||||
break
|
||||
}
|
||||
@ -157,6 +158,10 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) {
|
||||
}
|
||||
break
|
||||
}
|
||||
// 只获取一次数据
|
||||
if length == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return buffer[:index], err
|
||||
}
|
||||
@ -172,14 +177,18 @@ func (c *Conn) SendRecv(data []byte, receive int, retry...Retry) ([]byte, error)
|
||||
|
||||
// 带超时时间的数据获取
|
||||
func (c *Conn) RecvWithTimeout(length int, timeout time.Duration, retry...Retry) ([]byte, error) {
|
||||
c.SetRecvDeadline(time.Now().Add(timeout))
|
||||
if err := c.SetRecvDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer c.SetRecvDeadline(time.Time{})
|
||||
return c.Recv(length, retry...)
|
||||
}
|
||||
|
||||
// 带超时时间的数据发送
|
||||
func (c *Conn) SendWithTimeout(data []byte, timeout time.Duration, retry...Retry) error {
|
||||
c.SetSendDeadline(time.Now().Add(timeout))
|
||||
if err := c.SetSendDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.SetSendDeadline(time.Time{})
|
||||
return c.Send(data, retry...)
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// Deprecated.
|
||||
// 常见的二进制数据校验方式,生成校验结果
|
||||
func Checksum(buffer []byte) uint32 {
|
||||
var checksum uint32
|
||||
@ -22,18 +23,18 @@ func Checksum(buffer []byte) uint32 {
|
||||
// 创建标准库UDP链接操作对象
|
||||
func NewNetConn(raddr string, laddr...string) (*net.UDPConn, error) {
|
||||
var err error
|
||||
var rudpaddr, ludpaddr *net.UDPAddr
|
||||
rudpaddr, err = net.ResolveUDPAddr("udp", raddr)
|
||||
var remoteAddr, localAddr *net.UDPAddr
|
||||
remoteAddr, err = net.ResolveUDPAddr("udp", raddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(laddr) > 0 {
|
||||
ludpaddr, err = net.ResolveUDPAddr("udp", laddr[0])
|
||||
localAddr, err = net.ResolveUDPAddr("udp", laddr[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
conn, err := net.DialUDP("udp", ludpaddr, rudpaddr)
|
||||
conn, err := net.DialUDP("udp", localAddr, remoteAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -21,8 +21,9 @@ const (
|
||||
|
||||
// tcp server结构体
|
||||
type Server struct {
|
||||
address string
|
||||
handler func (*Conn)
|
||||
conn *Conn // UDP server connection object.
|
||||
address string // Listening address.
|
||||
handler func (*Conn)
|
||||
}
|
||||
|
||||
// Server表,用以存储和检索名称与Server对象之间的关联关系
|
||||
@ -30,7 +31,7 @@ var serverMapping = gmap.NewStrAnyMap()
|
||||
|
||||
// 获取/创建一个空配置的UDP Server
|
||||
// 单例模式,请保证name的唯一性
|
||||
func GetServer(name...interface{}) (*Server) {
|
||||
func GetServer(name...interface{}) *Server {
|
||||
serverName := gDEFAULT_SERVER
|
||||
if len(name) > 0 {
|
||||
serverName = gconv.String(name[0])
|
||||
@ -44,8 +45,11 @@ func GetServer(name...interface{}) (*Server) {
|
||||
}
|
||||
|
||||
// 创建一个tcp server对象,并且可以选择指定一个单例名字
|
||||
func NewServer (address string, handler func (*Conn), names...string) *Server {
|
||||
s := &Server{address, handler}
|
||||
func NewServer(address string, handler func (*Conn), names...string) *Server {
|
||||
s := &Server{
|
||||
address : address,
|
||||
handler : handler,
|
||||
}
|
||||
if len(names) > 0 {
|
||||
serverMapping.Set(names[0], s)
|
||||
}
|
||||
@ -58,10 +62,16 @@ func (s *Server) SetAddress (address string) {
|
||||
}
|
||||
|
||||
// 设置参数 - handler
|
||||
func (s *Server) SetHandler (handler func (*Conn)) {
|
||||
func (s *Server) SetHandler(handler func (*Conn)) {
|
||||
s.handler = handler
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
// It will make server shutdowns immediately.
|
||||
func (s *Server) Close() error {
|
||||
return s.conn.Close()
|
||||
}
|
||||
|
||||
// 执行监听
|
||||
func (s *Server) Run() error {
|
||||
if s.handler == nil {
|
||||
@ -79,7 +89,7 @@ func (s *Server) Run() error {
|
||||
glog.Error(err)
|
||||
return err
|
||||
}
|
||||
for {
|
||||
s.handler(NewConnByNetConn(conn))
|
||||
}
|
||||
s.conn = NewConnByNetConn(conn)
|
||||
s.handler(s.conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -55,20 +55,20 @@ func New(file...string) *Config {
|
||||
// Customized dir path from env/cmd.
|
||||
if envPath := cmdenv.Get("gf.gcfg.path").String(); envPath != "" {
|
||||
if gfile.Exists(envPath) {
|
||||
c.SetPath(envPath)
|
||||
_ = c.SetPath(envPath)
|
||||
} else {
|
||||
glog.Errorfln("Configuration directory path does not exist: %s", envPath)
|
||||
glog.Errorf("Configuration directory path does not exist: %s", envPath)
|
||||
}
|
||||
} else {
|
||||
// Dir path of working dir.
|
||||
c.SetPath(gfile.Pwd())
|
||||
_ = c.SetPath(gfile.Pwd())
|
||||
// Dir path of binary.
|
||||
if selfPath := gfile.SelfDir(); selfPath != "" && gfile.Exists(selfPath) {
|
||||
c.AddPath(selfPath)
|
||||
_ = c.AddPath(selfPath)
|
||||
}
|
||||
// Dir path of main package.
|
||||
if mainPath := gfile.MainPkgPath(); mainPath != "" && gfile.Exists(mainPath) {
|
||||
c.AddPath(mainPath)
|
||||
_ = c.AddPath(mainPath)
|
||||
}
|
||||
}
|
||||
return c
|
||||
@ -276,9 +276,9 @@ func (c *Config) getJson(file...string) *gjson.Json {
|
||||
return j
|
||||
} else {
|
||||
if filePath != "" {
|
||||
glog.Criticalfln(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error())
|
||||
glog.Criticalf(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error())
|
||||
} else {
|
||||
glog.Criticalfln(`[gcfg] Load configuration failed: %s`, err.Error())
|
||||
glog.Criticalf(`[gcfg] Load configuration failed: %s`, err.Error())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@ -115,7 +115,7 @@ func (entry *Entry) check() {
|
||||
return
|
||||
|
||||
case STATUS_CLOSED:
|
||||
glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s removed", entry.Name, entry.schedule.pattern, entry.jobName)
|
||||
glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s removed", entry.Name, entry.schedule.pattern, entry.jobName)
|
||||
entry.Close()
|
||||
|
||||
case STATUS_READY: fallthrough
|
||||
@ -130,12 +130,12 @@ func (entry *Entry) check() {
|
||||
if times < 2000000000 && times > 1000000000 {
|
||||
entry.times.Set(gDEFAULT_TIMES)
|
||||
}
|
||||
glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s start", entry.Name, entry.schedule.pattern, entry.jobName)
|
||||
glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s start", entry.Name, entry.schedule.pattern, entry.jobName)
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
glog.Path(path).Level(level).Errorfln("[gcron] %s(%s) %s end with error: %v", entry.Name, entry.schedule.pattern, entry.jobName, err)
|
||||
glog.Path(path).Level(level).Errorf("[gcron] %s(%s) %s end with error: %v", entry.Name, entry.schedule.pattern, entry.jobName, err)
|
||||
} else {
|
||||
glog.Path(path).Level(level).Debugfln("[gcron] %s(%s) %s end", entry.Name, entry.schedule.pattern, entry.jobName)
|
||||
glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s end", entry.Name, entry.schedule.pattern, entry.jobName)
|
||||
}
|
||||
if entry.entry.Status() == STATUS_CLOSED {
|
||||
entry.Close()
|
||||
|
||||
@ -27,23 +27,20 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// 文件分隔符
|
||||
// Separator for file system.
|
||||
Separator = string(filepath.Separator)
|
||||
// 默认的文件打开权限
|
||||
// Default perm for file opening.
|
||||
gDEFAULT_PERM = 0666
|
||||
)
|
||||
|
||||
var (
|
||||
// 源码的main包所在目录,仅仅会设置一次
|
||||
mainPkgPath = gtype.NewString()
|
||||
|
||||
// 编译时的 GOROOT 数值
|
||||
goRootOfBuild = gtype.NewString()
|
||||
// The absolute file path for main package.
|
||||
// It can be only checked and set once.
|
||||
mainPkgPath = gtype.NewString()
|
||||
)
|
||||
|
||||
// Create directories recursively.
|
||||
//
|
||||
// 给定目录的绝对路径创建目录(递归创建)。
|
||||
// Mkdir creates directories recursively with given <path>.
|
||||
// The parameter <path> is suggested to be absolute path.
|
||||
func Mkdir(path string) error {
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
@ -52,9 +49,8 @@ func Mkdir(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create file with given path recursively.
|
||||
//
|
||||
// 给定文件的绝对路径创建文件。
|
||||
// Create creates file with given <path> recursively.
|
||||
// The parameter <path> is suggested to be absolute path.
|
||||
func Create(path string) (*os.File, error) {
|
||||
dir := Dir(path)
|
||||
if !Exists(dir) {
|
||||
@ -63,23 +59,17 @@ func Create(path string) (*os.File, error) {
|
||||
return os.Create(path)
|
||||
}
|
||||
|
||||
// Open file/directory with readonly.
|
||||
//
|
||||
// 只读打开文件
|
||||
// Open opens file/directory readonly.
|
||||
func Open(path string) (*os.File, error) {
|
||||
return os.Open(path)
|
||||
}
|
||||
|
||||
// Open file/directory with given <flag> and <perm>.
|
||||
//
|
||||
// 打开文件(带flag&perm)
|
||||
// OpenFile opens file/directory with given <flag> and <perm>.
|
||||
func OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) {
|
||||
return os.OpenFile(path, flag, perm)
|
||||
}
|
||||
|
||||
// Open file/directory with default perm and given <flag>.
|
||||
//
|
||||
// 打开文件(带flag)
|
||||
// OpenWithFlag opens file/directory with default perm and given <flag>.
|
||||
func OpenWithFlag(path string, flag int) (*os.File, error) {
|
||||
f, err := os.OpenFile(path, flag, gDEFAULT_PERM)
|
||||
if err != nil {
|
||||
@ -88,9 +78,7 @@ func OpenWithFlag(path string, flag int) (*os.File, error) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Open file/directory with given <flag> and <perm>.
|
||||
//
|
||||
// 打开文件(带flag&perm)
|
||||
// OpenWithFlagPerm opens file/directory with given <flag> and <perm>.
|
||||
func OpenWithFlagPerm(path string, flag int, perm int) (*os.File, error) {
|
||||
f, err := os.OpenFile(path, flag, os.FileMode(perm))
|
||||
if err != nil {
|
||||
@ -99,9 +87,7 @@ func OpenWithFlagPerm(path string, flag int, perm int) (*os.File, error) {
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Check whether given path exist.
|
||||
//
|
||||
// 判断所给路径文件/文件夹是否存在
|
||||
// Exists checks whether given <path> exist.
|
||||
func Exists(path string) bool {
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
return true
|
||||
@ -109,9 +95,7 @@ func Exists(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check whether given path a directory.
|
||||
//
|
||||
// 判断所给路径是否为文件夹
|
||||
// IsDir checks whether given <path> a directory.
|
||||
func IsDir(path string) bool {
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
@ -120,17 +104,13 @@ func IsDir(path string) bool {
|
||||
return s.IsDir()
|
||||
}
|
||||
|
||||
// Get current working directory absolute path.
|
||||
//
|
||||
// 获取当前工作目录(注意与SelfDir的区别).
|
||||
// Pwd returns absolute path of current working directory.
|
||||
func Pwd() string {
|
||||
path, _ := os.Getwd()
|
||||
return path
|
||||
}
|
||||
|
||||
// Check whether given path a file(not a directory).
|
||||
//
|
||||
// 判断所给路径是否为文件
|
||||
// IsFile checks whether given <path> a file, which means it's not a directory.
|
||||
func IsFile(path string) bool {
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
@ -139,39 +119,32 @@ func IsFile(path string) bool {
|
||||
return !s.IsDir()
|
||||
}
|
||||
|
||||
// Alias of Stat.
|
||||
// See Stat.
|
||||
//
|
||||
// Stat 方法的别名。
|
||||
func Info(path string) (os.FileInfo, error) {
|
||||
return Stat(path)
|
||||
}
|
||||
|
||||
// Stat returns a FileInfo describing the named file.
|
||||
// If there is an error, it will be of type *PathError.
|
||||
//
|
||||
// 获取文件或目录信息.
|
||||
func Stat(path string) (os.FileInfo, error) {
|
||||
return os.Stat(path)
|
||||
}
|
||||
|
||||
// Move renames (moves) src to dst path.
|
||||
//
|
||||
// 文件移动/重命名
|
||||
// Move renames (moves) <src> to <dst> path.
|
||||
func Move(src string, dst string) error {
|
||||
return os.Rename(src, dst)
|
||||
}
|
||||
|
||||
// Rename renames (moves) src to dst path.
|
||||
//
|
||||
// 文件移动/重命名.
|
||||
// Alias of Move.
|
||||
// See Move.
|
||||
func Rename(src string, dst string) error {
|
||||
return Move(src, dst)
|
||||
}
|
||||
|
||||
// Copy file from src to dst.
|
||||
// Copy file from <src> to <dst>.
|
||||
//
|
||||
// 文件复制.
|
||||
// @TODO 支持目录复制.
|
||||
// @TODO directory copy support.
|
||||
func Copy(src string, dst string) error {
|
||||
srcFile, err := Open(src)
|
||||
if err != nil {
|
||||
@ -194,9 +167,7 @@ func Copy(src string, dst string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get sub-file names of path.
|
||||
//
|
||||
// 返回目录下的文件名列表
|
||||
// DirNames returns sub-file names of given directory <path>.
|
||||
func DirNames(path string) ([]string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
@ -218,8 +189,6 @@ func DirNames(path string) ([]string, error) {
|
||||
// Glob ignores file system errors such as I/O errors reading directories.
|
||||
// The only possible returned error is ErrBadPattern, when pattern
|
||||
// is malformed.
|
||||
//
|
||||
// 文件名正则匹配查找,第二个可选参数指定返回的列表是否仅为文件名(非绝对路径),默认返回绝对路径
|
||||
func Glob(pattern string, onlyNames...bool) ([]string, error) {
|
||||
if list, err := filepath.Glob(pattern); err == nil {
|
||||
if len(onlyNames) > 0 && onlyNames[0] && len(list) > 0 {
|
||||
@ -235,16 +204,13 @@ func Glob(pattern string, onlyNames...bool) ([]string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Remove file/directory with <path> parameter.
|
||||
//
|
||||
// 文件/目录删除
|
||||
// Remove deletes all file/directory with <path> parameter.
|
||||
// If parameter <path> is directory, it deletes it recursively.
|
||||
func Remove(path string) error {
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
// Check whether given <path> is readable.
|
||||
//
|
||||
// 文件是否可读(支持文件/目录)
|
||||
// IsReadable checks whether given <path> is readable.
|
||||
func IsReadable(path string) bool {
|
||||
result := true
|
||||
file, err := os.OpenFile(path, os.O_RDONLY, gDEFAULT_PERM)
|
||||
@ -255,14 +221,13 @@ func IsReadable(path string) bool {
|
||||
return result
|
||||
}
|
||||
|
||||
// Check whether given <path> is writable.
|
||||
// IsWritable checks whether given <path> is writable.
|
||||
//
|
||||
// 文件是否可写(支持文件/目录)
|
||||
// @TODO 改进性能,利用 golang.org/x/sys 来实现跨平台的权限判断。
|
||||
// @TODO improve performance; use golang.org/x/sys to cross-plat-form
|
||||
func IsWritable(path string) bool {
|
||||
result := true
|
||||
if IsDir(path) {
|
||||
// 如果是目录,那么创建一个临时文件进行写入测试
|
||||
// If it's a directory, create a temporary file to test whether it's writable.
|
||||
tmpFile := strings.TrimRight(path, Separator) + Separator + gconv.String(time.Now().UnixNano())
|
||||
if f, err := Create(tmpFile); err != nil || !Exists(tmpFile){
|
||||
result = false
|
||||
@ -282,16 +247,12 @@ func IsWritable(path string) bool {
|
||||
}
|
||||
|
||||
// See os.Chmod.
|
||||
//
|
||||
// 修改文件/目录权限
|
||||
func Chmod(path string, mode os.FileMode) error {
|
||||
return os.Chmod(path, mode)
|
||||
}
|
||||
|
||||
// Get all sub-files(absolute) of given <path>,
|
||||
// can be recursively with given parameter <recursive> true.
|
||||
//
|
||||
// 打开目录,并返回其下一级文件列表(绝对路径),按照文件名称大小写进行排序,支持目录递归遍历。
|
||||
// ScanDir returns all sub-files with absolute paths of given <path>,
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
func ScanDir(path string, pattern string, recursive ... bool) ([]string, error) {
|
||||
list, err := doScanDir(path, pattern, recursive...)
|
||||
if err != nil {
|
||||
@ -303,22 +264,24 @@ func ScanDir(path string, pattern string, recursive ... bool) ([]string, error)
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// 内部检索目录方法,支持递归,返回没有排序的文件绝对路径列表结果。
|
||||
// pattern参数支持多个文件名称模式匹配,使用','符号分隔多个模式。
|
||||
// doScanDir is an internal method which scans directory
|
||||
// and returns the absolute path list of files that are not sorted.
|
||||
//
|
||||
// The pattern parameter <pattern> supports multiple file name patterns,
|
||||
// using the ',' symbol to separate multiple patterns.
|
||||
//
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
func doScanDir(path string, pattern string, recursive ... bool) ([]string, error) {
|
||||
list := ([]string)(nil)
|
||||
// 打开目录
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
// 读取目录下的文件列表
|
||||
names, err := file.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 是否递归遍历
|
||||
for _, name := range names {
|
||||
path := fmt.Sprintf("%s%s%s", path, Separator, name)
|
||||
if IsDir(path) && len(recursive) > 0 && recursive[0] {
|
||||
@ -327,7 +290,7 @@ func doScanDir(path string, pattern string, recursive ... bool) ([]string, error
|
||||
list = append(list, array...)
|
||||
}
|
||||
}
|
||||
// 满足pattern才加入结果列表
|
||||
// If it meets pattern, then add it to the result list.
|
||||
for _, p := range strings.Split(pattern, ",") {
|
||||
if match, err := filepath.Match(strings.TrimSpace(p), name); err == nil && match {
|
||||
list = append(list, path)
|
||||
@ -337,10 +300,9 @@ func doScanDir(path string, pattern string, recursive ... bool) ([]string, error
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// See filepath.Abs.
|
||||
//
|
||||
// 将所给定的路径转换为绝对路径
|
||||
// 并判断文件路径是否存在,如果文件不存在,那么返回空字符串
|
||||
// RealPath converts the given <path> to its absolute path
|
||||
// and checks if the file path exists.
|
||||
// If the file does not exist, return an empty string.
|
||||
func RealPath(path string) string {
|
||||
p, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
@ -352,45 +314,46 @@ func RealPath(path string) string {
|
||||
return p
|
||||
}
|
||||
|
||||
// Get absolute file path of current running process(binary).
|
||||
//
|
||||
// 获取当前执行文件的绝对路径
|
||||
// SelfPath returns absolute file path of current running process(binary).
|
||||
func SelfPath() string {
|
||||
p, _ := filepath.Abs(os.Args[0])
|
||||
return p
|
||||
}
|
||||
|
||||
// Get absolute directory path of current running process(binary).
|
||||
//
|
||||
// 获取当前执行文件的目录绝对路径
|
||||
// SelfDir returns absolute directory path of current running process(binary).
|
||||
func SelfDir() string {
|
||||
return filepath.Dir(SelfPath())
|
||||
}
|
||||
|
||||
// See filepath.Base.
|
||||
//
|
||||
// 获取指定文件路径的文件名称
|
||||
// Basename returns the last element of path.
|
||||
// Trailing path separators are removed before extracting the last element.
|
||||
// If the path is empty, Base returns ".".
|
||||
// If the path consists entirely of separators, Base returns a single separator.
|
||||
func Basename(path string) string {
|
||||
return filepath.Base(path)
|
||||
}
|
||||
|
||||
// See filepath.Dir.
|
||||
//
|
||||
// 获取指定文件路径的目录地址绝对路径.
|
||||
// Dir returns all but the last element of path, typically the path's directory.
|
||||
// After dropping the final element, Dir calls Clean on the path and trailing
|
||||
// slashes are removed.
|
||||
// If the path is empty, Dir returns ".".
|
||||
// If the path consists entirely of separators, Dir returns a single separator.
|
||||
// The returned path does not end in a separator unless it is the root directory.
|
||||
func Dir(path string) string {
|
||||
return filepath.Dir(path)
|
||||
}
|
||||
|
||||
// See filepath.Ext.
|
||||
// Ext returns the file name extension used by path.
|
||||
// The extension is the suffix beginning at the final dot
|
||||
// in the final element of path; it is empty if there is
|
||||
// no dot.
|
||||
//
|
||||
// 获取指定文件路径的文件扩展名(包含"."号)
|
||||
// Note: the result contains symbol '.'.
|
||||
func Ext(path string) string {
|
||||
return filepath.Ext(path)
|
||||
}
|
||||
|
||||
// Get absolute home directory path of current user.
|
||||
//
|
||||
// 获取用户主目录
|
||||
// Home returns absolute path of current user's home directory.
|
||||
func Home() (string, error) {
|
||||
u, err := user.Current()
|
||||
if nil == err {
|
||||
@ -435,12 +398,15 @@ func homeWindows() (string, error) {
|
||||
return home, nil
|
||||
}
|
||||
|
||||
// Get absolute file path of main file, which contains the entrance function main.
|
||||
// Available in develop environment.
|
||||
// MainPkgPath returns absolute file path of package main,
|
||||
// which contains the entrance function main.
|
||||
//
|
||||
// 获取入口函数文件所在目录(main包文件目录),
|
||||
// **仅对源码开发环境有效(即仅对生成该可执行文件的系统下有效)**。
|
||||
// 注意:该方法被第一次调用时,如果是在异步的goroutine中,该方法可能无法获取到main包路径。
|
||||
// It's only available in develop environment.
|
||||
//
|
||||
// Note1: Only valid for source development environments,
|
||||
// IE only valid for systems that generate this executable.
|
||||
// Note2: When the method is called for the first time, if it is in an asynchronous goroutine,
|
||||
// the method may not get the main package path.
|
||||
func MainPkgPath() string {
|
||||
path := mainPkgPath.Val()
|
||||
if path != "" {
|
||||
@ -451,14 +417,16 @@ func MainPkgPath() string {
|
||||
}
|
||||
for i := 1; i < 10000; i++ {
|
||||
if _, file, _, ok := runtime.Caller(i); ok {
|
||||
// <file> is separated by '/'
|
||||
if gstr.Contains(file, "/gf/g/") {
|
||||
continue
|
||||
}
|
||||
if Ext(file) != ".go" {
|
||||
continue
|
||||
}
|
||||
path = file
|
||||
for path != "/" && gstr.Contains(path, "/") {
|
||||
// separator of <file> '/' will be converted to Separator.
|
||||
path = Dir(file)
|
||||
for path[len(path) - 1] != os.PathSeparator {
|
||||
files, _ := ScanDir(path, "*.go")
|
||||
for _, v := range files {
|
||||
if gregex.IsMatchString(`package\s+main`, GetContents(v)) {
|
||||
@ -473,14 +441,13 @@ func MainPkgPath() string {
|
||||
break
|
||||
}
|
||||
}
|
||||
// 找不到,下次不用再检索了
|
||||
// If it fails finding the path, then mark it as "-",
|
||||
// which means it will never do this search again.
|
||||
mainPkgPath.Set("-")
|
||||
return ""
|
||||
}
|
||||
|
||||
// See os.TempDir().
|
||||
//
|
||||
// 系统临时目录
|
||||
func TempDir() string {
|
||||
return os.TempDir()
|
||||
}
|
||||
|
||||
@ -13,18 +13,18 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// 方法中涉及到读取的时候的缓冲大小
|
||||
gREAD_BUFFER = 1024
|
||||
// 方法中涉及到文件指针池的默认缓存时间(毫秒)
|
||||
//gFILE_POOL_EXPIRE = 60000
|
||||
// Buffer size for reading file content.
|
||||
gREAD_BUFFER = 1024
|
||||
)
|
||||
|
||||
// (文本)读取文件内容
|
||||
// GetContents returns the file content of <path> as string.
|
||||
// It returns en empty string if it fails reading.
|
||||
func GetContents(path string) string {
|
||||
return string(GetBinContents(path))
|
||||
}
|
||||
|
||||
// (二进制)读取文件内容,如果文件不存在或者读取失败,返回nil。
|
||||
// GetBinContents returns the file content of <path> as []byte.
|
||||
// It returns nil if it fails reading.
|
||||
func GetBinContents(path string) []byte {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
@ -33,16 +33,16 @@ func GetBinContents(path string) []byte {
|
||||
return data
|
||||
}
|
||||
|
||||
// 写入文件内容
|
||||
// putContents puts binary content to file of <path>.
|
||||
func putContents(path string, data []byte, flag int, perm int) error {
|
||||
// 支持目录递归创建
|
||||
// It supports creating file of <path> recursively.
|
||||
dir := Dir(path)
|
||||
if !Exists(dir) {
|
||||
if err := Mkdir(dir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 创建/打开文件
|
||||
// Opening file with given <flag> and <perm>.
|
||||
f, err := OpenWithFlagPerm(path, flag, perm)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -56,32 +56,36 @@ func putContents(path string, data []byte, flag int, perm int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Truncate
|
||||
// Truncate truncates file of <path> to given size by <size>.
|
||||
func Truncate(path string, size int) error {
|
||||
return os.Truncate(path, int64(size))
|
||||
}
|
||||
|
||||
// (文本)写入文件内容
|
||||
// PutContents puts string <content> to file of <path>.
|
||||
// It creates file of <path> recursively if it does not exist.
|
||||
func PutContents(path string, content string) error {
|
||||
return putContents(path, []byte(content), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, gDEFAULT_PERM)
|
||||
}
|
||||
|
||||
// (文本)追加内容到文件末尾
|
||||
// PutContentsAppend appends string <content> to file of <path>.
|
||||
// It creates file of <path> recursively if it does not exist.
|
||||
func PutContentsAppend(path string, content string) error {
|
||||
return putContents(path, []byte(content), os.O_WRONLY|os.O_CREATE|os.O_APPEND, gDEFAULT_PERM)
|
||||
}
|
||||
|
||||
// (二进制)写入文件内容
|
||||
// PutBinContents puts binary <content> to file of <path>.
|
||||
// It creates file of <path> recursively if it does not exist.
|
||||
func PutBinContents(path string, content []byte) error {
|
||||
return putContents(path, content, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, gDEFAULT_PERM)
|
||||
}
|
||||
|
||||
// (二进制)追加内容到文件末尾
|
||||
// PutBinContentsAppend appends binary <content> to file of <path>.
|
||||
// It creates file of <path> recursively if it does not exist.
|
||||
func PutBinContentsAppend(path string, content []byte) error {
|
||||
return putContents(path, content, os.O_WRONLY|os.O_CREATE|os.O_APPEND, gDEFAULT_PERM)
|
||||
}
|
||||
|
||||
// 获得文件内容下一个指定字节的位置
|
||||
// GetNextCharOffset returns the file offset for given <char> starting from <start>.
|
||||
func GetNextCharOffset(reader io.ReaderAt, char byte, start int64) int64 {
|
||||
buffer := make([]byte, gREAD_BUFFER)
|
||||
offset := start
|
||||
@ -100,7 +104,8 @@ func GetNextCharOffset(reader io.ReaderAt, char byte, start int64) int64 {
|
||||
return -1
|
||||
}
|
||||
|
||||
// 获得文件内容下一个指定字节的位置
|
||||
// GetNextCharOffsetByPath returns the file offset for given <char> starting from <start>.
|
||||
// It opens file of <path> for reading with os.O_RDONLY flag and default perm.
|
||||
func GetNextCharOffsetByPath(path string, char byte, start int64) int64 {
|
||||
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, gDEFAULT_PERM); err == nil {
|
||||
defer f.Close()
|
||||
@ -109,7 +114,10 @@ func GetNextCharOffsetByPath(path string, char byte, start int64) int64 {
|
||||
return -1
|
||||
}
|
||||
|
||||
// 获得文件内容直到下一个指定字节的位置(返回值包含该位置字符内容)
|
||||
// GetBinContentsTilChar returns the contents of the file as []byte
|
||||
// until the next specified byte <char> position.
|
||||
//
|
||||
// Note: Returned value contains the character of the last position.
|
||||
func GetBinContentsTilChar(reader io.ReaderAt, char byte, start int64) ([]byte, int64) {
|
||||
if offset := GetNextCharOffset(reader, char, start); offset != -1 {
|
||||
return GetBinContentsByTwoOffsets(reader, start, offset + 1), offset
|
||||
@ -117,7 +125,11 @@ func GetBinContentsTilChar(reader io.ReaderAt, char byte, start int64) ([]byte,
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// 获得文件内容直到下一个指定字节的位置(返回值包含该位置字符内容)
|
||||
// GetBinContentsTilCharByPath returns the contents of the file given by <path> as []byte
|
||||
// until the next specified byte <char> position.
|
||||
// It opens file of <path> for reading with os.O_RDONLY flag and default perm.
|
||||
//
|
||||
// Note: Returned value contains the character of the last position.
|
||||
func GetBinContentsTilCharByPath(path string, char byte, start int64) ([]byte, int64) {
|
||||
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, gDEFAULT_PERM); err == nil {
|
||||
defer f.Close()
|
||||
@ -126,7 +138,9 @@ func GetBinContentsTilCharByPath(path string, char byte, start int64) ([]byte, i
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
// 获得文件内容中两个offset之间的内容 [start, end)
|
||||
// GetBinContentsByTwoOffsets returns the binary content as []byte from <start> to <end>.
|
||||
// Note: Returned value does not contain the character of the last position, which means
|
||||
// it returns content range as [start, end).
|
||||
func GetBinContentsByTwoOffsets(reader io.ReaderAt, start int64, end int64) []byte {
|
||||
buffer := make([]byte, end - start)
|
||||
if _, err := reader.ReadAt(buffer, start); err != nil {
|
||||
@ -135,7 +149,10 @@ func GetBinContentsByTwoOffsets(reader io.ReaderAt, start int64, end int64) []by
|
||||
return buffer
|
||||
}
|
||||
|
||||
// 获得文件内容中两个offset之间的内容 [start, end)
|
||||
// GetBinContentsByTwoOffsetsByPath returns the binary content as []byte from <start> to <end>.
|
||||
// Note: Returned value does not contain the character of the last position, which means
|
||||
// it returns content range as [start, end).
|
||||
// It opens file of <path> for reading with os.O_RDONLY flag and default perm.
|
||||
func GetBinContentsByTwoOffsetsByPath(path string, start int64, end int64) []byte {
|
||||
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, gDEFAULT_PERM); err == nil {
|
||||
defer f.Close()
|
||||
|
||||
@ -13,31 +13,25 @@ import (
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
)
|
||||
|
||||
|
||||
// 如果给定绝对路径将会去掉其中的相对路径符号后返回;
|
||||
// 如果是给定的相对路径,那么将会按照以下路径优先级搜索文件(重复路径会去重):
|
||||
// prioritySearchPaths、当前工作目录、二进制文件目录、源码main包目录(开发环境下)
|
||||
// Search searches file by name <name> in following paths with priority:
|
||||
// prioritySearchPaths, Pwd()、SelfDir()、MainPkgPath().
|
||||
// It returns the absolute file path of <name> if found, or en empty string if not found.
|
||||
func Search(name string, prioritySearchPaths...string) (realPath string, err error) {
|
||||
// 是否绝对路径
|
||||
// Check if it's a absolute path.
|
||||
realPath = RealPath(name)
|
||||
if realPath != "" {
|
||||
return
|
||||
}
|
||||
// 相对路径搜索
|
||||
// Search paths array.
|
||||
array := garray.NewStringArray(true)
|
||||
// 自定义优先路径
|
||||
array.Append(prioritySearchPaths...)
|
||||
// 用户工作目录
|
||||
array.Append(Pwd())
|
||||
// 二进制所在目录
|
||||
array.Append(SelfDir())
|
||||
// 源码main包目录
|
||||
array.Append(Pwd(), SelfDir())
|
||||
if path := MainPkgPath(); path != "" {
|
||||
array.Append(path)
|
||||
}
|
||||
// 路径去重
|
||||
// Remove repeated items.
|
||||
array.Unique()
|
||||
// 执行相对路径搜索
|
||||
// Do the searching.
|
||||
array.RLockFunc(func(array []string) {
|
||||
path := ""
|
||||
for _, v := range array {
|
||||
@ -48,7 +42,7 @@ func Search(name string, prioritySearchPaths...string) (realPath string, err err
|
||||
}
|
||||
}
|
||||
})
|
||||
// 目录不存在错误处理
|
||||
// If it fails searching, it returns formatted error.
|
||||
if realPath == "" {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString(fmt.Sprintf("cannot find file/folder \"%s\" in following paths:", name))
|
||||
|
||||
@ -11,7 +11,7 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// 文件大小(bytes)
|
||||
// Size returns the size of file specified by <path> in byte.
|
||||
func Size(path string) int64 {
|
||||
s, e := os.Stat(path)
|
||||
if e != nil {
|
||||
@ -20,12 +20,12 @@ func Size(path string) int64 {
|
||||
return s.Size()
|
||||
}
|
||||
|
||||
// 格式化文件大小
|
||||
// ReadableSize formats size of file given by <path>, for more human readable.
|
||||
func ReadableSize(path string) string {
|
||||
return FormatSize(float64(Size(path)))
|
||||
}
|
||||
|
||||
// 格式化文件大小
|
||||
// FormatSize formats size <raw> for more human readable.
|
||||
func FormatSize(raw float64) string {
|
||||
var t float64 = 1024
|
||||
var d float64 = 1
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// 文件修改时间(时间戳,秒)
|
||||
// MTime returns the modification time of file given by <path> in second.
|
||||
func MTime(path string) int64 {
|
||||
s, e := os.Stat(path)
|
||||
if e != nil {
|
||||
@ -19,7 +19,7 @@ func MTime(path string) int64 {
|
||||
return s.ModTime().Unix()
|
||||
}
|
||||
|
||||
// 文件修改时间(时间戳,毫秒)
|
||||
// MTimeMillisecond returns the modification time of file given by <path> in millisecond.
|
||||
func MTimeMillisecond(path string) int64 {
|
||||
s, e := os.Stat(path)
|
||||
if e != nil {
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
// Copyright 2017-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 gfpool provides io-reusable pool for file pointer.
|
||||
//
|
||||
// 文件指针池.
|
||||
package gfpool
|
||||
|
||||
import (
|
||||
@ -19,11 +17,11 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 文件指针池
|
||||
// File pointer pool.
|
||||
type Pool struct {
|
||||
id *gtype.Int // 指针池ID,用以识别指针池是否重建
|
||||
id *gtype.Int // 指针池ID,用以识别指针池是否需要重建
|
||||
pool *gpool.Pool // 底层对象池
|
||||
inited *gtype.Bool // 是否初始化(在执行第一次File方法后初始化,主要用于监听的添加,但是只能添加一次)
|
||||
inited *gtype.Bool // 是否初始化(在执行第一次执行File方法后初始化,主要用于文件监听的添加,但是只能添加一次)
|
||||
expire int // 过期时间
|
||||
}
|
||||
|
||||
@ -38,8 +36,11 @@ type File struct {
|
||||
path string // 绝对路径
|
||||
}
|
||||
|
||||
// 全局指针池,expire < 0表示不过期,expire = 0表示使用完立即回收,expire > 0表示超时回收
|
||||
var pools = gmap.NewStrAnyMap()
|
||||
|
||||
var (
|
||||
// 全局文件指针池Map, 不过期
|
||||
pools = gmap.NewStrAnyMap()
|
||||
)
|
||||
|
||||
// 获得文件对象,并自动创建指针池(过期时间单位:毫秒)
|
||||
func Open(path string, flag int, perm os.FileMode, expire...int) (file *File, err error) {
|
||||
@ -54,12 +55,14 @@ func Open(path string, flag int, perm os.FileMode, expire...int) (file *File, er
|
||||
return pool.File()
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
// See Open.
|
||||
func OpenFile(path string, flag int, perm os.FileMode, expire...int) (file *File, err error) {
|
||||
return Open(path, flag, perm, expire...)
|
||||
}
|
||||
|
||||
// 创建一个文件指针池,expire = 0表示不过期,expire < 0表示使用完立即回收,expire > 0表示超时回收,默认值为0不过期
|
||||
// 过期时间单位:毫秒
|
||||
// 创建一个文件指针池,expire = 0表示不过期,expire < 0表示使用完立即回收,expire > 0表示超时回收,默认值为0表示不过期。
|
||||
// 注意过期时间单位为:毫秒。
|
||||
func New(path string, flag int, perm os.FileMode, expire...int) *Pool {
|
||||
fpExpire := 0
|
||||
if len(expire) > 0 {
|
||||
@ -130,7 +133,7 @@ func (p *Pool) File() (*File, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// !p.inited.Val() 使用原子读取操作判断,保证该操作判断的效率;
|
||||
// 优先使用 !p.inited.Val() 原子读取操作判断,保证判断操作的效率;
|
||||
// p.inited.Set(true) == false 使用原子写入操作,保证该操作的原子性;
|
||||
if !p.inited.Val() && p.inited.Set(true) == false {
|
||||
gfsnotify.Add(f.path, func(event *gfsnotify.Event) {
|
||||
|
||||
@ -11,6 +11,7 @@ package glog
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/internal/cmdenv"
|
||||
"github.com/gogf/gf/g/os/grpool"
|
||||
"io"
|
||||
)
|
||||
|
||||
@ -29,6 +30,8 @@ const (
|
||||
var (
|
||||
// Default logger object, for package method usage
|
||||
logger = New()
|
||||
// Goroutine pool for async logging output.
|
||||
asyncPool = grpool.New(1)
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -83,6 +86,11 @@ func SetDebug(debug bool) {
|
||||
logger.SetDebug(debug)
|
||||
}
|
||||
|
||||
// SetAsync enables/disables async logging output feature for default logger.
|
||||
func SetAsync(enabled bool) {
|
||||
logger.SetAsync(enabled)
|
||||
}
|
||||
|
||||
// SetStdoutPrint sets whether ouptput the logging contents to stdout, which is false in default.
|
||||
func SetStdoutPrint(enabled bool) {
|
||||
logger.SetStdoutPrint(enabled)
|
||||
|
||||
@ -23,8 +23,8 @@ func Println(v ...interface{}) {
|
||||
logger.Println(v ...)
|
||||
}
|
||||
|
||||
// Printf prints <v> with newline and format <format> using fmt.Sprintf.
|
||||
// The param <v> can be multiple variables.
|
||||
// Deprecated.
|
||||
// Use Printf instead.
|
||||
func Printfln(format string, v ...interface{}) {
|
||||
logger.Printfln(format, v ...)
|
||||
}
|
||||
@ -34,12 +34,13 @@ func Fatal(v ...interface{}) {
|
||||
logger.Fatal(v ...)
|
||||
}
|
||||
|
||||
// Fatalf prints the logging content with [FATA] header and custom format, then exit the current process.
|
||||
// Fatalf prints the logging content with [FATA] header, custom format and newline, then exit the current process.
|
||||
func Fatalf(format string, v ...interface{}) {
|
||||
logger.Fatalf(format, v ...)
|
||||
}
|
||||
|
||||
// Fatalf prints the logging content with [FATA] header, custom format and newline, then exit the current process.
|
||||
// Deprecated.
|
||||
// Use Fatalf instead.
|
||||
func Fatalfln(format string, v ...interface{}) {
|
||||
logger.Fatalfln(format, v ...)
|
||||
}
|
||||
@ -49,12 +50,13 @@ func Panic(v ...interface{}) {
|
||||
logger.Panic(v ...)
|
||||
}
|
||||
|
||||
// Panicf prints the logging content with [PANI] header and custom format, then panics.
|
||||
// Panicf prints the logging content with [PANI] header, custom format and newline, then panics.
|
||||
func Panicf(format string, v ...interface{}) {
|
||||
logger.Panicf(format, v ...)
|
||||
}
|
||||
|
||||
// Panicfln prints the logging content with [PANI] header, newline and custom format, then panics.
|
||||
// Deprecated.
|
||||
// Use Panicf instead.
|
||||
func Panicfln(format string, v ...interface{}) {
|
||||
logger.Panicfln(format, v ...)
|
||||
}
|
||||
@ -64,12 +66,13 @@ func Info(v ...interface{}) {
|
||||
logger.Info(v...)
|
||||
}
|
||||
|
||||
// Infof prints the logging content with [INFO] header and custom format.
|
||||
// Infof prints the logging content with [INFO] header, custom format and newline.
|
||||
func Infof(format string, v ...interface{}) {
|
||||
logger.Infof(format, v...)
|
||||
}
|
||||
|
||||
// Infofln prints the logging content with [INFO] header, newline and custom format.
|
||||
// Deprecated.
|
||||
// Use Infof instead.
|
||||
func Infofln(format string, v ...interface{}) {
|
||||
logger.Infofln(format, v...)
|
||||
}
|
||||
@ -79,12 +82,13 @@ func Debug(v ...interface{}) {
|
||||
logger.Debug(v...)
|
||||
}
|
||||
|
||||
// Debugf prints the logging content with [DEBU] header and custom format.
|
||||
// Debugf prints the logging content with [DEBU] header, custom format and newline.
|
||||
func Debugf(format string, v ...interface{}) {
|
||||
logger.Debugf(format, v...)
|
||||
}
|
||||
|
||||
// Debugfln prints the logging content with [DEBU] header, newline and custom format.
|
||||
// Deprecated.
|
||||
// Use Debugf instead.
|
||||
func Debugfln(format string, v ...interface{}) {
|
||||
logger.Debugfln(format, v...)
|
||||
}
|
||||
@ -95,14 +99,14 @@ func Notice(v ...interface{}) {
|
||||
logger.Notice(v...)
|
||||
}
|
||||
|
||||
// Noticef prints the logging content with [NOTI] header and custom format.
|
||||
// Noticef prints the logging content with [NOTI] header, custom format and newline.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func Noticef(format string, v ...interface{}) {
|
||||
logger.Noticef(format, v...)
|
||||
}
|
||||
|
||||
// Noticefln prints the logging content with [NOTI] header, newline and custom format.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
// Deprecated.
|
||||
// Use Noticef instead.
|
||||
func Noticefln(format string, v ...interface{}) {
|
||||
logger.Noticefln(format, v...)
|
||||
}
|
||||
@ -113,14 +117,14 @@ func Warning(v ...interface{}) {
|
||||
logger.Warning(v...)
|
||||
}
|
||||
|
||||
// Warningf prints the logging content with [WARN] header and custom format.
|
||||
// Warningf prints the logging content with [WARN] header, custom format and newline.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func Warningf(format string, v ...interface{}) {
|
||||
logger.Warningf(format, v...)
|
||||
}
|
||||
|
||||
// Warningfln prints the logging content with [WARN] header, newline and custom format.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
// Deprecated.
|
||||
// Use Warningf instead.
|
||||
func Warningfln(format string, v ...interface{}) {
|
||||
logger.Warningfln(format, v...)
|
||||
}
|
||||
@ -131,14 +135,14 @@ func Error(v ...interface{}) {
|
||||
logger.Error(v...)
|
||||
}
|
||||
|
||||
// Errorf prints the logging content with [ERRO] header and custom format.
|
||||
// Errorf prints the logging content with [ERRO] header, custom format and newline.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func Errorf(format string, v ...interface{}) {
|
||||
logger.Errorf(format, v...)
|
||||
}
|
||||
|
||||
// Errorfln prints the logging content with [ERRO] header, newline and custom format.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
// Deprecated.
|
||||
// Use Errorf instead.
|
||||
func Errorfln(format string, v ...interface{}) {
|
||||
logger.Errorfln(format, v...)
|
||||
}
|
||||
@ -149,14 +153,14 @@ func Critical(v ...interface{}) {
|
||||
logger.Critical(v...)
|
||||
}
|
||||
|
||||
// Criticalf prints the logging content with [CRIT] header and custom format.
|
||||
// Criticalf prints the logging content with [CRIT] header, custom format and newline.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func Criticalf(format string, v ...interface{}) {
|
||||
logger.Criticalf(format, v...)
|
||||
}
|
||||
|
||||
// Criticalfln prints the logging content with [CRIT] header, newline and custom format.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
// Deprecated.
|
||||
// Use Criticalf instead.
|
||||
func Criticalfln(format string, v ...interface{}) {
|
||||
logger.Criticalfln(format, v...)
|
||||
}
|
||||
|
||||
@ -73,3 +73,9 @@ func Header(enabled...bool) *Logger {
|
||||
func Line(long...bool) *Logger {
|
||||
return logger.Line(long...)
|
||||
}
|
||||
|
||||
// Async is a chaining function,
|
||||
// which enables/disables async logging output feature.
|
||||
func Async(enabled...bool) *Logger {
|
||||
return logger.Async(enabled...)
|
||||
}
|
||||
|
||||
@ -3,8 +3,6 @@
|
||||
// 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.
|
||||
//
|
||||
// @author john, zseeker
|
||||
|
||||
package glog
|
||||
|
||||
@ -16,6 +14,7 @@ import (
|
||||
"github.com/gogf/gf/g/os/gfpool"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
@ -45,7 +44,8 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
F_FILE_LONG = 1 << iota // Print full file name and line number: /a/b/c/d.go:23.
|
||||
F_ASYNC = 1 << iota // Print logging content asynchronously。
|
||||
F_FILE_LONG // Print full file name and line number: /a/b/c/d.go:23.
|
||||
F_FILE_SHORT // Print final file name element and line number: d.go:23. overrides F_FILE_LONG.
|
||||
F_TIME_DATE // Print the date in the local time zone: 2009-01-23.
|
||||
F_TIME_TIME // Print the time in the local time zone: 01:23:23.
|
||||
@ -80,19 +80,10 @@ func New() *Logger {
|
||||
|
||||
// Clone returns a new logger, which is the clone the current logger.
|
||||
func (l *Logger) Clone() *Logger {
|
||||
logger := &Logger {
|
||||
parent : l,
|
||||
path : l.path,
|
||||
file : l.file,
|
||||
level : l.level,
|
||||
flags : l.flags,
|
||||
prefix : l.prefix,
|
||||
btSkip : l.btSkip,
|
||||
btStatus : l.btStatus,
|
||||
headerPrint : l.headerPrint,
|
||||
stdoutPrint : l.stdoutPrint,
|
||||
}
|
||||
return logger
|
||||
logger := Logger{}
|
||||
logger = *l
|
||||
logger.parent = l
|
||||
return &logger
|
||||
}
|
||||
|
||||
// SetLevel sets the logging level.
|
||||
@ -115,6 +106,15 @@ func (l *Logger) SetDebug(debug bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetAsync enables/disables async logging output feature.
|
||||
func (l *Logger) SetAsync(enabled bool) {
|
||||
if enabled {
|
||||
l.flags = l.flags | F_ASYNC
|
||||
} else {
|
||||
l.flags = l.flags & ^F_ASYNC
|
||||
}
|
||||
}
|
||||
|
||||
// SetFlags sets extra flags for logging output features.
|
||||
func (l *Logger) SetFlags(flags int) {
|
||||
l.flags = flags
|
||||
@ -226,67 +226,113 @@ func (l *Logger) SetPrefix(prefix string) {
|
||||
}
|
||||
|
||||
// print prints <s> to defined writer, logging file or passed <std>.
|
||||
func (l *Logger) print(std io.Writer, s string) {
|
||||
// Custom writer has the most high priority.
|
||||
func (l *Logger) print(std io.Writer, lead string, value...interface{}) {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if l.headerPrint {
|
||||
s = l.format(s)
|
||||
}
|
||||
if l.writer == nil {
|
||||
if f := l.getFilePointer(); f != nil {
|
||||
defer f.Close()
|
||||
if _, err := io.WriteString(f, s); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
}
|
||||
}
|
||||
// Allow output to stdout?
|
||||
if l.stdoutPrint {
|
||||
if _, err := std.Write([]byte(s)); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := l.writer.Write([]byte(s)); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
// Time.
|
||||
timeFormat := ""
|
||||
if l.flags & F_TIME_DATE > 0 {
|
||||
timeFormat += "2006-01-02 "
|
||||
}
|
||||
if l.flags & F_TIME_TIME > 0 {
|
||||
timeFormat += "15:04:05 "
|
||||
}
|
||||
if l.flags & F_TIME_MILLI > 0 {
|
||||
timeFormat += "15:04:05.000 "
|
||||
}
|
||||
if len(timeFormat) > 0 {
|
||||
buffer.WriteString(time.Now().Format(timeFormat))
|
||||
}
|
||||
// Lead string.
|
||||
if len(lead) > 0 {
|
||||
buffer.WriteString(lead)
|
||||
if len(value) > 0 {
|
||||
buffer.WriteByte(' ')
|
||||
}
|
||||
}
|
||||
// Caller path.
|
||||
callerPath := ""
|
||||
if l.flags & F_FILE_LONG > 0 {
|
||||
callerPath = l.getLongFile() + ": "
|
||||
}
|
||||
if l.flags & F_FILE_SHORT > 0 {
|
||||
callerPath = gfile.Basename(l.getLongFile()) + ": "
|
||||
}
|
||||
if len(callerPath) > 0 {
|
||||
buffer.WriteString(callerPath)
|
||||
}
|
||||
// Prefix.
|
||||
if len(l.prefix) > 0 {
|
||||
buffer.WriteString(l.prefix + " ")
|
||||
}
|
||||
}
|
||||
for k, v := range value {
|
||||
if k > 0 {
|
||||
buffer.WriteByte(' ')
|
||||
}
|
||||
buffer.WriteString(gconv.String(v))
|
||||
}
|
||||
buffer.WriteString(ln)
|
||||
if l.flags & F_ASYNC > 0 {
|
||||
asyncPool.Add(func() {
|
||||
l.printToWriter(std, buffer)
|
||||
})
|
||||
} else {
|
||||
l.printToWriter(std, buffer)
|
||||
}
|
||||
}
|
||||
|
||||
// printToWriter writes buffer to writer.
|
||||
func (l *Logger) printToWriter(std io.Writer, buffer *bytes.Buffer) {
|
||||
if l.writer == nil {
|
||||
if f := l.getFilePointer(); f != nil {
|
||||
defer f.Close()
|
||||
if _, err := io.WriteString(f, buffer.String()); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
}
|
||||
}
|
||||
// Allow output to stdout?
|
||||
if l.stdoutPrint {
|
||||
if _, err := std.Write(buffer.Bytes()); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err := l.writer.Write(buffer.Bytes()); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// printStd prints content <s> without backtrace.
|
||||
func (l *Logger) printStd(s string) {
|
||||
l.print(os.Stdout, s)
|
||||
func (l *Logger) printStd(lead string, value...interface{}) {
|
||||
l.print(os.Stdout, lead, value...)
|
||||
}
|
||||
|
||||
// printStd prints content <s> with backtrace check.
|
||||
func (l *Logger) printErr(s string) {
|
||||
func (l *Logger) printErr(lead string, value...interface{}) {
|
||||
if l.btStatus == 1 {
|
||||
s = l.appendBacktrace(s)
|
||||
if s := l.GetBacktrace(); s != "" {
|
||||
value = append(value, ln + "Backtrace:" + ln + s)
|
||||
}
|
||||
}
|
||||
// In matter of sequence, do not use stderr here, but use the same stdout.
|
||||
l.print(os.Stdout, s)
|
||||
l.print(os.Stdout, lead, value...)
|
||||
}
|
||||
|
||||
// appendBacktrace appends backtrace to the <s>.
|
||||
func (l *Logger) appendBacktrace(s string, skip...int) string {
|
||||
trace := l.GetBacktrace(skip...)
|
||||
if trace != "" {
|
||||
backtrace := "Backtrace:" + ln + trace
|
||||
if len(s) > 0 {
|
||||
if s[len(s)-1] == byte('\n') {
|
||||
s = s + backtrace + ln
|
||||
} else {
|
||||
s = s + ln + backtrace + ln
|
||||
}
|
||||
} else {
|
||||
s = backtrace
|
||||
}
|
||||
}
|
||||
return s
|
||||
// format formats <values> using fmt.Sprintf.
|
||||
func (l *Logger) format(format string, value...interface{}) string {
|
||||
return fmt.Sprintf(format, value...)
|
||||
}
|
||||
|
||||
// PrintBacktrace prints the caller backtrace,
|
||||
// the optional parameter <skip> specify the skipped backtrace offset from the end point.
|
||||
func (l *Logger) PrintBacktrace(skip...int) {
|
||||
l.Println(l.appendBacktrace("", skip...))
|
||||
if s := l.GetBacktrace(skip...); s != "" {
|
||||
l.Println("Backtrace:" + ln + s)
|
||||
} else {
|
||||
l.Println()
|
||||
}
|
||||
}
|
||||
|
||||
// GetBacktrace returns the caller backtrace content,
|
||||
@ -299,7 +345,7 @@ func (l *Logger) GetBacktrace(skip...int) string {
|
||||
backtrace := ""
|
||||
from := 0
|
||||
// Find the caller position exclusive of the glog file.
|
||||
for i := 0; i < 100; i++ {
|
||||
for i := 0; i < 1000; i++ {
|
||||
if _, file, _, ok := runtime.Caller(i); ok {
|
||||
if !gregex.IsMatchString("/g/os/glog/glog.+$", file) {
|
||||
from = i
|
||||
@ -310,8 +356,8 @@ func (l *Logger) GetBacktrace(skip...int) string {
|
||||
// Find the true caller file path using custom skip.
|
||||
index := 1
|
||||
goRoot := runtime.GOROOT()
|
||||
for i := from + customSkip + l.btSkip; i < 10000; i++ {
|
||||
if _, file, cline, ok := runtime.Caller(i); ok && file != "" {
|
||||
for i := from + customSkip + l.btSkip; i < 1000; i++ {
|
||||
if _, file, cline, ok := runtime.Caller(i); ok && len(file) > 2 {
|
||||
if (goRoot == "" || !gregex.IsMatchString("^" + goRoot, file)) && !gregex.IsMatchString(`<autogenerated>`, file) {
|
||||
backtrace += fmt.Sprintf(`%d. %s:%d%s`, index, file, cline, ln)
|
||||
index++
|
||||
@ -327,19 +373,18 @@ func (l *Logger) GetBacktrace(skip...int) string {
|
||||
func (l *Logger) getLongFile() string {
|
||||
from := 0
|
||||
// Find the caller position exclusive of the glog file.
|
||||
for i := 0; i < 100; i++ {
|
||||
if _, file, line, ok := runtime.Caller(i); ok {
|
||||
for i := 0; i < 1000; i++ {
|
||||
if _, file, _, ok := runtime.Caller(i); ok {
|
||||
if !gregex.IsMatchString("/g/os/glog/glog.+$", file) {
|
||||
from = i
|
||||
break
|
||||
return fmt.Sprintf(`%s:%d`, file, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Find the true caller file path using custom skip.
|
||||
goRoot := runtime.GOROOT()
|
||||
for i := from + l.btSkip; i < 10000; i++ {
|
||||
if _, file, line, ok := runtime.Caller(i); ok && file != "" {
|
||||
for i := from + l.btSkip; i < 1000; i++ {
|
||||
if _, file, line, ok := runtime.Caller(i); ok && len(file) > 2 {
|
||||
if (goRoot == "" || !gregex.IsMatchString("^" + goRoot, file)) && !gregex.IsMatchString(`<autogenerated>`, file) {
|
||||
return fmt.Sprintf(`%s:%d`, file, line)
|
||||
}
|
||||
@ -349,42 +394,3 @@ func (l *Logger) getLongFile() string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
// format formats the content according the flags.
|
||||
func (l *Logger) format(content string) string {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
// Time.
|
||||
timeFormat := ""
|
||||
if l.flags & F_TIME_DATE > 0 {
|
||||
timeFormat += "2006-01-02 "
|
||||
}
|
||||
if l.flags & F_TIME_TIME > 0 {
|
||||
timeFormat += "15:04:05 "
|
||||
}
|
||||
if l.flags & F_TIME_MILLI > 0 {
|
||||
timeFormat += "15:04:05.000 "
|
||||
}
|
||||
if len(timeFormat) > 0 {
|
||||
buffer.WriteString(time.Now().Format(timeFormat))
|
||||
}
|
||||
// Caller path.
|
||||
callerPath := ""
|
||||
if l.flags & F_FILE_LONG > 0 {
|
||||
callerPath = l.getLongFile() + ": "
|
||||
}
|
||||
if l.flags & F_FILE_SHORT > 0 {
|
||||
callerPath = gfile.Basename(l.getLongFile()) + ": "
|
||||
}
|
||||
if len(callerPath) > 0 {
|
||||
buffer.WriteString(callerPath)
|
||||
}
|
||||
// Prefix.
|
||||
if len(l.prefix) > 0 {
|
||||
buffer.WriteString(l.prefix + " ")
|
||||
}
|
||||
// Content.
|
||||
buffer.WriteString(content)
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
|
||||
@ -13,201 +13,201 @@ import (
|
||||
|
||||
// Print prints <v> with newline using fmt.Sprintln.
|
||||
// The param <v> can be multiple variables.
|
||||
func (l *Logger) Print(v ...interface{}) {
|
||||
l.printStd(fmt.Sprintln(v...))
|
||||
func (l *Logger) Print(v...interface{}) {
|
||||
l.printStd("", v...)
|
||||
}
|
||||
|
||||
// Printf prints <v> with format <format> using fmt.Sprintf.
|
||||
// The param <v> can be multiple variables.
|
||||
func (l *Logger) Printf(format string, v ...interface{}) {
|
||||
l.printStd(fmt.Sprintf(format, v...))
|
||||
func (l *Logger) Printf(format string, v...interface{}) {
|
||||
l.printStd(l.format(format, v...))
|
||||
}
|
||||
|
||||
// See Print.
|
||||
func (l *Logger) Println(v ...interface{}) {
|
||||
func (l *Logger) Println(v...interface{}) {
|
||||
l.Print(v...)
|
||||
}
|
||||
|
||||
// Printfln prints <v> with newline and format <format> using fmt.Sprintf.
|
||||
// The param <v> can be multiple variables.
|
||||
func (l *Logger) Printfln(format string, v ...interface{}) {
|
||||
l.printStd(fmt.Sprintf(format + ln, v...))
|
||||
// Deprecated.
|
||||
// Use Printf instead.
|
||||
func (l *Logger) Printfln(format string, v...interface{}) {
|
||||
l.printStd(l.format(format, v...))
|
||||
}
|
||||
|
||||
// Fatal prints the logging content with [FATA] header and newline, then exit the current process.
|
||||
func (l *Logger) Fatal(v ...interface{}) {
|
||||
l.printErr("[FATA] " + fmt.Sprintln(v...))
|
||||
func (l *Logger) Fatal(v...interface{}) {
|
||||
l.printErr("[FATA]", v...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalf prints the logging content with [FATA] header and custom format, then exit the current process.
|
||||
func (l *Logger) Fatalf(format string, v ...interface{}) {
|
||||
l.printErr("[FATA] " + fmt.Sprintf(format, v...))
|
||||
// Fatalf prints the logging content with [FATA] header, custom format and newline, then exit the current process.
|
||||
func (l *Logger) Fatalf(format string, v...interface{}) {
|
||||
l.printErr("[FATA]", l.format(format, v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalfln prints the logging content with [FATA] header, custom format and newline, then exit the current process.
|
||||
func (l *Logger) Fatalfln(format string, v ...interface{}) {
|
||||
l.printErr("[FATA] " + fmt.Sprintf(format + ln, v...))
|
||||
// Deprecated.
|
||||
// Use Fatalf instead.
|
||||
func (l *Logger) Fatalfln(format string, v...interface{}) {
|
||||
l.Fatalf(format, v...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Panic prints the logging content with [PANI] header and newline, then panics.
|
||||
func (l *Logger) Panic(v ...interface{}) {
|
||||
s := fmt.Sprintln(v...)
|
||||
l.printErr("[PANI] " + s)
|
||||
panic(s)
|
||||
func (l *Logger) Panic(v...interface{}) {
|
||||
l.printErr("[PANI]", v...)
|
||||
panic(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
// Panicf prints the logging content with [PANI] header and custom format, then panics.
|
||||
func (l *Logger) Panicf(format string, v ...interface{}) {
|
||||
s := fmt.Sprintf(format, v...)
|
||||
l.printErr("[PANI] " + s)
|
||||
panic(s)
|
||||
// Panicf prints the logging content with [PANI] header, custom format and newline, then panics.
|
||||
func (l *Logger) Panicf(format string, v...interface{}) {
|
||||
l.printErr("[PANI]", l.format(format, v...))
|
||||
panic(l.format(format, v...))
|
||||
}
|
||||
|
||||
// Panicfln prints the logging content with [PANI] header, newline and custom format, then panics.
|
||||
func (l *Logger) Panicfln(format string, v ...interface{}) {
|
||||
s := fmt.Sprintf(format + ln, v...)
|
||||
l.printErr("[PANI] " + s)
|
||||
panic(s)
|
||||
// Deprecated.
|
||||
// Use Panicf instead.
|
||||
func (l *Logger) Panicfln(format string, v...interface{}) {
|
||||
l.Panicf(format, v...)
|
||||
}
|
||||
|
||||
// Info prints the logging content with [INFO] header and newline.
|
||||
func (l *Logger) Info(v ...interface{}) {
|
||||
func (l *Logger) Info(v...interface{}) {
|
||||
if l.checkLevel(LEVEL_INFO) {
|
||||
l.printStd("[INFO] " + fmt.Sprintln(v...))
|
||||
l.printStd("[INFO]", v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Infof prints the logging content with [INFO] header and custom format.
|
||||
func (l *Logger) Infof(format string, v ...interface{}) {
|
||||
// Infof prints the logging content with [INFO] header, custom format and newline.
|
||||
func (l *Logger) Infof(format string, v...interface{}) {
|
||||
if l.checkLevel(LEVEL_INFO) {
|
||||
l.printStd("[INFO] " + fmt.Sprintf(format, v...))
|
||||
l.printStd("[INFO]", l.format(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Infofln prints the logging content with [INFO] header, newline and custom format.
|
||||
func (l *Logger) Infofln(format string, v ...interface{}) {
|
||||
// Deprecated.
|
||||
// Use Infof instead.
|
||||
func (l *Logger) Infofln(format string, v...interface{}) {
|
||||
if l.checkLevel(LEVEL_INFO) {
|
||||
l.printStd("[INFO] " + fmt.Sprintf(format, v...) + ln)
|
||||
l.Infof(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Debug prints the logging content with [DEBU] header and newline.
|
||||
func (l *Logger) Debug(v ...interface{}) {
|
||||
func (l *Logger) Debug(v...interface{}) {
|
||||
if l.checkLevel(LEVEL_DEBU) {
|
||||
l.printStd("[DEBU] " + fmt.Sprintln(v...))
|
||||
l.printStd("[DEBU]", v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Debugf prints the logging content with [DEBU] header and custom format.
|
||||
func (l *Logger) Debugf(format string, v ...interface{}) {
|
||||
// Debugf prints the logging content with [DEBU] header, custom format and newline.
|
||||
func (l *Logger) Debugf(format string, v...interface{}) {
|
||||
if l.checkLevel(LEVEL_DEBU) {
|
||||
l.printStd("[DEBU] " + fmt.Sprintf(format, v...))
|
||||
l.printStd("[DEBU]", l.format(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Debugfln prints the logging content with [DEBU] header, newline and custom format.
|
||||
func (l *Logger) Debugfln(format string, v ...interface{}) {
|
||||
// Deprecated.
|
||||
// Use Debugf instead.
|
||||
func (l *Logger) Debugfln(format string, v...interface{}) {
|
||||
if l.checkLevel(LEVEL_DEBU) {
|
||||
l.printStd("[DEBU] " + fmt.Sprintf(format, v...) + ln)
|
||||
l.Debugf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Notice prints the logging content with [NOTI] header and newline.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func (l *Logger) Notice(v ...interface{}) {
|
||||
func (l *Logger) Notice(v...interface{}) {
|
||||
if l.checkLevel(LEVEL_NOTI) {
|
||||
l.printErr("[NOTI] " + fmt.Sprintln(v...))
|
||||
l.printErr("[NOTI]", v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Noticef prints the logging content with [NOTI] header and custom format.
|
||||
// Noticef prints the logging content with [NOTI] header, custom format and newline.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func (l *Logger) Noticef(format string, v ...interface{}) {
|
||||
func (l *Logger) Noticef(format string, v...interface{}) {
|
||||
if l.checkLevel(LEVEL_NOTI) {
|
||||
l.printErr("[NOTI] " + fmt.Sprintf(format, v...))
|
||||
l.printErr("[NOTI]", l.format(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Noticefln prints the logging content with [NOTI] header, newline and custom format.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func (l *Logger) Noticefln(format string, v ...interface{}) {
|
||||
// Deprecated.
|
||||
// Use Noticef instead.
|
||||
func (l *Logger) Noticefln(format string, v...interface{}) {
|
||||
if l.checkLevel(LEVEL_NOTI) {
|
||||
l.printErr("[NOTI] " + fmt.Sprintf(format, v...) + ln)
|
||||
l.Noticef(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Warning prints the logging content with [WARN] header and newline.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func (l *Logger) Warning(v ...interface{}) {
|
||||
func (l *Logger) Warning(v...interface{}) {
|
||||
if l.checkLevel(LEVEL_WARN) {
|
||||
l.printErr("[WARN] " + fmt.Sprintln(v...))
|
||||
l.printErr("[WARN]", v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Warningf prints the logging content with [WARN] header and custom format.
|
||||
// Warningf prints the logging content with [WARN] header, custom format and newline.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func (l *Logger) Warningf(format string, v ...interface{}) {
|
||||
func (l *Logger) Warningf(format string, v...interface{}) {
|
||||
if l.checkLevel(LEVEL_WARN) {
|
||||
l.printErr("[WARN] " + fmt.Sprintf(format, v...))
|
||||
l.printErr("[WARN]", l.format(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Warningfln prints the logging content with [WARN] header, newline and custom format.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func (l *Logger) Warningfln(format string, v ...interface{}) {
|
||||
// Deprecated.
|
||||
// Use Warningf instead.
|
||||
func (l *Logger) Warningfln(format string, v...interface{}) {
|
||||
if l.checkLevel(LEVEL_WARN) {
|
||||
l.printErr("[WARN] " + fmt.Sprintf(format, v...) + ln)
|
||||
l.Warningf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Error prints the logging content with [ERRO] header and newline.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func (l *Logger) Error(v ...interface{}) {
|
||||
func (l *Logger) Error(v...interface{}) {
|
||||
if l.checkLevel(LEVEL_ERRO) {
|
||||
l.printErr("[ERRO] " + fmt.Sprintln(v...))
|
||||
l.printErr("[ERRO]", v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf prints the logging content with [ERRO] header and custom format.
|
||||
// Errorf prints the logging content with [ERRO] header, custom format and newline.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func (l *Logger) Errorf(format string, v ...interface{}) {
|
||||
func (l *Logger) Errorf(format string, v...interface{}) {
|
||||
if l.checkLevel(LEVEL_ERRO) {
|
||||
l.printErr("[ERRO] " + fmt.Sprintf(format, v...))
|
||||
l.printErr("[ERRO]", l.format(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Errorfln prints the logging content with [ERRO] header, newline and custom format.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func (l *Logger) Errorfln(format string, v ...interface{}) {
|
||||
// Deprecated.
|
||||
// Use Errorf instead.
|
||||
func (l *Logger) Errorfln(format string, v...interface{}) {
|
||||
if l.checkLevel(LEVEL_ERRO) {
|
||||
l.printErr("[ERRO] " + fmt.Sprintf(format, v...) + ln)
|
||||
l.Errorf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Critical prints the logging content with [CRIT] header and newline.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func (l *Logger) Critical(v ...interface{}) {
|
||||
func (l *Logger) Critical(v...interface{}) {
|
||||
if l.checkLevel(LEVEL_CRIT) {
|
||||
l.printErr("[CRIT] " + fmt.Sprintln(v...))
|
||||
l.printErr("[CRIT]", v...)
|
||||
}
|
||||
}
|
||||
|
||||
// Criticalf prints the logging content with [CRIT] header and custom format.
|
||||
// Criticalf prints the logging content with [CRIT] header, custom format and newline.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func (l *Logger) Criticalf(format string, v ...interface{}) {
|
||||
func (l *Logger) Criticalf(format string, v...interface{}) {
|
||||
if l.checkLevel(LEVEL_CRIT) {
|
||||
l.printErr("[CRIT] " + fmt.Sprintf(format, v...))
|
||||
l.printErr("[CRIT]", l.format(format, v...))
|
||||
}
|
||||
}
|
||||
|
||||
// Criticalfln prints the logging content with [CRIT] header, newline and custom format.
|
||||
// It also prints caller backtrace info if backtrace feature is enabled.
|
||||
func (l *Logger) Criticalfln(format string, v ...interface{}) {
|
||||
// Deprecated.
|
||||
// Use Criticalf instead.
|
||||
func (l *Logger) Criticalfln(format string, v...interface{}) {
|
||||
if l.checkLevel(LEVEL_CRIT) {
|
||||
l.printErr("[CRIT] " + fmt.Sprintf(format, v...) + ln)
|
||||
l.Criticalf(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -121,10 +121,11 @@ func (l *Logger) Stdout(enabled...bool) *Logger {
|
||||
} else {
|
||||
logger = l
|
||||
}
|
||||
if len(enabled) > 0 && enabled[0] {
|
||||
logger.stdoutPrint = true
|
||||
} else {
|
||||
// stdout printing is enabled if <enabled> is not passed.
|
||||
if len(enabled) > 0 && !enabled[0] {
|
||||
logger.stdoutPrint = false
|
||||
} else {
|
||||
logger.stdoutPrint = true
|
||||
}
|
||||
return logger
|
||||
}
|
||||
@ -145,10 +146,11 @@ func (l *Logger) Header(enabled...bool) *Logger {
|
||||
} else {
|
||||
logger = l
|
||||
}
|
||||
if len(enabled) > 0 && enabled[0] {
|
||||
logger.SetHeaderPrint(true)
|
||||
} else {
|
||||
// header is enabled if <enabled> is not passed.
|
||||
if len(enabled) > 0 && !enabled[0] {
|
||||
logger.SetHeaderPrint(false)
|
||||
} else {
|
||||
logger.SetHeaderPrint(true)
|
||||
}
|
||||
return logger
|
||||
}
|
||||
@ -170,4 +172,22 @@ func (l *Logger) Line(long...bool) *Logger {
|
||||
logger.flags |= F_FILE_SHORT
|
||||
}
|
||||
return logger
|
||||
}
|
||||
|
||||
// Async is a chaining function,
|
||||
// which enables/disables async logging output feature.
|
||||
func (l *Logger) Async(enabled...bool) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
if l.parent == nil {
|
||||
logger = l.Clone()
|
||||
} else {
|
||||
logger = l
|
||||
}
|
||||
// async feature is enabled if <enabled> is not passed.
|
||||
if len(enabled) > 0 && !enabled[0] {
|
||||
logger.SetAsync(false)
|
||||
} else {
|
||||
logger.SetAsync(true)
|
||||
}
|
||||
return logger
|
||||
}
|
||||
@ -4,43 +4,92 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gmlock implements a thread-safe memory locker.
|
||||
//
|
||||
// 内存锁.
|
||||
// Package gmlock implements a concurrent-safe memory-based locker.
|
||||
package gmlock
|
||||
|
||||
import "time"
|
||||
|
||||
var (
|
||||
// Default locker.
|
||||
locker = New()
|
||||
)
|
||||
|
||||
// 内存写锁,如果锁成功返回true,失败则返回false;过期时间单位为秒,默认为0表示不过期
|
||||
// TryLock tries locking the <key> with write lock,
|
||||
// it returns true if success, or if there's a write/read lock the <key>,
|
||||
// it returns false. The parameter <expire> specifies the max duration it locks.
|
||||
func TryLock(key string, expire...time.Duration) bool {
|
||||
return locker.TryLock(key, expire...)
|
||||
}
|
||||
|
||||
// 内存写锁,锁成功返回true,失败时阻塞,当失败时表示有其他写锁存在;过期时间单位为秒,默认为0表示不过期
|
||||
// Lock locks the <key> with write lock.
|
||||
// If there's a write/read lock the <key>,
|
||||
// it will blocks until the lock is released.
|
||||
// The parameter <expire> specifies the max duration it locks.
|
||||
func Lock(key string, expire...time.Duration) {
|
||||
locker.Lock(key, expire...)
|
||||
}
|
||||
|
||||
// 解除基于内存锁的写锁
|
||||
// Unlock unlocks the write lock of the <key>.
|
||||
func Unlock(key string) {
|
||||
locker.Unlock(key)
|
||||
}
|
||||
|
||||
// 内存读锁,如果锁成功返回true,失败则返回false; 过期时间单位为秒,默认为0表示不过期
|
||||
// TryRLock tries locking the <key> with read lock.
|
||||
// It returns true if success, or if there's a write lock on <key>, it returns false.
|
||||
func TryRLock(key string) bool {
|
||||
return locker.TryRLock(key)
|
||||
}
|
||||
|
||||
// 内存写锁,锁成功返回true,失败时阻塞,当失败时表示有写锁存在; 过期时间单位为秒,默认为0表示不过期
|
||||
// RLock locks the <key> with read lock.
|
||||
// If there's a write lock on <key>,
|
||||
// it will blocks until the write lock is released.
|
||||
func RLock(key string) {
|
||||
locker.RLock(key)
|
||||
}
|
||||
|
||||
// 解除基于内存锁的读锁
|
||||
// RUnlock unlocks the read lock of the <key>.
|
||||
func RUnlock(key string) {
|
||||
locker.RUnlock(key)
|
||||
}
|
||||
|
||||
// TryLockFunc locks the <key> with write lock and callback function <f>.
|
||||
// It returns true if success, or else if there's a write/read lock the <key>, it return false.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
//
|
||||
// The parameter <expire> specifies the max duration it locks.
|
||||
func TryLockFunc(key string, f func(), expire...time.Duration) bool {
|
||||
return locker.TryLockFunc(key, f, expire...)
|
||||
}
|
||||
|
||||
// TryRLockFunc locks the <key> with read lock and callback function <f>.
|
||||
// It returns true if success, or else if there's a write lock the <key>, it returns false.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
//
|
||||
// The parameter <expire> specifies the max duration it locks.
|
||||
func TryRLockFunc(key string, f func()) bool {
|
||||
return locker.TryRLockFunc(key, f)
|
||||
}
|
||||
|
||||
// LockFunc locks the <key> with write lock and callback function <f>.
|
||||
// If there's a write/read lock the <key>,
|
||||
// it will blocks until the lock is released.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
//
|
||||
// The parameter <expire> specifies the max duration it locks.
|
||||
func LockFunc(key string, f func(), expire...time.Duration) {
|
||||
locker.LockFunc(key, f, expire...)
|
||||
}
|
||||
|
||||
// RLockFunc locks the <key> with read lock and callback function <f>.
|
||||
// If there's a write lock the <key>,
|
||||
// it will blocks until the lock is released.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
//
|
||||
// The parameter <expire> specifies the max duration it locks.
|
||||
func RLockFunc(key string, f func()) {
|
||||
locker.RLockFunc(key, f)
|
||||
}
|
||||
@ -12,53 +12,119 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// 内存锁管理对象
|
||||
// Memory locker.
|
||||
type Locker struct {
|
||||
m *gmap.StrAnyMap
|
||||
}
|
||||
|
||||
// 创建一把内存锁, 底层使用的是Mutex
|
||||
// New creates and returns a new memory locker.
|
||||
// A memory locker can lock/unlock with dynamic string key.
|
||||
func New() *Locker {
|
||||
return &Locker{
|
||||
m : gmap.NewStrAnyMap(),
|
||||
}
|
||||
}
|
||||
|
||||
// 内存写锁,如果锁成功返回true,失败则返回false; 过期时间默认为0表示不过期
|
||||
// TryLock tries locking the <key> with write lock,
|
||||
// it returns true if success, or if there's a write/read lock the <key>,
|
||||
// it returns false. The parameter <expire> specifies the max duration it locks.
|
||||
func (l *Locker) TryLock(key string, expire...time.Duration) bool {
|
||||
return l.doLock(key, l.getExpire(expire...), true)
|
||||
}
|
||||
|
||||
// 内存写锁,锁成功返回true,失败时阻塞,当失败时表示有其他写锁存在;过期时间默认为0表示不过期
|
||||
// Lock locks the <key> with write lock.
|
||||
// If there's a write/read lock the <key>,
|
||||
// it will blocks until the lock is released.
|
||||
// The parameter <expire> specifies the max duration it locks.
|
||||
func (l *Locker) Lock(key string, expire...time.Duration) {
|
||||
l.doLock(key, l.getExpire(expire...), false)
|
||||
}
|
||||
|
||||
// 解除基于内存锁的写锁
|
||||
// Unlock unlocks the write lock of the <key>.
|
||||
func (l *Locker) Unlock(key string) {
|
||||
if v := l.m.Get(key); v != nil {
|
||||
v.(*Mutex).Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// 内存读锁,如果锁成功返回true,失败则返回false; 过期时间单位为秒,默认为0表示不过期
|
||||
// TryRLock tries locking the <key> with read lock.
|
||||
// It returns true if success, or if there's a write lock on <key>, it returns false.
|
||||
func (l *Locker) TryRLock(key string) bool {
|
||||
return l.doRLock(key, true)
|
||||
}
|
||||
|
||||
// 内存写锁,锁成功返回true,失败时阻塞,当失败时表示有写锁存在; 过期时间单位为秒,默认为0表示不过期
|
||||
// RLock locks the <key> with read lock.
|
||||
// If there's a write lock on <key>,
|
||||
// it will blocks until the write lock is released.
|
||||
func (l *Locker) RLock(key string) {
|
||||
l.doRLock(key, false)
|
||||
}
|
||||
|
||||
// 解除基于内存锁的读锁
|
||||
// RUnlock unlocks the read lock of the <key>.
|
||||
func (l *Locker) RUnlock(key string) {
|
||||
if v := l.m.Get(key); v != nil {
|
||||
v.(*Mutex).RUnlock()
|
||||
}
|
||||
}
|
||||
|
||||
// 获得过期时间,没有设置时默认为0不过期
|
||||
// TryLockFunc locks the <key> with write lock and callback function <f>.
|
||||
// It returns true if success, or else if there's a write/read lock the <key>, it return false.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
//
|
||||
// The parameter <expire> specifies the max duration it locks.
|
||||
func (l *Locker) TryLockFunc(key string, f func(), expire...time.Duration) bool {
|
||||
if l.TryLock(key, expire...) {
|
||||
defer l.Unlock(key)
|
||||
f()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TryRLockFunc locks the <key> with read lock and callback function <f>.
|
||||
// It returns true if success, or else if there's a write lock the <key>, it returns false.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
//
|
||||
// The parameter <expire> specifies the max duration it locks.
|
||||
func (l *Locker) TryRLockFunc(key string, f func()) bool {
|
||||
if l.TryRLock(key) {
|
||||
defer l.RUnlock(key)
|
||||
f()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LockFunc locks the <key> with write lock and callback function <f>.
|
||||
// If there's a write/read lock the <key>,
|
||||
// it will blocks until the lock is released.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
//
|
||||
// The parameter <expire> specifies the max duration it locks.
|
||||
func (l *Locker) LockFunc(key string, f func(), expire...time.Duration) {
|
||||
l.Lock(key, expire...)
|
||||
defer l.Unlock(key)
|
||||
f()
|
||||
}
|
||||
|
||||
// RLockFunc locks the <key> with read lock and callback function <f>.
|
||||
// If there's a write lock the <key>,
|
||||
// it will blocks until the lock is released.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
//
|
||||
// The parameter <expire> specifies the max duration it locks.
|
||||
func (l *Locker) RLockFunc(key string, f func()) {
|
||||
l.RLock(key)
|
||||
defer l.RUnlock(key)
|
||||
f()
|
||||
}
|
||||
|
||||
// getExpire returns the duration object passed.
|
||||
// If <expire> is not passed, it returns a default duration object.
|
||||
func (l *Locker) getExpire(expire...time.Duration) time.Duration {
|
||||
e := time.Duration(0)
|
||||
if len(expire) > 0 {
|
||||
@ -67,7 +133,14 @@ func (l *Locker) getExpire(expire...time.Duration) time.Duration {
|
||||
return e
|
||||
}
|
||||
|
||||
// 内存写锁,当try==true时,如果锁成功返回true,失败则返回false;try==false时,成功时立即返回,否则阻塞等待
|
||||
// doLock locks writing on <key>.
|
||||
// It returns true if success, or else returns false.
|
||||
//
|
||||
// The parameter <try> is true,
|
||||
// it returns false immediately if it fails getting the write lock.
|
||||
// If <true> is false, it blocks until it gets the write lock.
|
||||
//
|
||||
// The parameter <expire> specifies the max duration it locks.
|
||||
func (l *Locker) doLock(key string, expire time.Duration, try bool) bool {
|
||||
mu := l.getOrNewMutex(key)
|
||||
ok := true
|
||||
@ -77,7 +150,6 @@ func (l *Locker) doLock(key string, expire time.Duration, try bool) bool {
|
||||
mu.Lock()
|
||||
}
|
||||
if ok && expire > 0 {
|
||||
// 异步goroutine计时处理
|
||||
wid := mu.wid.Val()
|
||||
gtimer.AddOnce(expire, func() {
|
||||
if wid == mu.wid.Val() {
|
||||
@ -88,7 +160,12 @@ func (l *Locker) doLock(key string, expire time.Duration, try bool) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// 内存读锁,当try==true时,如果锁成功返回true,失败则返回false;try==false时,成功时立即返回,否则阻塞等待
|
||||
// doRLock locks reading on <key>.
|
||||
// It returns true if success, or else returns false.
|
||||
//
|
||||
// The parameter <try> is true,
|
||||
// it returns false immediately if it fails getting the read lock.
|
||||
// If <true> is false, it blocks until it gets the read lock.
|
||||
func (l *Locker) doRLock(key string, try bool) bool {
|
||||
mu := l.getOrNewMutex(key)
|
||||
ok := true
|
||||
@ -100,8 +177,9 @@ func (l *Locker) doRLock(key string, try bool) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// 根据指定key查询或者创建新的Mutex
|
||||
func (l *Locker) getOrNewMutex(key string) (*Mutex) {
|
||||
// getOrNewMutex returns the mutex of given <key> if it exists,
|
||||
// or else creates and returns a new one.
|
||||
func (l *Locker) getOrNewMutex(key string) *Mutex {
|
||||
return l.m.GetOrSetFuncLock(key, func() interface{} {
|
||||
return NewMutex()
|
||||
}).(*Mutex)
|
||||
|
||||
@ -7,19 +7,20 @@
|
||||
package gmlock
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"sync"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 互斥锁对象
|
||||
// The high level Mutex.
|
||||
// It wraps the sync.RWMutex to implements more rich features.
|
||||
type Mutex struct {
|
||||
mu sync.RWMutex
|
||||
wid *gtype.Int64 // 当前Lock产生的唯一id(主要用于计时Unlock的校验)
|
||||
rcount *gtype.Int // RLock次数
|
||||
wcount *gtype.Int // Lock次数
|
||||
wid *gtype.Int64 // Unique id, used for multiple safely Unlock.
|
||||
rcount *gtype.Int // Reading locks count.
|
||||
wcount *gtype.Int // Writing locks count.
|
||||
}
|
||||
|
||||
// 创建一把内存锁使用的底层RWMutex
|
||||
// NewMutex creates and returns a new mutex.
|
||||
func NewMutex() *Mutex {
|
||||
return &Mutex{
|
||||
wid : gtype.NewInt64(),
|
||||
@ -28,65 +29,123 @@ func NewMutex() *Mutex {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Mutex) Lock() {
|
||||
l.wcount.Add(1)
|
||||
l.mu.Lock()
|
||||
l.wid.Add(1)
|
||||
// Lock locks mutex for writing.
|
||||
// If the lock is already locked for reading or writing,
|
||||
// Lock blocks until the lock is available.
|
||||
func (m *Mutex) Lock() {
|
||||
m.wcount.Add(1)
|
||||
m.mu.Lock()
|
||||
m.wid.Add(1)
|
||||
}
|
||||
|
||||
// 安全的Unlock
|
||||
func (l *Mutex) Unlock() {
|
||||
if l.wcount.Val() > 0 {
|
||||
if l.wcount.Add(-1) >= 0 {
|
||||
l.mu.Unlock()
|
||||
// Unlock unlocks the write lock.
|
||||
// It is safe to be called multiple times.
|
||||
func (m *Mutex) Unlock() {
|
||||
if m.wcount.Val() > 0 {
|
||||
if m.wcount.Add(-1) >= 0 {
|
||||
m.mu.Unlock()
|
||||
} else {
|
||||
// 标准库这里会panic
|
||||
l.wcount.Add(1)
|
||||
m.wcount.Add(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Mutex) RLock() {
|
||||
l.rcount.Add(1)
|
||||
l.mu.RLock()
|
||||
// RLock locks mutex for reading.
|
||||
// If the mutex is already locked for writing,
|
||||
// It blocks until the lock is available.
|
||||
func (m *Mutex) RLock() {
|
||||
m.rcount.Add(1)
|
||||
m.mu.RLock()
|
||||
}
|
||||
|
||||
// 安全的RUnlock
|
||||
func (l *Mutex) RUnlock() {
|
||||
if l.rcount.Val() > 0 {
|
||||
if l.rcount.Add(-1) >= 0 {
|
||||
l.mu.RUnlock()
|
||||
// RUnlock undoes a single RLock call;
|
||||
// it does not affect other simultaneous readers.
|
||||
// It is a run-time error if mutex is not locked for reading
|
||||
// on entry to RUnlock.
|
||||
// It is safe to be called multiple times.
|
||||
func (m *Mutex) RUnlock() {
|
||||
if m.rcount.Val() > 0 {
|
||||
if m.rcount.Add(-1) >= 0 {
|
||||
m.mu.RUnlock()
|
||||
} else {
|
||||
// 标准库这里会panic
|
||||
l.rcount.Add(1)
|
||||
m.rcount.Add(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 不阻塞Lock
|
||||
func (l *Mutex) TryLock() bool {
|
||||
// 初步读写次数检查, 但无法保证原子性
|
||||
if l.wcount.Val() == 0 && l.rcount.Val() == 0 {
|
||||
// 第二次检查, 保证原子操作
|
||||
if l.wcount.Add(1) == 1 {
|
||||
l.mu.Lock()
|
||||
l.wid.Add(1)
|
||||
// TryLock tries locking the mutex for writing.
|
||||
// It returns true if success, or if there's a write/read lock on the mutex,
|
||||
// it returns false.
|
||||
func (m *Mutex) TryLock() bool {
|
||||
// The first check, but it cannot ensure the atomicity.
|
||||
if m.wcount.Val() == 0 && m.rcount.Val() == 0 {
|
||||
// The second check, it ensures the atomicity with atomic Add.
|
||||
if m.wcount.Add(1) == 1 {
|
||||
m.mu.Lock()
|
||||
m.wid.Add(1)
|
||||
return true
|
||||
} else {
|
||||
l.wcount.Add(-1)
|
||||
m.wcount.Add(-1)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 不阻塞RLock
|
||||
func (l *Mutex) TryRLock() bool {
|
||||
// 只要不存在写锁
|
||||
if l.wcount.Val() == 0 {
|
||||
l.rcount.Add(1)
|
||||
l.mu.RLock()
|
||||
// TryRLock tries locking the mutex for reading.
|
||||
// It returns true if success, or if there's a write lock on the mutex, it returns false.
|
||||
func (m *Mutex) TryRLock() bool {
|
||||
// There must be no write lock on mutex.
|
||||
if m.wcount.Val() == 0 {
|
||||
m.rcount.Add(1)
|
||||
m.mu.RLock()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TryLockFunc tries locking the mutex for writing with given callback function <f>.
|
||||
// it returns true if success, or if there's a write/read lock on the mutex,
|
||||
// it returns false.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
func (m *Mutex) TryLockFunc(f func()) bool {
|
||||
if m.TryLock() {
|
||||
defer m.Unlock()
|
||||
f()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TryRLockFunc tries locking the mutex for reading with given callback function <f>.
|
||||
// It returns true if success, or if there's a write lock on the mutex, it returns false.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
func (m *Mutex) TryRLockFunc(f func()) bool {
|
||||
if m.TryRLock() {
|
||||
defer m.RUnlock()
|
||||
f()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// LockFunc locks the mutex for writing with given callback function <f>.
|
||||
// If there's a write/read lock the mutex, it will blocks until the lock is released.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
func (m *Mutex) LockFunc(f func()) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
f()
|
||||
}
|
||||
|
||||
// RLockFunc locks the mutex for reading with given callback function <f>.
|
||||
// If there's a write lock the mutex, it will blocks until the lock is released.
|
||||
//
|
||||
// It releases the lock after <f> is executed.
|
||||
func (m *Mutex) RLockFunc(f func()) {
|
||||
m.RLock()
|
||||
defer m.RUnlock()
|
||||
f()
|
||||
}
|
||||
|
||||
@ -1,121 +1,104 @@
|
||||
// Copyright 2017-2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
// Copyright 2017-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 grpool implements a goroutine reusable pool.
|
||||
//
|
||||
// Goroutine池,
|
||||
// 用于goroutine复用,提升异步操作执行效率(避免goroutine限制,并节约内存开销).
|
||||
// 需要注意的是,grpool提供给的公共池不提供关闭方法,自创建的池可以手动关闭掉。
|
||||
package grpool
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/glist"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
"math"
|
||||
"github.com/gogf/gf/g/container/glist"
|
||||
"github.com/gogf/gf/g/container/gtype"
|
||||
)
|
||||
|
||||
// goroutine池对象
|
||||
// Goroutine Pool
|
||||
type Pool struct {
|
||||
workerChan chan struct{} // 使用channel限制最大的goroutine数量
|
||||
workerNum *gtype.Int // 当前正在运行的worker/goroutine数量
|
||||
jobQueue *glist.List // 待处理任务操作队列
|
||||
jobEvents chan struct{} // 任务添加事件(jobQueue+jobEvents结合使用)
|
||||
closed *gtype.Bool
|
||||
limit int // Max goroutine count limit.
|
||||
count *gtype.Int // Current running goroutine count.
|
||||
list *glist.List // Job list for asynchronous job adding purpose.
|
||||
closed *gtype.Bool // Is pool closed or not.
|
||||
}
|
||||
|
||||
// 默认的goroutine池管理对象
|
||||
// 该对象与进程同生命周期,无需Close
|
||||
var defaultPool = New()
|
||||
// Default goroutine pool.
|
||||
var pool = New()
|
||||
|
||||
// 创建goroutine池管理对象, 参数用于限制限制最大的goroutine数量/线程数/worker数量,非必需参数,默认不做限制
|
||||
func New(size...int) *Pool {
|
||||
s := 0
|
||||
if len(size) > 0 {
|
||||
s = size[0]
|
||||
}
|
||||
// New creates and returns a new goroutine pool object.
|
||||
// The param <limit> is used to limit the max goroutine count,
|
||||
// which is not limited in default.
|
||||
func New(limit...int) *Pool {
|
||||
p := &Pool {
|
||||
workerNum : gtype.NewInt(),
|
||||
jobQueue : glist.New(),
|
||||
jobEvents : make(chan struct{}, math.MaxInt32),
|
||||
workerChan : make(chan struct{}, s),
|
||||
closed : gtype.NewBool(),
|
||||
limit : -1,
|
||||
count : gtype.NewInt(),
|
||||
list : glist.New(),
|
||||
closed : gtype.NewBool(),
|
||||
}
|
||||
if len(limit) > 0 && limit[0] > 0 {
|
||||
p.limit = limit[0]
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// 添加异步任务(使用默认的池对象)
|
||||
func Add(f func()) error {
|
||||
return defaultPool.Add(f)
|
||||
// Add pushes a new job to the pool using default goroutine pool.
|
||||
// The job will be executed asynchronously.
|
||||
func Add(f func()) {
|
||||
pool.Add(f)
|
||||
}
|
||||
|
||||
// 查询当前goroutine总数
|
||||
// Size returns current goroutine count of default goroutine pool.
|
||||
func Size() int {
|
||||
return defaultPool.workerNum.Val()
|
||||
return pool.count.Val()
|
||||
}
|
||||
|
||||
// 查询当前等待处理的任务总数
|
||||
// Jobs returns current job count of default goroutine pool.
|
||||
func Jobs() int {
|
||||
return len(defaultPool.jobEvents)
|
||||
return pool.list.Len()
|
||||
}
|
||||
|
||||
// 添加异步任务
|
||||
func (p *Pool) Add(f func()) error {
|
||||
p.jobQueue.PushBack(f)
|
||||
p.jobEvents <- struct{}{}
|
||||
// 判断是否创建新的worker
|
||||
if p.Jobs() > 1 || p.workerNum.Val() == 0 {
|
||||
p.ForkWorker()
|
||||
// Add pushes a new job to the pool.
|
||||
// The job will be executed asynchronously.
|
||||
func (p *Pool) Add(f func()) {
|
||||
p.list.PushFront(f)
|
||||
// check whether to create a new goroutine or not.
|
||||
if p.count.Val() == p.limit {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
// ensure atomicity.
|
||||
if p.limit != -1 && p.count.Add(1) > p.limit {
|
||||
p.count.Add(-1)
|
||||
return
|
||||
}
|
||||
// fork a new goroutine to consume the job list.
|
||||
p.fork()
|
||||
}
|
||||
|
||||
// 查询当前goroutine worker总数
|
||||
|
||||
// Size returns current goroutine count of the pool.
|
||||
func (p *Pool) Size() int {
|
||||
return p.workerNum.Val()
|
||||
return p.count.Val()
|
||||
}
|
||||
|
||||
// 查询当前等待处理的任务总数
|
||||
// Jobs returns current job count of the pool.
|
||||
func (p *Pool) Jobs() int {
|
||||
return p.jobQueue.Len()
|
||||
return p.list.Size()
|
||||
}
|
||||
|
||||
// 创建新的worker执行任务
|
||||
func (p *Pool) ForkWorker() {
|
||||
if cap(p.workerChan) > 0 {
|
||||
// 如果worker数量已经达到限制,那么不创建新worker,直接返回
|
||||
if p.workerNum.Val() == cap(p.workerChan) {
|
||||
return
|
||||
}
|
||||
p.workerNum.Add(1)
|
||||
p.workerChan <- struct{}{}
|
||||
} else {
|
||||
p.workerNum.Add(1)
|
||||
}
|
||||
// fork creates a new goroutine pool.
|
||||
func (p *Pool) fork() {
|
||||
go func() {
|
||||
defer p.count.Add(-1)
|
||||
job := (interface{})(nil)
|
||||
for !p.closed.Val() {
|
||||
select {
|
||||
case <- p.jobEvents:
|
||||
if job := p.jobQueue.PopFront(); job != nil {
|
||||
job.(func())()
|
||||
} else {
|
||||
goto WorkerDone
|
||||
}
|
||||
default:
|
||||
goto WorkerDone
|
||||
}
|
||||
}
|
||||
WorkerDone:
|
||||
p.workerNum.Add(-1)
|
||||
if cap(p.workerChan) > 0 {
|
||||
<- p.workerChan
|
||||
if job = p.list.PopBack(); job != nil {
|
||||
job.(func())()
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 关闭池,所有的任务将会停止,此后继续添加的任务将不会被执行
|
||||
// Close closes the goroutine pool, which makes all goroutines exit.
|
||||
func (p *Pool) Close() {
|
||||
p.closed.Set(true)
|
||||
p.closed.Set(true)
|
||||
}
|
||||
95
g/os/grpool/grpool_unit_test.go
Normal file
95
g/os/grpool/grpool_unit_test.go
Normal file
@ -0,0 +1,95 @@
|
||||
// Copyright 2017 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=".*" -count=1
|
||||
|
||||
package grpool_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/container/garray"
|
||||
"github.com/gogf/gf/g/os/grpool"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
func Test_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
wg := sync.WaitGroup{}
|
||||
array := garray.NewArray()
|
||||
size := 100
|
||||
wg.Add(size)
|
||||
for i := 0; i < size; i++ {
|
||||
grpool.Add(func() {
|
||||
array.Append(1)
|
||||
wg.Done()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
gtest.Assert(array.Len(), size)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Limit1(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
wg := sync.WaitGroup{}
|
||||
array := garray.NewArray()
|
||||
size := 100
|
||||
pool := grpool.New(10)
|
||||
wg.Add(size)
|
||||
for i := 0; i < size; i++ {
|
||||
pool.Add(func() {
|
||||
array.Append(1)
|
||||
wg.Done()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
gtest.Assert(array.Len(), size)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Limit2(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
wg := sync.WaitGroup{}
|
||||
array := garray.NewArray()
|
||||
size := 100
|
||||
pool := grpool.New(1)
|
||||
wg.Add(size)
|
||||
for i := 0; i < size; i++ {
|
||||
pool.Add(func() {
|
||||
array.Append(1)
|
||||
wg.Done()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
gtest.Assert(array.Len(), size)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Limit3(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
array := garray.NewArray()
|
||||
size := 1000
|
||||
pool := grpool.New(100)
|
||||
for i := 0; i < size; i++ {
|
||||
pool.Add(func() {
|
||||
array.Append(1)
|
||||
time.Sleep(2*time.Second)
|
||||
})
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
gtest.Assert(pool.Size(), 100)
|
||||
gtest.Assert(pool.Jobs(), 900)
|
||||
gtest.Assert(array.Len(), 100)
|
||||
pool.Close()
|
||||
time.Sleep(2*time.Second)
|
||||
gtest.Assert(pool.Size(), 0)
|
||||
gtest.Assert(pool.Jobs(), 900)
|
||||
gtest.Assert(array.Len(), 100)
|
||||
})
|
||||
}
|
||||
@ -5,8 +5,6 @@
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gtime provides functionality for measuring and displaying time.
|
||||
//
|
||||
// 时间管理.
|
||||
package gtime
|
||||
|
||||
import (
|
||||
|
||||
@ -21,19 +21,21 @@ var (
|
||||
'd': "02", // 月份中的第几天,有前导零的 2 位数字(01 到 31)
|
||||
'D': "Mon", // 星期中的第几天,文本表示,3 个字母(Mon 到 Sun)
|
||||
'w': "Monday", // 星期中的第几天,数字型式的文本表示 0为星期天 6为星期六
|
||||
'W': "", // ISO-8601 格式年份中的第几周,每周从星期一开始 例如:42(当年的第 42 周)
|
||||
'N': "Monday", // ISO-8601 格式数字表示的星期中的第几天 1(表示星期一)到 7(表示星期天)
|
||||
'j': "=j=02", // 月份中的第几天,没有前导零(1 到 31)
|
||||
'S': "02", // 每月天数后面的英文后缀,2 个字符 st,nd,rd 或者 th。可以和 j 一起用
|
||||
'l': "Monday", // ("L"的小写字母)星期几,完整的文本格式(Sunday 到 Saturday)
|
||||
'z': "", // 年份中的第几天 0到365
|
||||
't': "", // 指定的月份有几天 28到31
|
||||
|
||||
// ================== 日 ==================
|
||||
'W': "", // ISO-8601 格式年份中的第几周,每周从星期一开始 例如:42(当年的第 42 周)
|
||||
|
||||
// ================== 月 ==================
|
||||
'F': "January", // 月份,完整的文本格式,例如 January 或者 March January 到 December
|
||||
'm': "01", // 数字表示的月份,有前导零(01 到 12)
|
||||
'M': "Jan", // 三个字母缩写表示的月份(Jan 到 Dec)
|
||||
'n': "1", // 数字表示的月份,没有前导零(1 到 12)
|
||||
't': "", // 指定的月份有几天 28到31
|
||||
|
||||
// ================== 年 ==================
|
||||
'Y': "2006", // 4 位数字完整表示的年份, 例如:1999 或 2003
|
||||
@ -49,6 +51,7 @@ var (
|
||||
'i': "04", // 有前导零的分钟数, 00 到 59
|
||||
's': "05", // 秒数,有前导零, 00 到 59
|
||||
'u': "=u=.000", // 毫秒(3位)
|
||||
'U': "", // 将时间格式化为Unix时间,即从时间点January 1, 1970 UTC到时间点t所经过的时间(单位秒)
|
||||
|
||||
// ================== 时区 ==================
|
||||
'O': "-0700", // 与UTC相差的小时数, 例如:+0200
|
||||
@ -75,56 +78,9 @@ var (
|
||||
dayOfMonth = []int{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}
|
||||
)
|
||||
|
||||
// 将自定义的格式转换为标准库时间格式
|
||||
func formatToStdLayout(format string) string {
|
||||
b := bytes.NewBuffer(nil)
|
||||
for i := 0; i < len(format); {
|
||||
switch format[i] {
|
||||
case '\\':
|
||||
if i < len(format)-1 {
|
||||
b.WriteByte(format[i+1])
|
||||
i += 2
|
||||
continue
|
||||
} else {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
default:
|
||||
if f, ok := formats[format[i]]; ok {
|
||||
// 有几个转换的符号需要特殊处理
|
||||
switch format[i] {
|
||||
case 'j': b.WriteString("02")
|
||||
case 'G': b.WriteString("15")
|
||||
case 'u':
|
||||
if i > 0 && format[i-1] == '.' {
|
||||
b.WriteString("000")
|
||||
} else {
|
||||
b.WriteString(".000")
|
||||
}
|
||||
|
||||
default:
|
||||
b.WriteString(f)
|
||||
}
|
||||
} else {
|
||||
b.WriteByte(format[i])
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// 将format格式转换为正则表达式规则
|
||||
func formatToRegexPattern(format string) string {
|
||||
s := gregex.Quote(formatToStdLayout(format))
|
||||
s, _ = gregex.ReplaceString(`[0-9]`, `[0-9]`, s)
|
||||
s, _ = gregex.ReplaceString(`[A-Za-z]`, `[A-Za-z]`, s)
|
||||
return s
|
||||
}
|
||||
|
||||
// 格式化,使用自定义日期格式
|
||||
// 使用自定义日期格式格式化输出日期。
|
||||
func (t *Time) Format(format string) string {
|
||||
runes := []rune(format)
|
||||
runes := []rune(format)
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for i := 0; i < len(runes); {
|
||||
switch runes[i] {
|
||||
@ -139,6 +95,7 @@ func (t *Time) Format(format string) string {
|
||||
case 'W': buffer.WriteString(strconv.Itoa(t.WeeksOfYear()))
|
||||
case 'z': buffer.WriteString(strconv.Itoa(t.DayOfYear()))
|
||||
case 't': buffer.WriteString(strconv.Itoa(t.DaysInMonth()))
|
||||
case 'U': buffer.WriteString(strconv.FormatInt(t.Unix(),10))
|
||||
default:
|
||||
if runes[i] > 255 {
|
||||
buffer.WriteRune(runes[i])
|
||||
@ -165,14 +122,21 @@ func (t *Time) Format(format string) string {
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
// 每月天数后面的英文后缀,2 个字符st nd,rd 或者 th
|
||||
func formatMonthDaySuffixMap(day string) string {
|
||||
switch day {
|
||||
case "01": return "st"
|
||||
case "02": return "nd"
|
||||
case "03": return "rd"
|
||||
default: return "th"
|
||||
}
|
||||
// 通过自定义格式转换当前日期为新的日期。
|
||||
func (t *Time) FormatTo(format string) *Time {
|
||||
t.Time = NewFromStr(t.Format(format)).Time
|
||||
return t
|
||||
}
|
||||
|
||||
// 使用标准库格式格式化输出日期。
|
||||
func (t *Time) Layout(layout string) string {
|
||||
return t.Time.Format(layout)
|
||||
}
|
||||
|
||||
// 通过标准库格式转换当前日期为新的日期。
|
||||
func (t *Time) LayoutTo(layout string) *Time {
|
||||
t.Time = NewFromStr(t.Layout(layout)).Time
|
||||
return t
|
||||
}
|
||||
|
||||
// 返回是否是润年
|
||||
@ -221,7 +185,61 @@ func (t *Time) WeeksOfYear() int {
|
||||
return week
|
||||
}
|
||||
|
||||
// 格式化使用标准库格式
|
||||
func (t *Time) Layout(layout string) string {
|
||||
return t.Time.Format(layout)
|
||||
// 将自定义的格式转换为标准库时间格式
|
||||
func formatToStdLayout(format string) string {
|
||||
b := bytes.NewBuffer(nil)
|
||||
for i := 0; i < len(format); {
|
||||
switch format[i] {
|
||||
case '\\':
|
||||
if i < len(format)-1 {
|
||||
b.WriteByte(format[i+1])
|
||||
i += 2
|
||||
continue
|
||||
} else {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
default:
|
||||
if f, ok := formats[format[i]]; ok {
|
||||
// 有几个转换的符号需要特殊处理
|
||||
switch format[i] {
|
||||
case 'j': b.WriteString("02")
|
||||
case 'G': b.WriteString("15")
|
||||
case 'u':
|
||||
if i > 0 && format[i-1] == '.' {
|
||||
b.WriteString("000")
|
||||
} else {
|
||||
b.WriteString(".000")
|
||||
}
|
||||
|
||||
default:
|
||||
b.WriteString(f)
|
||||
}
|
||||
} else {
|
||||
b.WriteByte(format[i])
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// 将format格式转换为正则表达式规则
|
||||
func formatToRegexPattern(format string) string {
|
||||
s := gregex.Quote(formatToStdLayout(format))
|
||||
s, _ = gregex.ReplaceString(`[0-9]`, `[0-9]`, s)
|
||||
s, _ = gregex.ReplaceString(`[A-Za-z]`, `[A-Za-z]`, s)
|
||||
return s
|
||||
}
|
||||
|
||||
// 每月天数后面的英文后缀,2 个字符st nd,rd 或者 th
|
||||
func formatMonthDaySuffixMap(day string) string {
|
||||
switch day {
|
||||
case "01": return "st"
|
||||
case "02": return "nd"
|
||||
case "03": return "rd"
|
||||
default: return "th"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ type Time struct {
|
||||
}
|
||||
|
||||
// 创建一个空的时间对象,参数可以是标准库时间对象,可选
|
||||
func New (t...time.Time) *Time {
|
||||
func New(t...time.Time) *Time {
|
||||
if len(t) > 0 {
|
||||
return NewFromTime(t[0])
|
||||
}
|
||||
@ -30,14 +30,14 @@ func Now() *Time {
|
||||
}
|
||||
|
||||
// 标准时间对象转换为自定义的时间对象
|
||||
func NewFromTime (t time.Time) *Time {
|
||||
func NewFromTime(t time.Time) *Time {
|
||||
return &Time{
|
||||
t,
|
||||
}
|
||||
}
|
||||
|
||||
// 从字符串转换为时间对象,复杂的时间字符串需要给定格式
|
||||
func NewFromStr (str string) *Time {
|
||||
func NewFromStr(str string) *Time {
|
||||
if t, err := StrToTime(str); err == nil {
|
||||
return t
|
||||
}
|
||||
@ -45,7 +45,7 @@ func NewFromStr (str string) *Time {
|
||||
}
|
||||
|
||||
// 从字符串转换为时间对象,指定字符串时间格式,format格式形如:Y-m-d H:i:s
|
||||
func NewFromStrFormat (str string, format string) *Time {
|
||||
func NewFromStrFormat(str string, format string) *Time {
|
||||
if t, err := StrToTimeFormat(str, format); err == nil {
|
||||
return t
|
||||
}
|
||||
@ -53,7 +53,7 @@ func NewFromStrFormat (str string, format string) *Time {
|
||||
}
|
||||
|
||||
// 从字符串转换为时间对象,通过标准库layout格式进行解析,layout格式形如:2006-01-02 15:04:05
|
||||
func NewFromStrLayout (str string, layout string) *Time {
|
||||
func NewFromStrLayout(str string, layout string) *Time {
|
||||
if t, err := StrToTimeLayout(str, layout); err == nil {
|
||||
return t
|
||||
}
|
||||
@ -61,7 +61,7 @@ func NewFromStrLayout (str string, layout string) *Time {
|
||||
}
|
||||
|
||||
// 时间戳转换为时间对象,时间戳支持到纳秒的数值
|
||||
func NewFromTimeStamp (timestamp int64) *Time {
|
||||
func NewFromTimeStamp(timestamp int64) *Time {
|
||||
if timestamp == 0 {
|
||||
return &Time {}
|
||||
}
|
||||
@ -98,7 +98,8 @@ func (t *Time) String() string {
|
||||
return t.Format("Y-m-d H:i:s")
|
||||
}
|
||||
|
||||
// 转换为标准库日期对象
|
||||
// Deprecated.
|
||||
// Directly use t.Time instead.
|
||||
func (t *Time) ToTime() time.Time {
|
||||
return t.Time
|
||||
}
|
||||
@ -121,13 +122,12 @@ func (t *Time) ToLocation(location *time.Location) *Time {
|
||||
}
|
||||
|
||||
// 时区转换为指定的时区(通过时区名称,如:Asia/Shanghai)
|
||||
func (t *Time) ToZone(zone string) *Time {
|
||||
func (t *Time) ToZone(zone string) (*Time, error) {
|
||||
if l, err := time.LoadLocation(zone); err == nil {
|
||||
t.Time = t.Time.In(l)
|
||||
return t
|
||||
return t, nil
|
||||
} else {
|
||||
//panic(err)
|
||||
return nil
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -42,6 +42,8 @@ func Test_Format(t *testing.T) {
|
||||
}
|
||||
gtest.Assert(timeTemp2.Format("Y-n-j G:i:s"), "2006-1-2 3:04:05")
|
||||
|
||||
gtest.Assert(timeTemp2.Format("U"), "1136142245")
|
||||
|
||||
// 测试数字型的星期
|
||||
times := []map[string]string{
|
||||
{"k": "2019-04-22", "f": "w", "r": "1"},
|
||||
@ -85,9 +87,24 @@ func Test_Format(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_FormatTo(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
timeTemp := gtime.Now()
|
||||
gtest.Assert(timeTemp.FormatTo("Y-m-01 00:00:01"), timeTemp.Time.Format("2006-01") + "-01 00:00:01")
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
func Test_Layout(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
timeTemp := gtime.Now()
|
||||
gtest.Assert(timeTemp.Layout("2006-01-02 15:04:05"), timeTemp.Time.Format("2006-01-02 15:04:05"))
|
||||
})
|
||||
}
|
||||
|
||||
func Test_LayoutTo(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
timeTemp := gtime.Now()
|
||||
gtest.Assert(timeTemp.LayoutTo("2006-01-02 00:00:00"), timeTemp.Time.Format("2006-01-02 00:00:00"))
|
||||
})
|
||||
}
|
||||
|
||||
@ -137,7 +137,7 @@ func Test_ToZone(t *testing.T) {
|
||||
timeTemp.ToLocation(loc)
|
||||
gtest.Assert(timeTemp.Time.Location().String(), "Asia/Shanghai")
|
||||
|
||||
timeTemp1 := timeTemp.ToZone("errZone")
|
||||
timeTemp1, _ := timeTemp.ToZone("errZone")
|
||||
if timeTemp1 != nil {
|
||||
t.Error("test fail")
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ func New(path...string) *View {
|
||||
if gfile.Exists(envPath) {
|
||||
view.SetPath(envPath)
|
||||
} else {
|
||||
glog.Errorfln("Template directory path does not exist: %s", envPath)
|
||||
glog.Errorf("Template directory path does not exist: %s", envPath)
|
||||
}
|
||||
} else {
|
||||
// Dir path of working dir.
|
||||
|
||||
@ -15,11 +15,17 @@ import (
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/os/gfsnotify"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/os/gmlock"
|
||||
"github.com/gogf/gf/g/os/gspath"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
const (
|
||||
// Template name for content parsing.
|
||||
gCONTENT_TEMPLATE_NAME = "template content"
|
||||
)
|
||||
|
||||
var (
|
||||
// Templates cache map for template folder.
|
||||
templates = gmap.NewStrAnyMap()
|
||||
@ -40,7 +46,7 @@ func (view *View) getTemplate(path string, pattern string) (tpl *template.Templa
|
||||
if tpl, err = tpl.ParseFiles(files...); err != nil {
|
||||
return nil
|
||||
}
|
||||
gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
_, _ = gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
templates.Remove(path)
|
||||
gfsnotify.Exit()
|
||||
})
|
||||
@ -102,7 +108,10 @@ func (view *View) Parse(file string, params...Params) (parsed string, err error)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tpl, err = tpl.Parse(gfcache.GetContents(path))
|
||||
// Using memory lock to ensure concurrent safety for template parsing.
|
||||
gmlock.LockFunc("gview-parsing:" + folder, func() {
|
||||
tpl, err = tpl.Parse(gfcache.GetContents(path))
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -146,8 +155,14 @@ func (view *View) Parse(file string, params...Params) (parsed string, err error)
|
||||
func (view *View) ParseContent(content string, params...Params) (string, error) {
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
tpl := template.New("template content").Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
tpl, err := tpl.Parse(content)
|
||||
err := (error)(nil)
|
||||
tpl := templates.GetOrSetFuncLock(gCONTENT_TEMPLATE_NAME, func() interface {} {
|
||||
return template.New(gCONTENT_TEMPLATE_NAME).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
}).(*template.Template)
|
||||
// Using memory lock to ensure concurrent safety for content parsing.
|
||||
gmlock.LockFunc("gview-parsing:content", func() {
|
||||
tpl, err = tpl.Parse(content)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@ -10,51 +10,29 @@ package gregex_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"testing"
|
||||
"regexp"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var pattern = `(.+):(\d+)`
|
||||
var src = "johng.cn:80"
|
||||
var replace = "johng.cn"
|
||||
var pattern = `(\w+).+\-\-\s*(.+)`
|
||||
var src = `GF is best! -- John`
|
||||
|
||||
func BenchmarkValidate(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
gregex.Validate(pattern)
|
||||
}
|
||||
func Benchmark_GF(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
gregex.IsMatchString(pattern, src)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsMatch(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
gregex.IsMatch(pattern, []byte(src))
|
||||
}
|
||||
func Benchmark_Compile(b *testing.B) {
|
||||
var wcdRegexp = regexp.MustCompile(pattern)
|
||||
for i := 0; i < b.N; i++ {
|
||||
wcdRegexp.MatchString(src)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsMatchString(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
gregex.IsMatchString(pattern, src)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMatchString(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
gregex.MatchString(pattern, src)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMatchAllString(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
gregex.MatchAllString(pattern, src)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReplace(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
gregex.Replace(pattern, []byte(replace), []byte(src))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReplaceString(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
gregex.ReplaceString(pattern, replace, src)
|
||||
}
|
||||
func Benchmark_Compile_Actual(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
wcdRegexp := regexp.MustCompile(pattern)
|
||||
wcdRegexp.MatchString(src)
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,6 +20,11 @@ type apiString interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
// Type assert api for Error().
|
||||
type apiError interface {
|
||||
Error() string
|
||||
}
|
||||
|
||||
var (
|
||||
// Empty strings.
|
||||
emptyStringMap = map[string]struct{}{
|
||||
@ -137,11 +142,16 @@ func String(i interface{}) string {
|
||||
case bool: return strconv.FormatBool(value)
|
||||
case string: return value
|
||||
case []byte: return string(value)
|
||||
case []rune: return string(value)
|
||||
default:
|
||||
if f, ok := value.(apiString); ok {
|
||||
// If the variable implements the String() interface,
|
||||
// then use that interface to perform the conversion
|
||||
return f.String()
|
||||
} else if f, ok := value.(apiError); ok {
|
||||
// If the variable implements the Error() interface,
|
||||
// then use that interface to perform the conversion
|
||||
return f.Error()
|
||||
} else {
|
||||
// Finally we use json.Marshal to convert.
|
||||
jsonContent, _ := json.Marshal(value)
|
||||
|
||||
@ -7,15 +7,19 @@
|
||||
package gconv
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/internal/empty"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"reflect"
|
||||
"strings"
|
||||
"github.com/gogf/gf/g/internal/empty"
|
||||
"github.com/gogf/gf/g/text/gstr"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Map converts any variable <i> to map[string]interface{}.
|
||||
// If the parameter <i> is not a map type, then the conversion will fail and returns nil.
|
||||
// If <i> is a struct object, the second parameter <tags> specifies the most priority
|
||||
const (
|
||||
gGCONV_TAG = "gconv"
|
||||
)
|
||||
|
||||
// Map converts any variable <value> to map[string]interface{}.
|
||||
// If the parameter <value> is not a map type, then the conversion will fail and returns nil.
|
||||
// If <value> is a struct object, the second parameter <tags> specifies the most priority
|
||||
// tags that will be detected, otherwise it detects the tags in order of: gconv, json.
|
||||
func Map(value interface{}, tags...string) map[string]interface{} {
|
||||
if value == nil {
|
||||
@ -24,7 +28,7 @@ func Map(value interface{}, tags...string) map[string]interface{} {
|
||||
if r, ok := value.(map[string]interface{}); ok {
|
||||
return r
|
||||
} else {
|
||||
// Only assert the common combination type of maps, and finally use reflection.
|
||||
// Only assert the common combination of types, and finally it uses reflection.
|
||||
m := make(map[string]interface{})
|
||||
switch value.(type) {
|
||||
case map[interface{}]interface{}:
|
||||
@ -71,7 +75,6 @@ func Map(value interface{}, tags...string) map[string]interface{} {
|
||||
for k, v := range value.(map[string]float64) {
|
||||
m[k] = v
|
||||
}
|
||||
|
||||
case map[int]interface{}:
|
||||
for k, v := range value.(map[int]interface{}) {
|
||||
m[String(k)] = v
|
||||
@ -102,8 +105,7 @@ func Map(value interface{}, tags...string) map[string]interface{} {
|
||||
case reflect.Struct:
|
||||
rt := rv.Type()
|
||||
name := ""
|
||||
gconvTag := "gconv"
|
||||
tagArray := []string{gconvTag, "json"}
|
||||
tagArray := []string{gGCONV_TAG, "json"}
|
||||
switch len(tags) {
|
||||
case 0:
|
||||
// No need handle.
|
||||
@ -112,8 +114,8 @@ func Map(value interface{}, tags...string) map[string]interface{} {
|
||||
default:
|
||||
tagArray = tags
|
||||
}
|
||||
if gstr.SearchArray(tagArray, gconvTag) < 0 {
|
||||
tagArray = append(tagArray, gconvTag)
|
||||
if gstr.SearchArray(tagArray, gGCONV_TAG) < 0 {
|
||||
tagArray = append(tagArray, gGCONV_TAG)
|
||||
}
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
// Only convert the public attributes.
|
||||
@ -159,3 +161,25 @@ func Map(value interface{}, tags...string) map[string]interface{} {
|
||||
return m
|
||||
}
|
||||
}
|
||||
|
||||
// MapDeep do Map function recursively.
|
||||
// See Map.
|
||||
func MapDeep(value interface{}, tags...string) map[string]interface{} {
|
||||
data := Map(value, tags...)
|
||||
for key, value := range data {
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
delete(data, key)
|
||||
for k, v := range MapDeep(value, tags...) {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
// Copyright 2017-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,
|
||||
@ -15,49 +15,49 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Struct maps the params key-value pairs to the corresponding struct object properties.
|
||||
// The third parameter mapping is unnecessary, indicating the mapping between the custom name
|
||||
// and the attribute name.
|
||||
// Struct maps the params key-value pairs to the corresponding struct object's properties.
|
||||
// The third parameter <mapping> is unnecessary, indicating the mapping rules between the custom key name
|
||||
// and the attribute name(case sensitive).
|
||||
//
|
||||
// Note:
|
||||
// 1. The <params> can be any type of may/struct, usually a map;
|
||||
// 2. The second parameter <objPointer> should be a pointer to the struct object;
|
||||
// 3. Only the public attributes of struct object can be mapped;
|
||||
// 1. The <params> can be any type of map/struct, usually a map.
|
||||
// 2. The second parameter <pointer> should be a pointer to the struct object.
|
||||
// 3. Only the public attributes of struct object can be mapped.
|
||||
// 4. If <params> is a map, the key of the map <params> can be lowercase.
|
||||
// It will automatically convert the first letter of the key to uppercase
|
||||
// in mapping procedure to do the matching.
|
||||
// If it does not match, ignore the key;
|
||||
func Struct(params interface{}, objPointer interface{}, attrMapping...map[string]string) error {
|
||||
// It ignores the map key, if it does not match.
|
||||
func Struct(params interface{}, pointer interface{}, mapping...map[string]string) error {
|
||||
if params == nil {
|
||||
return errors.New("params cannot be nil")
|
||||
}
|
||||
if objPointer == nil {
|
||||
if pointer == nil {
|
||||
return errors.New("object pointer cannot be nil")
|
||||
}
|
||||
paramsMap := Map(params)
|
||||
if paramsMap == nil {
|
||||
return fmt.Errorf("invalid params: %v", params)
|
||||
}
|
||||
// struct的反射对象
|
||||
elem := reflect.Value{}
|
||||
if v, ok := objPointer.(reflect.Value); ok {
|
||||
elem = v
|
||||
} else {
|
||||
rv := reflect.ValueOf(objPointer)
|
||||
// Using reflect to do the converting,
|
||||
// it also supports type of reflect.Value for <pointer>(always in internal usage).
|
||||
elem, ok := pointer.(reflect.Value)
|
||||
if !ok {
|
||||
rv := reflect.ValueOf(pointer)
|
||||
if kind := rv.Kind(); kind != reflect.Ptr {
|
||||
return fmt.Errorf("object pointer should be type of: %v", kind)
|
||||
}
|
||||
// Using IsNil on reflect.Ptr variable is OK.
|
||||
if !rv.IsValid() || rv.IsNil() {
|
||||
return errors.New("object pointer cannot be nil")
|
||||
}
|
||||
elem = rv.Elem()
|
||||
}
|
||||
// 已执行过转换的属性,只执行一次转换。
|
||||
// 或者是已经执行过转换检查的属性(即使不进行转换), 以便重复判断。
|
||||
// It only performs one converting to the same attribute.
|
||||
// doneMap is used to check repeated converting.
|
||||
doneMap := make(map[string]bool)
|
||||
// 首先按照传递的映射关系进行匹配
|
||||
if len(attrMapping) > 0 && len(attrMapping[0]) > 0 {
|
||||
for mapK, mapV := range attrMapping[0] {
|
||||
// It first checks the passed mapping rules.
|
||||
if len(mapping) > 0 && len(mapping[0]) > 0 {
|
||||
for mapK, mapV := range mapping[0] {
|
||||
if v, ok := paramsMap[mapK]; ok {
|
||||
doneMap[mapV] = true
|
||||
if err := bindVarToStructAttr(elem, mapV, v); err != nil {
|
||||
@ -66,25 +66,24 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
|
||||
}
|
||||
}
|
||||
}
|
||||
// 其次匹配对象定义时绑定的属性名称,
|
||||
// 标签映射关系map,如果有的话
|
||||
tagMap := getTagMapOfStruct(objPointer)
|
||||
for tagk, tagv := range tagMap {
|
||||
if _, ok := doneMap[tagv]; ok {
|
||||
// It secondly checks the tags of attributes.
|
||||
tagMap := getTagMapOfStruct(pointer)
|
||||
for tagK, tagV := range tagMap {
|
||||
if _, ok := doneMap[tagV]; ok {
|
||||
continue
|
||||
}
|
||||
if v, ok := paramsMap[tagk]; ok {
|
||||
doneMap[tagv] = true
|
||||
if err := bindVarToStructAttr(elem, tagv, v); err != nil {
|
||||
if v, ok := paramsMap[tagK]; ok {
|
||||
doneMap[tagV] = true
|
||||
if err := bindVarToStructAttr(elem, tagV, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// 最后按照默认规则进行匹配
|
||||
// It finally do the converting with default rules.
|
||||
attrMap := make(map[string]struct{})
|
||||
elemType := elem.Type()
|
||||
for i := 0; i < elem.NumField(); i++ {
|
||||
// 只转换公开属性
|
||||
// Only do converting to public attributes.
|
||||
if !gstr.IsLetterUpper(elemType.Field(i).Name[0]) {
|
||||
continue
|
||||
}
|
||||
@ -105,7 +104,7 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
|
||||
if _, ok := tagMap[checkName]; ok {
|
||||
continue
|
||||
}
|
||||
// 循环查找属性名称进行匹配
|
||||
// Loop to find the matched attribute name.
|
||||
for value, _ := range attrMap {
|
||||
if strings.EqualFold(checkName, value) {
|
||||
name = value
|
||||
@ -121,7 +120,7 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
|
||||
break
|
||||
}
|
||||
}
|
||||
// 如果没有匹配到属性名称,放弃
|
||||
// No matching, give up this attribute converting.
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
@ -132,15 +131,51 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
|
||||
return nil
|
||||
}
|
||||
|
||||
// StructDeep do Struct function recursively.
|
||||
// See Struct.
|
||||
func StructDeep(params interface{}, pointer interface{}, mapping...map[string]string) error {
|
||||
if err := Struct(params, pointer, mapping...); err != nil {
|
||||
return err
|
||||
} else {
|
||||
rv, ok := pointer.(reflect.Value)
|
||||
if !ok {
|
||||
rv = reflect.ValueOf(pointer)
|
||||
}
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
rt := rv.Type()
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
// Only do converting to public attributes.
|
||||
if !gstr.IsLetterUpper(rt.Field(i).Name[0]) {
|
||||
continue
|
||||
}
|
||||
trv := rv.Field(i)
|
||||
switch trv.Kind() {
|
||||
case reflect.Struct:
|
||||
if err := StructDeep(params, trv, mapping...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 解析指针对象的tag
|
||||
func getTagMapOfStruct(objPointer interface{}) map[string]string {
|
||||
tagmap := make(map[string]string)
|
||||
func getTagMapOfStruct(pointer interface{}) map[string]string {
|
||||
tagMap := make(map[string]string)
|
||||
// 反射类型判断
|
||||
fields := ([]*structs.Field)(nil)
|
||||
if v, ok := objPointer.(reflect.Value); ok {
|
||||
if v, ok := pointer.(reflect.Value); ok {
|
||||
fields = structs.Fields(v.Interface())
|
||||
} else {
|
||||
fields = structs.Fields(objPointer)
|
||||
fields = structs.Fields(pointer)
|
||||
}
|
||||
// 将struct中定义的属性转换名称构建成tagmap
|
||||
for _, field := range fields {
|
||||
@ -150,11 +185,11 @@ func getTagMapOfStruct(objPointer interface{}) map[string]string {
|
||||
}
|
||||
if tag != "" {
|
||||
for _, v := range strings.Split(tag, ",") {
|
||||
tagmap[strings.TrimSpace(v)] = field.Name()
|
||||
tagMap[strings.TrimSpace(v)] = field.Name()
|
||||
}
|
||||
}
|
||||
}
|
||||
return tagmap
|
||||
return tagMap
|
||||
}
|
||||
|
||||
// 将参数值绑定到对象指定名称的属性上
|
||||
@ -206,7 +241,9 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er
|
||||
switch structFieldValue.Kind() {
|
||||
// 属性为结构体
|
||||
case reflect.Struct:
|
||||
Struct(value, structFieldValue)
|
||||
if err := Struct(value, structFieldValue); err != nil {
|
||||
structFieldValue.Set(reflect.ValueOf(value))
|
||||
}
|
||||
|
||||
// 属性为数组类型
|
||||
case reflect.Slice: fallthrough
|
||||
@ -220,11 +257,15 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if t.Kind() == reflect.Ptr {
|
||||
e := reflect.New(t.Elem()).Elem()
|
||||
Struct(v.Index(i).Interface(), e)
|
||||
if err := Struct(v.Index(i).Interface(), e); err != nil {
|
||||
e.Set(reflect.ValueOf(v.Index(i).Interface()))
|
||||
}
|
||||
a.Index(i).Set(e.Addr())
|
||||
} else {
|
||||
e := reflect.New(t).Elem()
|
||||
Struct(v.Index(i).Interface(), e)
|
||||
if err := Struct(v.Index(i).Interface(), e); err != nil {
|
||||
e.Set(reflect.ValueOf(v.Index(i).Interface()))
|
||||
}
|
||||
a.Index(i).Set(e)
|
||||
}
|
||||
}
|
||||
@ -234,11 +275,15 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er
|
||||
t := a.Index(0).Type()
|
||||
if t.Kind() == reflect.Ptr {
|
||||
e := reflect.New(t.Elem()).Elem()
|
||||
Struct(value, e)
|
||||
if err := Struct(value, e); err != nil {
|
||||
e.Set(reflect.ValueOf(value))
|
||||
}
|
||||
a.Index(0).Set(e.Addr())
|
||||
} else {
|
||||
e := reflect.New(t).Elem()
|
||||
Struct(value, e)
|
||||
if err := Struct(value, e); err != nil {
|
||||
e.Set(reflect.ValueOf(value))
|
||||
}
|
||||
a.Index(0).Set(e)
|
||||
}
|
||||
}
|
||||
@ -247,7 +292,9 @@ func bindVarToReflectValue(structFieldValue reflect.Value, value interface{}) er
|
||||
// 属性为指针类型
|
||||
case reflect.Ptr:
|
||||
e := reflect.New(structFieldValue.Type().Elem()).Elem()
|
||||
Struct(value, e)
|
||||
if err := Struct(value, e); err != nil {
|
||||
e.Set(reflect.ValueOf(value))
|
||||
}
|
||||
structFieldValue.Set(e.Addr())
|
||||
|
||||
default:
|
||||
|
||||
@ -123,23 +123,30 @@ func Test_Map_PrivateAttribute(t *testing.T) {
|
||||
gtest.Assert(gconv.Map(user), g.Map{"Id" : 1})
|
||||
})
|
||||
}
|
||||
//
|
||||
//func Test_Map_StructInherit(t *testing.T) {
|
||||
// type Base struct {
|
||||
// Id int
|
||||
// }
|
||||
// type User struct {
|
||||
// Base
|
||||
// Name string
|
||||
// }
|
||||
// gtest.Case(t, func() {
|
||||
// user := &User{
|
||||
// Base : Base {
|
||||
// Id : 100,
|
||||
// },
|
||||
// Name : "john",
|
||||
// }
|
||||
// fmt.Println(gconv.Map(user))
|
||||
// //gtest.Assert(gconv.Map(user), g.Map{"Id" : 1})
|
||||
// })
|
||||
//}
|
||||
|
||||
func Test_Map_StructInherit(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
type Ids struct {
|
||||
Id int `json:"id"`
|
||||
Uid int `json:"uid"`
|
||||
}
|
||||
type Base struct {
|
||||
Ids
|
||||
CreateTime string `json:"create_time"`
|
||||
}
|
||||
type User struct {
|
||||
Base
|
||||
Passport string `json:"passport"`
|
||||
Password string `json:"password"`
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
user := new(User)
|
||||
user.Id = 100
|
||||
user.Nickname = "john"
|
||||
user.CreateTime = "2019"
|
||||
m := gconv.MapDeep(user)
|
||||
gtest.Assert(m["id"], user.Id)
|
||||
gtest.Assert(m["nickname"], user.Nickname)
|
||||
gtest.Assert(m["create_time"], user.CreateTime)
|
||||
})
|
||||
}
|
||||
@ -8,9 +8,11 @@ package gconv_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/os/gtime"
|
||||
"github.com/gogf/gf/g/test/gtest"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_Struct_Basic1(t *testing.T) {
|
||||
@ -102,6 +104,26 @@ func Test_Struct_Basic2(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// 带有指针的基础类型属性
|
||||
func Test_Struct_Basic3(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
type User struct {
|
||||
Uid int
|
||||
Name *string
|
||||
}
|
||||
user := new(User)
|
||||
params := g.Map {
|
||||
"uid" : 1,
|
||||
"Name" : "john",
|
||||
}
|
||||
if err := gconv.Struct(params, user); err != nil {
|
||||
gtest.Error(err)
|
||||
}
|
||||
gtest.Assert(user.Uid, 1)
|
||||
gtest.Assert(*user.Name, "john")
|
||||
})
|
||||
}
|
||||
|
||||
// slice类型属性的赋值
|
||||
func Test_Struct_Attr_Slice(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
@ -302,7 +324,6 @@ func Test_Struct_Attr_Struct_Slice_Ptr(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// 私有属性不会进行转换
|
||||
func Test_Struct_PrivateAttribute(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
@ -315,4 +336,100 @@ func Test_Struct_PrivateAttribute(t *testing.T) {
|
||||
gtest.Assert(user.Id, 1)
|
||||
gtest.Assert(user.name, "")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Struct_Deep(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
type Ids struct {
|
||||
Id int `json:"id"`
|
||||
Uid int `json:"uid"`
|
||||
}
|
||||
type Base struct {
|
||||
Ids
|
||||
CreateTime string `json:"create_time"`
|
||||
}
|
||||
type User struct {
|
||||
Base
|
||||
Passport string `json:"passport"`
|
||||
Password string `json:"password"`
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
data := g.Map{
|
||||
"id" : 100,
|
||||
"uid" : 101,
|
||||
"passport" : "t1",
|
||||
"password" : "123456",
|
||||
"nickname" : "T1",
|
||||
"create_time" : "2019",
|
||||
}
|
||||
user := new(User)
|
||||
gconv.StructDeep(data, user)
|
||||
gtest.Assert(user.Id, 100)
|
||||
gtest.Assert(user.Uid, 101)
|
||||
gtest.Assert(user.Nickname, "T1")
|
||||
gtest.Assert(user.CreateTime, "2019")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Struct_Time(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
type User struct {
|
||||
CreateTime time.Time
|
||||
}
|
||||
now := time.Now()
|
||||
user := new(User)
|
||||
gconv.Struct(g.Map{
|
||||
"create_time" : now,
|
||||
}, user)
|
||||
gtest.Assert(user.CreateTime.UTC().String(), now.UTC().String())
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
type User struct {
|
||||
CreateTime *time.Time
|
||||
}
|
||||
now := time.Now()
|
||||
user := new(User)
|
||||
gconv.Struct(g.Map{
|
||||
"create_time" : &now,
|
||||
}, user)
|
||||
gtest.Assert(user.CreateTime.UTC().String(), now.UTC().String())
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
type User struct {
|
||||
CreateTime *gtime.Time
|
||||
}
|
||||
now := time.Now()
|
||||
user := new(User)
|
||||
gconv.Struct(g.Map{
|
||||
"create_time" : &now,
|
||||
}, user)
|
||||
gtest.Assert(user.CreateTime.Time.UTC().String(), now.UTC().String())
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
type User struct {
|
||||
CreateTime gtime.Time
|
||||
}
|
||||
now := time.Now()
|
||||
user := new(User)
|
||||
gconv.Struct(g.Map{
|
||||
"create_time" : &now,
|
||||
}, user)
|
||||
gtest.Assert(user.CreateTime.Time.UTC().String(), now.UTC().String())
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
type User struct {
|
||||
CreateTime gtime.Time
|
||||
}
|
||||
now := time.Now()
|
||||
user := new(User)
|
||||
gconv.Struct(g.Map{
|
||||
"create_time" : now,
|
||||
}, user)
|
||||
gtest.Assert(user.CreateTime.Time.UTC().String(), now.UTC().String())
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -17,9 +17,9 @@ import (
|
||||
|
||||
func Test_Time(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
t1 := "2011-10-10 01:02:03.456"
|
||||
gtest.AssertEQ(gconv.GTime(t1), gtime.NewFromStr(t1))
|
||||
gtest.AssertEQ(gconv.Time(t1), gtime.NewFromStr(t1).Time)
|
||||
gtest.AssertEQ(gconv.Duration(100), 100*time.Nanosecond)
|
||||
})
|
||||
t1 := "2011-10-10 01:02:03.456"
|
||||
gtest.AssertEQ(gconv.GTime(t1), gtime.NewFromStr(t1))
|
||||
gtest.AssertEQ(gconv.Time(t1), gtime.NewFromStr(t1).Time)
|
||||
gtest.AssertEQ(gconv.Duration(100), 100*time.Nanosecond)
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user