Compare commits

...

93 Commits

Author SHA1 Message Date
cc0a385c22 RELEASE updates 2019-06-09 11:56:58 +08:00
0b8c9713e6 improve grpool 2019-06-09 10:37:35 +08:00
e400a94ffb improving grpool 2019-06-09 10:33:16 +08:00
136ad3b0b5 add more unit test cases for hook feature of ghttp.Server 2019-06-08 19:38:46 +08:00
f9826104d8 improve gconv 2019-06-08 19:25:20 +08:00
c0c97b76fb update lead string position for glog 2019-06-07 23:00:34 +08:00
a908e4d4b1 fix issue in session initialization for ghttp.Server 2019-06-07 22:27:37 +08:00
055074246e add TryLockFunc/TryRLockFunc for gmlock.Mutex 2019-06-05 23:50:24 +08:00
4bbe51fb4b add Close function for gtcp.Server/gudp.Server 2019-06-05 22:12:22 +08:00
841224372b update comment for gmlock 2019-06-05 21:58:27 +08:00
d1f0fa1a47 fix issue in concurrent safety for gview.Parse; rename glog.*fln functions to glog.*f 2019-06-05 20:22:57 +08:00
6fecf8bb01 update example 2019-06-05 18:41:11 +08:00
6d44f02a38 fix issue in internal/empty 2019-06-05 18:40:26 +08:00
1458e486d7 v1.6.17 2019-06-04 23:39:33 +08:00
dc29822e69 improve gtcp; add tls support for gtcp 2019-06-04 23:33:46 +08:00
dae7722da1 Merge branch 'master' into develop 2019-06-04 18:35:06 +08:00
16d978dc58 fix issue in gfile.MainPkgPath 2019-06-04 18:30:02 +08:00
5d3c154b45 improve gtcp 2019-06-04 18:26:32 +08:00
00a8ef63b6 improve gtcp 2019-06-03 23:53:59 +08:00
6ac437a3a5 merge master 2019-06-03 21:33:16 +08:00
33b24eba01 Merge pull request #147 from jroam/master 2019-06-03 21:00:26 +08:00
3a99c6e5f5 improve TryCatch function for gutil 2019-06-03 20:58:30 +08:00
4c5d2839bd donators updates 2019-06-03 20:34:19 +08:00
85b104bafa update benchmark test cases for gregex 2019-06-03 16:59:33 +08:00
74e5d03a78 Modify documentation 2019-06-03 16:55:18 +08:00
cc43324ede modify some instructions. 2019-06-03 16:41:27 +08:00
e4f9e1000d fix issue in status handler for ghttp.Server 2019-06-03 16:39:45 +08:00
62829b0698 modify some instructions 2019-06-03 16:39:39 +08:00
2551e990cb add gtime formats of 'U' 2019-06-03 16:30:34 +08:00
45c34319b5 Merge pull request #18 from gogf/master
日常更新
2019-06-03 09:28:29 +08:00
75dcc566b3 improve grpool to ensure atomicity for function Add 2019-06-03 09:09:40 +08:00
4f1047e853 add KeepAlive configuration for ghttp.Server 2019-06-02 22:19:52 +08:00
79f765c961 update example for gconv 2019-06-02 16:34:31 +08:00
eead2fad2c version updates 2019-06-02 13:46:05 +08:00
455c9e09ab README updates 2019-06-02 13:45:21 +08:00
4665c3565c README updates 2019-06-02 13:44:24 +08:00
7456b4b4ad README updates 2019-06-02 13:43:11 +08:00
32a6454065 README updates 2019-06-02 13:40:42 +08:00
c52640c672 README updates 2019-06-02 13:32:03 +08:00
d62ef17290 improve glog 2019-06-01 22:36:12 +08:00
216af6a662 updates glog 2019-06-01 20:34:57 +08:00
35d860427e add GetLastSql function for gdb 2019-06-01 20:31:29 +08:00
9206574bae add json tag most priority for gjson.New 2019-06-01 20:01:45 +08:00
9c6f54131f updates unit test cases for gdb,grpool 2019-06-01 19:37:00 +08:00
d67b95c593 add async logging feature for glog 2019-06-01 19:34:03 +08:00
2bf2f1b822 improve grpool 2019-06-01 15:11:32 +08:00
9ad94eccad add json output support for glog 2019-06-01 11:01:57 +08:00
6e8a900f25 fix issue in gjson/gparser in loading xml content; add *time.Time support for gdb 2019-06-01 00:02:05 +08:00
d9aa9e4480 improve grpool 2019-05-31 22:54:57 +08:00
7335126064 mark deprecated for *fln functions of glog; fix issue in gfile.MainPkgPath in Windows 2019-05-29 15:18:12 +08:00
945dd71251 TODO updates 2019-05-29 11:53:10 +08:00
652aa29370 Merge branch 'master' of https://github.com/gogf/gf into develop 2019-05-29 11:26:27 +08:00
7034e2015e add FormatTo/LayoutTo functions for gtime; use custom ExpireFunc to close poool items when pool is close for gpool 2019-05-29 11:25:11 +08:00
0a890ad871 Merge branch 'qiangg_gdb_struct_inherit' into develop 2019-05-28 21:41:23 +08:00
b52bb1124e Merge branch 'master' into develop 2019-05-28 21:41:08 +08:00
41f33af51b add gconv.StructDeep/MapDeep functions for gconv; add struct inherit converting support for gdb 2019-05-28 21:41:00 +08:00
6b4763c7da Update README.MD
README updates.
2019-05-27 11:00:04 +08:00
be07889a45 Merge branch 'develop' into qiangg_gdb_struct_inherit 2019-05-24 20:07:20 +08:00
1b3243c09c comment update for gfile/gaes/gcrc32/gdes/gmd5/gsha1 2019-05-24 19:59:08 +08:00
fee1c9eccf Create FUNDING.yml 2019-05-24 19:24:25 +08:00
084f6c31cb comment updates for gfile 2019-05-23 21:58:04 +08:00
592bf76eb0 add browser mode support for ghttp.Client.Post 2019-05-23 21:20:46 +08:00
6f0aee1cc5 version updates 2019-05-23 20:43:09 +08:00
3575e20137 update Writer feature of glog 2019-05-23 20:34:06 +08:00
f69f529258 Merge branch 'master' into develop 2019-05-23 19:14:19 +08:00
3e7416ca7d add Skip for glog 2019-05-23 19:14:16 +08:00
9da883a50c comment updates 2019-05-23 11:49:33 +08:00
ea3e03aaba example updates for glog 2019-05-23 09:26:37 +08:00
8d9fdfeafc change parameter to unnecssary for glog.Header/Stdout; version updates 2019-05-22 21:43:56 +08:00
b2a8285ecb remove unnecessary pkg gscanner; update comment for gsmtp 2019-05-22 21:20:17 +08:00
162e8f7e51 comment updates for glog 2019-05-22 20:31:14 +08:00
6c658813cd refract glog to improve performance 2019-05-22 09:19:21 +08:00
0839ea385f refract glog to improve performance; 2019-05-21 23:57:03 +08:00
68fa235412 add gjson.GetJsonMap; refract glog to improve performance; 2019-05-21 21:52:17 +08:00
417ce4b470 Merge branch 'master' into qiangg_gdb_struct_inherit 2019-05-20 18:33:45 +08:00
48deaa5f57 improving package feature for gtcp 2019-05-20 08:59:16 +08:00
e9f7b8bc0c README updates 2019-05-18 14:55:15 +08:00
e31861af2e README updates 2019-05-18 13:52:18 +08:00
1af482d950 improve gfile.MainPkgPath 2019-05-17 21:37:48 +08:00
dd2436925b remove package feature for gudp, considering UDP protocal has its own border for message handling 2019-05-17 00:17:45 +08:00
b92b69564b improve gfile.MainPkgPath 2019-05-16 23:41:09 +08:00
635d228c86 adding inherit struct support for gdb 2019-05-16 13:56:49 +08:00
75725db6fb remove gmlock for print of glog; add time.Time support for gdb 2019-05-15 23:53:48 +08:00
5cd8475143 add time.Time support for convertParam function of gdb 2019-05-15 16:47:39 +08:00
5629f37939 version updates 2019-05-14 22:38:03 +08:00
08ec04d8b6 fix issue in unit test case of gredis 2019-05-14 22:37:13 +08:00
c0b46f364a version updates 2019-05-14 22:02:09 +08:00
303d03d43c Merge pull request #17 from gogf/master
日常更新。
2019-05-14 21:36:00 +08:00
8c5f74e8bb add DoVar/ReceiveVar function for gredis 2019-05-14 21:34:38 +08:00
94832262e3 version updates 2019-05-13 22:37:31 +08:00
aefbfd52e9 add more example for gtree 2019-05-13 22:37:05 +08:00
f3f0689bd4 rename LinkMap to ListMap for gmap 2019-05-13 22:26:39 +08:00
50f561dbd2 Merge pull request #16 from gogf/master
日常更新
2019-05-08 13:36:03 +08:00
176 changed files with 4090 additions and 2074 deletions

21
DONATOR.MD Normal file
View 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"/>

View File

@ -1,5 +1,6 @@
# GoFrame
<img align="right" height="150px" src="https://goframe.org/cover.png">
<div align=center>
<img src="https://goframe.org/logo.png" width="100"/>
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf)
[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf)
@ -8,12 +9,15 @@
[![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/gogf/gf)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
</div>
<!--
GoFrame is a modular, loose-coupled, production-ready and most-powerful application development framework of golang. Providing a series of core components and dozens of practical modules, such as: cache, logging, containers, timer, validator, database orm, etc. Supporting web server integrated with router, cookie, session, logger, configure, template, https, hooks, rewrites and many more features.
-->
`GF(GoFrame)` is a modular, loose-coupled, production-ready and most-powerful application development framework of golang. Providing a series of core components and dozens of practical modules, such as: memcache, configure, validator, logging, array/queue/set/map containers, timer/timing tasks, file/memory lock, object pool, database ORM, etc. Supporting web server integrated with router, cookie, session, logger, template, https, hooks, rewrites and many more features.
# Installation
```
go get -u github.com/gogf/gf
@ -25,7 +29,7 @@ require github.com/gogf/gf latest
# Limitation
```
golang version >= 1.9.2
golang version >= 1.10
```
# Documentation
@ -64,40 +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
- [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.
-->

View File

@ -1,5 +1,6 @@
# GoFrame
<img align="right" height="150px" src="https://goframe.org/cover.png">
<div align=center>
<img src="https://goframe.org/logo.png" width="100"/>
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf)
[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf)
@ -8,13 +9,15 @@
[![Production Ready](https://img.shields.io/badge/production-ready-blue.svg)](https://github.com/gogf/gf)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
</div>
`GF(Go Frame)`是一款模块化、松耦合、生产级Go应用开发框架。提供了常用的核心开发组件缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、数据校验、数据编码、文件监控、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、
并发安全容器等等。并提供了Web服务开发的系列核心组件Router、Cookie、Session、服务注册、配置管理、模板引擎等等支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。
# 特点
* 模块化、松耦合设计;
* 丰富实用的开发模块;
* 模块丰富,开箱即用
* 详尽的开发文档及示例;
* 完善的本地中文化支持;
* 致力于项目的通用方案;
@ -33,7 +36,7 @@ require github.com/gogf/gf latest
# 限制
```shell
golang版本 >= 1.9.2
golang版本 >= 1.10
```
# 架构
@ -76,42 +79,8 @@ func main() {
# 捐赠
捐赠支持`GF`框架的研发,
请在捐赠时备注您的`github`/`gitee`账号名称。
<a href="https://goframe.org/images/donate.png" target="_blank">
<img src="https://goframe.org/images/donate.png" width="300"/>
</a>
# 贡献者
- [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)
如果您喜欢`GF`,要不[给开发者来杯咖啡吧](DONATOR.MD)
请在捐赠时备注您的`github`/`gitee`账号名称。
# 感谢
<a href="https://www.jetbrains.com/?from=GoFrame"><img src="https://goframe.org/images/jetbrains.png" width="100" alt="JetBrains"/></a>

View File

@ -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)
## 新功能/改进

View File

@ -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标签失效问题

View File

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

View File

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

View File

@ -12,74 +12,74 @@ import (
"github.com/gogf/gf/g/internal/rwmutex"
)
type LinkMap struct {
type ListMap struct {
mu *rwmutex.RWMutex
data map[interface{}]*glist.Element
list *glist.List
}
type gLinkMapNode struct {
type gListMapNode struct {
key interface{}
value interface{}
}
// NewLinkMap returns an empty link map.
// LinkMap is backed by a hash table to store values and doubly-linked list to store ordering.
// NewListMap returns an empty link map.
// ListMap is backed by a hash table to store values and doubly-linked list to store ordering.
// The param <unsafe> used to specify whether using map in un-concurrent-safety,
// which is false in default, means concurrent-safe.
func NewLinkMap(unsafe ...bool) *LinkMap {
return &LinkMap{
func NewListMap(unsafe ...bool) *ListMap {
return &ListMap{
mu : rwmutex.New(unsafe...),
data : make(map[interface{}]*glist.Element),
list : glist.New(true),
}
}
// NewLinkMapFrom returns a link map from given map <data>.
// NewListMapFrom returns a link map from given map <data>.
// Note that, the param <data> map will be set as the underlying data map(no deep copy),
// there might be some concurrent-safe issues when changing the map outside.
func NewLinkMapFrom(data map[interface{}]interface{}, unsafe...bool) *LinkMap {
m := NewLinkMap(unsafe...)
func NewListMapFrom(data map[interface{}]interface{}, unsafe...bool) *ListMap {
m := NewListMap(unsafe...)
m.Sets(data)
return m
}
// Iterator is alias of IteratorAsc.
func (m *LinkMap) Iterator(f func (key, value interface{}) bool) {
func (m *ListMap) Iterator(f func (key, value interface{}) bool) {
m.IteratorAsc(f)
}
// IteratorAsc iterates the map in ascending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *LinkMap) IteratorAsc(f func (key interface{}, value interface{}) bool) {
func (m *ListMap) IteratorAsc(f func (key interface{}, value interface{}) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
node := (*gLinkMapNode)(nil)
node := (*gListMapNode)(nil)
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gLinkMapNode)
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
})
}
// IteratorDesc iterates the map in descending order with given callback function <f>.
// If <f> returns true, then it continues iterating; or false to stop.
func (m *LinkMap) IteratorDesc(f func (key interface{}, value interface{}) bool) {
func (m *ListMap) IteratorDesc(f func (key interface{}, value interface{}) bool) {
m.mu.RLock()
defer m.mu.RUnlock()
node := (*gLinkMapNode)(nil)
node := (*gListMapNode)(nil)
m.list.IteratorDesc(func(e *glist.Element) bool {
node = e.Value.(*gLinkMapNode)
node = e.Value.(*gListMapNode)
return f(node.key, node.value)
})
}
// Clone returns a new link map with copy of current map data.
func (m *LinkMap) Clone(unsafe ...bool) *LinkMap {
return NewLinkMapFrom(m.Map(), unsafe ...)
func (m *ListMap) Clone(unsafe ...bool) *ListMap {
return NewListMapFrom(m.Map(), unsafe ...)
}
// Clear deletes all data of the map, it will remake a new underlying data map.
func (m *LinkMap) Clear() {
func (m *ListMap) Clear() {
m.mu.Lock()
m.data = make(map[interface{}]*glist.Element)
m.list = glist.New(true)
@ -87,12 +87,12 @@ func (m *LinkMap) Clear() {
}
// Map returns a copy of the data of the map.
func (m *LinkMap) Map() map[interface{}]interface{} {
func (m *ListMap) Map() map[interface{}]interface{} {
m.mu.RLock()
node := (*gLinkMapNode)(nil)
node := (*gListMapNode)(nil)
data := make(map[interface{}]interface{}, len(m.data))
m.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gLinkMapNode)
node = e.Value.(*gListMapNode)
data[node.key] = node.value
return true
})
@ -101,24 +101,24 @@ func (m *LinkMap) Map() map[interface{}]interface{} {
}
// Set sets key-value to the map.
func (m *LinkMap) Set(key interface{}, value interface{}) {
func (m *ListMap) Set(key interface{}, value interface{}) {
m.mu.Lock()
if e, ok := m.data[key]; !ok {
m.data[key] = m.list.PushBack(&gLinkMapNode{key, value})
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
} else {
e.Value = &gLinkMapNode{key, value}
e.Value = &gListMapNode{key, value}
}
m.mu.Unlock()
}
// Sets batch sets key-values to the map.
func (m *LinkMap) Sets(data map[interface{}]interface{}) {
func (m *ListMap) Sets(data map[interface{}]interface{}) {
m.mu.Lock()
for key, value := range data {
if e, ok := m.data[key]; !ok {
m.data[key] = m.list.PushBack(&gLinkMapNode{key, value})
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
} else {
e.Value = &gLinkMapNode{key, value}
e.Value = &gListMapNode{key, value}
}
}
m.mu.Unlock()
@ -126,10 +126,10 @@ func (m *LinkMap) Sets(data map[interface{}]interface{}) {
// Search searches the map with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (m *LinkMap) Search(key interface{}) (value interface{}, found bool) {
func (m *ListMap) Search(key interface{}) (value interface{}, found bool) {
m.mu.RLock()
if e, ok := m.data[key]; ok {
value = e.Value.(*gLinkMapNode).value
value = e.Value.(*gListMapNode).value
found = ok
}
m.mu.RUnlock()
@ -137,10 +137,10 @@ func (m *LinkMap) Search(key interface{}) (value interface{}, found bool) {
}
// Get returns the value by given <key>.
func (m *LinkMap) Get(key interface{}) (value interface{}) {
func (m *ListMap) Get(key interface{}) (value interface{}) {
m.mu.RLock()
if e, ok := m.data[key]; ok {
value = e.Value.(*gLinkMapNode).value
value = e.Value.(*gListMapNode).value
}
m.mu.RUnlock()
return
@ -155,22 +155,22 @@ func (m *LinkMap) Get(key interface{}) (value interface{}) {
// and its return value will be set to the map with <key>.
//
// It returns value with given <key>.
func (m *LinkMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
func (m *ListMap) doSetWithLockCheck(key interface{}, value interface{}) interface{} {
m.mu.Lock()
defer m.mu.Unlock()
if e, ok := m.data[key]; ok {
return e.Value.(*gLinkMapNode).value
return e.Value.(*gListMapNode).value
}
if f, ok := value.(func() interface {}); ok {
value = f()
}
m.data[key] = m.list.PushBack(&gLinkMapNode{key, value})
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
return value
}
// GetOrSet returns the value by key,
// or set value with given <value> if not exist and returns this value.
func (m *LinkMap) GetOrSet(key interface{}, value interface{}) interface{} {
func (m *ListMap) GetOrSet(key interface{}, value interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, value)
} else {
@ -181,7 +181,7 @@ func (m *LinkMap) GetOrSet(key interface{}, value interface{}) interface{} {
// GetOrSetFunc returns the value by key,
// or sets value with return value of callback function <f> if not exist
// and returns this value.
func (m *LinkMap) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
func (m *ListMap) GetOrSetFunc(key interface{}, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f())
} else {
@ -195,7 +195,7 @@ func (m *LinkMap) GetOrSetFunc(key interface{}, f func() interface{}) interface{
//
// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function <f>
// with mutex.Lock of the map.
func (m *LinkMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
func (m *ListMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} {
if v, ok := m.Search(key); !ok {
return m.doSetWithLockCheck(key, f)
} else {
@ -205,31 +205,31 @@ func (m *LinkMap) GetOrSetFuncLock(key interface{}, f func() interface{}) interf
// GetVar returns a gvar.Var with the value by given <key>.
// The returned gvar.Var is un-concurrent safe.
func (m *LinkMap) GetVar(key interface{}) *gvar.Var {
func (m *ListMap) GetVar(key interface{}) *gvar.Var {
return gvar.New(m.Get(key), true)
}
// GetVarOrSet returns a gvar.Var with result from GetVarOrSet.
// The returned gvar.Var is un-concurrent safe.
func (m *LinkMap) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
func (m *ListMap) GetVarOrSet(key interface{}, value interface{}) *gvar.Var {
return gvar.New(m.GetOrSet(key, value), true)
}
// GetVarOrSetFunc returns a gvar.Var with result from GetOrSetFunc.
// The returned gvar.Var is un-concurrent safe.
func (m *LinkMap) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
func (m *ListMap) GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFunc(key, f), true)
}
// GetVarOrSetFuncLock returns a gvar.Var with result from GetOrSetFuncLock.
// The returned gvar.Var is un-concurrent safe.
func (m *LinkMap) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
func (m *ListMap) GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var {
return gvar.New(m.GetOrSetFuncLock(key, f), true)
}
// SetIfNotExist sets <value> to the map if the <key> does not exist, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *LinkMap) SetIfNotExist(key interface{}, value interface{}) bool {
func (m *ListMap) SetIfNotExist(key interface{}, value interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, value)
return true
@ -239,7 +239,7 @@ func (m *LinkMap) SetIfNotExist(key interface{}, value interface{}) bool {
// SetIfNotExistFunc sets value with return value of callback function <f>, then return true.
// It returns false if <key> exists, and <value> would be ignored.
func (m *LinkMap) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
func (m *ListMap) SetIfNotExistFunc(key interface{}, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f())
return true
@ -252,7 +252,7 @@ func (m *LinkMap) SetIfNotExistFunc(key interface{}, f func() interface{}) bool
//
// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that
// it executes function <f> with mutex.Lock of the map.
func (m *LinkMap) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
func (m *ListMap) SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool {
if !m.Contains(key) {
m.doSetWithLockCheck(key, f)
return true
@ -261,10 +261,10 @@ func (m *LinkMap) SetIfNotExistFuncLock(key interface{}, f func() interface{}) b
}
// Remove deletes value from map by given <key>, and return this deleted value.
func (m *LinkMap) Remove(key interface{}) (value interface{}) {
func (m *ListMap) Remove(key interface{}) (value interface{}) {
m.mu.Lock()
if e, ok := m.data[key]; ok {
value = e.Value.(*gLinkMapNode).value
value = e.Value.(*gListMapNode).value
delete(m.data, key)
m.list.Remove(e)
}
@ -273,7 +273,7 @@ func (m *LinkMap) Remove(key interface{}) (value interface{}) {
}
// Removes batch deletes values of the map by keys.
func (m *LinkMap) Removes(keys []interface{}) {
func (m *ListMap) Removes(keys []interface{}) {
m.mu.Lock()
for _, key := range keys {
if e, ok := m.data[key]; ok {
@ -285,12 +285,12 @@ func (m *LinkMap) Removes(keys []interface{}) {
}
// Keys returns all keys of the map as a slice in ascending order.
func (m *LinkMap) Keys() []interface{} {
func (m *ListMap) Keys() []interface{} {
m.mu.RLock()
keys := make([]interface{}, m.list.Len())
index := 0
m.list.IteratorAsc(func(e *glist.Element) bool {
keys[index] = e.Value.(*gLinkMapNode).key
keys[index] = e.Value.(*gListMapNode).key
index++
return true
})
@ -299,12 +299,12 @@ func (m *LinkMap) Keys() []interface{} {
}
// Values returns all values of the map as a slice.
func (m *LinkMap) Values() []interface{} {
func (m *ListMap) Values() []interface{} {
m.mu.RLock()
values := make([]interface{}, m.list.Len())
index := 0
m.list.IteratorAsc(func(e *glist.Element) bool {
values[index] = e.Value.(*gLinkMapNode).value
values[index] = e.Value.(*gListMapNode).value
index++
return true
})
@ -314,7 +314,7 @@ func (m *LinkMap) Values() []interface{} {
// Contains checks whether a key exists.
// It returns true if the <key> exists, or else false.
func (m *LinkMap) Contains(key interface{}) (ok bool) {
func (m *ListMap) Contains(key interface{}) (ok bool) {
m.mu.RLock()
_, ok = m.data[key]
m.mu.RUnlock()
@ -322,7 +322,7 @@ func (m *LinkMap) Contains(key interface{}) (ok bool) {
}
// Size returns the size of the map.
func (m *LinkMap) Size() (size int) {
func (m *ListMap) Size() (size int) {
m.mu.RLock()
size = len(m.data)
m.mu.RUnlock()
@ -331,12 +331,12 @@ func (m *LinkMap) Size() (size int) {
// IsEmpty checks whether the map is empty.
// It returns true if map is empty, or else false.
func (m *LinkMap) IsEmpty() bool {
func (m *ListMap) IsEmpty() bool {
return m.Size() == 0
}
// Flip exchanges key-value of the map to value-key.
func (m *LinkMap) Flip() {
func (m *ListMap) Flip() {
data := m.Map()
m.Clear()
for key, value := range data {
@ -346,20 +346,20 @@ func (m *LinkMap) Flip() {
// Merge merges two link maps.
// The <other> map will be merged into the map <m>.
func (m *LinkMap) Merge(other *LinkMap) {
func (m *ListMap) Merge(other *ListMap) {
m.mu.Lock()
defer m.mu.Unlock()
if other != m {
other.mu.RLock()
defer other.mu.RUnlock()
}
node := (*gLinkMapNode)(nil)
node := (*gListMapNode)(nil)
other.list.IteratorAsc(func(e *glist.Element) bool {
node = e.Value.(*gLinkMapNode)
node = e.Value.(*gListMapNode)
if e, ok := m.data[node.key]; !ok {
m.data[node.key] = m.list.PushBack(&gLinkMapNode{node.key, node.value})
m.data[node.key] = m.list.PushBack(&gListMapNode{node.key, node.value})
} else {
e.Value = &gLinkMapNode{node.key, node.value}
e.Value = &gListMapNode{node.key, node.value}
}
return true
})

View File

@ -0,0 +1,55 @@
// 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 gm file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package gmap_test
import (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/util/gutil"
"testing"
)
var hashMap = gmap.New()
var listMap = gmap.NewListMap()
var treeMap = gmap.NewTreeMap(gutil.ComparatorInt)
func Benchmark_HashMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
hashMap.Set(i, i)
}
}
func Benchmark_ListMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
listMap.Set(i, i)
}
}
func Benchmark_TreeMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {
treeMap.Set(i, i)
}
}
func Benchmark_HashMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
hashMap.Get(i)
}
}
func Benchmark_ListMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
listMap.Get(i)
}
}
func Benchmark_TreeMap_Get(b *testing.B) {
for i := 0; i < b.N; i++ {
treeMap.Get(i)
}
}

View File

@ -6,21 +6,21 @@
// go test *.go -bench=".*" -benchmem
package gmap
package gmap_test
import (
"testing"
"github.com/gogf/gf/g/container/gmap"
"testing"
"strconv"
)
var iim = NewIntIntMap()
var iifm = NewIntAnyMap()
var ism = NewIntStrMap()
var ififm = New()
var sim = NewStrIntMap()
var sifm = NewStrAnyMap()
var ssm = NewStrStrMap()
var ififm = gmap.New()
var iim = gmap.NewIntIntMap()
var iifm = gmap.NewIntAnyMap()
var ism = gmap.NewIntStrMap()
var sim = gmap.NewStrIntMap()
var sifm = gmap.NewStrAnyMap()
var ssm = gmap.NewStrStrMap()
func Benchmark_IntIntMap_Set(b *testing.B) {
for i := 0; i < b.N; i++ {

View File

@ -6,21 +6,21 @@
// go test *.go -bench=".*" -benchmem
package gmap
package gmap_test
import (
"testing"
"github.com/gogf/gf/g/container/gmap"
"testing"
"strconv"
)
var iimUnsafe = NewIntIntMap(true)
var iifmUnsafe = NewIntAnyMap(true)
var ismUnsafe = NewIntStrMap(true)
var ififmUnsafe = New(true)
var simUnsafe = NewStrIntMap(true)
var sifmUnsafe = NewStrAnyMap(true)
var ssmUnsafe = NewStrStrMap(true)
var ififmUnsafe = gmap.New(true)
var iimUnsafe = gmap.NewIntIntMap(true)
var iifmUnsafe = gmap.NewIntAnyMap(true)
var ismUnsafe = gmap.NewIntStrMap(true)
var simUnsafe = gmap.NewStrIntMap(true)
var sifmUnsafe = gmap.NewStrAnyMap(true)
var ssmUnsafe = gmap.NewStrStrMap(true)
// 写入性能测试

View File

@ -13,9 +13,9 @@ import (
"testing"
)
func Test_Link_Map_Basic(t *testing.T) {
func Test_List_Map_Basic(t *testing.T) {
gtest.Case(t, func() {
m := gmap.NewLinkMap()
m := gmap.NewListMap()
m.Set("key1", "val1")
gtest.Assert(m.Keys(), []interface{}{"key1"})
@ -43,12 +43,12 @@ func Test_Link_Map_Basic(t *testing.T) {
gtest.Assert(m.Size(), 0)
gtest.Assert(m.IsEmpty(), true)
m2 := gmap.NewLinkMapFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
m2 := gmap.NewListMapFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
gtest.Assert(m2.Map(), map[interface{}]interface{}{1: 1, "key1": "val1"})
})
}
func Test_Link_Map_Set_Fun(t *testing.T) {
m := gmap.NewLinkMap()
func Test_List_Map_Set_Fun(t *testing.T) {
m := gmap.NewListMap()
m.GetOrSetFunc("fun", getValue)
m.GetOrSetFuncLock("funlock", getValue)
gtest.Assert(m.Get("funlock"), 3)
@ -58,17 +58,17 @@ func Test_Link_Map_Set_Fun(t *testing.T) {
gtest.Assert(m.SetIfNotExistFuncLock("funlock", getValue), false)
}
func Test_Link_Map_Batch(t *testing.T) {
m := gmap.NewLinkMap()
func Test_List_Map_Batch(t *testing.T) {
m := gmap.NewListMap()
m.Sets(map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
gtest.Assert(m.Map(), map[interface{}]interface{}{1: 1, "key1": "val1", "key2": "val2", "key3": "val3"})
m.Removes([]interface{}{"key1", 1})
gtest.Assert(m.Map(), map[interface{}]interface{}{"key2": "val2", "key3": "val3"})
}
func Test_Link_Map_Iterator(t *testing.T){
func Test_List_Map_Iterator(t *testing.T){
expect :=map[interface{}]interface{}{1: 1, "key1": "val1"}
m := gmap.NewLinkMapFrom(expect)
m := gmap.NewListMapFrom(expect)
m.Iterator(func(k interface{}, v interface{}) bool {
gtest.Assert(expect[k], v)
return true
@ -88,9 +88,9 @@ func Test_Link_Map_Iterator(t *testing.T){
gtest.Assert(j, 1)
}
func Test_Link_Map_Clone(t *testing.T) {
func Test_List_Map_Clone(t *testing.T) {
//clone 方法是深克隆
m := gmap.NewLinkMapFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
m := gmap.NewListMapFrom(map[interface{}]interface{}{1: 1, "key1": "val1"})
m_clone := m.Clone()
m.Remove(1)
//修改原 map,clone 后的 map 不影响
@ -101,17 +101,17 @@ func Test_Link_Map_Clone(t *testing.T) {
gtest.AssertIN("key1", m.Keys())
}
func Test_Link_Map_Basic_Merge(t *testing.T) {
m1 := gmap.NewLinkMap()
m2 := gmap.NewLinkMap()
func Test_List_Map_Basic_Merge(t *testing.T) {
m1 := gmap.NewListMap()
m2 := gmap.NewListMap()
m1.Set("key1", "val1")
m2.Set("key2", "val2")
m1.Merge(m2)
gtest.Assert(m1.Map(), map[interface{}]interface{}{"key1": "val1", "key2": "val2"})
}
func Test_Link_Map_Order(t *testing.T) {
m := gmap.NewLinkMap()
func Test_List_Map_Order(t *testing.T) {
m := gmap.NewListMap()
m.Set("k1", "v1")
m.Set("k2", "v2")
m.Set("k3", "v3")

View File

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

View File

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

View File

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

View File

@ -388,10 +388,6 @@ func (tree *AVLTree) Print() {
fmt.Println(tree.String())
}
func (node *AVLTreeNode) String() string {
return fmt.Sprintf("%v", node.Key)
}
// Map returns all key-value items as map.
func (tree *AVLTree) Map() map[interface{}]interface{} {
m := make(map[interface{}]interface{}, tree.Size())
@ -692,7 +688,7 @@ func output(node *AVLTreeNode, prefix string, isTail bool, str *string) {
} else {
*str += "┌── "
}
*str += node.String() + "\n"
*str += fmt.Sprintf("%v\n", node.Key)
if node.children[0] != nil {
newPrefix := prefix
if isTail {

View File

@ -371,10 +371,6 @@ func (tree *BTree) Print() {
fmt.Println(tree.String())
}
func (entry *BTreeEntry) String() string {
return fmt.Sprintf("%v", entry.Key)
}
// Iterator is alias of IteratorAsc.
func (tree *BTree) Iterator(f func (key, value interface{}) bool) {
tree.IteratorAsc(f)

View File

@ -546,10 +546,6 @@ func (tree *RedBlackTree) Print() {
fmt.Println(tree.String())
}
func (node *RedBlackTreeNode) String() string {
return fmt.Sprintf("%v", node.Key)
}
// Search searches the tree with given <key>.
// Second return parameter <found> is true if key was found, otherwise false.
func (tree *RedBlackTree) Search(key interface{}) (value interface{}, found bool) {
@ -600,7 +596,7 @@ func (tree *RedBlackTree) output(node *RedBlackTreeNode, prefix string, isTail b
} else {
*str += "┌── "
}
*str += node.String() + "\n"
*str += fmt.Sprintf("%v\n", node.Key)
if node.left != nil {
newPrefix := prefix
if isTail {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -84,6 +84,7 @@ type DB interface {
SetDebug(debug bool)
SetSchema(schema string)
GetQueriedSqls() []*Sql
GetLastSql() *Sql
PrintQueriedSqls()
SetMaxIdleConns(n int)
SetMaxOpenConns(n int)

View File

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

View File

@ -7,18 +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{}) {
// 条件字符串处理
@ -35,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 ")
}
@ -136,6 +142,19 @@ func convertParam(value interface{}) interface{} {
}
switch kind {
case reflect.Struct:
// 底层数据库引擎支持 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
@ -160,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
}
@ -182,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)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"
)
// 基本测试

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

View File

@ -7,14 +7,17 @@
// Package gredis provides convenient client for redis server.
//
// Redis Client.
//
// Redis Commands Official: https://redis.io/commands
//
// Redis Chinese Documentation: http://redisdoc.com/
package gredis
import (
"fmt"
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/third/github.com/gomodule/redigo/redis"
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/third/github.com/gomodule/redigo/redis"
"time"
)
@ -31,7 +34,9 @@ type Redis struct {
}
// Redis connection.
type Conn redis.Conn
type Conn struct {
redis.Conn
}
// Redis configuration.
type Config struct {
@ -134,16 +139,17 @@ func (r *Redis) Close() error {
return r.pool.Close()
}
// Alias of GetConn, see GetConn.
func (r *Redis) Conn() Conn {
return r.GetConn()
}
// GetConn returns a raw underlying connection object,
// Conn returns a raw underlying connection object,
// which expose more methods to communicate with server.
// **You should call Close function manually if you do not use this connection any further.**
func (r *Redis) GetConn() Conn {
return r.pool.Get().(Conn)
func (r *Redis) Conn() *Conn {
return &Conn{ r.pool.Get() }
}
// Alias of Conn, see Conn.
func (r *Redis) GetConn() *Conn {
return r.Conn()
}
// SetMaxIdle sets the MaxIdle attribute of the connection pool.
@ -175,15 +181,21 @@ func (r *Redis) Stats() *PoolStats {
// Do automatically get a connection from pool, and close it when reply received.
// It does not really "close" the connection, but drop it back to the connection pool.
func (r *Redis) Do(command string, args ...interface{}) (interface{}, error) {
conn := r.pool.Get()
conn := &Conn{ r.pool.Get() }
defer conn.Close()
return conn.Do(command, args...)
}
// DoVar returns value from Do as gvar.Var.
func (r *Redis) DoVar(command string, args ...interface{}) (*gvar.Var, error) {
v, err := r.Do(command, args...)
return gvar.New(v, true), err
}
// Deprecated.
// Send writes the command to the client's output buffer.
func (r *Redis) Send(command string, args ...interface{}) error {
conn := r.pool.Get()
conn := &Conn{ r.pool.Get() }
defer conn.Close()
return conn.Send(command, args...)
}

View File

@ -0,0 +1,21 @@
// 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 gredis
import "github.com/gogf/gf/g/container/gvar"
// DoVar returns value from Do as gvar.Var.
func (c *Conn) DoVar(command string, args ...interface{}) (*gvar.Var, error) {
v, err := c.Do(command, args...)
return gvar.New(v, true), err
}
// ReceiveVar receives a single reply as gvar.Var from the Redis server.
func (c *Conn) ReceiveVar() (*gvar.Var, error) {
v, err := c.Receive()
return gvar.New(v, true), err
}

View File

@ -71,7 +71,7 @@ func Test_Stats(t *testing.T) {
redis.SetIdleTimeout(500*time.Millisecond)
redis.SetMaxConnLifetime(500*time.Millisecond)
array := make([]gredis.Conn, 0)
array := make([]*gredis.Conn, 0)
for i := 0; i < 10; i++ {
array = append(array, redis.Conn())
}

View File

@ -64,29 +64,44 @@ func (j *Json) GetMap(pattern string, def...interface{}) map[string]interface{}
}
// GetJson gets the value by specified <pattern>,
// and converts it to a Json object.
// and converts it to a un-concurrent-safe Json object.
func (j *Json) GetJson(pattern string, def...interface{}) *Json {
result := j.Get(pattern, def...)
if result != nil {
return New(result)
return New(result, true)
}
return nil
}
// GetJsons gets the value by specified <pattern>,
// and converts it to a slice of Json object.
// and converts it to a slice of un-concurrent-safe Json object.
func (j *Json) GetJsons(pattern string, def...interface{}) []*Json {
array := j.GetArray(pattern, def...)
if len(array) > 0 {
jsons := make([]*Json, len(array))
jsonSlice := make([]*Json, len(array))
for i := 0; i < len(array); i++ {
jsons[i] = New(array[i], !j.mu.IsSafe())
jsonSlice[i] = New(array[i], true)
}
return jsons
return jsonSlice
}
return nil
}
// GetJsonMap gets the value by specified <pattern>,
// and converts it to a map of un-concurrent-safe Json object.
func (j *Json) GetJsonMap(pattern string, def...interface{}) map[string]*Json {
m := j.GetMap(pattern, def...)
if len(m) > 0 {
jsonMap := make(map[string]*Json, len(m))
for k, v := range m {
jsonMap[k] = New(v, true)
}
return jsonMap
}
return nil
}
// GetArray gets the value by specified <pattern>,
// and converts it to a slice of []interface{}.
func (j *Json) GetArray(pattern string, def...interface{}) []interface{} {

View File

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

View File

@ -264,3 +264,39 @@ func TestJson_ToJson(t *testing.T) {
})
}
func TestJson_Default(t *testing.T) {
gtest.Case(t, func() {
j := gjson.New(nil)
gtest.AssertEQ(j.Get("no", 100), 100)
gtest.AssertEQ(j.GetString("no", 100), "100")
gtest.AssertEQ(j.GetBool("no", "on"), true)
gtest.AssertEQ(j.GetInt("no", 100), 100)
gtest.AssertEQ(j.GetInt8("no", 100), int8(100))
gtest.AssertEQ(j.GetInt16("no", 100), int16(100))
gtest.AssertEQ(j.GetInt32("no", 100), int32(100))
gtest.AssertEQ(j.GetInt64("no", 100), int64(100))
gtest.AssertEQ(j.GetUint("no", 100), uint(100))
gtest.AssertEQ(j.GetUint8("no", 100), uint8(100))
gtest.AssertEQ(j.GetUint16("no", 100), uint16(100))
gtest.AssertEQ(j.GetUint32("no", 100), uint32(100))
gtest.AssertEQ(j.GetUint64("no", 100), uint64(100))
gtest.AssertEQ(j.GetFloat32("no", 123.456), float32(123.456))
gtest.AssertEQ(j.GetFloat64("no", 123.456), float64(123.456))
gtest.AssertEQ(j.GetArray("no", g.Slice{1,2,3}), g.Slice{1,2,3})
gtest.AssertEQ(j.GetInts("no", g.Slice{1,2,3}), g.SliceInt{1,2,3})
gtest.AssertEQ(j.GetFloats("no", g.Slice{1,2,3}), []float64{1,2,3})
gtest.AssertEQ(j.GetMap("no", g.Map{"k":"v"}), g.Map{"k":"v"})
gtest.AssertEQ(j.GetVar("no", 123.456).Float64(), float64(123.456))
gtest.AssertEQ(j.GetJson("no", g.Map{"k":"v"}).Get("k"), "v")
gtest.AssertEQ(j.GetJsons("no", g.Slice{
g.Map{"k1":"v1"},
g.Map{"k2":"v2"},
g.Map{"k3":"v3"},
})[0].Get("k1"), "v1")
gtest.AssertEQ(j.GetJsonMap("no", g.Map{
"m1" : g.Map{"k1":"v1"},
"m2" : g.Map{"k2":"v2"},
})["m2"].Get("k2"), "v2")
})
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -46,6 +46,7 @@ type ServerConfig struct {
IdleTimeout time.Duration // 等待超时
MaxHeaderBytes int // 最大的header长度
TLSConfig tls.Config
KeepAlive bool
// 静态文件配置
IndexFiles []string // 默认访问的文件列表
@ -76,7 +77,7 @@ type ServerConfig struct {
// 日志配置
LogPath string // 存放日志的目录路径(默认为空,表示不写文件)
LogHandler LogHandler // 自定义日志处理回调方法(默认为空)
LogStdPrint bool // 是否打印日志到终端(默认开启)
LogStdout bool // 是否打印日志到终端(默认开启)
ErrorLogEnabled bool // 是否开启error log(默认开启)
AccessLogEnabled bool // 是否开启access log(默认关闭)
@ -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,
@ -111,7 +113,7 @@ var defaultServerConfig = ServerConfig {
SessionMaxAge : gDEFAULT_SESSION_MAX_AGE,
SessionIdName : gDEFAULT_SESSION_ID_NAME,
LogStdPrint : true,
LogStdout : true,
ErrorLogEnabled : true,
AccessLogEnabled : false,
GzipContentTypes : defaultGzipContentTypes,
@ -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

View File

@ -28,12 +28,12 @@ func (s *Server)SetLogPath(path string) {
// 设置日志内容是否输出到终端,默认情况下只有错误日志才会自动输出到终端。
// 如果需要输出请求日志到终端默认情况下使用SetAccessLogEnabled方法开启请求日志特性即可。
func (s *Server)SetLogStdPrint(enabled bool) {
func (s *Server)SetLogStdout(enabled bool) {
if s.Status() == SERVER_STATUS_RUNNING {
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
return
}
s.config.LogStdPrint = enabled
s.config.LogStdout = enabled
}
// 设置是否开启access log日志功能

View File

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

View File

@ -32,7 +32,7 @@ func (s *Server) handleAccessLog(r *Request) {
)
content += fmt.Sprintf(` %.3f`, float64(r.LeaveTime - r.EnterTime)/1000)
content += fmt.Sprintf(`, %s, "%s", "%s"`, r.GetClientIp(), r.Referer(), r.UserAgent())
s.logger.Cat("access").Backtrace(false, 2).StdPrint(s.config.LogStdPrint).Println(content)
s.logger.Cat("access").Backtrace(false, 2).Stdout(s.config.LogStdout).Println(content)
}
// 处理服务错误信息主要是panichttp请求的status由access log进行管理
@ -60,5 +60,5 @@ func (s *Server) handleErrorLog(error interface{}, r *Request) {
content += fmt.Sprintf(` %.3f`, float64(gtime.Microsecond() - r.EnterTime)/1000)
}
content += fmt.Sprintf(`, %s, "%s", "%s"`, r.GetClientIp(), r.Referer(), r.UserAgent())
s.logger.Cat("error").Backtrace(true, 2).StdPrint(s.config.LogStdPrint).Error(content)
s.logger.Cat("error").Backtrace(true, 2).Stdout(s.config.LogStdout).Error(content)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,93 +0,0 @@
// 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.
// Package gscanner provides a port scanner for local intranet.
package gscanner
import (
"net"
"fmt"
"sync"
"time"
"errors"
"github.com/gogf/gf/g/net/gipv4"
)
type scanner struct {
timeout time.Duration
}
// 初始化一个扫描器
func New() *scanner {
return &scanner{
6*time.Second,
}
}
// 设置超时时间,注意这个时间是每一次扫描的超时时间,而不是总共的超时时间
func (s *scanner) SetTimeout(t time.Duration) *scanner {
s.timeout = t
return s
}
// 异步TCP扫描网段及端口如果扫描的端口是打开的那么将链接给定给回调函数进行调用
// 注意startIp和endIp需要是同一个网段否则会报错,并且回调函数不会执行
func (s *scanner) ScanIp(startIp string, endIp string, port int, callback func(net.Conn)) error {
if callback == nil {
return errors.New("callback function should not be nil")
}
var waitGroup sync.WaitGroup
startIplong := gipv4.Ip2long(startIp)
endIplong := gipv4.Ip2long(endIp)
result := endIplong - startIplong
if startIplong == 0 || endIplong == 0 {
return errors.New("invalid startip or endip: ipv4 string should be given")
}
if result < 0 || result > 255 {
return errors.New("invalid startip and endip: startip and endip should be in the same ip segment")
}
for i := startIplong; i <= endIplong; i++ {
waitGroup.Add(1)
go func(ip string) {
//fmt.Println("scanning:", ip)
// 这里必需设置超时时间
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), s.timeout)
if err == nil {
callback(conn)
conn.Close()
}
//fmt.Println("scanning:", ip, "done")
waitGroup.Done()
}(gipv4.Long2ip(i))
}
waitGroup.Wait()
return nil
}
// 扫描目标主机打开的端口列表
func (s *scanner) ScanPort(ip string, callback func(net.Conn)) error {
if callback == nil {
return errors.New("callback function should not be nil")
}
var waitGroup sync.WaitGroup
for i := 0; i <= 65536; i++ {
waitGroup.Add(1)
//fmt.Println("scanning:", i)
go func(port int) {
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, port), s.timeout)
if err == nil {
callback(conn)
conn.Close()
}
waitGroup.Done()
}(i)
}
waitGroup.Wait()
return nil
}

View File

@ -5,6 +5,10 @@
// You can obtain one at https://github.com/gogf/gf.
// Package gsmtp provides a SMTP client to access remote mail server.
//
// eg:
// s := smtp.New("smtp.exmail.qq.com:25", "notify@a.com", "password")
// glog.Println(s.SendMail("notify@a.com", "ulric@b.com;rain@c.com", "subject", "body, <font color=red>red</font>"))
package gsmtp
import (
@ -14,30 +18,31 @@ import (
"strings"
)
// 示例:
// s := smtp.New("smtp.exmail.qq.com:25", "notify@a.com", "password")
// glog.Println(s.SendMail("notify@a.com", "ulric@b.com;rain@c.com", "这是subject", "这是body,<font color=red>red</font>"))
type Smtp struct {
type SMTP struct {
Address string
Username string
Password string
}
func New(address, username, password string) *Smtp {
return &Smtp{
// New creates and returns a new SMTP object.
func New(address, username, password string) *SMTP {
return &SMTP{
Address: address,
Username: username,
Password: password,
}
}
func (this *Smtp) SendMail(from, tos, subject, body string, contentType ...string) error {
if this.Address == "" {
// SendMail connects to the server at addr, switches to TLS if
// possible, authenticates with the optional mechanism a if possible,
// and then sends an email from address from, to addresses to, with
// message msg.
func (s *SMTP) SendMail(from, tos, subject, body string, contentType ...string) error {
if s.Address == "" {
return fmt.Errorf("address is necessary")
}
hp := strings.Split(this.Address, ":")
hp := strings.Split(s.Address, ":")
if len(hp) != 2 {
return fmt.Errorf("address format error")
}
@ -56,14 +61,13 @@ func (this *Smtp) SendMail(from, tos, subject, body string, contentType ...strin
return fmt.Errorf("tos invalid")
}
tos = strings.Join(safeArr, ";")
tos = strings.Join(safeArr, ";")
b64 := base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")
header := make(map[string]string)
header["From"] = from
header["To"] = tos
header["Subject"] = fmt.Sprintf("=?UTF-8?B?%s?=", b64.EncodeToString([]byte(subject)))
header := make(map[string]string)
header["From"] = from
header["To"] = tos
header["Subject"] = fmt.Sprintf("=?UTF-8?B?%s?=", b64.EncodeToString([]byte(subject)))
header["MIME-Version"] = "1.0"
ct := "text/plain; charset=UTF-8"
@ -71,7 +75,7 @@ func (this *Smtp) SendMail(from, tos, subject, body string, contentType ...strin
ct = "text/html; charset=UTF-8"
}
header["Content-Type"] = ct
header["Content-Type"] = ct
header["Content-Transfer-Encoding"] = "base64"
message := ""
@ -80,6 +84,6 @@ func (this *Smtp) SendMail(from, tos, subject, body string, contentType ...strin
}
message += "\r\n" + b64.EncodeToString([]byte(body))
auth := smtp.PlainAuth("", this.Username, this.Password, hp[0])
return smtp.SendMail(this.Address, auth, from, strings.Split(tos, ";"), []byte(message))
auth := smtp.PlainAuth("", s.Username, s.Password, hp[0])
return smtp.SendMail(s.Address, auth, from, strings.Split(tos, ";"), []byte(message))
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@ import (
"io"
)
// 封装的链接对象
// 封装的UDP链接对象
type Conn struct {
conn *net.UDPConn // 底层链接对象
raddr *net.UDPAddr // 远程地址
@ -24,7 +24,7 @@ type Conn struct {
const (
gDEFAULT_RETRY_INTERVAL = 100 // (毫秒)默认重试时间间隔
gDEFAULT_READ_BUFFER_SIZE = 1024 // 默认数据读取缓冲区大小
gDEFAULT_READ_BUFFER_SIZE = 64 // (KB)默认数据读取缓冲区大小
gRECV_ALL_WAIT_TIMEOUT = time.Millisecond // 读取全部缓冲数据时,没有缓冲数据时的等待间隔
)
@ -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,15 +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协议数据.
//
// 注意事项:
// 1、UDP协议存在消息边界因此使用 length < 0 可以获取缓冲区所有消息包数据,即一个完整包;
// 2、当length = 0时表示获取当前的缓冲区数据获取一次后立即返回
func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) {
var err error // 读取错误
var size int // 读取长度
@ -104,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 {
@ -138,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
}
@ -156,6 +158,10 @@ func (c *Conn) Recv(length int, retry...Retry) ([]byte, error) {
}
break
}
// 只获取一次数据
if length == 0 {
break
}
}
return buffer[:index], err
}
@ -171,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...)
}

View File

@ -1,115 +0,0 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gudp
import (
"encoding/binary"
"errors"
"fmt"
"time"
)
const (
// 允许最大的简单协议包大小(byte), 15MB
PKG_MAX_SIZE = 0xFFFFFF
// 消息包头大小: "总长度"3字节+"校验码"4字节
PKG_HEADER_SIZE = 7
)
// 根据简单协议发送数据包。
// 简单协议数据格式:总长度(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))
}
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...)
}
// 简单协议: 带超时时间的数据发送
func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, retry...Retry) error {
c.SetSendDeadline(time.Now().Add(timeout))
defer c.SetSendDeadline(time.Time{})
return c.SendPkg(data, retry...)
}
// 简单协议: 发送数据并等待接收返回数据
func (c *Conn) SendRecvPkg(data []byte, retry...Retry) ([]byte, error) {
if err := c.SendPkg(data, retry...); err == nil {
return c.RecvPkg(retry...)
} 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...)
} else {
return nil, err
}
}
// 简单协议: 获取一个数据包。
func (c *Conn) RecvPkg(retry...Retry) (result []byte, err error) {
var temp []byte
var length uint32
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 uint32(len(c.buffer)) < length {
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: ]
return
} else {
break
}
}
// 读取系统socket缓冲区的完整数据
temp, err = c.Recv(-1, retry...)
if err != nil {
break
}
if len(temp) > 0 {
c.buffer = append(c.buffer, temp...)
}
//fmt.Println("RecvPkg:", c.buffer)
}
return
}
// 简单协议: 带超时时间的消息包获取
func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, retry...Retry) ([]byte, error) {
c.SetRecvDeadline(time.Now().Add(timeout))
defer c.SetRecvDeadline(time.Time{})
return c.RecvPkg(retry...)
}

View File

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

View File

@ -1,49 +0,0 @@
// 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.
package gudp
import "time"
// 简单协议: (面向短链接)发送消息包
func SendPkg(addr string, data []byte, retry...Retry) error {
conn, err := NewConn(addr)
if err != nil {
return err
}
defer conn.Close()
return conn.SendPkg(data, retry...)
}
// 简单协议: (面向短链接)发送数据并等待接收返回数据
func SendRecvPkg(addr string, data []byte, retry...Retry) ([]byte, error) {
conn, err := NewConn(addr)
if err != nil {
return nil, err
}
defer conn.Close()
return conn.SendRecvPkg(data, retry...)
}
// 简单协议: (面向短链接)带超时时间的数据发送
func SendPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) error {
conn, err := NewConn(addr)
if err != nil {
return err
}
defer conn.Close()
return conn.SendPkgWithTimeout(data, timeout, retry...)
}
// 简单协议: (面向短链接)发送数据并等待接收返回数据(带返回超时等待时间)
func SendRecvPkgWithTimeout(addr string, data []byte, timeout time.Duration, retry...Retry) ([]byte, error) {
conn, err := NewConn(addr)
if err != nil {
return nil, err
}
defer conn.Close()
return conn.SendRecvPkgWithTimeout(data, timeout, retry...)
}

View File

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

View File

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

View File

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

View File

@ -5,8 +5,6 @@
// You can obtain one at https://github.com/gogf/gf.
// Package gfile provides easy-to-use operations for file system.
//
// 文件管理.
package gfile
import (
@ -15,6 +13,7 @@ import (
"fmt"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
"io"
"os"
@ -28,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 {
@ -53,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) {
@ -64,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 {
@ -89,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 {
@ -100,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
@ -110,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 {
@ -121,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 {
@ -140,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 {
@ -195,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 {
@ -219,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 {
@ -236,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)
@ -256,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
@ -283,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 {
@ -304,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] {
@ -328,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)
@ -338,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 {
@ -353,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 {
@ -436,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 != "" {
@ -452,23 +417,37 @@ func MainPkgPath() string {
}
for i := 1; i < 10000; i++ {
if _, file, _, ok := runtime.Caller(i); ok {
if gregex.IsMatchString(`package\s+main`, GetContents(file)) {
path = Dir(file)
mainPkgPath.Set(path)
return path
// <file> is separated by '/'
if gstr.Contains(file, "/gf/g/") {
continue
}
if Ext(file) != ".go" {
continue
}
// 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)) {
mainPkgPath.Set(path)
return path
}
}
path = Dir(path)
}
} else {
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()
}

View File

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

View File

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

View File

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

View File

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

View File

@ -653,8 +653,8 @@ func TestMkdir(t *testing.T) {
func TestStat(t *testing.T) {
gtest.Case(t, func() {
var (
tpath1 string = "/testfile_t1.txt"
tpath2 string = "./testfile_t1_no.txt"
tpath1 = "/testfile_t1.txt"
tpath2 = "./testfile_t1_no.txt"
err error
fileiofo os.FileInfo
)
@ -675,12 +675,7 @@ func TestStat(t *testing.T) {
func TestMainPkgPath(t *testing.T) {
gtest.Case(t, func() {
var (
reads string
)
reads = gfile.MainPkgPath()
reads := gfile.MainPkgPath()
gtest.Assert(reads, "")
})
}

View File

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

View File

@ -10,9 +10,9 @@
package glog
import (
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/cmdenv"
"io"
"github.com/gogf/gf/g/internal/cmdenv"
"github.com/gogf/gf/g/os/grpool"
"io"
)
const (
@ -28,11 +28,10 @@ const (
)
var (
// Default level for log
defaultLevel = gtype.NewInt(LEVEL_ALL)
// Default logger object, for package method usage
logger = New()
// Goroutine pool for async logging output.
asyncPool = grpool.New(1)
)
func init() {
@ -44,6 +43,12 @@ func SetPath(path string) {
logger.SetPath(path)
}
// GetPath returns the logging directory path for file logging.
// It returns empty string if no directory path set.
func GetPath() string {
return logger.GetPath()
}
// SetFile sets the file name <pattern> for file logging.
// Datetime pattern can be used in <pattern>, eg: access-{Ymd}.log.
// The default file name pattern is: Y-m-d.log, eg: 2018-01-01.log
@ -54,7 +59,11 @@ func SetFile(pattern string) {
// SetLevel sets the default logging level.
func SetLevel(level int) {
logger.SetLevel(level)
defaultLevel.Set(level)
}
// GetLevel returns the default logging level value.
func GetLevel() int {
return logger.GetLevel()
}
// SetWriter sets the customized logging <writer> for logging.
@ -71,26 +80,41 @@ func GetWriter() io.Writer {
return logger.GetWriter()
}
// GetLevel returns the default logging level value.
func GetLevel() int {
return defaultLevel.Val()
}
// SetDebug enables/disables the debug level for default logger.
// The debug level is enbaled in default.
func SetDebug(debug bool) {
logger.SetDebug(debug)
}
// SetStdPrint sets whether ouptput the logging contents to stdout, which is false in default.
func SetStdPrint(open bool) {
logger.SetStdPrint(open)
// SetAsync enables/disables async logging output feature for default logger.
func SetAsync(enabled bool) {
logger.SetAsync(enabled)
}
// GetPath returns the logging directory path for file logging.
// It returns empty string if no directory path set.
func GetPath() string {
return logger.GetPath()
// SetStdoutPrint sets whether ouptput the logging contents to stdout, which is false in default.
func SetStdoutPrint(enabled bool) {
logger.SetStdoutPrint(enabled)
}
// SetHeaderPrint sets whether output header of the logging contents, which is true in default.
func SetHeaderPrint(enabled bool) {
logger.SetHeaderPrint(enabled)
}
// SetPrefix sets prefix string for every logging content.
// Prefix is part of header, which means if header output is shut, no prefix will be output.
func SetPrefix(prefix string) {
logger.SetPrefix(prefix)
}
// SetFlags sets extra flags for logging output features.
func SetFlags(flags int) {
logger.SetFlags(flags)
}
// GetFlags returns the flags of logger.
func GetFlags() int {
return logger.GetFlags()
}
// PrintBacktrace prints the caller backtrace,
@ -109,166 +133,3 @@ func GetBacktrace(skip...int) string {
func SetBacktrace(enabled bool) {
logger.SetBacktrace(enabled)
}
// To is a chaining function,
// which redirects current logging content output to the sepecified <writer>.
func To(writer io.Writer) *Logger {
return logger.To(writer)
}
// Path is a chaining function,
// which sets the directory path to <path> for current logging content output.
func Path(path string) *Logger {
return logger.Path(path)
}
// Cat is a chaining function,
// which sets the category to <category> for current logging content output.
func Cat(category string) *Logger {
return logger.Cat(category)
}
// File is a chaining function,
// which sets file name <pattern> for the current logging content output.
func File(pattern string) *Logger {
return logger.File(pattern)
}
// Level is a chaining function,
// which sets logging level for the current logging content output.
func Level(level int) *Logger {
return logger.Level(level)
}
// Backtrace is a chaining function,
// which sets backtrace options for the current logging content output .
func Backtrace(enabled bool, skip...int) *Logger {
return logger.Backtrace(enabled, skip...)
}
// StdPrint is a chaining function,
// which enables/disables stdout for the current logging content output.
func StdPrint(enabled bool) *Logger {
return logger.StdPrint(enabled)
}
// Header is a chaining function,
// which enables/disables log header for the current logging content output.
func Header(enabled bool) *Logger {
return logger.Header(enabled)
}
func Print(v ...interface{}) {
logger.Print(v ...)
}
func Printf(format string, v ...interface{}) {
logger.Printf(format, v ...)
}
func Println(v ...interface{}) {
logger.Println(v ...)
}
func Printfln(format string, v ...interface{}) {
logger.Printfln(format, v ...)
}
// Fatal prints the logging content with [FATA] header and newline, then exit the current process.
func Fatal(v ...interface{}) {
logger.Fatal(v ...)
}
// Fatalf prints the logging content with [FATA] header and custom format, 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.
func Fatalfln(format string, v ...interface{}) {
logger.Fatalfln(format, v ...)
}
func Panic(v ...interface{}) {
logger.Panic(v ...)
}
func Panicf(format string, v ...interface{}) {
logger.Panicf(format, v ...)
}
func Panicfln(format string, v ...interface{}) {
logger.Panicfln(format, v ...)
}
func Info(v ...interface{}) {
logger.Info(v...)
}
func Debug(v ...interface{}) {
logger.Debug(v...)
}
func Notice(v ...interface{}) {
logger.Notice(v...)
}
func Warning(v ...interface{}) {
logger.Warning(v...)
}
func Error(v ...interface{}) {
logger.Error(v...)
}
func Critical(v ...interface{}) {
logger.Critical(v...)
}
func Infof(format string, v ...interface{}) {
logger.Infof(format, v...)
}
func Debugf(format string, v ...interface{}) {
logger.Debugf(format, v...)
}
func Noticef(format string, v ...interface{}) {
logger.Noticef(format, v...)
}
func Warningf(format string, v ...interface{}) {
logger.Warningf(format, v...)
}
func Errorf(format string, v ...interface{}) {
logger.Errorf(format, v...)
}
func Criticalf(format string, v ...interface{}) {
logger.Criticalf(format, v...)
}
func Infofln(format string, v ...interface{}) {
logger.Infofln(format, v...)
}
func Debugfln(format string, v ...interface{}) {
logger.Debugfln(format, v...)
}
func Noticefln(format string, v ...interface{}) {
logger.Noticefln(format, v...)
}
func Warningfln(format string, v ...interface{}) {
logger.Warningfln(format, v...)
}
func Errorfln(format string, v ...interface{}) {
logger.Errorfln(format, v...)
}
func Criticalfln(format string, v ...interface{}) {
logger.Criticalfln(format, v...)
}

166
g/os/glog/glog_api.go Normal file
View File

@ -0,0 +1,166 @@
// 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.
package glog
// Print prints <v> with newline using fmt.Sprintln.
// The param <v> can be multiple variables.
func Print(v ...interface{}) {
logger.Print(v ...)
}
// Printf prints <v> with format <format> using fmt.Sprintf.
// The param <v> can be multiple variables.
func Printf(format string, v ...interface{}) {
logger.Printf(format, v ...)
}
// See Print.
func Println(v ...interface{}) {
logger.Println(v ...)
}
// Deprecated.
// Use Printf instead.
func Printfln(format string, v ...interface{}) {
logger.Printfln(format, v ...)
}
// Fatal prints the logging content with [FATA] header and newline, then exit the current process.
func Fatal(v ...interface{}) {
logger.Fatal(v ...)
}
// 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 ...)
}
// Deprecated.
// Use Fatalf instead.
func Fatalfln(format string, v ...interface{}) {
logger.Fatalfln(format, v ...)
}
// Panic prints the logging content with [PANI] header and newline, then panics.
func Panic(v ...interface{}) {
logger.Panic(v ...)
}
// Panicf prints the logging content with [PANI] header, custom format and newline, then panics.
func Panicf(format string, v ...interface{}) {
logger.Panicf(format, v ...)
}
// Deprecated.
// Use Panicf instead.
func Panicfln(format string, v ...interface{}) {
logger.Panicfln(format, v ...)
}
// Info prints the logging content with [INFO] header and newline.
func Info(v ...interface{}) {
logger.Info(v...)
}
// Infof prints the logging content with [INFO] header, custom format and newline.
func Infof(format string, v ...interface{}) {
logger.Infof(format, v...)
}
// Deprecated.
// Use Infof instead.
func Infofln(format string, v ...interface{}) {
logger.Infofln(format, v...)
}
// Debug prints the logging content with [DEBU] header and newline.
func Debug(v ...interface{}) {
logger.Debug(v...)
}
// Debugf prints the logging content with [DEBU] header, custom format and newline.
func Debugf(format string, v ...interface{}) {
logger.Debugf(format, v...)
}
// Deprecated.
// Use Debugf instead.
func Debugfln(format string, v ...interface{}) {
logger.Debugfln(format, v...)
}
// Notice prints the logging content with [NOTI] header and newline.
// It also prints caller backtrace info if backtrace feature is enabled.
func Notice(v ...interface{}) {
logger.Notice(v...)
}
// 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...)
}
// Deprecated.
// Use Noticef instead.
func Noticefln(format string, v ...interface{}) {
logger.Noticefln(format, v...)
}
// Warning prints the logging content with [WARN] header and newline.
// It also prints caller backtrace info if backtrace feature is enabled.
func Warning(v ...interface{}) {
logger.Warning(v...)
}
// 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...)
}
// Deprecated.
// Use Warningf instead.
func Warningfln(format string, v ...interface{}) {
logger.Warningfln(format, v...)
}
// Error prints the logging content with [ERRO] header and newline.
// It also prints caller backtrace info if backtrace feature is enabled.
func Error(v ...interface{}) {
logger.Error(v...)
}
// 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...)
}
// Deprecated.
// Use Errorf instead.
func Errorfln(format string, v ...interface{}) {
logger.Errorfln(format, v...)
}
// Critical prints the logging content with [CRIT] header and newline.
// It also prints caller backtrace info if backtrace feature is enabled.
func Critical(v ...interface{}) {
logger.Critical(v...)
}
// 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...)
}
// Deprecated.
// Use Criticalf instead.
func Criticalfln(format string, v ...interface{}) {
logger.Criticalfln(format, v...)
}

View File

@ -0,0 +1,81 @@
// 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.
package glog
import (
"io"
)
// To is a chaining function,
// which redirects current logging content output to the sepecified <writer>.
func To(writer io.Writer) *Logger {
return logger.To(writer)
}
// Path is a chaining function,
// which sets the directory path to <path> for current logging content output.
func Path(path string) *Logger {
return logger.Path(path)
}
// Cat is a chaining function,
// which sets the category to <category> for current logging content output.
func Cat(category string) *Logger {
return logger.Cat(category)
}
// File is a chaining function,
// which sets file name <pattern> for the current logging content output.
func File(pattern string) *Logger {
return logger.File(pattern)
}
// Level is a chaining function,
// which sets logging level for the current logging content output.
func Level(level int) *Logger {
return logger.Level(level)
}
// Skip is a chaining function,
// which sets backtrace skip for the current logging content output.
// It also affects the caller file path checks when line number printing enabled.
func Skip(skip int) *Logger {
return logger.Skip(skip)
}
// Backtrace is a chaining function,
// which sets backtrace options for the current logging content output .
func Backtrace(enabled bool, skip...int) *Logger {
return logger.Backtrace(enabled, skip...)
}
// StdPrint is a chaining function,
// which enables/disables stdout for the current logging content output.
// It's enabled in default.
func Stdout(enabled...bool) *Logger {
return logger.Stdout(enabled...)
}
// Header is a chaining function,
// which enables/disables log header for the current logging content output.
// It's enabled in default.
func Header(enabled...bool) *Logger {
return logger.Header(enabled...)
}
// Line is a chaining function,
// which enables/disables printing its caller file along with its line number.
// The param <long> specified whether print the long absolute file path, eg: /a/b/c/d.go:23.
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...)
}

View File

@ -3,39 +3,37 @@
// 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
import (
"errors"
"fmt"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gfpool"
"github.com/gogf/gf/g/os/gmlock"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/text/gregex"
"io"
"os"
"runtime"
"strings"
"sync"
"time"
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/g/os/gfile"
"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"
"strings"
"time"
)
type Logger struct {
mu sync.RWMutex
pr *Logger // Parent logger.
writer io.Writer // Customized io.Writer.
path *gtype.String // Logging directory path.
file *gtype.String // Format for logging file.
level *gtype.Int // Output level.
btSkip *gtype.Int // Skip count for backtrace.
btStatus *gtype.Int // Backtrace status(1: enabled - default; 0: disabled)
printHeader *gtype.Bool // Print header or not(true in default).
alsoStdPrint *gtype.Bool // Output to stdout or not(true in default).
parent *Logger // Parent logger.
writer io.Writer // Customized io.Writer.
flags int // Extra flags for logging output features.
path string // Logging directory path.
file string // Format for logging file.
level int // Output level.
prefix string // Prefix string for every logging content.
btSkip int // Skip count for backtrace.
btStatus int // Backtrace status(1: enabled - default; 0: disabled)
headerPrint bool // Print header or not(true in default).
stdoutPrint bool // Output to stdout or not(true in default).
}
const (
@ -45,11 +43,19 @@ const (
gDEFAULT_FPOOL_EXPIRE = 60000
)
const (
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.
F_TIME_MILLI // Print the time with milliseconds in the local time zone: 01:23:23.675.
F_TIME_STD = F_TIME_DATE | F_TIME_MILLI
)
var (
// Default line break.
ln = "\n"
// Mutex to ensure log output sequence.
stdMu = sync.RWMutex{}
ln = "\n"
)
func init() {
@ -62,70 +68,75 @@ func init() {
// New creates and returns a custom logger.
func New() *Logger {
logger := &Logger {
path : gtype.NewString(),
file : gtype.NewString(gDEFAULT_FILE_FORMAT),
level : gtype.NewInt(defaultLevel.Val()),
btSkip : gtype.NewInt(),
btStatus : gtype.NewInt(1),
printHeader : gtype.NewBool(true),
alsoStdPrint : gtype.NewBool(true),
}
logger.writer = &Writer {
logger : logger,
file : gDEFAULT_FILE_FORMAT,
flags : F_TIME_STD,
level : LEVEL_ALL,
btStatus : 1,
headerPrint : true,
stdoutPrint : true,
}
return logger
}
// Clone returns a new logger, which is the clone the current logger.
func (l *Logger) Clone() *Logger {
logger := &Logger {
pr : l,
path : l.path.Clone(),
file : l.file.Clone(),
level : l.level.Clone(),
btSkip : l.btSkip.Clone(),
btStatus : l.btStatus.Clone(),
printHeader : l.printHeader.Clone(),
alsoStdPrint : l.alsoStdPrint.Clone(),
}
logger.writer = &Writer {
logger : logger,
}
return logger
logger := Logger{}
logger = *l
logger.parent = l
return &logger
}
// SetLevel sets the logging level.
func (l *Logger) SetLevel(level int) {
l.level.Set(level)
l.level = level
}
// GetLevel returns the logging level value.
func (l *Logger) GetLevel() int {
return l.level.Val()
return l.level
}
// SetDebug enables/disables the debug level for logger.
// The debug level is enabled in default.
func (l *Logger) SetDebug(debug bool) {
if debug {
l.level.Set(l.level.Val() | LEVEL_DEBU)
l.level = l.level | LEVEL_DEBU
} else {
l.level.Set(l.level.Val() & ^LEVEL_DEBU)
l.level = l.level & ^LEVEL_DEBU
}
}
// 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
}
// GetFlags returns the flags of logger.
func (l *Logger) GetFlags() int {
return l.flags
}
// SetBacktrace enables/disables the backtrace feature in failure logging outputs.
func (l *Logger) SetBacktrace(enabled bool) {
if enabled {
l.btStatus.Set(1)
l.btStatus = 1
} else {
l.btStatus.Set(0)
l.btStatus = 0
}
}
// SetBacktraceSkip sets the backtrace offset from the end point.
func (l *Logger) SetBacktraceSkip(skip int) {
l.btSkip.Set(skip)
l.btSkip = skip
}
// SetWriter sets the customized logging <writer> for logging.
@ -133,26 +144,21 @@ func (l *Logger) SetBacktraceSkip(skip int) {
// Developer can use customized logging <writer> to redirect logging output to another service,
// eg: kafka, mysql, mongodb, etc.
func (l *Logger) SetWriter(writer io.Writer) {
l.mu.Lock()
l.writer = writer
l.mu.Unlock()
}
// GetWriter returns the customized writer object, which implements the io.Writer interface.
// It returns a default writer if no customized writer set.
// It returns nil if no writer previously set.
func (l *Logger) GetWriter() io.Writer {
l.mu.RLock()
r := l.writer
l.mu.RUnlock()
return r
return l.writer
}
// getFilePointer returns the file pinter for file logging.
// It returns nil if file logging is disabled, or file opening fails.
func (l *Logger) getFilePointer() *gfpool.File {
if path := l.path.Val(); path != "" {
if path := l.path; path != "" {
// Content containing "{}" in the file name is formatted using gtime
file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.file.Val(), func(s string) string {
file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.file, func(s string) string {
return gtime.Now().Format(strings.Trim(s, "{}"))
})
// Create path if it does not exist。
@ -162,8 +168,11 @@ func (l *Logger) getFilePointer() *gfpool.File {
return nil
}
}
fpath := path + gfile.Separator + file
if fp, err := gfpool.Open(fpath, gDEFAULT_FILE_POOL_FLAGS, gDEFAULT_FPOOL_PERM, gDEFAULT_FPOOL_EXPIRE); err == nil {
if fp, err := gfpool.Open(
path + gfile.Separator + file,
gDEFAULT_FILE_POOL_FLAGS,
gDEFAULT_FPOOL_PERM,
gDEFAULT_FPOOL_EXPIRE); err == nil {
return fp
} else {
fmt.Fprintln(os.Stderr, err)
@ -183,101 +192,147 @@ func (l *Logger) SetPath(path string) error {
return err
}
}
l.path.Set(strings.TrimRight(path, gfile.Separator))
l.path = strings.TrimRight(path, gfile.Separator)
return nil
}
// GetPath returns the logging directory path for file logging.
// It returns empty string if no directory path set.
func (l *Logger) GetPath() string {
return l.path.Val()
return l.path
}
// SetFile sets the file name <pattern> for file logging.
// Datetime pattern can be used in <pattern>, eg: access-{Ymd}.log.
// The default file name pattern is: Y-m-d.log, eg: 2018-01-01.log
func (l *Logger) SetFile(pattern string) {
l.file.Set(pattern)
l.file = pattern
}
// SetStdPrint sets whether output the logging contents to stdout, which is false in default.
func (l *Logger) SetStdPrint(enabled bool) {
l.alsoStdPrint.Set(enabled)
// SetStdoutPrint sets whether output the logging contents to stdout, which is true in default.
func (l *Logger) SetStdoutPrint(enabled bool) {
l.stdoutPrint = enabled
}
// SetHeaderPrint sets whether output header of the logging contents, which is true in default.
func (l *Logger) SetHeaderPrint(enabled bool) {
l.headerPrint = enabled
}
// SetPrefix sets prefix string for every logging content.
// Prefix is part of header, which means if header output is shut, no prefix will be output.
func (l *Logger) SetPrefix(prefix string) {
l.prefix = prefix
}
// print prints <s> to defined writer, logging file or passed <std>.
// It internally uses memory lock for file logging to ensure logging sequence.
func (l *Logger) print(std io.Writer, s string) {
// Customized writer has the most high priority.
if l.printHeader.Val() {
s = l.format(s)
}
writer := l.GetWriter()
if _, ok := writer.(*Writer); ok {
if f := l.getFilePointer(); f != nil {
defer f.Close()
key := l.path.Val()
gmlock.Lock(key)
_, err := io.WriteString(f, s)
gmlock.Unlock(key)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
}
}
// Also output to stdout?
if l.alsoStdPrint.Val() {
l.doStdLockPrint(std, s)
}
} else {
l.doStdLockPrint(writer, s)
func (l *Logger) print(std io.Writer, lead string, value...interface{}) {
buffer := bytes.NewBuffer(nil)
if l.headerPrint {
// 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)
}
}
// doStdLockPrint prints <s> to <std> concurrent-safely.
func (l *Logger) doStdLockPrint(std io.Writer, s string) {
stdMu.Lock()
if _, err := std.Write([]byte(s)); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
}
stdMu.Unlock()
// 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())
}
}
}
// stdPrint prints content <s> without backtrace.
func (l *Logger) stdPrint(s string) {
l.print(os.Stdout, s)
// printStd prints content <s> without backtrace.
func (l *Logger) printStd(lead string, value...interface{}) {
l.print(os.Stdout, lead, value...)
}
// stdPrint prints content <s> with backtrace check.
func (l *Logger) errPrint(s string) {
if l.btStatus.Val() == 1 {
s = l.appendBacktrace(s)
// printStd prints content <s> with backtrace check.
func (l *Logger) printErr(lead string, value...interface{}) {
if l.btStatus == 1 {
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,
@ -288,10 +343,9 @@ func (l *Logger) GetBacktrace(skip...int) string {
customSkip = skip[0]
}
backtrace := ""
index := 1
from := 0
// 首先定位业务文件开始位置
for i := 0; i < 10; i++ {
// Find the caller position exclusive of the glog file.
for i := 0; i < 1000; i++ {
if _, file, _, ok := runtime.Caller(i); ok {
if !gregex.IsMatchString("/g/os/glog/glog.+$", file) {
from = i
@ -299,11 +353,11 @@ func (l *Logger) GetBacktrace(skip...int) string {
}
}
}
// 从业务文件开始位置根据自定义的skip开始backtrace
// Find the true caller file path using custom skip.
index := 1
goRoot := runtime.GOROOT()
for i := from + customSkip + l.btSkip.Val(); i < 10000; i++ {
if _, file, cline, ok := runtime.Caller(i); ok && file != "" {
// 不打印出go源码路径及glog包文件路径日志打印必须从业务源码文件开始且从glog包文件开始检索
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++
@ -315,171 +369,28 @@ func (l *Logger) GetBacktrace(skip...int) string {
return backtrace
}
func (l *Logger) format(s string) string {
return time.Now().Format("2006-01-02 15:04:05.000 ") + s
// getLongFile returns the absolute file path along with its line number of the caller.
func (l *Logger) getLongFile() string {
from := 0
// Find the caller position exclusive of the glog file.
for i := 0; i < 1000; i++ {
if _, file, _, ok := runtime.Caller(i); ok {
if !gregex.IsMatchString("/g/os/glog/glog.+$", file) {
from = i
break
}
}
}
// Find the true caller file path using custom skip.
goRoot := runtime.GOROOT()
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)
}
} else {
break
}
}
return ""
}
func (l *Logger) Print(v ...interface{}) {
l.stdPrint(fmt.Sprintln(v...))
}
func (l *Logger) Printf(format string, v ...interface{}) {
l.stdPrint(fmt.Sprintf(format, v...))
}
func (l *Logger) Println(v ...interface{}) {
l.stdPrint(fmt.Sprintln(v...))
}
func (l *Logger) Printfln(format string, v ...interface{}) {
l.stdPrint(fmt.Sprintf(format + ln, v...))
}
// Fatal prints the logging content with [FATA] header and newline, then exit the current process.
func (l *Logger) Fatal(v ...interface{}) {
l.errPrint("[FATA] " + fmt.Sprintln(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.errPrint("[FATA] " + fmt.Sprintf(format, v...))
os.Exit(1)
}
// Fatalf 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.errPrint("[FATA] " + fmt.Sprintf(format + ln, v...))
os.Exit(1)
}
func (l *Logger) Panic(v ...interface{}) {
s := fmt.Sprintln(v...)
l.errPrint("[PANI] " + s)
panic(s)
}
func (l *Logger) Panicf(format string, v ...interface{}) {
s := fmt.Sprintf(format, v...)
l.errPrint("[PANI] " + s)
panic(s)
}
func (l *Logger) Panicfln(format string, v ...interface{}) {
s := fmt.Sprintf(format + ln, v...)
l.errPrint("[PANI] " + s)
panic(s)
}
func (l *Logger) Info(v ...interface{}) {
if l.checkLevel(LEVEL_INFO) {
l.stdPrint("[INFO] " + fmt.Sprintln(v...))
}
}
func (l *Logger) Infof(format string, v ...interface{}) {
if l.checkLevel(LEVEL_INFO) {
l.stdPrint("[INFO] " + fmt.Sprintf(format, v...))
}
}
func (l *Logger) Infofln(format string, v ...interface{}) {
if l.checkLevel(LEVEL_INFO) {
l.stdPrint("[INFO] " + fmt.Sprintf(format, v...) + ln)
}
}
func (l *Logger) Debug(v ...interface{}) {
if l.checkLevel(LEVEL_DEBU) {
l.stdPrint("[DEBU] " + fmt.Sprintln(v...))
}
}
func (l *Logger) Debugf(format string, v ...interface{}) {
if l.checkLevel(LEVEL_DEBU) {
l.stdPrint("[DEBU] " + fmt.Sprintf(format, v...))
}
}
func (l *Logger) Debugfln(format string, v ...interface{}) {
if l.checkLevel(LEVEL_DEBU) {
l.stdPrint("[DEBU] " + fmt.Sprintf(format, v...) + ln)
}
}
func (l *Logger) Notice(v ...interface{}) {
if l.checkLevel(LEVEL_NOTI) {
l.errPrint("[NOTI] " + fmt.Sprintln(v...))
}
}
func (l *Logger) Noticef(format string, v ...interface{}) {
if l.checkLevel(LEVEL_NOTI) {
l.errPrint("[NOTI] " + fmt.Sprintf(format, v...))
}
}
func (l *Logger) Noticefln(format string, v ...interface{}) {
if l.checkLevel(LEVEL_NOTI) {
l.errPrint("[NOTI] " + fmt.Sprintf(format, v...) + ln)
}
}
func (l *Logger) Warning(v ...interface{}) {
if l.checkLevel(LEVEL_WARN) {
l.errPrint("[WARN] " + fmt.Sprintln(v...))
}
}
func (l *Logger) Warningf(format string, v ...interface{}) {
if l.checkLevel(LEVEL_WARN) {
l.errPrint("[WARN] " + fmt.Sprintf(format, v...))
}
}
func (l *Logger) Warningfln(format string, v ...interface{}) {
if l.checkLevel(LEVEL_WARN) {
l.errPrint("[WARN] " + fmt.Sprintf(format, v...) + ln)
}
}
func (l *Logger) Error(v ...interface{}) {
if l.checkLevel(LEVEL_ERRO) {
l.errPrint("[ERRO] " + fmt.Sprintln(v...))
}
}
func (l *Logger) Errorf(format string, v ...interface{}) {
if l.checkLevel(LEVEL_ERRO) {
l.errPrint("[ERRO] " + fmt.Sprintf(format, v...))
}
}
func (l *Logger) Errorfln(format string, v ...interface{}) {
if l.checkLevel(LEVEL_ERRO) {
l.errPrint("[ERRO] " + fmt.Sprintf(format, v...) + ln)
}
}
func (l *Logger) Critical(v ...interface{}) {
if l.checkLevel(LEVEL_CRIT) {
l.errPrint("[CRIT] " + fmt.Sprintln(v...))
}
}
func (l *Logger) Criticalf(format string, v ...interface{}) {
if l.checkLevel(LEVEL_CRIT) {
l.errPrint("[CRIT] " + fmt.Sprintf(format, v...))
}
}
func (l *Logger) Criticalfln(format string, v ...interface{}) {
if l.checkLevel(LEVEL_CRIT) {
l.errPrint("[CRIT] " + fmt.Sprintf(format, v...) + ln)
}
}
// checkLevel checks whether the given <level> could be output.
func (l *Logger) checkLevel(level int) bool {
return l.level.Val() & level > 0
}

View File

@ -0,0 +1,217 @@
// 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.
package glog
import (
"fmt"
"os"
)
// Print prints <v> with newline using fmt.Sprintln.
// The param <v> can be multiple variables.
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(l.format(format, v...))
}
// See Print.
func (l *Logger) Println(v...interface{}) {
l.Print(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]", v...)
os.Exit(1)
}
// 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)
}
// 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{}) {
l.printErr("[PANI]", v...)
panic(fmt.Sprint(v...))
}
// 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...))
}
// 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{}) {
if l.checkLevel(LEVEL_INFO) {
l.printStd("[INFO]", v...)
}
}
// 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]", l.format(format, v...))
}
}
// Deprecated.
// Use Infof instead.
func (l *Logger) Infofln(format string, v...interface{}) {
if l.checkLevel(LEVEL_INFO) {
l.Infof(format, v...)
}
}
// Debug prints the logging content with [DEBU] header and newline.
func (l *Logger) Debug(v...interface{}) {
if l.checkLevel(LEVEL_DEBU) {
l.printStd("[DEBU]", v...)
}
}
// 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]", l.format(format, v...))
}
}
// Deprecated.
// Use Debugf instead.
func (l *Logger) Debugfln(format string, v...interface{}) {
if l.checkLevel(LEVEL_DEBU) {
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{}) {
if l.checkLevel(LEVEL_NOTI) {
l.printErr("[NOTI]", v...)
}
}
// 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{}) {
if l.checkLevel(LEVEL_NOTI) {
l.printErr("[NOTI]", l.format(format, v...))
}
}
// Deprecated.
// Use Noticef instead.
func (l *Logger) Noticefln(format string, v...interface{}) {
if l.checkLevel(LEVEL_NOTI) {
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{}) {
if l.checkLevel(LEVEL_WARN) {
l.printErr("[WARN]", v...)
}
}
// 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{}) {
if l.checkLevel(LEVEL_WARN) {
l.printErr("[WARN]", l.format(format, v...))
}
}
// Deprecated.
// Use Warningf instead.
func (l *Logger) Warningfln(format string, v...interface{}) {
if l.checkLevel(LEVEL_WARN) {
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{}) {
if l.checkLevel(LEVEL_ERRO) {
l.printErr("[ERRO]", v...)
}
}
// 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{}) {
if l.checkLevel(LEVEL_ERRO) {
l.printErr("[ERRO]", l.format(format, v...))
}
}
// Deprecated.
// Use Errorf instead.
func (l *Logger) Errorfln(format string, v...interface{}) {
if l.checkLevel(LEVEL_ERRO) {
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{}) {
if l.checkLevel(LEVEL_CRIT) {
l.printErr("[CRIT]", v...)
}
}
// 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{}) {
if l.checkLevel(LEVEL_CRIT) {
l.printErr("[CRIT]", l.format(format, v...))
}
}
// Deprecated.
// Use Criticalf instead.
func (l *Logger) Criticalfln(format string, v...interface{}) {
if l.checkLevel(LEVEL_CRIT) {
l.Criticalf(format, v...)
}
}
// checkLevel checks whether the given <level> could be output.
func (l *Logger) checkLevel(level int) bool {
return l.level & level > 0
}

View File

@ -15,7 +15,7 @@ import (
// which redirects current logging content output to the specified <writer>.
func (l *Logger) To(writer io.Writer) *Logger {
logger := (*Logger)(nil)
if l.pr == nil {
if l.parent == nil {
logger = l.Clone()
} else {
logger = l
@ -28,7 +28,7 @@ func (l *Logger) To(writer io.Writer) *Logger {
// which sets the directory path to <path> for current logging content output.
func (l *Logger) Path(path string) *Logger {
logger := (*Logger)(nil)
if l.pr == nil {
if l.parent == nil {
logger = l.Clone()
} else {
logger = l
@ -44,14 +44,13 @@ func (l *Logger) Path(path string) *Logger {
// Param <category> can be hierarchical, eg: module/user.
func (l *Logger) Cat(category string) *Logger {
logger := (*Logger)(nil)
if l.pr == nil {
if l.parent == nil {
logger = l.Clone()
} else {
logger = l
}
path := l.path.Val()
if path != "" {
logger.SetPath(path + gfile.Separator + category)
if logger.path != "" {
logger.SetPath(logger.path + gfile.Separator + category)
}
return logger
}
@ -60,7 +59,7 @@ func (l *Logger) Cat(category string) *Logger {
// which sets file name <pattern> for the current logging content output.
func (l *Logger) File(file string) *Logger {
logger := (*Logger)(nil)
if l.pr == nil {
if l.parent == nil {
logger = l.Clone()
} else {
logger = l
@ -73,7 +72,7 @@ func (l *Logger) File(file string) *Logger {
// which sets logging level for the current logging content output.
func (l *Logger) Level(level int) *Logger {
logger := (*Logger)(nil)
if l.pr == nil {
if l.parent == nil {
logger = l.Clone()
} else {
logger = l
@ -82,11 +81,25 @@ func (l *Logger) Level(level int) *Logger {
return logger
}
// Skip is a chaining function,
// which sets backtrace skip for the current logging content output.
// It also affects the caller file path checks when line number printing enabled.
func (l *Logger) Skip(skip int) *Logger {
logger := (*Logger)(nil)
if l.parent == nil {
logger = l.Clone()
} else {
logger = l
}
logger.SetBacktraceSkip(skip)
return logger
}
// Backtrace is a chaining function,
// which sets backtrace options for the current logging content output .
func (l *Logger) Backtrace(enabled bool, skip...int) *Logger {
logger := (*Logger)(nil)
if l.pr == nil {
if l.parent == nil {
logger = l.Clone()
} else {
logger = l
@ -98,28 +111,83 @@ func (l *Logger) Backtrace(enabled bool, skip...int) *Logger {
return logger
}
// StdPrint is a chaining function,
// Stdout is a chaining function,
// which enables/disables stdout for the current logging content output.
func (l *Logger) StdPrint(enabled bool) *Logger {
logger := (*Logger)(nil)
if l.pr == nil {
logger = l.Clone()
} else {
logger = l
}
logger.SetStdPrint(enabled)
return logger
// It's enabled in default.
func (l *Logger) Stdout(enabled...bool) *Logger {
logger := (*Logger)(nil)
if l.parent == nil {
logger = l.Clone()
} else {
logger = l
}
// stdout printing is enabled if <enabled> is not passed.
if len(enabled) > 0 && !enabled[0] {
logger.stdoutPrint = false
} else {
logger.stdoutPrint = true
}
return logger
}
// See Stdout.
// Deprecated.
func (l *Logger) StdPrint(enabled...bool) *Logger {
return l.Stdout(enabled...)
}
// Header is a chaining function,
// which enables/disables log header for the current logging content output.
func (l *Logger) Header(enabled bool) *Logger {
// It's enabled in default.
func (l *Logger) Header(enabled...bool) *Logger {
logger := (*Logger)(nil)
if l.pr == nil {
if l.parent == nil {
logger = l.Clone()
} else {
logger = l
}
logger.printHeader.Set(enabled)
// header is enabled if <enabled> is not passed.
if len(enabled) > 0 && !enabled[0] {
logger.SetHeaderPrint(false)
} else {
logger.SetHeaderPrint(true)
}
return logger
}
// Line is a chaining function,
// which enables/disables printing its caller file path along with its line number.
// The param <long> specified whether print the long absolute file path, eg: /a/b/c/d.go:23,
// or else short one: d.go:23.
func (l *Logger) Line(long...bool) *Logger {
logger := (*Logger)(nil)
if l.parent == nil {
logger = l.Clone()
} else {
logger = l
}
if len(long) > 0 && long[0] {
logger.flags |= F_FILE_LONG
} else {
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
}

Some files were not shown because too many files have changed in this diff Show More