Compare commits

...

92 Commits

Author SHA1 Message Date
4a483c08fd VERSION up 2018-11-11 14:26:02 +08:00
2b1db2350b Merge branch 'develop' 2018-11-11 14:24:44 +08:00
6c49132d41 glog增加文件指针池使用 2018-11-11 14:24:04 +08:00
9186c209d9 glog增加文件指针池使用 2018-11-11 14:21:22 +08:00
92f46f7306 改进gfsnotify在windows系统下的监控 2018-11-11 13:59:40 +08:00
5903e82e40 Merge branch 'develop' of https://gitee.com/johng/gf into develop 2018-11-11 13:55:37 +08:00
7c1796bf8e 增加ghttp下载文件示例 2018-11-11 13:55:32 +08:00
2590c4385c 改进gfsnotify,支持编辑器对文件非标准编辑时(RENAME+CHMOD)的热更新问题 2018-11-11 11:26:03 +08:00
619c27cdf9 增加HTTP下载文件示例程序 2018-11-10 16:18:50 +08:00
9c66426cd0 修改gview调试信息 2018-11-10 12:35:27 +08:00
ceaf8314b9 修复在windows系统下的gfile.MainPkgPath方法失效问题 2018-11-10 12:31:19 +08:00
5602454af9 解决没有配置文件时的Config内置变量报错问题 2018-11-09 23:57:20 +08:00
5a68608693 改进gview内置变量在没有配置管理文件时的处理 2018-11-09 23:29:31 +08:00
1e60c35496 添加模板测试代码 2018-11-09 17:48:57 +08:00
02f6bd8a17 gvalid测试 2018-11-08 18:53:43 +08:00
df571adfc1 改进gfile文件内容操作为使用gfpool 2018-11-08 14:00:28 +08:00
4af6b653ad 改进gfpool,提升IO性能比标准库高100%以上 2018-11-08 13:38:12 +08:00
806bd63ba6 改进gtime.Format支持中文 2018-11-07 17:55:45 +08:00
52a182b7e0 third package updates 2018-11-07 15:38:54 +08:00
c66300fe11 ghttp.Request.Exit示例代码更新 2018-11-06 19:02:11 +08:00
c27aa61e02 改进ghttp.Request.Exit,使得调用该方法时立即退出业务执行,开发者无需调用Exit方法时再使用return返回;glog模块增加To链式操作方法,执行下一次写入的writer 2018-11-06 18:53:25 +08:00
7fa564a8f0 修复gtime时间解析问题 2018-11-06 17:42:01 +08:00
0e60e61d41 修复gdb中sqlite数据库识别键名问题 2018-11-06 15:09:47 +08:00
58ad87e4c3 增加g.Throw抛异常方法,g.TryCatch异常捕获方法封装 2018-11-06 13:53:06 +08:00
61d02f8a94 改进gconv.Int/Uint对float类型字符串的转换支持 2018-11-06 12:41:01 +08:00
08929c9830 merge master 2018-11-06 11:22:07 +08:00
81edda7508 注释完善 2018-11-06 11:03:59 +08:00
105e492b65 改进gtime对日期时间字符串的解析 2018-11-06 10:59:32 +08:00
80f32c7f18 Merge branch 'qiangg_gfsnotify_callback' 2018-11-06 10:08:17 +08:00
afbae75934 完善gfsnotify callback特性 2018-11-06 10:08:08 +08:00
89f9397703 Merge branch 'master' of https://gitee.com/johng/gf into qiangg_gfsnotify_callback 2018-11-06 09:50:56 +08:00
944bc3182f Merge branch 'qiangg_gfsnotify_callback' 2018-11-06 09:43:47 +08:00
f79c70d77d 修改gfsnotify默认的Watcher数量,并可以通过命令行参数或者环境变量进行修改 2018-11-06 09:42:09 +08:00
9a85c5eb53 改进gfsnotify默认实例数限制处理 2018-11-05 19:21:59 +08:00
bb309a5ed0 修复gins.Set问题 2018-11-05 18:57:35 +08:00
4b8aa1522c 初步完成对gfsnotify的改进,增加对特定回调的取消注册功能 2018-11-05 16:26:08 +08:00
b456d94ccc 改进grand随机数生成设计,底层使用crypto/rand+缓冲区实现高速的随机数生成;gtime模块增加FuncCost函数执行耗时计算 2018-11-05 13:53:17 +08:00
853c723202 TODO++ 2018-11-05 11:07:08 +08:00
6e1c541f57 TODO++ 2018-11-05 10:45:39 +08:00
bbf7ede777 默认的gfsnotify.Watcher对象只有在使用时才会创建 2018-11-05 10:39:18 +08:00
0b04e9430a 添加gfsnotify默认Watcher实例数为8 2018-11-05 10:34:28 +08:00
383026f356 添加gfsnotify默认Watcher实例数为8 2018-11-05 10:34:21 +08:00
44b804ec7d gcron增加DelayAdd方法 2018-11-05 10:29:58 +08:00
bba2aa9a63 Merge branch 'master' of https://gitee.com/johng/gf 2018-11-03 17:50:15 +08:00
72046a48db 改进gfsnotify模块,去掉与gfile模块的依赖;改进gfpool文件指针池,加上对gfsnotify模块的依赖,使用全局文件监控对象 2018-11-03 17:50:00 +08:00
1b11416521 merge master 2018-11-03 16:00:22 +08:00
846db9c5fd 增加模板对象变量示例程序 2018-11-03 15:59:24 +08:00
cf5929c641 临时去掉gfpool文件指针池的使用 2018-11-03 15:27:48 +08:00
1a99a395fc Merge branch 'master' of https://gitee.com/johng/gf into develop 2018-11-02 22:50:10 +08:00
ebcfb6e1ff gfpool改进中 2018-11-02 19:21:57 +08:00
198e7854e3 TODO++ 2018-11-02 13:41:25 +08:00
a9644abceb gconv增加对带' '参数键名到struct属性的转换,如:nick name => struct.NickName,并完善示例程序 2018-11-02 13:13:01 +08:00
b6f86de8f9 gconv增加对带'-'及'_'参数键名到struct属性的转换,如:nick_name/nick-name => struct.NickName 2018-11-02 12:59:45 +08:00
32e32993d3 修复gproc.ShellRun在windows下的执行问题 2018-11-01 18:18:45 +08:00
bf82381b9e 改进gfpool文件指针池,增加对文件mv/rm后的指针池重建;改进gtime,增加对时间字符串更多的默认时间格式支持,新增gtime.ParseTimeFromContent方法 2018-11-01 17:22:55 +08:00
6fba59be4b Merge branch 'master' of https://gitee.com/johng/gf into develop 2018-11-01 14:39:15 +08:00
4263465345 Merge branch 'master' of https://gitee.com/johng/gf into qiangg_gfpool20181031 2018-11-01 10:22:25 +08:00
77b8584809 增加oracle数据库支持 2018-11-01 09:58:46 +08:00
e4086ad9a1 Merge branch 'master' of https://gitee.com/johng/gf into develop 2018-11-01 09:53:56 +08:00
74dd93a934 修改配置管理、模板引擎、调试模式的环境变量名称为大写下划线标准格式 2018-11-01 09:46:35 +08:00
2c071579a6 Merge branch 'master' of https://gitee.com/johng/gf into develop 2018-11-01 09:06:14 +08:00
c7af9e5007 gform配置文件支持linkinfo字段 2018-11-01 09:00:00 +08:00
95b7a013e7 Merge branch 'master' of https://gitee.com/johng/gf into develop 2018-11-01 08:44:48 +08:00
7a6dbf159c README updates 2018-11-01 08:44:06 +08:00
35fa199c5a 调整服务注册时不符合接口定义的错误日志方式: glog.Warning->glog.Error 2018-11-01 08:37:23 +08:00
cdd3d507b8 服务注册时增加方法定义判断;模板引擎增加strlimit/hidestr/highlight/toupper/tolower/nl2br内置模板函数 2018-10-31 22:39:58 +08:00
6fcf17140d gfpool改进中 2018-10-31 19:00:28 +08:00
371ab48d7a 改进gtime.StrToTime对常用时间格式匹配模式 2018-10-31 16:57:13 +08:00
ee9362666e 还原glog.GetBacktrace文件打印开始位置 2018-10-31 16:23:31 +08:00
293cf81cf1 gconv增加基准测试代码 2018-10-31 09:57:37 +08:00
6e4ef6d0b5 改进gconv.Struct转换默认规则,支持不区分大小写的键名与属性名称匹配 2018-10-31 09:15:17 +08:00
4bbaf6db1b 改进gconv.Struct转换默认规则,支持不区分大小写的键名与属性名称匹配 2018-10-31 09:14:52 +08:00
ff126dfbaa 框架一些模块的细节改进 2018-10-30 23:58:10 +08:00
28813577e1 !10 orm新增对oracle的支持
Merge pull request !10 from 蚊子/master
2018-10-30 22:46:41 +08:00
21b6f169bb 增加注释 2018-10-30 22:41:53 +08:00
c653ffa7a5 完善garray接口定义 2018-10-30 18:52:47 +08:00
f4f57e7a91 TODO++ 2018-10-30 10:15:02 +08:00
827c66a702 去掉gview.HTML 2018-10-29 23:22:01 +08:00
7582a604af 模板引擎新增若干内置函数;gstr增加对中文截取方法 2018-10-29 22:58:30 +08:00
b109845d0e bug fix #INZS1 2018-10-29 21:05:15 +08:00
c4ff01702c bug fix #INZS1 2018-10-29 21:00:50 +08:00
2ffebf2500 改进gfile在开发环境下的main包绝对路径获取方式;ghttp.Request增加idFileServe属性 2018-10-29 13:59:06 +08:00
2b4bc53d71 gcfg增加获取配置变量为*gvar.Var; gfsnotify修复'假删除'处理逻辑 2018-10-27 16:05:36 +08:00
fe2bd315db gfile.ScanDir支持pattern多个文件模式匹配,使用','符号分隔多个匹配模式 2018-10-26 18:39:03 +08:00
b98560c894 修复路由变量匹配到'/'字符的问题 2018-10-26 11:39:17 +08:00
bdf714825e 添加测试文件 2018-10-26 11:02:15 +08:00
8c459d499a 去掉gspath模块调试语句 2018-10-25 22:18:36 +08:00
6597ca3ad1 gvar.Set方法返回旧的变量值 2018-10-25 12:10:05 +08:00
8baf0b5619 改进gtype.Set方法,增加Set原子操作返回旧的变量值;改进gcron,增加自定义的Cron管理对象,增加New/Start/Stop方法;TODO++ 2018-10-25 10:08:08 +08:00
9534334714 glog增加全局的BackTrace关闭功能 2018-10-24 17:38:07 +08:00
c4172fba9b 完善gfile注释 2018-10-24 14:51:32 +08:00
44575bdef3 version updates 2018-10-24 09:59:17 +08:00
135 changed files with 3445 additions and 1362 deletions

View File

@ -36,10 +36,10 @@ golang版本 >= 1.9.2
1. 提供了对基本数据类型的并发安全封装,提供了常用的数据结构容器;
1. 支持Go变量/Json/Xml/Yml/Toml任意数据格式之间的相互转换及创建
1. 强大的数据库ORM支持应用层级的集群管理、读写分离、负载均衡查询缓存、方法及链式ORM操作
1. 更多特点请查阅框架[手册](http://gf.johng.cn)和[源码](https://godoc.org/github.com/johng-cn/gf)
1. 更多特点请查阅框架[手册](https://gfer.me)和[源码](https://godoc.org/github.com/johng-cn/gf)
# 文档
GoFrame开发文档[http://gf.johng.cn](http://gf.johng.cn)
GoFrame开发文档[gfer.me](https://gfer.me)
# 使用
@ -178,4 +178,4 @@ if tx, err := db.Begin(); err == nil {
...
更多特性及示例请查看官方开发文档:[gf.johng.cn](http://gf.johng.cn)
更多特性及示例请查看官方开发文档:[gfer.me](https://gfer.me)

86
TODO
View File

@ -1,86 +0,0 @@
ON THE WAY:
orm增加更多数据库支持
增加对于数据表Model的封装
更多数据库的ORM功能支持
考虑gdb对象管理增加二级连接池特性提高New&Close性能
增加图形验证码支持,至少支持数字和英文字母;
增加热编译工具,提高开发环境的开发/测试效率媲美PHP开发效率
增加可选择性的orm tag特性用以数据表记录与struct对象转换的键名属性映射
ghttp.Response增加输出内容后自动退出当前请求机制不需要用户手动return参考beego如何实现
Cookie&Session数据池化处理
ghttp.Client增加proxy特性
gtime增加对时区转换的封装并简化失去转换时对类似+80500时区的支持
orm增加sqlite对Save方法的支持(去掉触发器语句);
ghttp.Server增加Ip访问控制功能(DenyIps&AllowIps)
ghttp路由功能增加分组路由特性
ghttp增加返回数据压缩机制
gview中的template标签失效问题
gfile文件stat信息使用gfsnotify进行缓存更新改进
ghttp.Server增加proxy功能特性本地proxy和远程proxy本地即将路由规则映射远程即反向代理
gjson对大json数据的解析效率问题
ghttp增加route name特性并同时支持backend和template(提供内置函数)引用可以通过RedirectRoute方法给定route name和路由参数跳转到指定的路由地址上
ghttp.Client自动Close机制
gvalid校验支持当第一个规则失败后便不再校验后续的规则最好做成链式操作
检查ghttp.Server超时问题
gvalid增加支持对[]rune的长度校验(一个中文占3个字节)
ghttp.Request增加对输入参数的自动HtmlEncode机制
常量命名风格根据golint进行修改
开放rwmutex包并将gjson的互斥锁使用自定义的mutex替换
文档完善:
gconv struct tag、
控制器及执行对象注册的Init&Shut方法、
ghttp.Response&ServeFile、gfcache、gproc shell执行、
ghttp Server&Client basic auth、
glog分类&日志等级&链式操作、gdb debug自动输出调试信息、gmlock内存锁、
服务注册域名增加对泛域名的支持;
服务注册时判断方法定义满足规范时才执行绑定否则提示WARN信息
Cookie设置中文失效问题
ghttp hook回调使用方式在注册路由比较多的时候优先级可能使得开发者混乱考虑方式便于管理
使用gconv将slice映射到struct属性上例如redis hscan的结果集
DONE:
1. gconv完善针对不同类型的判断例如尽量减少sprintf("%v", xxx)来执行string类型的转换
2. ghttp.Server请求执行中增加服务退出的方法不再执行后续操作
3. ghttp.Response对象完善并改进数据返回方法(Write/WriteString)
4. ghttp.Server请求执行中增加服务退出的方法不再执行后续操作
5. 增加fsnotify包支持
6. 改进gcfg和gview的文件自动更新机制
7. 将模板变量进行暴露,以便应用端可以进行灵活控制;
8. 跟踪第三方mxj包的issue问题https://github.com/clbanning/mxj/issues/48
9. gdb Where方法参数的改进研究是否可以将string参数类型修改为interface{}
10. gpage分页控制功能
11. https支持
12. ghttp.Server日志中增加请求时间和返回时间以便计算执行时间差
13. 由于去掉了gdb的单例模式并且将gins的部分对象封装迁移到了g包中需要同时梳理文档完善修改
14. 在代码中增加https与http同时开启使用的示例代码这块大家问得比较多
15. ghttp.Server多个事件之间通过ghttp.Request.Param自定义参数传参
16. 研究是否增加配置文件目录检索功能,特别是如何友好改进开发环境的配置文件默认目录问题;
17. 增加ghttp.Server不同状态码的自定义处理方法
18. ghttp.Server平滑重启方案
19. 完善gconv类型转换功能增加time.Time/time.Duration类型转换并增加benchmark测试脚本
20. 当二进制参数为nil时gjson.LoadContent并将gjson.Json对象ToMap时会报错
21. 改进控制器及执行对象注册,更友好地支持动态路由注册,例如:注册规则为 /channel/:name现有的控制器及执行对象注册很难友好支持这种动态形式
22. 当前gpage分页包的输出标签不支持li大多数CSS框架都是li+a标签模式需要提供可更加灵活的定制化功能实现
23. 平滑重启机制改进,以便于开发阶段调试;
24. 对grpool进行优化改进包括属性原子操作封装采用gtype实现修正设计BUGhttps://github.com/johng-cn/gf/issues/6
25. gredis增加redis密码支持
26. 改进ghttp.Server平滑重启机制当新进程接管服务后再使用进程间通信方式通知父进程销毁
27. gproc进程间通信增加分组特性不同的进程间可以通过进程ID以及分组名称发送/获取进程消息;
28. ORM增加获取被执行的sql语句的方法
29. gdb增加查询缓存特性
30. gpage分页增加对自定义后缀的支持如:2.html, 2.php等等
31. gvalid包增加struct tag的校验规则、自定义错误提示信息绑定的支持特性
32. 增加文件缓存包可根据fsnotify机制进行缓存更新
33. *any/:name路由匹配路由改进支持不带名字的*/:路由规则;
34. ghttp静态文件服务改进(特别是403返回状态的修改)
35. map转struct增加对tag的支持
36. gcache检查在i386下的int64->int转换问题
37. ghttp获取参数支持直接转struct功能
38. gfsnotify增加对于目录的监控
39. 检查windows下的平滑重启失效问题
40. ghttp.Server的Cookie及Session锁机制优化(去掉map锁机制);
41. 解决glog串日志情况
42. glog增加对日志文件名称的生成规则设定支持时间格式规则
43. ghttp日志增加客户端IP信息

100
TODO.MD Normal file
View File

@ -0,0 +1,100 @@
# ON THE WAY
1. orm增加更多数据库支持
1. 增加对于数据表Model的封装
1. 更多数据库的ORM功能支持
1. 考虑gdb对象管理增加二级连接池特性提高New&Close性能
1. 增加图形验证码支持,至少支持数字和英文字母;
1. 增加热编译工具,提高开发环境的开发/测试效率媲美PHP开发效率
1. 增加可选择性的orm tag特性用以数据表记录与struct对象转换的键名属性映射
1. ghttp.Response增加输出内容后自动退出当前请求机制不需要用户手动return参考beego如何实现
1. Cookie&Session数据池化处理
1. ghttp.Client增加proxy特性
1. gtime增加对时区转换的封装并简化失去转换时对类似+80500时区的支持
1. orm增加sqlite对Save方法的支持(去掉触发器语句);
1. ghttp.Server增加Ip访问控制功能(DenyIps&AllowIps)
1. ghttp路由功能增加分组路由特性
1. ghttp增加返回数据压缩机制
1. gview中的template标签失效问题
1. gfile文件stat信息使用gfsnotify进行缓存更新改进
1. ghttp.Server增加proxy功能特性本地proxy和远程proxy本地即将路由规则映射远程即反向代理
1. gjson对大json数据的解析效率问题
1. ghttp增加route name特性并同时支持backend和template(提供内置函数)引用可以通过RedirectRoute方法给定route name和路由参数跳转到指定的路由地址上
1. ghttp.Client自动Close机制
1. gvalid校验支持当第一个规则失败后便不再校验后续的规则最好做成链式操作
1. 检查ghttp.Server超时问题
1. gvalid增加支持对[]rune的长度校验(一个中文占3个字节)
1. ghttp.Request增加对输入参数的自动HtmlEncode机制
1. 常量命名风格根据golint进行修改
1. 开放rwmutex包并将gjson的互斥锁使用自定义的mutex替换
1. 文档完善:
- gconv struct tag、
- 控制器及执行对象注册的Init&Shut方法、
- ghttp.Response&ServeFile、gfcache、gproc shell执行、
- ghttp Server&Client basic auth、
- glog分类&日志等级&链式操作、gdb debug自动输出调试信息、gmlock内存锁、
1. 服务注册域名增加对泛域名的支持;
1. Cookie设置中文失效问题
1. ghttp hook回调使用方式在注册路由比较多的时候优先级可能使得开发者混乱考虑方式便于管理
1. 使用gconv将slice映射到struct属性上例如redis hscan的结果集
1. 项目参考:
- https://github.com/namreg/godown
- https://github.com/Masterminds/sprig
1. gform参考 https://gohouse.github.io/gorose/dist/index.html 进行改进
1. 模板引擎增加对对象的支持(参考https://segmentfault.com/q/1010000016829214)
1. 由于系统对inotify实例数量(`fs.inotify.max_user_instances`)以及队列大小(`fs.inotify.max_user_watches`)有限制,需要改进`gfsnotify`
1. 改进gfpool在文件指针变化时的更新
1. `gfsnotify`增加添加监听文件时的监听ID返回以便调用端删除监听时只删除自己添加的监听而不影响其他对该同一文件的监听回调
1. `gfsnotify`针对添加目录监听时无法使用多个`Watcher`,考虑改进,并考虑动态扩容全局`Watcher`方案;
# DONE
1. gconv完善针对不同类型的判断例如尽量减少sprintf("%v", xxx)来执行string类型的转换
2. ghttp.Server请求执行中增加服务退出的方法不再执行后续操作
3. ghttp.Response对象完善并改进数据返回方法(Write/WriteString)
4. ghttp.Server请求执行中增加服务退出的方法不再执行后续操作
5. 增加fsnotify包支持
6. 改进gcfg和gview的文件自动更新机制
7. 将模板变量进行暴露,以便应用端可以进行灵活控制;
8. 跟踪第三方mxj包的issue问题https://github.com/clbanning/mxj/issues/48
9. gdb Where方法参数的改进研究是否可以将string参数类型修改为interface{}
10. gpage分页控制功能
11. https支持
12. ghttp.Server日志中增加请求时间和返回时间以便计算执行时间差
13. 由于去掉了gdb的单例模式并且将gins的部分对象封装迁移到了g包中需要同时梳理文档完善修改
14. 在代码中增加https与http同时开启使用的示例代码这块大家问得比较多
15. ghttp.Server多个事件之间通过ghttp.Request.Param自定义参数传参
16. 研究是否增加配置文件目录检索功能,特别是如何友好改进开发环境的配置文件默认目录问题;
17. 增加ghttp.Server不同状态码的自定义处理方法
18. ghttp.Server平滑重启方案
19. 完善gconv类型转换功能增加time.Time/time.Duration类型转换并增加benchmark测试脚本
20. 当二进制参数为nil时gjson.LoadContent并将gjson.Json对象ToMap时会报错
21. 改进控制器及执行对象注册,更友好地支持动态路由注册,例如:注册规则为 /channel/:name现有的控制器及执行对象注册很难友好支持这种动态形式
22. 当前gpage分页包的输出标签不支持li大多数CSS框架都是li+a标签模式需要提供可更加灵活的定制化功能实现
23. 平滑重启机制改进,以便于开发阶段调试;
24. 对grpool进行优化改进包括属性原子操作封装采用gtype实现修正设计BUGhttps://github.com/johng-cn/gf/issues/6
25. gredis增加redis密码支持
26. 改进ghttp.Server平滑重启机制当新进程接管服务后再使用进程间通信方式通知父进程销毁
27. gproc进程间通信增加分组特性不同的进程间可以通过进程ID以及分组名称发送/获取进程消息;
28. ORM增加获取被执行的sql语句的方法
29. gdb增加查询缓存特性
30. gpage分页增加对自定义后缀的支持如:2.html, 2.php等等
31. gvalid包增加struct tag的校验规则、自定义错误提示信息绑定的支持特性
32. 增加文件缓存包可根据fsnotify机制进行缓存更新
33. *any/:name路由匹配路由改进支持不带名字的*/:路由规则;
34. ghttp静态文件服务改进(特别是403返回状态的修改)
35. map转struct增加对tag的支持
36. gcache检查在i386下的int64->int转换问题
37. ghttp获取参数支持直接转struct功能
38. gfsnotify增加对于目录的监控
39. 检查windows下的平滑重启失效问题
40. ghttp.Server的Cookie及Session锁机制优化(去掉map锁机制);
41. 解决glog串日志情况
42. glog增加对日志文件名称的生成规则设定支持时间格式规则
43. ghttp日志增加客户端IP信息
44. 完善gform配置管理说明g.DB/Database和gdb.New的区别
1. 完善配置管理章节,说明默认的配置文件更改方式;
1. 服务注册时判断方法定义满足规范时才执行绑定否则提示WARN信息

View File

@ -126,11 +126,11 @@ func (a *SortedIntArray) Slice() []int {
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
// 返回值: 最后比较位置, 比较结果
func (a *SortedIntArray) Search(value int) (int, int) {
func (a *SortedIntArray) Search(value int) (index int, result int) {
return a.binSearch(value, true)
}
func (a *SortedIntArray) binSearch(value int, lock bool) (int, int) {
func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int) {
if len(a.array) == 0 {
return -1, -2
}

View File

@ -117,13 +117,13 @@ func (a *SortedArray) Slice() []interface{} {
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
// 返回值: 最后比较位置, 比较结果
func (a *SortedArray) Search(value interface{}) (int, int) {
func (a *SortedArray) Search(value interface{}) (index int, result int) {
return a.binSearch(value, true)
}
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
// 返回值: 最后比较位置, 比较结果
func (a *SortedArray) binSearch(value interface{}, lock bool) (int, int) {
func (a *SortedArray) binSearch(value interface{}, lock bool)(index int, result int) {
if len(a.array) == 0 {
return -1, -2
}

View File

@ -120,11 +120,11 @@ func (a *SortedStringArray) Slice() []string {
// 查找指定数值的索引位置,返回索引位置(具体匹配位置或者最后对比位置)及查找结果
// 返回值: 最后比较位置, 比较结果
func (a *SortedStringArray) Search(value string) (int, int) {
func (a *SortedStringArray) Search(value string) (index int, result int) {
return a.binSearch(value, true)
}
func (a *SortedStringArray) binSearch(value string, lock bool) (int, int) {
func (a *SortedStringArray) binSearch(value string, lock bool) (index int, result int) {
if len(a.array) == 0 {
return -1, -2
}

View File

@ -65,6 +65,11 @@ func (p *Pool) Put(value interface{}) {
p.list.PushBack(item)
}
// 清空对象池
func (p *Pool) Clear() {
p.list.RemoveAll()
}
// 从池中获得一个临时对象
func (p *Pool) Get() (interface{}, error) {
for !p.closed.Val() {

View File

@ -30,12 +30,14 @@ func (t *Bool) Clone() *Bool {
return NewBool(t.Val())
}
func (t *Bool) Set(value bool) {
// 并发安全设置变量值,返回之前的旧值
func (t *Bool) Set(value bool) (old bool) {
if value {
atomic.StoreInt32(&t.val, 1)
old = atomic.SwapInt32(&t.val, 1) == 1
} else {
atomic.StoreInt32(&t.val, 0)
old = atomic.SwapInt32(&t.val, 0) == 1
}
return
}
func (t *Bool) Val() bool {

View File

@ -25,8 +25,9 @@ func (t *Byte) Clone() *Byte {
return NewByte(t.Val())
}
func (t *Byte) Set(value byte) {
atomic.StoreInt32(&t.val, int32(value))
// 并发安全设置变量值,返回之前的旧值
func (t *Byte) Set(value byte) (old byte) {
return byte(atomic.SwapInt32(&t.val, int32(value)))
}
func (t *Byte) Val() byte {

View File

@ -24,13 +24,14 @@ func (t *Bytes) Clone() *Bytes {
return NewBytes(t.Val())
}
func (t *Bytes) Set(value []byte) {
func (t *Bytes) Set(value []byte) (old []byte) {
old = t.Val()
t.val.Store(value)
return
}
func (t *Bytes) Val() []byte {
s := t.val.Load()
if s != nil {
if s := t.val.Load(); s != nil {
return s.([]byte)
}
return nil

View File

@ -26,8 +26,8 @@ func (t *Float32) Clone() *Float32 {
return NewFloat32(t.Val())
}
func (t *Float32) Set(value float32) {
atomic.StoreUint32(&t.val, float32ToUint32InBits(value) )
func (t *Float32) Set(value float32) (old float32) {
return uint32ToFloat32InBits(atomic.SwapUint32(&t.val, float32ToUint32InBits(value)))
}
func (t *Float32) Val() float32 {

View File

@ -26,8 +26,8 @@ func (t *Float64) Clone() *Float64 {
return NewFloat64(t.Val())
}
func (t *Float64) Set(value float64) {
atomic.StoreUint64(&t.val, float64ToUint64InBits(value) )
func (t *Float64) Set(value float64) (old float64) {
return uint64ToFloat64InBits(atomic.SwapUint64(&t.val, float64ToUint64InBits(value)))
}
func (t *Float64) Val() float64 {

View File

@ -25,8 +25,9 @@ func (t *Int) Clone() *Int {
return NewInt(t.Val())
}
func (t *Int) Set(value int) {
atomic.StoreInt64(&t.val, int64(value))
// 并发安全设置变量值,返回之前的旧值
func (t *Int) Set(value int) (old int) {
return int(atomic.SwapInt64(&t.val, int64(value)))
}
func (t *Int) Val() int {

View File

@ -25,8 +25,8 @@ func (t *Int32) Clone() *Int32 {
return NewInt32(t.Val())
}
func (t *Int32) Set(value int32) {
atomic.StoreInt32(&t.val, value)
func (t *Int32) Set(value int32) (old int32) {
return atomic.SwapInt32(&t.val, value)
}
func (t *Int32) Val() int32 {

View File

@ -25,8 +25,8 @@ func (t *Int64) Clone() *Int64 {
return NewInt64(t.Val())
}
func (t *Int64) Set(value int64) {
atomic.StoreInt64(&t.val, value)
func (t *Int64) Set(value int64) (old int64) {
return atomic.SwapInt64(&t.val, value)
}
func (t *Int64) Val() int64 {

View File

@ -23,15 +23,17 @@ func NewInterface(value...interface{}) *Interface {
return t
}
func (t *Interface) Clone() *Interface{
func (t *Interface) Clone() *Interface {
return NewInterface(t.Val())
}
func (t *Interface) Set(value interface{}) {
func (t *Interface) Set(value interface{}) (old interface{}) {
if value == nil {
return
}
old = t.Val()
t.val.Store(value)
return
}
func (t *Interface) Val() interface{} {

View File

@ -26,8 +26,10 @@ func (t *String) Clone() *String {
return NewString(t.Val())
}
func (t *String) Set(value string) {
func (t *String) Set(value string) (old string) {
old = t.Val()
t.val.Store(value)
return
}
func (t *String) Val() string {

View File

@ -25,8 +25,8 @@ func (t *Uint) Clone() *Uint {
return NewUint(t.Val())
}
func (t *Uint) Set(value uint) {
atomic.StoreUint64(&t.val, uint64(value))
func (t *Uint) Set(value uint) (old uint) {
return uint(atomic.SwapUint64(&t.val, uint64(value)))
}
func (t *Uint) Val() uint {

View File

@ -25,8 +25,8 @@ func (t *Uint32) Clone() *Uint32 {
return NewUint32(t.Val())
}
func (t *Uint32) Set(value uint32) {
atomic.StoreUint32(&t.val, value)
func (t *Uint32) Set(value uint32) (old uint32) {
return atomic.SwapUint32(&t.val, value)
}
func (t *Uint32) Val() uint32 {

View File

@ -25,8 +25,8 @@ func (t *Uint64) Clone() *Uint64 {
return NewUint64(t.Val())
}
func (t *Uint64) Set(value uint64) {
atomic.StoreUint64(&t.val, value)
func (t *Uint64) Set(value uint64) (old uint64) {
return atomic.SwapUint64(&t.val, value)
}
func (t *Uint64) Val() uint64 {

View File

@ -30,12 +30,14 @@ func New(value interface{}, safe...bool) *Var {
return v
}
func (v *Var) Set(value interface{}) {
func (v *Var) Set(value interface{}) (old interface{}) {
if v.safe {
v.value.(*gtype.Interface).Set(value)
old = v.value.(*gtype.Interface).Set(value)
} else {
old = v.value
v.value = value
}
return
}
func (v *Var) Val() interface{} {

View File

@ -9,17 +9,17 @@
package gdb
import (
"fmt"
"time"
"errors"
"database/sql"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/container/gring"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/os/gcache"
"gitee.com/johng/gf/g/util/grand"
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
"gitee.com/johng/gf/g/container/gvar"
"errors"
"fmt"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/container/gring"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/container/gvar"
"gitee.com/johng/gf/g/os/gcache"
"gitee.com/johng/gf/g/util/grand"
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
"time"
)
const (
@ -122,18 +122,18 @@ type Map = map[string]interface{}
// 关联数组列表(索引从0开始的数组),绑定多条记录(使用别名)
type List = []Map
// MySQL接口对象
var linkMysql = &dbmysql{}
// PostgreSQL接口对象
var linkPgsql = &dbpgsql{}
// Sqlite接口对象
// @author wxkj<wxscz@qq.com>
var linkSqlite = &dbsqlite{}
// 数据库查询缓存对象map使用数据库连接名称作为键名键值为查询缓存对象
var dbCaches = gmap.NewStringInterfaceMap()
var (
// 支持的数据库类型map
driverMap = make(map[string]interface{})
// 数据库查询缓存对象map使用数据库连接名称作为键名键值为查询缓存对象
dbCaches = gmap.NewStringInterfaceMap()
)
func init() {
driverMap["mysql"] = linkMysql
driverMap["oracle"] = linkOracle
driverMap["sqlite"] = linkSqlite
driverMap["pgsql"] = linkPgsql
}
// 使用默认/指定分组配置进行连接数据库集群配置项default
func New(groupName ...string) (*Db, error) {
@ -239,15 +239,10 @@ func getConfigNodeByPriority(cg ConfigGroup) *ConfigNode {
// 根据配置的数据库类型获得Link接口对象
func getLinkByType(dbType string) (Link, error) {
switch dbType {
case "mysql":
return linkMysql, nil
case "pgsql":
return linkPgsql, nil
case "sqlite":
return linkSqlite, nil
default:
return nil, errors.New(fmt.Sprintf("unsupported db type '%s'", dbType))
if dblink, ok := driverMap[dbType]; ok == false {
return nil, errors.New(fmt.Sprintf("unsupported db type '%s'", dbType))
} else {
return dblink.(Link), nil
}
}

View File

@ -349,8 +349,8 @@ func (db *Db) insert(table string, data Map, option uint8) (sql.Result, error) {
updatestr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
}
return db.Exec(
fmt.Sprintf("%s INTO %s%s%s(%s) VALUES(%s) %s",
operation, db.charl, table, db.charr, strings.Join(fields, ","),
fmt.Sprintf("%s INTO %s(%s) VALUES(%s) %s",
operation, table, strings.Join(fields, ","),
strings.Join(values, ","),
updatestr),
params...
@ -413,8 +413,8 @@ func (db *Db) batchInsert(table string, list List, batch int, option uint8) (sql
}
bvalues = append(bvalues, valueHolderStr)
if len(bvalues) == batch {
r, err := db.Exec(fmt.Sprintf("%s INTO %s%s%s(%s) VALUES%s %s",
operation, db.charl, table, db.charr, keyStr, strings.Join(bvalues, ","),
r, err := db.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
updatestr),
params...)
if err != nil {
@ -427,8 +427,8 @@ func (db *Db) batchInsert(table string, list List, batch int, option uint8) (sql
}
// 处理最后不构成指定批量的数据
if len(bvalues) > 0 {
r, err := db.Exec(fmt.Sprintf("%s INTO %s%s%s(%s) VALUES%s %s",
operation, db.charl, table, db.charr, keyStr, strings.Join(bvalues, ","),
r, err := db.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
updatestr),
params...)
if err != nil {
@ -474,12 +474,12 @@ func (db *Db) Update(table string, data interface{}, condition interface{}, args
for _, v := range args {
params = append(params, gconv.String(v))
}
return db.Exec(fmt.Sprintf("UPDATE %s%s%s SET %s WHERE %s", db.charl, table, db.charr, updates, db.formatCondition(condition)), params...)
return db.Exec(fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, db.formatCondition(condition)), params...)
}
// CURD操作:删除数据
func (db *Db) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
return db.Exec(fmt.Sprintf("DELETE FROM %s%s%s WHERE %s", db.charl, table, db.charr, db.formatCondition(condition)), args...)
return db.Exec(fmt.Sprintf("DELETE FROM %s WHERE %s", table, db.formatCondition(condition)), args...)
}
// 格式化SQL查询条件

View File

@ -12,6 +12,10 @@ import (
"database/sql"
)
// MySQL接口对象
var linkMysql = &dbmysql{}
// 数据库链接对象
type dbmysql struct {
Db

View File

@ -0,0 +1,149 @@
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
/*
@author wenzi1<liyz23@qq.com>
@date 20181026
说明:
1.需要导入oracle驱动 github.com/mattn/go-oci8
2.不支持save/replace方法可以调用这2个方法估计会报错还没测试过,(应该是可以通过oracle的merge来实现这2个功能的还没仔细研究)
3.不支持LastInsertId方法
*/
package gdb
import (
"database/sql"
"fmt"
"gitee.com/johng/gf/g/util/gregex"
"strconv"
"strings"
)
var linkOracle = &dboracle{}
// 数据库链接对象
type dboracle struct {
Db
}
// 创建SQL操作对象
func (db *dboracle) Open(c *ConfigNode) (*sql.DB, error) {
var source string
if c.Linkinfo != "" {
source = c.Linkinfo
} else {
source = fmt.Sprintf("%s/%s@%s", c.User, c.Pass, c.Name)
}
if db, err := sql.Open("oci8", source); err == nil {
return db, nil
} else {
return nil, err
}
}
// 获得关键字操作符 - 左
func (db *dboracle) getQuoteCharLeft() string {
return "\""
}
// 获得关键字操作符 - 右
func (db *dboracle) getQuoteCharRight() string {
return "\""
}
// 在执行sql之前对sql进行进一步处理
func (db *dboracle) handleSqlBeforeExec(q *string) *string {
index := 0
str, _ := gregex.ReplaceStringFunc("\\?", *q, func(s string) string {
index++
return fmt.Sprintf(":%d", index)
})
str, _ = gregex.ReplaceString("\"", "", str)
return db.parseSql(&str)
}
//由于ORACLE中对LIMIT和批量插入的语法与MYSQL不一致所以这里需要对LIMIT和批量插入做语法上的转换
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..")
return sql
}
res, err := gregex.MatchAllString(patten, *sql)
if err != nil {
fmt.Println("MatchString error.", err)
return nil
}
index := 0
keyword := strings.TrimSpace(res[index][0])
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)
queryExpr[0] = strings.TrimRight(queryExpr[0], "LIMIT")
queryExpr[0] = strings.TrimRight(queryExpr[0], "limit")
//取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
}
}
//也可以使用between,据说这种写法的性能会比between好点,里层SQL中的ROWNUM_ >= limit可以缩小查询后的数据集规模
*sql = fmt.Sprintf("SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d", queryExpr[0], 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

@ -18,6 +18,10 @@ import (
// _ "gitee.com/johng/gf/third/github.com/lib/pq"
// @todo 需要完善replace和save的操作覆盖
// PostgreSQL接口对象
var linkPgsql = &dbpgsql{}
// 数据库链接对象
type dbpgsql struct {
Db
@ -57,5 +61,4 @@ func (db *dbpgsql) handleSqlBeforeExec(q *string) *string {
return fmt.Sprintf("$%d", index)
})
return &str
}
}

View File

@ -14,6 +14,11 @@ import (
// 使用时需要import:
// _ "gitee.com/johng/gf/third/github.com/mattn/go-sqlite3"
// Sqlite接口对象
// @author wxkj<wxscz@qq.com>
var linkSqlite = &dbsqlite{}
// 数据库链接对象
type dbsqlite struct {
Db
@ -49,4 +54,4 @@ func (db *dbsqlite) getQuoteCharRight() string {
func (db *dbsqlite) handleSqlBeforeExec(q *string) *string {
return q
}
}

View File

@ -222,8 +222,8 @@ func (tx *Tx) insert(table string, data Map, option uint8) (sql.Result, error) {
updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
}
return tx.Exec(
fmt.Sprintf("%s INTO %s%s%s(%s) VALUES(%s) %s",
operation, tx.db.charl, table, tx.db.charr, strings.Join(keys, ","),
fmt.Sprintf("%s INTO %s(%s) VALUES(%s) %s",
operation, table, strings.Join(keys, ","),
strings.Join(values, ","),
updatestr),
params...
@ -281,8 +281,8 @@ func (tx *Tx) batchInsert(table string, list List, batch int, option uint8) (sql
}
bvalues = append(bvalues, valueHolderStr)
if len(bvalues) == batch {
r, err := tx.Exec(fmt.Sprintf("%s INTO %s%s%s(%s) VALUES%s %s",
operation, tx.db.charl, table, tx.db.charr, keyStr, strings.Join(bvalues, ","),
r, err := tx.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
updatestr),
params...)
if err != nil {
@ -295,8 +295,8 @@ func (tx *Tx) batchInsert(table string, list List, batch int, option uint8) (sql
}
// 处理最后不构成指定批量的数据
if len(bvalues) > 0 {
r, err := tx.Exec(fmt.Sprintf("%s INTO %s%s%s(%s) VALUES%s %s",
operation, tx.db.charl, table, tx.db.charr, keyStr, strings.Join(bvalues, ","),
r, err := tx.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
updatestr),
params...)
if err != nil {
@ -342,11 +342,11 @@ func (tx *Tx) Update(table string, data interface{}, condition interface{}, args
for _, v := range args {
params = append(params, gconv.String(v))
}
return tx.Exec(fmt.Sprintf("UPDATE %s%s%s SET %s WHERE %s", tx.db.charl, table, tx.db.charr, updates, tx.db.formatCondition(condition)), params...)
return tx.Exec(fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, tx.db.formatCondition(condition)), params...)
}
// CURD操作:删除数据
func (tx *Tx) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
return tx.Exec(fmt.Sprintf("DELETE FROM %s%s%s WHERE %s", tx.db.charl, table, tx.db.charr, tx.db.formatCondition(condition)), args...)
return tx.Exec(fmt.Sprintf("DELETE FROM %s WHERE %s", table, tx.db.formatCondition(condition)), args...)
}

View File

@ -12,6 +12,7 @@ import (
"gitee.com/johng/gf/g/os/gcfg"
"gitee.com/johng/gf/g/os/gcmd"
"gitee.com/johng/gf/g/os/genv"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/os/gview"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/container/gmap"
@ -40,7 +41,7 @@ func Get(key string) interface{} {
// 设置单例对象
func Set(key string, value interface{}) {
instances.Set(key, key)
instances.Set(key, value)
}
// 当键名存在时返回其键值,否则写入指定的键值
@ -73,7 +74,7 @@ func View(name...string) *gview.View {
return instances.GetOrSetFuncLock(key, func() interface{} {
path := gcmd.Option.Get("gf.viewpath")
if path == "" {
path = genv.Get("gf.viewpath")
path = genv.Get("GF_VIEWPATH")
if path == "" {
path = gfile.SelfDir()
}
@ -100,7 +101,7 @@ func Config(file...string) *gcfg.Config {
func() interface{} {
path := gcmd.Option.Get("gf.cfgpath")
if path == "" {
path = genv.Get("gf.cfgpath")
path = genv.Get("GF_CFGPATH")
if path == "" {
path = gfile.SelfDir()
}
@ -125,7 +126,7 @@ func Database(name...string) *gdb.Db {
db := instances.GetOrSetFuncLock(key, func() interface{} {
m := config.GetMap("database")
if m == nil {
panic(fmt.Sprintf(`incomplete configuration for database: "database" node not found in config file "%s"`, config.GetFilePath()))
glog.Errorfln(`incomplete configuration for database: "database" node not found in config file "%s"`, config.GetFilePath())
}
for group, v := range m {
cg := gdb.ConfigGroup{}
@ -160,6 +161,9 @@ func Database(name...string) *gdb.Db {
if value, ok := nodem["priority"]; ok {
node.Priority = gconv.Int(value)
}
if value, ok := nodem["linkinfo"]; ok {
node.Linkinfo = gconv.String(value)
}
if value, ok := nodem["max-idle"]; ok {
node.MaxIdleConnCount = gconv.Int(value)
}
@ -181,7 +185,7 @@ func Database(name...string) *gdb.Db {
if db, err := gdb.New(name...); err == nil {
return db
} else {
panic(err)
glog.Error(err)
}
return nil
})
@ -213,13 +217,13 @@ func Redis(name...string) *gredis.Redis {
Pass : array[4],
})
} else {
panic(fmt.Sprintf(`invalid redis node configuration: "%s"`, line))
glog.Errorfln(`invalid redis node configuration: "%s"`, line)
}
} else {
panic(fmt.Sprintf(`configuration for redis not found for group "%s"`, group))
glog.Errorfln(`configuration for redis not found for group "%s"`, group)
}
} else {
panic(fmt.Sprintf(`incomplete configuration for redis: "redis" node not found in config file "%s"`, config.GetFilePath()))
glog.Errorfln(`incomplete configuration for redis: "redis" node not found in config file "%s"`, config.GetFilePath())
}
return nil
})
@ -230,7 +234,7 @@ func Redis(name...string) *gredis.Redis {
}
// 模板内置方法config
func funcConfig(pattern string, file...string) gview.HTML {
return gview.HTML(Config().GetString(pattern, file...))
func funcConfig(pattern string, file...string) string {
return Config().GetString(pattern, file...)
}

View File

@ -33,17 +33,17 @@ func Wait() {
ghttp.Wait()
}
// 是否显示调试信息
func SetDebug(debug bool) {
glog.SetDebug(debug)
}
// 设置日志的显示等级
func SetLogLevel(level int) {
glog.SetLevel(level)
}
// 打印变量
func Dump(i...interface{}) {
gutil.Dump(i...)
}
// 抛出一个异常
func Throw(exception interface{}) {
gutil.Throw(exception)
}
// try...catch...
func TryCatch(try func(), catch ... func(exception interface{})) {
gutil.TryCatch(try, catch...)
}

View File

@ -6,3 +6,33 @@
package g
import (
"gitee.com/johng/gf/g/os/gcmd"
"gitee.com/johng/gf/g/os/genv"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/util/gconv"
)
func init() {
if v := genv.Get("GF_DEBUG"); v != "" {
SetDebug(gconv.Bool(v))
}
if v := gcmd.Option.Get("gf.debug"); v != "" {
SetDebug(gconv.Bool(v))
}
}
// 是否显示调试信息
func SetDebug(debug bool) {
glog.SetDebug(debug)
}
// 设置日志的显示等级
func SetLogLevel(level int) {
glog.SetLevel(level)
}
// 获取设置的日志显示等级
func GetLogLevel() int {
return glog.GetLevel()
}

View File

@ -38,6 +38,7 @@ type Request struct {
parsedHost *gtype.String // 解析过后不带端口号的服务器域名名称
clientIp *gtype.String // 解析过后的客户端IP地址
isFileRequest bool // 是否为静态文件请求(非服务请求,当静态文件存在时,优先级会被服务请求高,被识别为文件请求)
isFileServe bool // 是否为文件处理(调用Server.serveFile时设置为true), isFileRequest为true时isFileServe也为true
}
// 创建一个Request对象
@ -153,6 +154,7 @@ func (r *Request) GetToStruct(object interface{}, mapping...map[string]string) {
// 退出当前请求执行原理是在Request.exit做标记由服务逻辑流程做判断自行停止
func (r *Request) Exit() {
r.exit.Set(true)
panic(gEXCEPTION_EXIT)
}
// 判断当前请求是否停止执行
@ -180,6 +182,11 @@ func (r *Request) IsFileRequest() bool {
return r.isFileRequest
}
// 判断请求是否为文件处理
func (r *Request) IsFileServe() bool {
return r.isFileServe
}
// 判断是否为AJAX请求
func (r *Request) IsAjaxRequest() bool {
return strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest")

View File

@ -65,7 +65,13 @@ func (r *Response) buildInVars(params map[string]interface{}) map[string]interfa
if params == nil {
params = make(map[string]interface{})
}
params["Config"] = gins.Config().GetMap("")
c := gins.Config()
if c.GetFilePath() != "" {
params["Config"] = c.GetMap("")
} else {
params["Config"] = nil
}
params["Cookie"] = r.request.Cookie.Map()
params["Session"] = r.request.Session.Data()
return params
@ -83,16 +89,16 @@ func (r *Response) buildInFuncs(funcmap map[string]interface{}) map[string]inter
}
// 模板内置函数: get
func (r *Response) funcGet(key string, def...string) gview.HTML {
return gview.HTML(r.request.GetQueryString(key, def...))
func (r *Response) funcGet(key string, def...string) string {
return r.request.GetQueryString(key, def...)
}
// 模板内置函数: post
func (r *Response) funcPost(key string, def...string) gview.HTML {
return gview.HTML(r.request.GetPostString(key, def...))
func (r *Response) funcPost(key string, def...string) string {
return r.request.GetPostString(key, def...)
}
// 模板内置函数: request
func (r *Response) funcRequest(key string, def...string) gview.HTML {
return gview.HTML(r.request.Get(key, def...))
func (r *Response) funcRequest(key string, def...string) string {
return r.request.Get(key, def...)
}

View File

@ -52,6 +52,7 @@ const (
gROUTE_REGISTER_HANDLER = 1
gROUTE_REGISTER_OBJECT = 2
gROUTE_REGISTER_CONTROLLER = 3
gEXCEPTION_EXIT = "exit"
)
// ghttp.Server结构体
@ -213,9 +214,14 @@ func (s *Server) Start() error {
// 如果设置了静态文件目录,那么优先按照静态文件目录进行检索,其次是当前可执行文件工作目录;
// 并且如果是开发环境默认也会添加main包的源码目录路径做为二级检索。
if s.config.ServerRoot != "" {
s.paths.Set(s.config.ServerRoot)
if rp, err := s.paths.Set(s.config.ServerRoot); err != nil {
glog.Error("ghttp.SetServerRoot failed:", err.Error())
return err
} else {
glog.Debug("ghttp.SetServerRoot:", rp)
}
}
s.paths.Add(gfile.SelfDir())
s.AddSearchPath(gfile.SelfDir())
if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) {
s.paths.Add(p)
}
@ -271,7 +277,7 @@ func (s *Server) Start() error {
// 打印展示路由表
func (s *Server) DumpRoutesMap() {
if s.config.DumpRouteMap {
if s.config.DumpRouteMap && len(s.routesMap) > 0 {
// (等待一定时间后)当所有框架初始化信息打印完毕之后才打印路由表信息
gtime.SetTimeout(50*time.Millisecond, func() {
glog.Header(false).Println(fmt.Sprintf("\n%s\n", s.GetRouteMap()))
@ -291,7 +297,7 @@ func (s *Server) GetRouteMap() string {
buf := bytes.NewBuffer(nil)
table := tablewriter.NewWriter(buf)
table.SetHeader([]string{"SERVER", "ADDRESS", "DOMAIN", "METHOD", "ROUTE", "HANDLER", "HOOK"})
table.SetHeader([]string{"SERVER", "ADDRESS", "DOMAIN", "METHOD", "P", "ROUTE", "HANDLER", "HOOK"})
table.SetRowLine(true)
table.SetBorder(false)
table.SetCenterSeparator("|")
@ -323,17 +329,22 @@ func (s *Server) GetRouteMap() string {
}
m[item.domain].Add(item)
}
addr := s.config.Addr
if s.config.HTTPSAddr != "" {
addr += ",tls" + s.config.HTTPSAddr
}
for _, a := range m {
data := make([]string, 7)
data := make([]string, 8)
for _, v := range a.Slice() {
item := v.(*tableItem)
data[0] = s.name
data[1] = s.config.Addr
data[1] = addr
data[2] = item.domain
data[3] = item.method
data[4] = item.route
data[5] = item.handler
data[6] = item.hook
data[4] = gconv.String(len(strings.Split(item.route, "/")) - 1)
data[5] = item.route
data[6] = item.handler
data[7] = item.hook
table.Append(data)
}
}

View File

@ -303,7 +303,13 @@ func (s *Server) SetDumpRouteMap(enabled bool) {
// 添加静态文件搜索目录,必须给定目录的绝对路径
func (s *Server) AddSearchPath(path string) error {
return s.paths.Add(path)
if rp, err := s.paths.Add(path); err != nil {
glog.Error("ghttp.AddSearchPath failed:", err.Error())
return err
} else {
glog.Debug("ghttp.AddSearchPath:", rp)
}
return nil
}
// 获取

View File

@ -8,16 +8,16 @@
package ghttp
import (
"os"
"fmt"
"sort"
"reflect"
"strings"
"net/url"
"net/http"
"gitee.com/johng/gf/g/encoding/ghtml"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/encoding/ghtml"
"net/http"
"net/url"
"os"
"reflect"
"sort"
"strings"
)
// 默认HTTP Server处理入口http包底层默认使用了gorutine异步处理请求所以这里不再异步执行
@ -118,6 +118,11 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
// 初始化控制器
func (s *Server)callServeHandler(h *handlerItem, r *Request) {
defer func() {
if e := recover(); e != nil && e != gEXCEPTION_EXIT {
panic(e)
}
}()
if h.faddr == nil {
// 新建一个控制器对象处理请求
c := reflect.New(h.ctype)
@ -142,6 +147,8 @@ func (s *Server)callServeHandler(h *handlerItem, r *Request) {
// http server静态文件处理path可以为相对路径也可以为绝对路径
func (s *Server)serveFile(r *Request, path string) {
r.isFileServe = true
// 首先判断是否给定的path已经是一个绝对路径
if !gfile.Exists(path) {
path = s.paths.Search(path)

View File

@ -275,11 +275,11 @@ func (s *Server) patternToRegRule(rule string) (regrule string, names []string)
switch v[0] {
case ':':
if len(v) > 1 {
regrule += `/(.+)`
regrule += `/([^/]+)`
names = append(names, v[1:])
break
} else {
regrule += `/.+`
regrule += `/[^/]+`
break
}
fallthrough
@ -302,7 +302,7 @@ func (s *Server) patternToRegRule(rule string) (regrule string, names []string)
})
s, _ := gregex.ReplaceStringFunc(`\{[\w\.\-]+\}`, v, func(s string) string {
names = append(names, s[1 : len(s) - 1])
return `(.+)`
return `([^/]+)`
})
if strings.EqualFold(s, v) {
regrule += "/" + v

View File

@ -43,6 +43,11 @@ func (s *Server)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerF
func (s *Server) callHookHandler(hook string, r *Request) {
hookItems := s.getHookHandlerWithCache(hook, r)
if len(hookItems) > 0 {
defer func() {
if e := recover(); e != nil && e != gEXCEPTION_EXIT {
panic(e)
}
}()
// 备份原有的router变量
oldRouterVars := r.routerVars
for _, item := range hookItems {

View File

@ -9,6 +9,7 @@ package ghttp
import (
"errors"
"gitee.com/johng/gf/g/os/glog"
"strings"
"reflect"
"fmt"
@ -42,6 +43,14 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) e
if mname == "Init" || mname == "Shut" || mname == "Exit" {
continue
}
if _, ok := v.Method(i).Interface().(func()); !ok {
if methodMap != nil {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
}
continue
}
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if ctlName[0] == '*' {
ctlName = fmt.Sprintf(`(%s)`, ctlName)
@ -82,9 +91,15 @@ func (s *Server)BindControllerMethod(pattern string, c Controller, method string
t := v.Type()
sname := t.Elem().Name()
mname := strings.TrimSpace(method)
if !v.MethodByName(mname).IsValid() {
fval := v.MethodByName(mname)
if !fval.IsValid() {
return errors.New("invalid method name:" + mname)
}
if _, ok := fval.Interface().(func()); !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, fval.Type().String())
glog.Error(s)
return errors.New(s)
}
pkgPath := t.Elem().PkgPath()
pkgName := gfile.Basename(pkgPath)
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
@ -119,6 +134,11 @@ func (s *Server)BindControllerRest(pattern string, c Controller) error {
if _, ok := s.methodsMap[method]; !ok {
continue
}
if _, ok := v.Method(i).Interface().(func()); !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
}
pkgName := gfile.Basename(pkgPath)
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if ctlName[0] == '*' {

View File

@ -9,6 +9,7 @@ package ghttp
import (
"errors"
"gitee.com/johng/gf/g/os/glog"
"strings"
"reflect"
"fmt"
@ -48,6 +49,15 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
if mname == "Init" || mname == "Shut" {
continue
}
faddr, ok := v.Method(i).Interface().(func(*Request))
if !ok {
if methodMap != nil {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func(*Request))" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
}
continue
}
objName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if objName[0] == '*' {
objName = fmt.Sprintf(`(%s)`, objName)
@ -58,7 +68,7 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
rtype : gROUTE_REGISTER_OBJECT,
ctype : nil,
fname : "",
faddr : v.Method(i).Interface().(func(*Request)),
faddr : faddr,
finit : finit,
fshut : fshut,
}
@ -76,7 +86,7 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
rtype : gROUTE_REGISTER_OBJECT,
ctype : nil,
fname : "",
faddr : v.Method(i).Interface().(func(*Request)),
faddr : faddr,
finit : finit,
fshut : fshut,
}
@ -97,6 +107,12 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string)
if !fval.IsValid() {
return errors.New("invalid method name:" + mname)
}
faddr, ok := fval.Interface().(func(*Request))
if !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func(*Request)" is required`, fval.Type().String())
glog.Error(s)
return errors.New(s)
}
finit := (func(*Request))(nil)
fshut := (func(*Request))(nil)
if v.MethodByName("Init").IsValid() {
@ -117,7 +133,7 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string)
rtype : gROUTE_REGISTER_OBJECT,
ctype : nil,
fname : "",
faddr : fval.Interface().(func(*Request)),
faddr : faddr,
finit : finit,
fshut : fshut,
}
@ -146,6 +162,12 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
if _, ok := s.methodsMap[method]; !ok {
continue
}
faddr, ok := v.Method(i).Interface().(func(*Request))
if !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
}
pkgName := gfile.Basename(pkgPath)
objName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if objName[0] == '*' {
@ -157,7 +179,7 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
rtype : gROUTE_REGISTER_OBJECT,
ctype : nil,
fname : "",
faddr : v.Method(i).Interface().(func(*Request)),
faddr : faddr,
finit : finit,
fshut : fshut,
}

View File

@ -26,7 +26,7 @@ type Session struct {
server *Server // 所属Server
}
// 生成一个唯一的sessionid字符串
// 生成一个唯一的sessionid字符串长度16
func makeSessionId() string {
return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 32) + grand.RandStr(3))
}

View File

@ -9,13 +9,14 @@
package gcfg
import (
"gitee.com/johng/gf/g/os/gspath"
"gitee.com/johng/gf/g/os/gfsnotify"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/encoding/gjson"
"gitee.com/johng/gf/g/container/gtype"
"errors"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/container/gvar"
"gitee.com/johng/gf/g/encoding/gjson"
"gitee.com/johng/gf/g/os/gfsnotify"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/os/gspath"
)
const (
@ -36,14 +37,14 @@ func New(path string, file...string) *Config {
if len(file) > 0 {
name = file[0]
}
s := gspath.New()
s.Set(path)
return &Config {
c := &Config {
name : gtype.NewString(name),
paths : s,
paths : gspath.New(),
jsons : gmap.NewStringInterfaceMap(),
vc : gtype.NewBool(),
}
c.SetPath(path)
return c
}
// 判断从哪个配置文件中获取内容,返回配置文件的绝对路径
@ -57,12 +58,13 @@ func (c *Config) filePath(file...string) string {
// 设置配置管理器的配置文件存放目录绝对路径
func (c *Config) SetPath(path string) error {
if err := c.paths.Set(path); err != nil {
glog.Debug("gcfg.SetPath failed:", path, err)
if rp, err := c.paths.Set(path); err != nil {
glog.Error("gcfg.SetPath failed:", err.Error())
return err
} else {
c.jsons.Clear()
glog.Debug("gcfg.SetPath:", rp)
}
c.jsons.Clear()
glog.Debug("gcfg.SetPath:", path)
return nil
}
@ -75,11 +77,12 @@ func (c *Config) SetViolenceCheck(check bool) {
// 添加配置管理器的配置文件搜索路径
func (c *Config) AddPath(path string) error {
if err := c.paths.Add(path); err != nil {
glog.Debug("gcfg.AddPath failed:", path, err)
if rp, err := c.paths.Add(path); err != nil {
glog.Debug("gcfg.AddPath failed:", err.Error())
return err
} else {
glog.Debug("gcfg.AddPath:", rp)
}
glog.Debug("gcfg.AddPath:", path)
return nil
}
@ -94,6 +97,7 @@ func (c *Config) GetFilePath(file...string) string {
// 设置配置管理对象的默认文件名称
func (c *Config) SetFileName(name string) {
glog.Debug("gcfg.SetFileName:", name)
c.name.Set(name)
}
@ -108,6 +112,8 @@ func (c *Config) getJson(file...string) *gjson.Json {
c.addMonitor(fpath)
c.jsons.Set(fpath, j)
return j
} else {
glog.Errorfln(`gcfg.Load config file "%s" failed: %s`, fpath, err.Error())
}
return nil
}
@ -120,6 +126,14 @@ func (c *Config) Get(pattern string, file...string) interface{} {
return nil
}
// 获得配置项,返回动态变量
func (c *Config) GetVar(pattern string, file...string) *gvar.Var {
if j := c.getJson(file...); j != nil {
return gvar.New(j.Get(pattern))
}
return nil
}
// 获得一个键值对关联数组/哈希表,方便操作,不需要自己做类型转换
// 注意如果获取的值不存在或者类型与json类型不匹配那么将会返回nil
func (c *Config) GetMap(pattern string, file...string) map[string]interface{} {

View File

@ -8,107 +8,74 @@
package gcron
import (
"errors"
"fmt"
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/third/github.com/robfig/cron"
"reflect"
"runtime"
)
// 定时任务项
type Entry struct {
Spec string // 注册定时任务时间格式
Cmd string // 注册定时任务名称
Time *gtime.Time // 注册时间
Name string // 定时任务名称
cron *cron.Cron // 底层定时管理对象
Spec string // 注册定时任务时间格式
Cmd string // 注册定时任务名称
Time *gtime.Time // 注册时间
Name string // 定时任务名称
Status *gtype.Int // 定时任务状态(0: 未执行; > 0: 运行中)
cron *cron.Cron // 定时任务单独的底层定时管理对象
}
// 定时任务管理对象
type Cron struct {
cron *cron.Cron // 底层定时管理对象
entries *garray.Array // 定时任务注册项
status *gtype.Int // 默认定时任务管理对象状态(不带名称的定时任务0: 未执行; > 0: 运行中)
}
var (
// 默认的cron管理对象
defaultCron = cron.New()
// 当前cron的运行状态(0: 未执行; > 0: 运行中)
cronStatus = gtype.NewInt()
// 注册定时任务项
cronEntries = garray.New(0, 0, true)
defaultCron = New()
)
// 创建自定义的定时任务管理对象
func New() *Cron {
return &Cron {
cron : cron.New(),
entries : garray.New(0, 0, true),
status : gtype.NewInt(),
}
}
// 添加执行方法,可以给定名字,以便于后续执行删除
func Add(spec string, f func(), name ... string) error {
if len(name) > 0 {
if Search(name[0]) != nil {
return errors.New(fmt.Sprintf(`cron job "%s" already exists`, name[0]))
}
c := cron.New()
if err := c.AddFunc(spec, f); err == nil {
cronEntries.Append(Entry{
Spec : spec,
Cmd : runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name(),
Time : gtime.Now(),
Name : name[0],
cron : c,
})
go c.Run()
} else {
return err
}
} else {
if err := defaultCron.AddFunc(spec, f); err == nil {
if cronStatus.Add(1) == 1 {
go defaultCron.Run()
}
cronEntries.Append(Entry{
Spec : spec,
Cmd : runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name(),
Time : gtime.Now(),
})
} else {
return err
}
}
return nil
return defaultCron.Add(spec, f, name...)
}
// 延迟添加定时任务delay参数单位为秒
func DelayAdd(delay int, spec string, f func(), name ... string) {
defaultCron.DelayAdd(delay, spec, f, name...)
}
// 检索指定名称的定时任务
func Search(name string) *Entry {
entry, _ := searchEntry(name)
return entry
}
// 检索指定名称的定时任务
func searchEntry(name string) (*Entry, int) {
entry := (*Entry)(nil)
index := -1
cronEntries.RLockFunc(func(array []interface{}) {
for k, v := range array {
e := v.(Entry)
if e.Name == name {
entry = &e
index = k
break
}
}
})
return entry, index
return defaultCron.Search(name)
}
// 根据指定名称删除定时任务
func Remove(name string) {
if entry, index := searchEntry(name); index >= 0 {
entry.cron.Stop()
cronEntries.Remove(index)
}
defaultCron.Remove(name)
}
// 获取所有已注册的定时任务项
func Entries() []Entry {
length := cronEntries.Len()
entries := make([]Entry, length)
for i := 0; i < length; i++ {
entries[i] = cronEntries.Get(i).(Entry)
}
return entries
func Entries() []*Entry {
return defaultCron.Entries()
}
// 启动指定的定时任务
func Start(name string) {
defaultCron.Start(name)
}
// 停止指定的定时任务
func Stop(name string) {
defaultCron.Stop(name)
}

142
g/os/gcron/gcron_cron.go Normal file
View File

@ -0,0 +1,142 @@
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
package gcron
import (
"errors"
"fmt"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/third/github.com/robfig/cron"
"reflect"
"runtime"
"time"
)
// 添加定时任务
func (c *Cron) Add(spec string, f func(), name ... string) error {
if len(name) > 0 {
if Search(name[0]) != nil {
return errors.New(fmt.Sprintf(`cron job "%s" already exists`, name[0]))
}
jobCron := cron.New()
if err := jobCron.AddFunc(spec, f); err == nil {
entry := &Entry{
Spec : spec,
Cmd : runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name(),
Time : gtime.Now(),
Name : name[0],
Status : gtype.NewInt(0),
cron : jobCron,
}
entry.Start()
c.entries.Append(entry)
} else {
return err
}
} else {
if err := c.cron.AddFunc(spec, f); err == nil {
entry := &Entry {
Spec : spec,
Cmd : runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name(),
Time : gtime.Now(),
Status : c.status,
cron : c.cron,
}
entry.Start()
c.entries.Append(entry)
} else {
return err
}
}
return nil
}
// 延迟添加定时任务delay参数单位为秒
func (c *Cron) DelayAdd(delay int, spec string, f func(), name ... string) {
gtime.SetTimeout(time.Duration(delay)*time.Second, func() {
if err := c.Add(spec, f, name ...); err != nil {
panic(err)
}
})
}
// 检索指定名称的定时任务
func (c *Cron) Search(name string) *Entry {
entry, _ := c.searchEntry(name)
return entry
}
// 检索指定名称的定时任务
func (c *Cron) searchEntry(name string) (*Entry, int) {
entry := (*Entry)(nil)
index := -1
c.entries.RLockFunc(func(array []interface{}) {
for k, v := range array {
e := v.(*Entry)
if e.Name == name {
entry = e
index = k
break
}
}
})
return entry, index
}
// 根据指定名称删除定时任务
func (c *Cron) Remove(name string) {
if entry, index := c.searchEntry(name); index >= 0 {
entry.cron.Stop()
c.entries.Remove(index)
}
}
// 开启定时任务执行(可以指定特定名称的一个或若干个定时任务)
func (c *Cron) Start(name...string) {
if len(name) > 0 {
for _, v := range name {
if entry := c.Search(v); entry != nil {
entry.Start()
}
}
} else {
c.entries.RLockFunc(func(array []interface{}) {
for _, v := range array {
v.(*Entry).Start()
}
})
}
}
// 关闭定时任务执行(可以指定特定名称的一个或若干个定时任务)
func (c *Cron) Stop(name...string) {
if len(name) > 0 {
for _, v := range name {
if entry := c.Search(v); entry != nil {
entry.Stop()
}
}
} else {
c.entries.RLockFunc(func(array []interface{}) {
for _, v := range array {
v.(*Entry).Stop()
}
})
}
}
// 获取所有已注册的定时任务项
func (c *Cron) Entries() []*Entry {
length := c.entries.Len()
entries := make([]*Entry, length)
for i := 0; i < length; i++ {
entries[i] = c.entries.Get(i).(*Entry)
}
return entries
}

21
g/os/gcron/gcron_entry.go Normal file
View File

@ -0,0 +1,21 @@
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
package gcron
// 启动定时任务
func (entry *Entry) Start() {
if entry.Status.Set(1) == 0 {
entry.cron.Start()
}
}
// 关闭定时任务
func (entry *Entry) Stop() {
if entry.Status.Set(0) == 1 {
entry.cron.Stop()
}
}

View File

@ -8,16 +8,14 @@
package gfcache
import (
"gitee.com/johng/gf/g/os/gcache"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/os/gfsnotify"
"gitee.com/johng/gf/g/os/gcache"
)
type Cache struct {
cap *gtype.Int // 缓存容量(byte)设置为0表示不限制
size *gtype.Int // 缓存大小(Byte)
cache *gcache.Cache // 缓存对象
notify *gfsnotify.Watcher // 文件监控管理对象
}
const (
@ -35,12 +33,10 @@ func New(cap ... int) *Cache {
if len(cap) > 0 {
c = cap[0]
}
notify, _ := gfsnotify.New()
return &Cache {
cap : gtype.NewInt(c),
size : gtype.NewInt(),
cache : gcache.New(),
notify : notify,
}
}

View File

@ -53,7 +53,7 @@ func (c *Cache) addMonitor(path string) {
if c.cache.Get(path) != nil {
return
}
c.notify.Add(path, func(event *gfsnotify.Event) {
gfsnotify.Add(path, func(event *gfsnotify.Event) {
//glog.Debug("gfcache:", event)
r := c.cache.Get(path).([]byte)
// 是否删除

View File

@ -14,6 +14,7 @@ import (
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/util/gregex"
"gitee.com/johng/gf/g/util/gstr"
"io"
"os"
"os/exec"
@ -251,7 +252,8 @@ func ScanDir(path string, pattern string, recursive ... bool) ([]string, error)
return list, nil
}
// 内部检索目录方法,支持递归,返回没有排序的文件绝对路径列表结果
// 内部检索目录方法,支持递归,返回没有排序的文件绝对路径列表结果
// pattern参数支持多个文件名称模式匹配使用','符号分隔多个模式。
func doScanDir(path string, pattern string, recursive ... bool) ([]string, error) {
var list []string
// 打开目录
@ -275,8 +277,10 @@ func doScanDir(path string, pattern string, recursive ... bool) ([]string, error
}
}
// 满足pattern才加入结果列表
if match, err := filepath.Match(pattern, name); err == nil && match {
list = append(list, path)
for _, p := range strings.Split(pattern, ",") {
if match, err := filepath.Match(strings.TrimSpace(p), name); err == nil && match {
list = append(list, path)
}
}
}
return list, nil
@ -317,7 +321,7 @@ func Dir(path string) string {
return filepath.Dir(path)
}
// 获取指定文件路径的文件扩展名
// 获取指定文件路径的文件扩展名(包含"."号)
func Ext(path string) string {
return filepath.Ext(path)
}
@ -374,56 +378,41 @@ func MainPkgPath() string {
if path != "" {
return path
}
f := ""
f := ""
goroot := runtime.GOROOT()
// runtime.GOROOT() 在windows下有可能是以'\'符号分隔,
// 而 runtime.Caller(i) 获取到的文件路径却是以'/'符号分隔,
// 因此这里统一转换为'/'符号再进行比较
goroot = gstr.Replace(goroot, "\\", "/")
for i := 1; i < 10000; i++ {
if _, file, _, ok := runtime.Caller(i); ok {
if strings.EqualFold("<autogenerated>", file) {
// 如果是通过init包方法进入那么无法得到准确的文件路径
f = ""
} else {
goroot := GoRootOfBuild()
if goroot != "" && !gregex.IsMatchString("^" + GoRootOfBuild(), file) {
// 不包含go源码路径
f = file
}
// 不包含go源码路径
if file != "" && goroot != "" &&
!gregex.IsMatchString("^" + goroot, file) &&
!strings.EqualFold("<autogenerated>", file) {
f = file
}
} else {
break
}
}
if f != "" {
p := Dir(f)
mainPkgPath.Set(p)
return p
}
return ""
}
// 编译时环境的GOROOT数值(对init初始化方法调用时无效获取不了ROOT值)
// 注意:可能返回空
func GoRootOfBuild() string {
if v := goRootOfBuild.Val(); v != "" {
return v
}
firstEntry := ""
for i := 0; i < 10000; i++ {
if _, file, _, ok := runtime.Caller(i); ok {
firstEntry = file
} else {
break
for {
p := Dir(f)
if p == f {
break
}
if paths, err := ScanDir(p, "*.go"); err == nil && len(paths) > 0 {
for _, path := range paths {
if gregex.IsMatchString(`package\s+main`, GetContents(path)) {
mainPkgPath.Set(p)
return p
}
}
}
f = p
}
}
if len(firstEntry) > 0 {
sep := "/"
array := strings.Split(firstEntry, sep)
if len(array) == 1 {
sep = "\\"
array = strings.Split(firstEntry, sep)
}
root := strings.Join(array[0 : len(array) - 3], sep)
goRootOfBuild.Set(root)
return root
}
return ""
}

View File

@ -16,8 +16,8 @@ import (
const (
// 方法中涉及到读取的时候的缓冲大小
gREAD_BUFFER = 1024
// 方法中涉及到文件指针池的默认缓存时间(秒)
gFILE_POOL_EXPIRE = 60
// 方法中涉及到文件指针池的默认缓存时间(秒)
gFILE_POOL_EXPIRE = 60000
)
// (文本)读取文件内容
@ -35,7 +35,7 @@ func GetBinContents(path string) []byte {
}
// 写入文件内容
func putContents(path string, data []byte, flag int, perm os.FileMode) error {
func putContents(path string, data []byte, flag int, perm int) error {
// 支持目录递归创建
dir := Dir(path)
if !Exists(dir) {
@ -43,8 +43,8 @@ func putContents(path string, data []byte, flag int, perm os.FileMode) error {
return err
}
}
// 创建/打开文件使用文件指针池默认60秒
f, err := gfpool.OpenFile(path, flag, perm, gFILE_POOL_EXPIRE*1000)
// 创建/打开文件
f, err := gfpool.Open(path, flag, os.FileMode(perm), gFILE_POOL_EXPIRE)
if err != nil {
return err
}
@ -83,11 +83,11 @@ func PutBinContentsAppend(path string, content []byte) error {
}
// 获得文件内容下一个指定字节的位置
func GetNextCharOffset(file *os.File, char byte, start int64) int64 {
func GetNextCharOffset(reader io.ReaderAt, char byte, start int64) int64 {
buffer := make([]byte, gREAD_BUFFER)
offset := start
for {
if n, err := file.ReadAt(buffer, offset); n > 0 {
if n, err := reader.ReadAt(buffer, offset); n > 0 {
for i := 0; i < n; i++ {
if buffer[i] == char {
return int64(i) + offset
@ -103,38 +103,38 @@ func GetNextCharOffset(file *os.File, char byte, start int64) int64 {
// 获得文件内容下一个指定字节的位置
func GetNextCharOffsetByPath(path string, char byte, start int64) int64 {
if f, err := gfpool.Open(path, os.O_RDONLY, gDEFAULT_PERM, gFILE_POOL_EXPIRE*1000); err == nil {
if f, err := gfpool.Open(path, os.O_RDONLY, gDEFAULT_PERM, gFILE_POOL_EXPIRE); err == nil {
defer f.Close()
return GetNextCharOffset(&f.File, char, start)
return GetNextCharOffset(f, char, start)
} else {
// panic(err)
panic(err)
}
return -1
}
// 获得文件内容直到下一个指定字节的位置(返回值包含该位置字符内容)
func GetBinContentsTilChar(file *os.File, char byte, start int64) ([]byte, int64) {
if offset := GetNextCharOffset(file, char, start); offset != -1 {
return GetBinContentsByTwoOffsets(file, start, offset + 1), offset
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
}
return nil, -1
}
// 获得文件内容直到下一个指定字节的位置(返回值包含该位置字符内容)
func GetBinContentsTilCharByPath(path string, char byte, start int64) ([]byte, int64) {
if f, err := gfpool.Open(path, os.O_RDONLY, gDEFAULT_PERM, gFILE_POOL_EXPIRE*1000); err == nil {
if f, err := gfpool.Open(path, os.O_RDONLY, gDEFAULT_PERM, gFILE_POOL_EXPIRE); err == nil {
defer f.Close()
return GetBinContentsTilChar(&f.File, char, start)
return GetBinContentsTilChar(f, char, start)
} else {
// panic(err)
panic(err)
}
return nil, -1
}
// 获得文件内容中两个offset之间的内容 [start, end)
func GetBinContentsByTwoOffsets(file *os.File, start int64, end int64) []byte {
func GetBinContentsByTwoOffsets(reader io.ReaderAt, start int64, end int64) []byte {
buffer := make([]byte, end - start)
if _, err := file.ReadAt(buffer, start); err != nil {
if _, err := reader.ReadAt(buffer, start); err != nil {
return nil
}
return buffer
@ -142,11 +142,11 @@ func GetBinContentsByTwoOffsets(file *os.File, start int64, end int64) []byte {
// 获得文件内容中两个offset之间的内容 [start, end)
func GetBinContentsByTwoOffsetsByPath(path string, start int64, end int64) []byte {
if f, err := gfpool.Open(path, os.O_RDONLY, gDEFAULT_PERM, gFILE_POOL_EXPIRE*1000); err == nil {
if f, err := gfpool.Open(path, os.O_RDONLY, gDEFAULT_PERM, gFILE_POOL_EXPIRE); err == nil {
defer f.Close()
return GetBinContentsByTwoOffsets(&f.File, start, end)
return GetBinContentsByTwoOffsets(f, start, end)
} else {
// panic(err)
panic(err)
}
return nil
}

View File

@ -8,23 +8,30 @@
package gfpool
import (
"os"
"sync"
"fmt"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/container/gpool"
"fmt"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/os/gfsnotify"
"os"
"sync"
)
// 文件指针池
type Pool struct {
pool *gpool.Pool // 底层对象池
id *gtype.Int // 指针池ID用以识别指针池是否重建
pool *gpool.Pool // 底层对象池
inited *gtype.Bool // 是否初始化(在执行第一次File方法后初始化主要用于监听的添加但是只能添加一次)
closeChan chan struct{} // 关闭事件
expire int // 过期时间
}
// 文件指针池指针
type File struct {
os.File // 底层文件指针
*os.File // 底层文件指针
mu sync.RWMutex // 互斥锁
pool *Pool // 所属池
poolid int // 所属池ID如果池ID不同表示池已经重建那么该文件指针也应当销毁不能重新丢到原有的池中
flag int // 打开标志
perm os.FileMode // 打开权限
path string // 绝对路径
@ -34,22 +41,19 @@ type File struct {
var pools = gmap.NewStringInterfaceMap()
// 获得文件对象,并自动创建指针池(过期时间单位:毫秒)
func Open(path string, flag int, perm os.FileMode, expire...int) (*File, error) {
func Open(path string, flag int, perm os.FileMode, expire...int) (file *File, err error) {
fpExpire := 0
if len(expire) > 0 {
fpExpire = expire[0]
}
key := fmt.Sprintf("%s&%d&%d&%d", path, flag, expire, perm)
result := pools.Get(key)
if result != nil {
return result.(*Pool).File()
}
pool := New(path, flag, perm, fpExpire)
pools.Set(key, pool)
pool := pools.GetOrSetFuncLock(fmt.Sprintf("%s&%d&%d&%d", path, flag, expire, perm), func() interface{} {
return New(path, flag, perm, fpExpire)
}).(*Pool)
return pool.File()
}
func OpenFile(path string, flag int, perm os.FileMode, expire...int) (*File, error) {
func OpenFile(path string, flag int, perm os.FileMode, expire...int) (file *File, err error) {
return Open(path, flag, perm, expire...)
}
@ -60,24 +64,36 @@ func New(path string, flag int, perm os.FileMode, expire...int) *Pool {
if len(expire) > 0 {
fpExpire = expire[0]
}
p := &Pool {}
p.pool = gpool.New(fpExpire, func() (interface{}, error) {
p := &Pool {
id : gtype.NewInt(),
expire : fpExpire,
inited : gtype.NewBool(),
closeChan : make(chan struct{}),
}
p.pool = newFilePool(p, path, flag, perm, fpExpire)
return p
}
// 创建文件指针池
func newFilePool(p *Pool, path string, flag int, perm os.FileMode, expire int) *gpool.Pool {
pool := gpool.New(expire, func() (interface{}, error) {
file, err := os.OpenFile(path, flag, perm)
if err != nil {
return nil, err
}
return &File{
File : *file,
pool : p,
flag : flag,
perm : perm,
path : path,
return &File {
File : file,
pool : p,
poolid : p.id.Val(),
flag : flag,
perm : perm,
path : path,
}, nil
})
p.pool.SetExpireFunc(func(i interface{}) {
pool.SetExpireFunc(func(i interface{}) {
i.(*File).File.Close()
})
return p
return pool
}
// 获得一个文件打开指针
@ -85,31 +101,49 @@ func (p *Pool) File() (*File, error) {
if v, err := p.pool.Get(); err != nil {
return nil, err
} else {
f := v.(*File)
f := v.(*File)
stat, err := os.Stat(f.path)
if f.flag & os.O_CREATE > 0 {
if _, err := os.Stat(f.path); os.IsNotExist(err) {
if file, err := os.OpenFile(f.path, f.flag, f.perm); err != nil {
return nil, err
} else {
f.File = *file
}
}
if os.IsNotExist(err) {
if file, err := os.OpenFile(f.path, f.flag, f.perm); err != nil {
return nil, err
} else {
f.File = file
if stat, err = f.Stat(); err != nil {
return nil, err
}
}
}
}
if f.flag & os.O_TRUNC > 0 {
if stat, err := f.Stat(); err == nil {
if stat.Size() > 0 {
if err := f.Truncate(0); err != nil {
return nil, err
}
}
}
if stat.Size() > 0 {
if err := f.Truncate(0); err != nil {
return nil, err
}
}
}
if f.flag & os.O_APPEND > 0 {
if _, err := f.Seek(0, 2); err != nil {
return nil, err
}
if _, err := f.Seek(0, 2); err != nil {
return nil, err
}
} else {
f.Seek(0, 0)
if _, err := f.Seek(0, 0); err != nil {
return nil, err
}
}
if !p.inited.Set(true) {
gfsnotify.Add(f.path, func(event *gfsnotify.Event) {
// 如果文件被删除或者重命名,立即重建指针池
if event.IsRemove() || event.IsRename() {
// 原有的指针都不要了
p.id.Add(1)
// Clear相当于重建指针池
p.pool.Clear()
// 为保证原子操作,但又不想加锁,
// 这里再执行一次原子Add将在两次Add中间可能分配出去的文件指针丢弃掉
p.id.Add(1)
}
}, false)
}
return f, nil
}
@ -117,12 +151,15 @@ func (p *Pool) File() (*File, error) {
// 关闭指针池(返回error是标准库io.ReadWriteCloser接口实现)
func (p *Pool) Close() error {
close(p.closeChan)
p.pool.Close()
return nil
}
// 获得底层文件指针(返回error是标准库io.ReadWriteCloser接口实现)
func (f *File) Close() error {
f.pool.pool.Put(f)
if f.poolid == f.pool.id.Val() {
f.pool.pool.Put(f)
}
return nil
}

View File

@ -5,17 +5,44 @@ import (
"os"
)
func Benchmark_os_Open_Close(b *testing.B) {
func Benchmark_os_Open_Close_ALLFlags(b *testing.B) {
for i := 0; i < b.N; i++ {
f, _ := os.OpenFile("/tmp/bench-test", os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0766)
f, _ := os.OpenFile("/tmp/bench-test", os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
f.Close()
}
}
func Benchmark_gfpool_Open_Close(b *testing.B) {
func Benchmark_gfpool_Open_Close_ALLFlags(b *testing.B) {
for i := 0; i < b.N; i++ {
f, _ := Open("/tmp/bench-test", os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0766)
f, _ := Open("/tmp/bench-test", os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666)
f.Close()
}
}
func Benchmark_os_Open_Close_RDWR(b *testing.B) {
for i := 0; i < b.N; i++ {
f, _ := os.OpenFile("/tmp/bench-test", os.O_RDWR, 0666)
f.Close()
}
}
func Benchmark_gfpool_Open_Close_RDWR(b *testing.B) {
for i := 0; i < b.N; i++ {
f, _ := Open("/tmp/bench-test", os.O_RDWR, 0666)
f.Close()
}
}
func Benchmark_os_Open_Close_RDONLY(b *testing.B) {
for i := 0; i < b.N; i++ {
f, _ := os.OpenFile("/tmp/bench-test", os.O_RDONLY, 0666)
f.Close()
}
}
func Benchmark_gfpool_Open_Close_RDONLY(b *testing.B) {
for i := 0; i < b.N; i++ {
f, _ := Open("/tmp/bench-test", os.O_RDONLY, 0666)
f.Close()
}
}

View File

@ -5,37 +5,55 @@
// You can obtain one at https://gitee.com/johng/gf.
// 文件监控.
// 使用时需要注意的是,一旦一个文件被删除,那么对其的监控将会失效。
package gfsnotify
import (
"container/list"
"errors"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/third/github.com/fsnotify/fsnotify"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/container/glist"
"gitee.com/johng/gf/g/container/gqueue"
"fmt"
"gitee.com/johng/gf/g/container/glist"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/container/gqueue"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/encoding/ghash"
"gitee.com/johng/gf/g/os/gcache"
"gitee.com/johng/gf/g/os/gcmd"
"gitee.com/johng/gf/g/os/genv"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/third/github.com/fsnotify/fsnotify"
)
// 监听管理对象
type Watcher struct {
watcher *fsnotify.Watcher // 底层fsnotify对象
events *gqueue.Queue // 过滤后的事件通知,不会出现重复事件
closeChan chan struct{} // 关闭事件
callbacks *gmap.StringInterfaceMap // 监听的回调函数
watcher *fsnotify.Watcher // 底层fsnotify对象
events *gqueue.Queue // 过滤后的事件通知,不会出现重复事件
closeChan chan struct{} // 关闭事件
callbacks *gmap.StringInterfaceMap // 监听的回调函数
cache *gcache.Cache // 缓存对象,用于事件重复过滤
}
// 注册的监听回调方法
type Callback struct {
Id int // 唯一ID
Func func(event *Event) // 回调方法
Path string // 监听的文件/目录
elem *list.Element // 指向监听链表中的元素项位置
parent *Callback // 父级callback有这个属性表示该callback为被自动管理的callback
subs *glist.List // 子级回调对象指针列表
}
// 监听事件对象
type Event struct {
Path string // 文件绝对路径
Op Op // 触发监听的文件操作
event fsnotify.Event // 底层事件对象
Path string // 文件绝对路径
Op Op // 触发监听的文件操作
Watcher *Watcher // 事件对应的监听对象
}
// 按位进行识别的操作集合
type Op uint32
// 必须放到一个const分组里面
const (
CREATE Op = 1 << iota
WRITE
@ -44,17 +62,53 @@ const (
CHMOD
)
// 全局监听对象,方便应用端调用
var watcher, _ = New()
const (
REPEAT_EVENT_FILTER_INTERVAL = 1 // (毫秒)重复事件过滤间隔
DEFAULT_WATCHER_COUNT = 4 // 默认创建的监控对象数量(使用哈希取模)
)
// 创建监听管理对象
var (
// 全局监听对象,方便应用端调用
watchers []*Watcher
// 全局默认的监听watcher数量
watcherCount int
// 默认的watchers是否初始化使用时才创建
watcherInited = gtype.NewBool()
// 回调方法ID与对象指针的映射哈希表用于根据ID快速查找回调对象
callbackIdMap = gmap.NewIntInterfaceMap()
)
// 初始化创建watcher对象用于包默认管理监听
func initWatcher() {
if !watcherInited.Set(true) {
// 默认的创建的inotify数量
watcherCount = gconv.Int(genv.Get("GF_INOTIFY_COUNT"))
if watcherCount == 0 {
watcherCount = gconv.Int(gcmd.Option.Get("gf.inotify-count"))
}
if watcherCount == 0 {
watcherCount = DEFAULT_WATCHER_COUNT
}
watchers = make([]*Watcher, watcherCount)
for i := 0; i < watcherCount; i++ {
if w, err := New(); err == nil {
watchers[i] = w
} else {
panic(err)
}
}
}
}
// 创建监听管理对象主要注意的是创建监听对象会占用系统的inotify句柄数量受到 fs.inotify.max_user_instances 的限制
func New() (*Watcher, error) {
if watch, err := fsnotify.NewWatcher(); err == nil {
w := &Watcher {
watcher : watch,
events : gqueue.New(),
closeChan : make(chan struct{}),
callbacks : gmap.NewStringInterfaceMap(),
cache : gcache.New(),
watcher : watch,
events : gqueue.New(),
closeChan : make(chan struct{}),
callbacks : gmap.NewStringInterfaceMap(),
}
w.startWatchLoop()
w.startEventLoop()
@ -65,163 +119,29 @@ func New() (*Watcher, error) {
}
// 添加对指定文件/目录的监听,并给定回调函数;如果给定的是一个目录,默认递归监控。
func Add(path string, callback func(event *Event), recursive...bool) error {
if watcher == nil {
return errors.New("global watcher creating failed")
}
return watcher.Add(path, callback, recursive...)
func Add(path string, callbackFunc func(event *Event), recursive...bool) (callback *Callback, err error) {
return getWatcherByPath(path).Add(path, callbackFunc, recursive...)
}
// 移除监听,默认递归删除。
// 递归移除对指定文件/目录的所有监听回调
func Remove(path string) error {
if watcher == nil {
return errors.New("global watcher creating failed")
return getWatcherByPath(path).Remove(path)
}
// 根据指定的回调函数ID移出指定的inotify回调函数
func RemoveCallback(callbackId int) error {
callback := (*Callback)(nil)
if r := callbackIdMap.Get(callbackId); r != nil {
callback = r.(*Callback)
}
return watcher.Remove(path)
}
// 关闭监听管理对象
func (w *Watcher) Close() {
w.watcher.Close()
w.events.Close()
close(w.closeChan)
}
// 添加对指定文件/目录的监听,并给定回调函数
func (w *Watcher) addWatch(path string, callback func(event *Event)) error {
// 这里统一转换为当前系统的绝对路径,便于统一监控文件名称
t := gfile.RealPath(path)
if t == "" {
return errors.New(fmt.Sprintf(`"%s" does not exist`, path))
if callback == nil {
return errors.New(fmt.Sprintf(`callback for id %d not found`, callbackId))
}
path = t
// 注册回调函数
w.callbacks.LockFunc(func(m map[string]interface{}) {
var result interface{}
if v, ok := m[path]; !ok {
result = glist.New()
m[path] = result
} else {
result = v
}
result.(*glist.List).PushBack(callback)
})
// 添加底层监听
w.watcher.Add(path)
return nil
return getWatcherByPath(callback.Path).RemoveCallback(callbackId)
}
// 添加监控path参数支持文件或者目录路径recursive为非必需参数默认为递归添加监控(当path为目录时)
func (w *Watcher) Add(path string, callback func(event *Event), recursive...bool) error {
if gfile.IsDir(path) && (len(recursive) == 0 || recursive[0]) {
paths, _ := gfile.ScanDir(path, "*", true)
list := []string{path}
list = append(list, paths...)
for _, v := range list {
if err := w.addWatch(v, callback); err != nil {
return err
}
}
return nil
} else {
return w.addWatch(path, callback)
}
// 根据path计算对应的watcher对象
func getWatcherByPath(path string) *Watcher {
initWatcher()
return watchers[ghash.BKDRHash([]byte(path)) % uint32(watcherCount)]
}
// 移除监听
func (w *Watcher) removeWatch(path string) error {
w.callbacks.Remove(path)
return w.watcher.Remove(path)
}
// 递归移除监听
func (w *Watcher) Remove(path string) error {
if gfile.IsDir(path) {
paths, _ := gfile.ScanDir(path, "*", true)
list := []string{path}
list = append(list, paths...)
for _, v := range list {
if err := w.removeWatch(v); err != nil {
return err
}
}
return nil
} else {
return w.removeWatch(path)
}
}
// 监听循环
func (w *Watcher) startWatchLoop() {
go func() {
for {
select {
// 关闭事件
case <- w.closeChan:
return
// 监听事件
case ev := <- w.watcher.Events:
//glog.Debug("gfsnotify:", ev)
w.events.Push(&Event{
Path : ev.Name,
Op : Op(ev.Op),
})
case err := <- w.watcher.Errors:
glog.Error("error : ", err);
}
}
}()
}
// 检索给定path的回调方法**列表**
func (w *Watcher) getCallbacks(path string) *glist.List {
for path != "/" {
if l := w.callbacks.Get(path); l != nil {
return l.(*glist.List)
} else {
path = gfile.Dir(path)
}
}
return nil
}
// 事件循环
func (w *Watcher) startEventLoop() {
go func() {
for {
if v := w.events.Pop(); v != nil {
event := v.(*Event)
if event.IsRemove() {
if gfile.Exists(event.Path) {
// 如果是文件删除事件,判断该文件是否存在,如果存在,那么将此事件认为“假删除”,
// 并重新添加监控(底层fsnotify会自动删除掉监控这里重新添加回去)
w.watcher.Add(event.Path)
continue
} else {
// 如果是真实删除,那么递归删除监控信息
w.Remove(event.Path)
}
}
callbacks := w.getCallbacks(event.Path)
// 如果创建了新的目录,那么将这个目录递归添加到监控中
if event.IsCreate() && gfile.IsDir(event.Path) {
for _, callback := range callbacks.FrontAll() {
w.Add(event.Path, callback.(func(event *Event)))
}
}
if callbacks != nil {
go func(callbacks *glist.List) {
for _, callback := range callbacks.FrontAll() {
callback.(func(event *Event))(event)
}
}(callbacks)
}
} else {
break
}
}
}()
}

View File

@ -6,9 +6,13 @@
package gfsnotify
func (e *Event) String() string {
return e.event.String()
}
// 文件/目录创建
func (e *Event) IsCreate() bool {
return e.Op & CREATE == CREATE
return e.Op == 1 || e.Op & CREATE == CREATE
}
// 文件/目录修改
@ -29,4 +33,4 @@ func (e *Event) IsRename() bool {
// 文件/目录修改权限
func (e *Event) IsChmod() bool {
return e.Op & CHMOD == CHMOD
}
}

View File

@ -0,0 +1,96 @@
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
package gfsnotify
import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
)
// 获取指定文件路径的目录地址绝对路径
func fileDir(path string) string {
return filepath.Dir(path)
}
// 将所给定的路径转换为绝对路径
// 并判断文件路径是否存在,如果文件不存在,那么返回空字符串
func fileRealPath(path string) string {
p, err := filepath.Abs(path)
if err != nil {
return ""
}
if !fileExists(p) {
return ""
}
return p
}
// 判断所给路径文件/文件夹是否存在
func fileExists(path string) bool {
if _, err := os.Stat(path); !os.IsNotExist(err) {
return true
}
return false
}
// 判断所给路径是否为文件夹
func fileIsDir(path string) bool {
s, err := os.Stat(path)
if err != nil {
return false
}
return s.IsDir()
}
// 打开目录,并返回其下一级文件列表(绝对路径),按照文件名称大小写进行排序,支持目录递归遍历。
func fileScanDir(path string, pattern string, recursive ... bool) ([]string, error) {
list, err := fileDoScanDir(path, pattern, recursive...)
if err != nil {
return nil, err
}
if len(list) > 0 {
sort.Strings(list)
}
return list, nil
}
// 内部检索目录方法,支持递归,返回没有排序的文件绝对路径列表结果。
// pattern参数支持多个文件名称模式匹配使用','符号分隔多个模式。
func fileDoScanDir(path string, pattern string, recursive ... bool) ([]string, error) {
var list []string
// 打开目录
dfile, err := os.Open(path)
if err != nil {
return nil, err
}
defer dfile.Close()
// 读取目录下的文件列表
names, err := dfile.Readdirnames(-1)
if err != nil {
return nil, err
}
// 是否递归遍历
for _, name := range names {
path := fmt.Sprintf("%s%s%s", path, string(filepath.Separator), name)
if fileIsDir(path) && len(recursive) > 0 && recursive[0] {
array, _ := fileDoScanDir(path, pattern, true)
if len(array) > 0 {
list = append(list, array...)
}
}
// 满足pattern才加入结果列表
for _, p := range strings.Split(pattern, ",") {
if match, err := filepath.Match(strings.TrimSpace(p), name); err == nil && match {
list = append(list, path)
}
}
}
return list, nil
}

View File

@ -0,0 +1,284 @@
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
package gfsnotify
import (
"errors"
"fmt"
"gitee.com/johng/gf/g/container/glist"
"gitee.com/johng/gf/g/os/gtime"
)
// 关闭监听管理对象
func (w *Watcher) Close() {
w.watcher.Close()
w.events.Close()
close(w.closeChan)
}
// 添加对指定文件/目录的监听,并给定回调函数
func (w *Watcher) addWatch(path string, calbackFunc func(event *Event), parentCallback *Callback) (callback *Callback, err error) {
// 这里统一转换为当前系统的绝对路径,便于统一监控文件名称
t := fileRealPath(path)
if t == "" {
return nil, errors.New(fmt.Sprintf(`"%s" does not exist`, path))
}
path = t
// 添加成功后会注册该callback id到全局的哈希表并绑定到父级的注册回调中
defer func() {
if err == nil {
if parentCallback == nil {
// 只有主callback才记录到id map中因为子callback是自动管理的无需添加到全局id映射map中
callbackIdMap.Set(callback.Id, callback)
}
if parentCallback != nil {
// 添加到直属父级的subs属性中建立关联关系便于后续删除
parentCallback.subs.PushBack(callback)
}
}
}()
callback = &Callback {
Id : int(gtime.Nanosecond()),
Func : calbackFunc,
Path : path,
subs : glist.New(),
parent : parentCallback,
}
// 注册回调函数
w.callbacks.LockFunc(func(m map[string]interface{}) {
var result interface{}
if v, ok := m[path]; !ok {
result = glist.New()
m[path] = result
} else {
result = v
}
callback.elem = result.(*glist.List).PushBack(callback)
})
// 添加底层监听
w.watcher.Add(path)
return
}
// 添加监控path参数支持文件或者目录路径recursive为非必需参数默认为递归添加监控(当path为目录时)。
// 如果添加目录这里只会返回目录的callback按照callback删除时会递归删除。
func (w *Watcher) addWithCallback(parentCallback *Callback, path string, callbackFunc func(event *Event), recursive...bool) (callback *Callback, err error) {
// 首先添加这个目录
if callback, err = w.addWatch(path, callbackFunc, parentCallback); err != nil {
return nil, err
}
// 其次递归添加其下的文件/目录
if fileIsDir(path) && (len(recursive) == 0 || recursive[0]) {
paths, _ := fileScanDir(path, "*", true)
for _, v := range paths {
w.addWatch(v, callbackFunc, callback)
}
}
return
}
// 添加监控path参数支持文件或者目录路径recursive为非必需参数默认为递归添加监控(当path为目录时)。
// 如果添加目录这里只会返回目录的callback按照callback删除时会递归删除。
func (w *Watcher) Add(path string, callbackFunc func(event *Event), recursive...bool) (callback *Callback, err error) {
return w.addWithCallback(nil, path, callbackFunc, recursive...)
}
// 递归移除对指定文件/目录的所有监听回调
func (w *Watcher) Remove(path string) error {
if fileIsDir(path) {
paths, _ := fileScanDir(path, "*", true)
paths = append(paths, path)
for _, v := range paths {
if err := w.removeAll(v); err != nil {
return err
}
}
return nil
} else {
return w.removeAll(path)
}
}
// 移除对指定文件/目录的所有监听
func (w *Watcher) removeAll(path string) error {
// 首先移除所有该path的回调注册
if r := w.callbacks.Get(path); r != nil {
list := r.(*glist.List)
for {
if r := list.PopFront(); r != nil {
w.removeCallback(r.(*Callback))
} else {
break
}
}
}
// 其次移除该path的监听注册
w.callbacks.Remove(path)
// 最后移除底层的监听
return w.watcher.Remove(path)
}
// 根据指定的回调函数ID移出指定的inotify回调函数
func (w *Watcher) RemoveCallback(callbackId int) error {
callback := (*Callback)(nil)
if r := callbackIdMap.Get(callbackId); r != nil {
callback = r.(*Callback)
}
if callback == nil {
return errors.New(fmt.Sprintf(`callback for id %d not found`, callbackId))
}
w.removeCallback(callback)
return nil
}
// 移除对指定文件/目录的所有监听
func (w *Watcher) removeCallback(callback *Callback) error {
if r := w.callbacks.Get(callback.Path); r != nil {
list := r.(*glist.List)
list.Remove(callback.elem)
// 如果存在子级callback那么也一并递归删除
if callback.subs.Len() > 0 {
for {
if r := callback.subs.PopFront(); r != nil {
w.removeCallback(r.(*Callback))
} else {
break
}
}
}
// 如果该文件/目录的所有回调都被删除,那么移除监听
if list.Len() == 0 {
return w.watcher.Remove(callback.Path)
}
} else {
return errors.New(fmt.Sprintf(`callbacks not found for "%s"`, callback.Path))
}
return nil
}
// 监听循环
func (w *Watcher) startWatchLoop() {
go func() {
for {
select {
// 关闭事件
case <- w.closeChan:
return
// 监听事件
case ev := <- w.watcher.Events:
key := ev.String()
if !w.cache.Contains(key) {
w.cache.Set(key, struct {}{}, REPEAT_EVENT_FILTER_INTERVAL)
w.events.Push(&Event{
event : ev,
Path : ev.Name,
Op : Op(ev.Op),
Watcher : w,
})
}
case err := <- w.watcher.Errors:
panic("error : " + err.Error());
}
}
}()
}
// 检索给定path的回调方法**列表**
func (w *Watcher) getCallbacks(path string) *glist.List {
for {
if l := w.callbacks.Get(path); l != nil {
return l.(*glist.List)
} else {
if p := fileDir(path); p != path {
path = p
} else {
break
}
}
}
return nil
}
// 获得真正监听的文件路径,判断规则:
// 1、在 callbacks 中应当有回调注册函数(否则监听根本没意义)
// 2、如果该path下不存在回调注册函数则按照path长度从右往左递减直到减到目录地址为止(不包含)
// 3、如果仍旧无法匹配回调函数那么忽略否则使用查找到的新path覆盖掉event的path
func (w *Watcher) getWatchTruePath(path string) string {
if w.getCallbacks(path) != nil {
return path
}
dirPath := fileDir(path)
for {
path = path[0 : len(path) - 1]
if path == dirPath {
break
}
if w.getCallbacks(path) != nil {
return path
}
}
return ""
}
// 事件循环
func (w *Watcher) startEventLoop() {
go func() {
for {
if v := w.events.Pop(); v != nil {
event := v.(*Event)
if path := w.getWatchTruePath(event.Path); path == "" {
continue
} else {
event.Path = path
}
switch {
// 如果是删除操作,那么需要判断是否文件真正不存在了,如果存在,那么将此事件认为“假删除”
case event.IsRemove():
if fileExists(event.Path) {
// 重新添加监控(底层fsnotify会自动删除掉监控这里重新添加回去)
// 注意这里调用的是底层fsnotify添加监控只会产生回调事件并不会使回调函数重复注册
w.watcher.Add(event.Path)
// 修改事件操作为重命名(相当于重命名为自身名称,最终名称没变)
event.Op = RENAME
} else {
// 如果是真实删除,那么递归删除监控信息
w.Remove(event.Path)
}
// 如果是删除操作,那么需要判断是否文件真正不存在了,如果存在,那么将此事件认为“假命名”
// (特别是某些编辑器在编辑文件时会先对文件RENAME再CHMOD)
case event.IsRename():
if fileExists(event.Path) {
// 重新添加监控
w.watcher.Add(event.Path)
}
}
callbacks := w.getCallbacks(event.Path)
// 如果创建了新的目录,那么将这个目录递归添加到监控中
if event.IsCreate() && fileIsDir(event.Path) {
for _, v := range callbacks.FrontAll() {
callback := v.(*Callback)
w.addWithCallback(callback, event.Path, callback.Func)
}
}
// 执行回调处理,异步处理
if callbacks != nil {
go func(callbacks *glist.List) {
for _, v := range callbacks.FrontAll() {
go v.(*Callback).Func(event)
}
}(callbacks)
}
} else {
break
}
}
}()
}

View File

@ -11,6 +11,7 @@ package glog
import (
"gitee.com/johng/gf/g/container/gtype"
"io"
)
const (
@ -47,6 +48,16 @@ func SetLevel(level int) {
defaultLevel.Set(level)
}
// 可自定义IO接口IO可以是文件输出、标准输出、网络输出
func SetWriter(writer io.Writer) {
logger.SetWriter(writer)
}
// 返回自定义的IO默认为nil
func GetWriter() io.Writer {
return logger.GetWriter()
}
// 获取全局的日志记录等级
func GetLevel() int {
return defaultLevel.Val()
@ -77,6 +88,16 @@ func GetBacktrace(skip...int) string {
return logger.GetBacktrace(skip...)
}
// 是否关闭全局的backtrace信息
func SetBacktrace(enabled bool) {
logger.SetBacktrace(enabled)
}
// 链式操作设置下一次写入日志内容的Writer
func To(writer io.Writer) *Logger {
return logger.To(writer)
}
// 设置下一次输出的分类,支持多级分类设置
func Cat(category string) *Logger {
return logger.Cat(category)

View File

@ -8,19 +8,19 @@
package glog
import (
"os"
"io"
"time"
"fmt"
"strings"
"runtime"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/util/gregex"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/os/gmlock"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/os/gfpool"
"sync"
"gitee.com/johng/gf/g/os/gmlock"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/util/gregex"
"io"
"os"
"runtime"
"strings"
"sync"
"time"
)
type Logger struct {
@ -39,6 +39,8 @@ type Logger struct {
const (
gDEFAULT_FILE_FORMAT = `{Y-m-d}.log`
gDEFAULT_FILE_POOL_FLAGS = os.O_CREATE|os.O_WRONLY|os.O_APPEND
gDEFAULT_FPOOL_PERM = os.FileMode(0666)
gDEFAULT_FPOOL_EXPIRE = 60000
)
var (
@ -113,9 +115,9 @@ func (l *Logger) SetBacktraceSkip(skip int) {
}
// 可自定义IO接口IO可以是文件输出、标准输出、网络输出
func (l *Logger) SetWriter(w io.Writer) {
func (l *Logger) SetWriter(writer io.Writer) {
l.mu.Lock()
l.io = w
l.io = writer
l.mu.Unlock()
}
@ -135,7 +137,7 @@ func (l *Logger) getFilePointer() *gfpool.File {
return gtime.Now().Format(strings.Trim(s, "{}"))
})
fpath := path + gfile.Separator + file
if fp, err := gfpool.Open(fpath, gDEFAULT_FILE_POOL_FLAGS, 0666); err == nil {
if fp, err := gfpool.Open(fpath, gDEFAULT_FILE_POOL_FLAGS, gDEFAULT_FPOOL_PERM, gDEFAULT_FPOOL_EXPIRE); err == nil {
return fp
} else {
fmt.Fprintln(os.Stderr, err)
@ -192,13 +194,13 @@ func (l *Logger) print(std io.Writer, s string) {
fmt.Fprintln(os.Stderr, err.Error())
}
}
// 当没有设置writer时需要判断是否允许输出到标准输出
if l.alsoStdPrint.Val() {
l.doStdLockPrint(std, s)
}
} else {
l.doStdLockPrint(writer, s)
}
// 是否允许输出到标准输出
if l.alsoStdPrint.Val() {
l.doStdLockPrint(std, s)
}
}
// 并发安全打印到标准输出
@ -249,20 +251,20 @@ func (l *Logger) GetBacktrace(skip...int) string {
from := 0
// 首先定位业务文件开始位置
for i := 0; i < 10; i++ {
if _, cfile, _, ok := runtime.Caller(i); ok {
if !gregex.IsMatchString("/g/os/glog/glog.+$", cfile) {
if _, file, _, ok := runtime.Caller(i); ok {
if !gregex.IsMatchString("/g/os/glog/glog.+$", file) {
from = i
break
}
}
}
// 从业务文件开始位置根据自定义的skip开始backtrace
goroot := gfile.GoRootOfBuild()
goRoot := runtime.GOROOT()
for i := from + customSkip + l.btSkip.Val(); i < 10000; i++ {
if _, cfile, cline, ok := runtime.Caller(i); ok && cfile != "" {
if _, file, cline, ok := runtime.Caller(i); ok && file != "" {
// 不打印出go源码路径及glog包文件路径日志打印必须从业务源码文件开始且从glog包文件开始检索
if (goroot == "" || !gregex.IsMatchString("^" + goroot, cfile)) && !gregex.IsMatchString(`<autogenerated>`, cfile) {
backtrace += fmt.Sprintf(`%d. %s:%d%s`, index, cfile, cline, ln)
if (goRoot == "" || !gregex.IsMatchString("^" + goRoot, file)) && !gregex.IsMatchString(`<autogenerated>`, file) {
backtrace += fmt.Sprintf(`%d. %s:%d%s`, index, file, cline, ln)
index++
}
} else {

View File

@ -6,7 +6,22 @@
package glog
import "gitee.com/johng/gf/g/os/gfile"
import (
"gitee.com/johng/gf/g/os/gfile"
"io"
)
// 链式操作设置下一次写入日志内容的Writer
func (l *Logger) To(writer io.Writer) *Logger {
logger := (*Logger)(nil)
if l.pr == nil {
logger = l.Clone()
} else {
logger = l
}
logger.SetWriter(writer)
return logger
}
// 链式操作,设置下一次输出的日志分类(可以按照文件目录层级设置)在当前logpath或者当前工作目录下创建category目录
// 这是一个链式操作,可以设置多个分类,将会创建层级的日志分类目录。

View File

@ -145,7 +145,7 @@ func searchBinFromEnvPath(file string) string {
switch runtime.GOOS {
case "windows":
array = strings.Split(os.Getenv("Path"), ";")
if gfile.Ext(file) != "exe" {
if gfile.Ext(file) != ".exe" {
file += ".exe"
}
default:

View File

@ -9,13 +9,13 @@
package gspath
import (
"sync"
"errors"
"strings"
"gitee.com/johng/gf/g/os/gfile"
"fmt"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/os/gfsnotify"
"gitee.com/johng/gf/g/os/glog"
"strings"
"sync"
)
// 文件目录搜索管理对象
@ -33,46 +33,52 @@ func New () *SPath {
}
// 设置搜索路径,只保留当前设置项,其他搜索路径被清空
func (sp *SPath) Set(path string) error {
r := gfile.RealPath(path)
if r == "" {
r = sp.Search(path)
if r == "" {
r = gfile.RealPath(gfile.Pwd() + gfile.Separator + path)
func (sp *SPath) Set(path string) (realpath string, err error) {
realpath = gfile.RealPath(path)
if realpath == "" {
realpath = sp.Search(path)
if realpath == "" {
realpath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path)
}
}
if r != "" && gfile.IsDir(r) {
r = strings.TrimRight(r, gfile.Separator)
if realpath == "" {
return realpath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
}
if realpath != "" && gfile.IsDir(realpath) {
realpath = strings.TrimRight(realpath, gfile.Separator)
sp.mu.Lock()
sp.paths = []string{r}
sp.paths = []string{realpath}
sp.mu.Unlock()
sp.cache.Clear()
glog.Debug("gspath.SetPath:", r)
return nil
//glog.Debug("gspath.SetPath:", r)
return realpath, nil
}
glog.Warning("gspath.SetPath failed:", path)
return errors.New("invalid path:" + path)
//glog.Warning("gspath.SetPath failed:", path)
return realpath, errors.New("invalid path:" + path)
}
// 添加搜索路径
func (sp *SPath) Add(path string) error {
r := gfile.RealPath(path)
if r == "" {
r = sp.Search(path)
if r == "" {
r = gfile.RealPath(gfile.Pwd() + gfile.Separator + path)
func (sp *SPath) Add(path string) (realpath string, err error) {
realpath = gfile.RealPath(path)
if realpath == "" {
realpath = sp.Search(path)
if realpath == "" {
realpath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path)
}
}
if r != "" && gfile.IsDir(r) {
r = strings.TrimRight(r, gfile.Separator)
sp.mu.Lock()
sp.paths = append(sp.paths, r)
sp.mu.Unlock()
glog.Debug("gspath.Add:", r)
return nil
if realpath == "" {
return realpath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
}
glog.Warning("gspath.Add failed:", path)
return errors.New("invalid path:" + path)
if realpath != "" && gfile.IsDir(realpath) {
realpath = strings.TrimRight(realpath, gfile.Separator)
sp.mu.Lock()
sp.paths = append(sp.paths, realpath)
sp.mu.Unlock()
//glog.Debug("gspath.Add:", r)
return realpath, nil
}
//glog.Warning("gspath.Add failed:", path)
return realpath, errors.New("invalid path:" + path)
}
// 按照优先级搜索文件,返回搜索到的文件绝对路径,找不到该文件时,返回空字符串
@ -106,9 +112,9 @@ func (sp *SPath) Size() int {
// 添加文件监控,当文件删除时,同时也删除搜索结果缓存
func (sp *SPath) addMonitor(name, path string) {
glog.Debug("gspath.addMonitor:", name, path)
//glog.Debug("gspath.addMonitor:", name, path)
gfsnotify.Add(path, func(event *gfsnotify.Event) {
glog.Debug("gspath.monitor:", event)
//glog.Debug("gspath.monitor:", event)
if event.IsRemove() {
sp.cache.Remove(name)
}

View File

@ -8,11 +8,12 @@
package gtime
import (
"time"
"regexp"
"strings"
"strconv"
"errors"
"gitee.com/johng/gf/g/util/gregex"
"regexp"
"strconv"
"strings"
"time"
)
const (
@ -25,14 +26,49 @@ const (
// "2018-02-09 20:46:17.897",
// "2018-02-09T20:46:17Z",
// "2018-02-09 20:46:17",
// "2018/10/31 - 16:38:46"
// "2018-02-09",
// 日期连接符号支持'-'或者'/'
TIME_REAGEX_PATTERN = `(\d{2,4}[-/]\d{2}[-/]\d{2})[\sT]{0,1}(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)`
TIME_REAGEX_PATTERN1 = `(\d{2,4}[-/]\d{2}[-/]\d{2})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)`
// 01-Nov-2018 11:50:28
// 01/Nov/2018 11:50:28
// 01/Nov/2018:11:50:28
// 01/Nov/18 11:50:28
// 01/Nov/18 11:50:28
TIME_REAGEX_PATTERN2 = `(\d{1,2}[-/][A-Za-z]{3,}[-/]\d{2,4})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)`
)
var (
// 使用正则判断会比直接使用ParseInLocation挨个轮训判断要快很多
timeRegex, _ = regexp.Compile(TIME_REAGEX_PATTERN)
timeRegex1, _ = regexp.Compile(TIME_REAGEX_PATTERN1)
timeRegex2, _ = regexp.Compile(TIME_REAGEX_PATTERN2)
// 月份英文与阿拉伯数字对应关系
monthMap = map[string]int {
"jan" : 1,
"feb" : 2,
"mar" : 3,
"apr" : 4,
"may" : 5,
"jun" : 6,
"jul" : 7,
"aug" : 8,
"sep" : 9,
"sept" : 9,
"oct" : 10,
"nov" : 11,
"dec" : 12,
"january" : 1,
"february" : 2,
"march" : 3,
"april" : 4,
"june" : 6,
"july" : 7,
"august" : 8,
"september" : 9,
"october" : 10,
"november" : 11,
"december" : 12,
}
)
// 类似与js中的SetTimeout一段时间后执行回调函数
@ -95,53 +131,73 @@ func Datetime() string {
return time.Now().Format("2006-01-02 15:04:05")
}
// 字符串转换为时间对象
func StrToTime(str string) (time.Time, error) {
var result time.Time
var local = time.Local
if match := timeRegex.FindStringSubmatch(str); len(match) > 0 {
var year, month, day, hour, min, sec, nsec int
var array []string
// 日期(支持'-'或'/'连接符号)
array = strings.Split(match[1], "-")
if len(array) < 3 {
array = strings.Split(match[1], "/")
// 解析日期字符串(支持'-'或'/'连接符号)
func parseDateStr(s string) (year, month, day int) {
array := strings.Split(s, "-")
if len(array) < 3 {
array = strings.Split(s, "/")
}
if len(array) >= 3 {
// 年是否为缩写,如果是,那么需要补上前缀
year, _ = strconv.Atoi(array[0])
if year < 100 {
year = int(time.Now().Year()/100)*100 + year
}
if len(array) >= 3 {
// 年是否为缩写,如果是,那么需要补上前缀
year, _ = strconv.Atoi(array[0])
if year < 100 {
year = int(time.Now().Year()/100)*100 + year
}
month, _ = strconv.Atoi(array[1])
day, _ = strconv.Atoi(array[2])
month, _ = strconv.Atoi(array[1])
day, _ = strconv.Atoi(array[2])
}
return
}
// 字符串转换为时间对象第二个参数指定格式的format(如: Y-m-d H:i:s)当指定第二个参数时同StrToTimeFormat方法
func StrToTime(str string, format...string) (*Time, error) {
if len(format) > 0 {
return StrToTimeFormat(str, format[0])
}
var year, month, day int
var hour, min, sec, nsec int
var match []string
var local = time.Local
if match = timeRegex1.FindStringSubmatch(str); len(match) > 0 {
for k, v := range match {
match[k] = strings.TrimSpace(v)
}
// 时间
if len(match[2]) > 0 {
array = strings.Split(match[2], ":")
hour, _ = strconv.Atoi(array[0])
if len(array) >= 2 {
min, _ = strconv.Atoi(array[1])
}
if len(array) >= 3 {
sec, _ = strconv.Atoi(array[2])
}
year, month, day = parseDateStr(match[1])
} else if match = timeRegex2.FindStringSubmatch(str); len(match) > 0 {
for k, v := range match {
match[k] = strings.TrimSpace(v)
}
// 纳秒,检查并执行位补齐
if len(match[3]) > 0 {
nsec, _ = strconv.Atoi(match[3])
for i := 0; i < 9 - len(match[3]); i++ {
nsec *= 10
}
year, month, day = parseDateStr(match[1])
} else {
return nil, errors.New("unsupported time format")
}
// 时间
if len(match[2]) > 0 {
s := strings.Replace(match[2], ":", "", -1)
if len(s) < 6 {
s += strings.Repeat("0", 6 - len(s))
}
// 如果字符串中有时区信息(具体时间信息)那么执行时区转换将时区转成UTC
if match[4] != "" && match[6] == "" {
match[6] = "000000"
hour, _ = strconv.Atoi(s[0 : 2])
min, _ = strconv.Atoi(s[2 : 4])
sec, _ = strconv.Atoi(s[4 : 6])
}
// 纳秒,检查并执行位补齐
if len(match[3]) > 0 {
nsec, _ = strconv.Atoi(match[3])
for i := 0; i < 9 - len(match[3]); i++ {
nsec *= 10
}
// 如果offset有值优先处理offset否则处理后面的时区名称
if match[6] != "" {
zone := strings.Replace(match[6], ":", "", -1)
zone = strings.TrimLeft(zone, "+-")
}
// 如果字符串中有时区信息(具体时间信息)那么执行时区转换将时区转成UTC
if match[4] != "" && match[6] == "" {
match[6] = "000000"
}
// 如果offset有值优先处理offset否则处理后面的时区名称
if match[6] != "" {
zone := strings.Replace(match[6], ":", "", -1)
zone = strings.TrimLeft(zone, "+-")
if len(zone) <= 6 {
zone += strings.Repeat("0", 6 - len(zone))
h, _ := strconv.Atoi(zone[0 : 2])
m, _ := strconv.Atoi(zone[2 : 4])
@ -157,65 +213,89 @@ func StrToTime(str string) (time.Time, error) {
operation = "-"
}
switch operation {
case "+":
if h > 0 {
hour -= h
}
if m > 0 {
min -= m
}
if s > 0 {
sec -= s
}
case "-":
if h > 0 {
hour += h
}
if m > 0 {
min += m
}
if s > 0 {
sec += s
}
case "+":
if h > 0 {
hour -= h
}
if m > 0 {
min -= m
}
if s > 0 {
sec -= s
}
case "-":
if h > 0 {
hour += h
}
if m > 0 {
min += m
}
if s > 0 {
sec += s
}
}
}
}
// 生成UTC时间对象
result = time.Date(year, time.Month(month), day, hour, min, sec, nsec, local)
return result, nil
}
return result, errors.New("unsupported time format")
// 统一生成UTC时间对象
return NewFromTime(time.Date(year, time.Month(month), day, hour, min, sec, nsec, local)), nil
}
// 时区转换
func ConvertZone(strTime string, toZone string, fromZone...string) (time.Time, error) {
func ConvertZone(strTime string, toZone string, fromZone...string) (*Time, error) {
t, err := StrToTime(strTime)
if err != nil {
return time.Time{}, err
return nil, err
}
if len(fromZone) > 0 {
if l, err := time.LoadLocation(fromZone[0]); err != nil {
return time.Time{}, err
return nil, err
} else {
t = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), l)
t.Time = time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Time.Second(), t.Time.Nanosecond(), l)
}
}
if l, err := time.LoadLocation(toZone); err != nil {
return time.Time{}, err
return nil, err
} else {
return t.In(l), nil
return t.ToLocation(l), nil
}
}
// 字符串转换为时间对象指定字符串时间格式format格式形如Y-m-d H:i:s
func StrToTimeFormat(str string, format string) (time.Time, error) {
func StrToTimeFormat(str string, format string) (*Time, error) {
return StrToTimeLayout(str, formatToStdLayout(format))
}
// 字符串转换为时间对象通过标准库layout格式进行解析layout格式形如2006-01-02 15:04:05
func StrToTimeLayout(str string, layout string) (time.Time, error) {
func StrToTimeLayout(str string, layout string) (*Time, error) {
if t, err := time.ParseInLocation(layout, str, time.Local); err == nil {
return t, nil
return NewFromTime(t), nil
} else {
return time.Time{}, err
return nil, err
}
}
// 从字符串内容中(也可以是文件名称等等)解析时间并返回解析成功的时间对象否则返回nil。
// 注意当内容中存在多个时间时,会解析第一个。
// format参数可以指定需要解析的时间格式。
func ParseTimeFromContent(content string, format...string) *Time {
if len(format) > 0 {
if match, err := gregex.MatchString(formatToRegexPattern(format[0]), content); err == nil && len(match) > 0 {
return NewFromStrFormat(match[0], format[0])
}
} else {
if match := timeRegex1.FindStringSubmatch(content); len(match) >= 1 {
return NewFromStr(strings.Trim(match[0], "./_- \n\r"))
} else if match := timeRegex2.FindStringSubmatch(content); len(match) >= 1 {
return NewFromStr(strings.Trim(match[0], "./_- \n\r"))
}
}
return nil
}
// 计算函数f执行的时间单位纳秒
func FuncCost(f func()) int64 {
t := Nanosecond()
f()
return Nanosecond() - t
}

View File

@ -7,8 +7,9 @@
package gtime
import (
"strings"
"bytes"
"gitee.com/johng/gf/g/util/gregex"
"strings"
)
var (
@ -68,6 +69,7 @@ func formatToStdLayout(format string) string {
default:
if f, ok := formats[format[i]]; ok {
// 有几个转换的符号需要特殊处理
switch format[i] {
case 'j':
b.WriteString("02")
@ -92,40 +94,51 @@ func formatToStdLayout(format string) string {
return b.String()
}
// 将format格式转换为正则表达式规则
func formatToRegexPattern(format string) string {
s := gregex.Quote(formatToStdLayout(format))
s, _ = gregex.ReplaceString(`[0-9]`, `[0-9]`, s)
s, _ = gregex.ReplaceString(`[A-Za-z]`, `[A-Za-z]`, s)
return s
}
// 格式化,使用自定义日期格式
func (t *Time) Format(format string) string {
s := ""
for i := 0; i < len(format); {
switch format[i] {
runes := []rune(format)
buffer := bytes.NewBuffer(nil)
for i := 0; i < len(runes); {
switch runes[i] {
case '\\':
if i < len(format) - 1 {
s += string(format[i + 1])
if i < len(runes) - 1 {
buffer.WriteRune(runes[i + 1])
i += 2
continue
} else {
return s
return buffer.String()
}
default:
if f, ok := formats[format[i]]; ok {
r := t.Time.Format(f)
switch format[i] {
case 'j':
s += strings.Replace(r, "=j=0", "", -1)
case 'G':
s += strings.Replace(r, "=G=0", "", -1)
case 'u':
s += strings.Replace(r, "=u=.", "", -1)
if runes[i] > 255 {
buffer.WriteRune(runes[i])
break
}
if f, ok := formats[byte(runes[i])]; ok {
result := t.Time.Format(f)
// 有几个转换的符号需要特殊处理
switch runes[i] {
case 'j': buffer.WriteString(strings.Replace(result, "=j=0", "", -1))
case 'G': buffer.WriteString(strings.Replace(result, "=G=0", "", -1))
case 'u': buffer.WriteString(strings.Replace(result, "=u=.", "", -1))
default:
s += r
buffer.WriteString(result)
}
} else {
s += string(format[i])
buffer.WriteRune(runes[i])
}
}
i++
}
return s
return buffer.String()
}
// 格式化,使用标准库格式

View File

@ -39,9 +39,7 @@ func NewFromTime (t time.Time) *Time {
// 从字符串转换为时间对象,复杂的时间字符串需要给定格式
func NewFromStr (str string) *Time {
if t, err := StrToTime(str); err == nil {
return &Time{
t,
}
return t
}
return nil
}
@ -49,9 +47,7 @@ func NewFromStr (str string) *Time {
// 从字符串转换为时间对象指定字符串时间格式format格式形如Y-m-d H:i:s
func NewFromStrFormat (str string, format string) *Time {
if t, err := StrToTimeFormat(str, format); err == nil {
return &Time{
t,
}
return t
}
return nil
}
@ -59,9 +55,7 @@ func NewFromStrFormat (str string, format string) *Time {
// 从字符串转换为时间对象通过标准库layout格式进行解析layout格式形如2006-01-02 15:04:05
func NewFromStrLayout (str string, layout string) *Time {
if t, err := StrToTimeLayout(str, layout); err == nil {
return &Time{
t,
}
return t
}
return nil
}

View File

@ -8,10 +8,16 @@
package gview
import (
"fmt"
"gitee.com/johng/gf/g/encoding/gurl"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/util/gstr"
"strings"
"sync"
"bytes"
"errors"
"html/template"
"text/template"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/encoding/ghash"
"gitee.com/johng/gf/g/util/gconv"
@ -29,9 +35,6 @@ type View struct {
delimiters []string // 模板变量分隔符号
}
// 输出到模板页面时保留HTML标签原意不做自动escape处理
type HTML = template.HTML
// 模板变量
type Params = map[string]interface{}
@ -69,30 +72,55 @@ func Get(path string) *View {
// 生成一个视图对象
func New(path string) *View {
s := gspath.New()
s.Set(path)
view := &View {
paths : s,
paths : gspath.New(),
data : make(map[string]interface{}),
funcmap : make(map[string]interface{}),
delimiters : make([]string, 2),
}
view.SetPath(path)
view.SetDelimiters("{{", "}}")
// 内置方法
view.BindFunc("text", view.funcText)
view.BindFunc("html", view.funcHtml)
view.BindFunc("include", view.funcInclude)
view.BindFunc("text", view.funcText)
view.BindFunc("html", view.funcHtmlEncode)
view.BindFunc("htmlencode", view.funcHtmlEncode)
view.BindFunc("htmldecode", view.funcHtmlDecode)
view.BindFunc("url", view.funcUrlEncode)
view.BindFunc("urlencode", view.funcUrlEncode)
view.BindFunc("urldecode", view.funcUrlDecode)
view.BindFunc("date", view.funcDate)
view.BindFunc("substr", view.funcSubStr)
view.BindFunc("strlimit", view.funcStrLimit)
view.BindFunc("compare", view.funcCompare)
view.BindFunc("hidestr", view.funcHideStr)
view.BindFunc("highlight", view.funcHighlight)
view.BindFunc("toupper", view.funcToUpper)
view.BindFunc("tolower", view.funcToLower)
view.BindFunc("nl2br", view.funcNl2Br)
view.BindFunc("include", view.funcInclude)
return view
}
// 设置模板目录绝对路径
func (view *View) SetPath(path string) error {
return view.paths.Set(path)
if rp, err := view.paths.Set(path); err != nil {
glog.Error("gview.SetPath failed:", err.Error())
return err
} else {
glog.Debug("gview.SetPath:", rp)
}
return nil
}
// 添加模板目录搜索路径
func (view *View) AddPath(path string) error {
return view.paths.Add(path)
if rp, err := view.paths.Add(path); err != nil {
glog.Error("gview.AddPath failed:", err.Error())
return err
} else {
glog.Debug("gview.AddPath:", rp)
}
return nil
}
// 批量绑定模板变量,即调用之后每个线程都会生效,因此有并发安全控制
@ -206,16 +234,16 @@ func (view *View) BindFunc(name string, function interface{}) {
}
// 模板内置方法include
func (view *View) funcInclude(file string, data...map[string]interface{}) template.HTML {
func (view *View) funcInclude(file string, data...map[string]interface{}) string {
var m map[string]interface{} = nil
if len(data) > 0 {
m = data[0]
}
content, err := view.Parse(file, m)
if err != nil {
return template.HTML(err.Error())
return err.Error()
}
return template.HTML(content)
return string(content)
}
// 模板内置方法text
@ -224,8 +252,72 @@ func (view *View) funcText(html interface{}) string {
}
// 模板内置方法html
func (view *View) funcHtml(html interface{}) template.HTML {
return template.HTML(gconv.String(html))
func (view *View) funcHtmlEncode(html interface{}) string {
return ghtml.Entities(gconv.String(html))
}
// 模板内置方法htmldecode
func (view *View) funcHtmlDecode(html interface{}) string {
return ghtml.EntitiesDecode(gconv.String(html))
}
// 模板内置方法url
func (view *View) funcUrlEncode(url interface{}) string {
return gurl.Encode(gconv.String(url))
}
// 模板内置方法urldecode
func (view *View) funcUrlDecode(url interface{}) string {
if content, err := gurl.Decode(gconv.String(url)); err == nil {
return content
} else {
return err.Error()
}
}
// 模板内置方法date
func (view *View) funcDate(format string, timestamp interface{}) string {
return gtime.NewFromTimeStamp(gconv.Int64(timestamp)).Format(format)
}
// 模板内置方法compare
func (view *View) funcCompare(value1, value2 interface{}) int {
return strings.Compare(gconv.String(value1), gconv.String(value2))
}
// 模板内置方法substr
func (view *View) funcSubStr(start, end int, str interface{}) string {
return gstr.SubStr(gconv.String(str), start, end)
}
// 模板内置方法strlimit
func (view *View) funcStrLimit(length int, suffix string, str interface{}) string {
return gstr.StrLimit(gconv.String(str), length, suffix)
}
// 模板内置方法highlight
func (view *View) funcHighlight(key string, color string, str interface{}) string {
return gstr.Replace(gconv.String(str), key, fmt.Sprintf(`<span style="color:%s;">%s</span>`, color, key))
}
// 模板内置方法hidestr
func (view *View) funcHideStr(percent int, hide string, str interface{}) string {
return gstr.HideStr(gconv.String(str), percent, hide)
}
// 模板内置方法toupper
func (view *View) funcToUpper(str interface{}) string {
return gstr.ToUpper(gconv.String(str))
}
// 模板内置方法toupper
func (view *View) funcToLower(str interface{}) string {
return gstr.ToLower(gconv.String(str))
}
// 模板内置方法nl2br
func (view *View) funcNl2Br(str interface{}) string {
return gstr.Nl2Br(gconv.String(str))
}

View File

@ -15,7 +15,7 @@ import (
"strings"
)
// 将变量i转换为字符串指定的类型t非必须参数extraParams泳衣额外的参数传递
// 将变量i转换为字符串指定的类型t非必须参数extraParams用以额外的参数传递
func Convert(i interface{}, t string, extraParams...interface{}) interface{} {
switch t {
case "int": return Int(i)
@ -123,8 +123,7 @@ func Int(i interface{}) int {
}
return 0
default:
v, _ := strconv.Atoi(strings.TrimSpace(String(value)))
return v
return int(Float64(value))
}
}
@ -211,8 +210,7 @@ func Uint(i interface{}) uint {
}
return 0
default:
v, _ := strconv.ParseUint(strings.TrimSpace(String(value)), 10, 64)
return uint(v)
return uint(Float64(value))
}
}

View File

@ -7,6 +7,7 @@
package gconv
import (
"gitee.com/johng/gf/g/container/gset"
"gitee.com/johng/gf/g/util/gstr"
"reflect"
"gitee.com/johng/gf/third/github.com/fatih/structs"
@ -83,16 +84,48 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
}
}
// 最后按照默认规则进行匹配
attrset := gset.NewStringSet(false)
elemtype := elem.Type()
for i := 0; i < elem.NumField(); i++ {
attrset.Add(elemtype.Field(i).Name)
}
for mapk, mapv := range paramsMap {
name := gstr.UcFirst(mapk)
if _, ok := dmap[name]; ok {
name := ""
for _, checkName := range []string {
gstr.UcFirst(mapk),
gstr.ReplaceByMap(mapk, map[string]string{
"_" : "",
"-" : "",
" " : "",
})} {
if _, ok := dmap[checkName]; ok {
continue
}
if _, ok := tagmap[checkName]; ok {
continue
}
// 循环查找属性名称进行匹配
attrset.Iterator(func(value string) bool {
if strings.EqualFold(checkName, value) {
name = value
return false
}
if strings.EqualFold(checkName, gstr.Replace(value, "_", "")) {
name = value
return false
}
return true
})
if name != "" {
break
}
}
// 如果没有匹配到属性名称,放弃
if name == "" {
continue
}
// 后续tag逻辑中会处理的key(重复的键名)这里便不处理
if _, ok := tagmap[mapk]; !ok {
if err := bindVarToStruct(elem, name, mapv); err != nil {
return err
}
if err := bindVarToStruct(elem, name, mapv); err != nil {
return err
}
}
return nil
@ -120,7 +153,7 @@ func getTagMapOfStruct(objPointer interface{}) map[string]string {
}
// 将参数值绑定到对象指定名称的属性上
func bindVarToStruct(elem reflect.Value, name string, value interface{}) error {
func bindVarToStruct(elem reflect.Value, name string, value interface{}) (err error) {
structFieldValue := elem.FieldByName(name)
// 键名与对象属性匹配检测map中如果有struct不存在的属性那么不做处理直接return
if !structFieldValue.IsValid() {
@ -136,7 +169,7 @@ func bindVarToStruct(elem reflect.Value, name string, value interface{}) error {
defer func() {
// 如果转换失败,那么可能是类型不匹配造成(例如属性包含自定义类型),那么执行递归转换
if recover() != nil {
bindVarToStructIfDefaultConvertionFailed(structFieldValue, value)
err = bindVarToStructIfDefaultConvertionFailed(structFieldValue, value)
}
}()
structFieldValue.Set(reflect.ValueOf(Convert(value, structFieldValue.Type().String())))
@ -144,7 +177,7 @@ func bindVarToStruct(elem reflect.Value, name string, value interface{}) error {
}
// 将参数值绑定到对象指定索引位置的属性上
func bindVarToStructByIndex(elem reflect.Value, index int, value interface{}) error {
func bindVarToStructByIndex(elem reflect.Value, index int, value interface{}) (err error) {
structFieldValue := elem.FieldByIndex([]int{index})
// 键名与对象属性匹配检测
if !structFieldValue.IsValid() {
@ -160,7 +193,7 @@ func bindVarToStructByIndex(elem reflect.Value, index int, value interface{}) er
defer func() {
// 如果转换失败,那么可能是类型不匹配造成(例如属性包含自定义类型),那么执行递归转换
if recover() != nil {
bindVarToStructIfDefaultConvertionFailed(structFieldValue, value)
err = bindVarToStructIfDefaultConvertionFailed(structFieldValue, value)
}
}()
structFieldValue.Set(reflect.ValueOf(Convert(value, structFieldValue.Type().String())))
@ -168,7 +201,7 @@ func bindVarToStructByIndex(elem reflect.Value, index int, value interface{}) er
}
// 当默认的基本类型转换失败时通过recover判断后执行反射类型转换
func bindVarToStructIfDefaultConvertionFailed(structFieldValue reflect.Value, value interface{}) {
func bindVarToStructIfDefaultConvertionFailed(structFieldValue reflect.Value, value interface{}) error {
switch structFieldValue.Kind() {
case reflect.Struct:
Struct(value, structFieldValue)
@ -190,7 +223,8 @@ func bindVarToStructIfDefaultConvertionFailed(structFieldValue reflect.Value, va
}
structFieldValue.Set(a)
default:
panic(errors.New(fmt.Sprintf(`cannot convert to type "%s"`, structFieldValue.Type().String())))
return errors.New(fmt.Sprintf(`cannot convert to type "%s"`, structFieldValue.Type().String()))
}
return nil
}

137
g/util/gconv/gconv_test.go Normal file
View File

@ -0,0 +1,137 @@
// Copyright 2017-2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
// go test *.go -bench=".*" -benchmem
package gconv
import (
"testing"
)
var value = 123456789
func BenchmarkString(b *testing.B) {
for i := 0; i < b.N; i++ {
String(value)
}
}
func BenchmarkInt(b *testing.B) {
for i := 0; i < b.N; i++ {
Int(value)
}
}
func BenchmarkInt8(b *testing.B) {
for i := 0; i < b.N; i++ {
Int8(value)
}
}
func BenchmarkInt16(b *testing.B) {
for i := 0; i < b.N; i++ {
Int16(value)
}
}
func BenchmarkInt32(b *testing.B) {
for i := 0; i < b.N; i++ {
Int32(value)
}
}
func BenchmarkInt64(b *testing.B) {
for i := 0; i < b.N; i++ {
Int(value)
}
}
func BenchmarkUint(b *testing.B) {
for i := 0; i < b.N; i++ {
Uint(value)
}
}
func BenchmarkUint8(b *testing.B) {
for i := 0; i < b.N; i++ {
Uint8(value)
}
}
func BenchmarkUint16(b *testing.B) {
for i := 0; i < b.N; i++ {
Uint16(value)
}
}
func BenchmarkUint32(b *testing.B) {
for i := 0; i < b.N; i++ {
Uint32(value)
}
}
func BenchmarkUint64(b *testing.B) {
for i := 0; i < b.N; i++ {
Uint64(value)
}
}
func BenchmarkFloat32(b *testing.B) {
for i := 0; i < b.N; i++ {
Float32(value)
}
}
func BenchmarkFloat64(b *testing.B) {
for i := 0; i < b.N; i++ {
Float64(value)
}
}
func BenchmarkTime(b *testing.B) {
for i := 0; i < b.N; i++ {
Time(value)
}
}
func BenchmarkTimeDuration(b *testing.B) {
for i := 0; i < b.N; i++ {
TimeDuration(value)
}
}
func BenchmarkBytes(b *testing.B) {
for i := 0; i < b.N; i++ {
Bytes(value)
}
}
func BenchmarkStrings(b *testing.B) {
for i := 0; i < b.N; i++ {
Strings(value)
}
}
func BenchmarkInts(b *testing.B) {
for i := 0; i < b.N; i++ {
Ints(value)
}
}
func BenchmarkFloats(b *testing.B) {
for i := 0; i < b.N; i++ {
Floats(value)
}
}
func BenchmarkInterfaces(b *testing.B) {
for i := 0; i < b.N; i++ {
Interfaces(value)
}
}

View File

@ -18,13 +18,13 @@ func Time(i interface{}, format...string) time.Time {
// 优先使用用户输入日期格式进行转换
if len(format) > 0 {
t, _ := gtime.StrToTimeFormat(s, format[0])
return t
return t.Time
}
if gstr.IsNumeric(s) {
return gtime.NewFromTimeStamp(Int64(s)).Time
} else {
t, _ := gtime.StrToTime(s)
return t
return t.Time
}
}

View File

@ -7,18 +7,11 @@
// 随机数管理
package grand
import (
"time"
var (
letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
digits = []rune("0123456789")
)
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
var digits = []rune("0123456789")
// 自定义的 rand.Intn
func intn (max int) int {
return int(time.Now().UnixNano())%max
}
// 获得一个 min, max 之间的随机数(min <= x <= max)
func Rand (min, max int) int {
if min >= max {

View File

@ -0,0 +1,45 @@
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
package grand
import (
"crypto/rand"
"encoding/binary"
)
const (
gBUFFER_SIZE = 10000 // 缓冲区uint64数量大小
)
var (
bufferChan = make(chan uint64, gBUFFER_SIZE)
)
// 使用缓冲区实现快速的随机数生成
func init() {
buffer := make([]byte, 1024)
go func() {
for {
if n, err := rand.Read(buffer); err != nil {
panic(err)
} else {
for i := 0; i < n - 8; i += 8 {
bufferChan <- binary.LittleEndian.Uint64(buffer[i : i + 8])
}
}
}
}()
}
// 自定义的 rand.Intn ,绝对随机
func intn (max int) int {
n := int(<- bufferChan)%max
if n < 0 {
return -n
}
return n
}

View File

@ -15,6 +15,6 @@ import (
func Benchmark_Rand(b *testing.B) {
for i := 0; i < b.N; i++ {
grand.Rand(0, 200)
grand.Rand(0, 999999999)
}
}

View File

@ -8,12 +8,12 @@
package gregex
import (
"gitee.com/johng/gf/g/container/gmap"
"regexp"
"gitee.com/johng/gf/g/os/gcache"
)
// 缓存对象主要用于缓存底层regx对象
var regxCache = gcache.New()
var regxCache = gmap.NewStringInterfaceMap(true)
// 根据pattern生成对应的regexp正则对象
func getRegexp(pattern string) (*regexp.Regexp, error) {
@ -21,7 +21,7 @@ func getRegexp(pattern string) (*regexp.Regexp, error) {
return v.(*regexp.Regexp), nil
}
if r, err := regexp.Compile(pattern); err == nil {
regxCache.Set(pattern, r, 0)
regxCache.Set(pattern, r)
return r, nil
} else {
return nil, err

View File

@ -7,11 +7,19 @@
// 字符串操作.
package gstr
import "strings"
import (
"bytes"
"math"
"strings"
)
// 字符串替换
func Replace(origin, search, replace string) string {
return strings.Replace(origin, search, replace, -1)
func Replace(origin, search, replace string, count...int) string {
n := -1
if len(count) > 0 {
n = count[0]
}
return strings.Replace(origin, search, replace, n)
}
// 使用map进行字符串替换
@ -23,6 +31,16 @@ func ReplaceByMap(origin string, replaces map[string]string) string {
return result
}
// 字符串转换为小写
func ToLower(s string) string {
return strings.ToLower(s)
}
// 字符串转换为大写
func ToUpper(s string) string {
return strings.ToUpper(s)
}
// 字符串首字母转换为大写
func UcFirst(s string) string {
if len(s) == 0 {
@ -84,4 +102,77 @@ func IsNumeric(s string) bool {
}
}
return true
}
// 字符串截取,支持中文
func SubStr(str string, start int, length...int) (substr string) {
// 将字符串的转换成[]rune
rs := []rune(str)
lth := len(rs)
// 简单的越界判断
if start < 0 {
start = 0
}
if start >= lth {
start = lth
}
end := lth
if len(length) > 0 {
end = start + length[0]
if end < start {
end = lth
}
}
if end > lth {
end = lth
}
// 返回子串
return string(rs[start : end])
}
// 字符串长度截取限制,超过长度限制被截取并在字符串末尾追加指定的内容,支持中文
func StrLimit(str string, length int, suffix...string) (string) {
rs := []rune(str)
if len(str) < length {
return str
}
addstr := "..."
if len(suffix) > 0 {
addstr = suffix[0]
}
return string(rs[0 : length]) + addstr
}
// 按照百分比从字符串中间向两边隐藏字符(主要用于姓名、手机号、邮箱地址、身份证号等的隐藏)支持utf-8中文支持email格式。
func HideStr(str string, percent int, hide string) string {
array := strings.Split(str, "@")
if len(array) > 1 {
str = array[0]
}
rs := []rune(str)
length := len(rs)
mid := math.Floor(float64(length/2))
hideLen := int(math.Floor(float64(length) * (float64(percent)/100)))
start := int(mid - math.Floor(float64(hideLen) / 2))
hideStr := []rune("")
hideRune := []rune(hide)
for i := 0; i < int(hideLen); i++ {
hideStr = append(hideStr, hideRune...)
}
buffer := bytes.NewBuffer(nil)
buffer.WriteString(string(rs[0 : start]))
buffer.WriteString(string(hideStr))
buffer.WriteString(string(rs[start + hideLen : ]))
if len(array) > 1 {
buffer.WriteString(array[1])
}
return buffer.String()
}
// 将\n\r替换为html中的<br>标签。
func Nl2Br(str string) string {
str = Replace(str, "\r\n", "\n")
str = Replace(str, "\n\r", "\n")
str = Replace(str, "\n", "<br />")
return str
}

View File

@ -65,3 +65,20 @@ func PrintBacktrace() {
glog.Header(false).Print(buffer.String())
}
// 抛出一个异常
func Throw(exception interface{}) {
panic(exception)
}
// try...catch...
func TryCatch(try func(), catch ... func(exception interface{})) {
if len(catch) > 0 {
defer func() {
if e := recover(); e != nil {
catch[0](e)
}
}()
}
try()
}

View File

@ -0,0 +1,23 @@
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
// go test *.go -bench=".*" -benchmem
package gutil
import (
"testing"
)
func Benchmark_TryCatch(b *testing.B) {
for i := 0; i < b.N; i++ {
TryCatch(func() {
}, func(err interface{}) {
})
}
}

View File

@ -0,0 +1,580 @@
package main
import (
"fmt"
"time"
_ "github.com/mattn/go-oci8"
"gitee.com/johng/gf/g/database/gdb"
"gitee.com/johng/gf/g"
)
// 本文件用于gf框架的mysql数据库操作示例不作为单元测试使用
var db *gdb.Db
// 初始化配置及创建数据库
func init () {
gdb.AddDefaultConfigNode(gdb.ConfigNode {
Host : "192.168.146.0",
Port : "1521",
User : "test",
Pass : "test",
Name : "orcl",
Type : "oracle",
Role : "master",
Charset : "utf8",
})
db, _= gdb.New()
//gins.Config().SetPath("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/frame")
//db = g.Database()
//gdb.SetConfig(gdb.ConfigNode {
// Host : "127.0.0.1",
// Port : 3306,
// User : "root",
// Pass : "123456",
// Name : "test",
// Type : "mysql",
//})
//db, _ = gdb.Instance()
//gdb.SetConfig(gdb.Config {
// "default" : gdb.ConfigGroup {
// gdb.ConfigNode {
// Host : "127.0.0.1",
// Port : "3306",
// User : "root",
// Pass : "123456",
// Name : "test",
// Type : "mysql",
// Role : "master",
// Priority : 100,
// },
// gdb.ConfigNode {
// Host : "127.0.0.2",
// Port : "3306",
// User : "root",
// Pass : "123456",
// Name : "test",
// Type : "mysql",
// Role : "master",
// Priority : 100,
// },
// gdb.ConfigNode {
// Host : "127.0.0.3",
// Port : "3306",
// User : "root",
// Pass : "123456",
// Name : "test",
// Type : "mysql",
// Role : "master",
// Priority : 100,
// },
// gdb.ConfigNode {
// Host : "127.0.0.4",
// Port : "3306",
// User : "root",
// Pass : "123456",
// Name : "test",
// Type : "mysql",
// Role : "master",
// Priority : 100,
// },
// },
//})
//db, _ = gdb.Instance()
}
// 创建测试数据库
func create() error {
fmt.Println("drop table aa_user:")
_, err := db.Exec("drop table aa_user")
if err != nil {
fmt.Println("drop table aa_user error.",err)
}
s := `
CREATE TABLE aa_user (
id number(10) not null,
name VARCHAR2(45),
age number(8),
addr varchar2(60),
PRIMARY KEY (id)
)
`
fmt.Println("create table aa_user:")
_, err = db.Exec(s)
if err != nil {
fmt.Println("create table error.",err)
return err
}
_, err = db.Exec("drop sequence id_seq")
if err != nil {
fmt.Println("drop sequence id_seq", err)
}
/*fmt.Println("create sequence id_seq")
_, err = db.Exec("create sequence id_seq increment by 1 start with 1 maxvalue 9999999999 cycle cache 10")
if err != nil {
fmt.Println("create sequence id_seq error.", err)
return err
}
s = `
CREATE TRIGGER id_trigger before insert on aa_user for each row
begin
select id_seq.nextval into :new.id from dual;
end;
`
_, err = db.Exec(s)
if err != nil {
fmt.Println("create trigger error.", err)
return err
}*/
_, err = db.Exec("drop table user_detail")
if err != nil {
fmt.Println("drop table user_detail", err)
}
s = `
CREATE TABLE user_detail (
id number(10) not null,
site VARCHAR2(255),
PRIMARY KEY (id)
)
`
fmt.Println("create table user_detail:")
_, err = db.Exec(s)
if err != nil {
fmt.Println("create table user_detail error.",err)
return err
}
fmt.Println("create table success.")
return nil
}
// 数据写入
func insert(id int) {
fmt.Println("insert:")
r, err := db.Insert("aa_user", gdb.Map {
"id": id,
"name": "john",
"age": id,
})
fmt.Println(r.LastInsertId())
fmt.Println(r.RowsAffected())
if err == nil {
r, err = db.Insert("user_detail", gdb.Map {
"id" : id,
"site" : "http://johng.cn",
})
if err == nil {
fmt.Printf("id: %d\n", id)
} else {
fmt.Println(err)
}
} else {
fmt.Println(err)
}
fmt.Println()
}
// 基本sql查询
func query() {
fmt.Println("query:")
list, err := db.GetAll("select * from aa_user")
if err == nil {
fmt.Println(list)
} else {
fmt.Println(err)
}
list, err = db.Table("aa_user").OrderBy("id").Limit(0,2).Select()
if err == nil {
fmt.Println(list)
} else {
fmt.Println(err)
}
fmt.Println()
}
// replace into
func replace() {
fmt.Println("replace:")
r, err := db.Save("aa_user", gdb.Map {
"id" : 1,
"name" : "john",
})
if err == nil {
fmt.Println(r.LastInsertId())
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 数据保存
func save() {
fmt.Println("save:")
r, err := db.Save("aa_user", gdb.Map {
"id" : 1,
"name" : "john",
})
if err == nil {
fmt.Println(r.LastInsertId())
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 批量写入
func batchInsert() {
fmt.Println("batchInsert:")
_, err := db.BatchInsert("aa_user", gdb.List {
{"id":11,"name": "batchInsert_john_1", "age": 11},
{"id":12,"name": "batchInsert_john_2", "age": 12},
{"id":13,"name": "batchInsert_john_3", "age": 13},
{"id":14,"name": "batchInsert_john_4", "age": 14},
}, 10)
if err != nil {
fmt.Println(err)
}
fmt.Println()
}
// 数据更新
func update1() {
fmt.Println("update1:")
r, err := db.Update("aa_user", gdb.Map {"name": "john1","age":1}, "id=?", 1)
if err == nil {
fmt.Println(r.LastInsertId())
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 数据更新
func update2() {
fmt.Println("update2:")
r, err := db.Update("aa_user", gdb.Map{"name" : "john6","age":6}, "id=?", 2)
if err == nil {
fmt.Println(r.LastInsertId())
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 数据更新
func update3() {
fmt.Println("update3:")
r, err := db.Update("aa_user", "name=?", "id=?", "john2", 3)
if err == nil {
fmt.Println(r.LastInsertId())
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式查询操作1
func linkopSelect1() {
fmt.Println("linkopSelect1:")
r, err := db.Table("aa_user u").LeftJoin("user_detail ud", "u.id=ud.id").Fields("u.*, ud.site").Where("u.id > ?", 1).Limit(0, 2).Select()
if err == nil {
fmt.Println(r)
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式查询操作2
func linkopSelect2() {
fmt.Println("linkopSelect2:")
r, err := db.Table("aa_user u").LeftJoin("user_detail ud", "u.id=ud.id").Fields("u.*,ud.site").Where("u.id=?", 1).One()
if err == nil {
fmt.Println(r)
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式查询操作3
func linkopSelect3() {
fmt.Println("linkopSelect3:")
r, err := db.Table("aa_user u").LeftJoin("user_detail ud", "u.id=ud.id").Fields("ud.site").Where("u.id=?", 1).Value()
if err == nil {
fmt.Println(r.String())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式查询数量1
func linkopCount1() {
fmt.Println("linkopCount1:")
r, err := db.Table("aa_user u").LeftJoin("user_detail ud", "u.id=ud.id").Where("name like ?", "john").Count()
if err == nil {
fmt.Println(r)
} else {
fmt.Println(err)
}
fmt.Println()
}
// 错误操作
func linkopUpdate1() {
fmt.Println("linkopUpdate1:")
r, err := db.Table("henghe_setting").Update()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println("error",err)
}
fmt.Println()
}
// 通过Map指针方式传参方式
func linkopUpdate2() {
fmt.Println("linkopUpdate2:")
r, err := db.Table("aa_user").Data(gdb.Map{"name" : "john2"}).Where("name=?", "john").Update()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 通过字符串方式传参
func linkopUpdate3() {
fmt.Println("linkopUpdate3:")
r, err := db.Table("aa_user").Data("name='john3'").Where("name=?", "john2").Update()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// Where条件使用Map
func linkopUpdate4() {
fmt.Println("linkopUpdate4:")
r, err := db.Table("aa_user").Data(gdb.Map{"name" : "john11111"}).Where(g.Map{"id" : 1}).Update()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式批量写入
func linkopBatchInsert1() {
fmt.Println("linkopBatchInsert1:")
r, err := db.Table("aa_user").Data(gdb.List{
{"id":21,"name": "linkopBatchInsert1_john_1"},
{"id":22,"name": "linkopBatchInsert1_john_2"},
{"id":23,"name": "linkopBatchInsert1_john_3"},
{"id":24,"name": "linkopBatchInsert1_john_4"},
}).Insert()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式批量写入,指定每批次写入的条数
func linkopBatchInsert2() {
fmt.Println("linkopBatchInsert2:")
r, err := db.Table("aa_user").Data(gdb.List{
{"id":25,"name": "linkopBatchInsert2john_1"},
{"id":26,"name": "linkopBatchInsert2john_2"},
{"id":27,"name": "linkopBatchInsert2john_3"},
{"id":28,"name": "linkopBatchInsert2john_4"},
}).Batch(2).Insert()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式批量保存
func linkopBatchSave() {
fmt.Println("linkopBatchSave:")
r, err := db.Table("aa_user").Data(gdb.List{
{"id":1, "name": "john_1"},
{"id":2, "name": "john_2"},
{"id":3, "name": "john_3"},
{"id":4, "name": "john_4"},
}).Save()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 事务操作示例1
func transaction1() {
fmt.Println("transaction1:")
if tx, err := db.Begin(); err == nil {
r, err := tx.Insert("aa_user", gdb.Map{
"id" : 30,
"name" : "transaction1",
})
tx.Rollback()
fmt.Println(r, err)
}
fmt.Println()
}
// 事务操作示例2
func transaction2() {
fmt.Println("transaction2:")
if tx, err := db.Begin(); err == nil {
r, err := tx.Table("user_detail").Data(gdb.Map{"id":5, "site": "www.baidu.com哈哈哈*?~!@#$%^&*()"}).Insert()
tx.Commit()
fmt.Println(r, err)
}
fmt.Println()
}
// 主从io复用测试在mysql中使用 show full processlist 查看链接信息
func keepPing() {
fmt.Println("keepPing:")
for i := 0; i < 30; i++ {
fmt.Println("ping...",i)
err := db.PingMaster()
if err != nil {
fmt.Println(err)
return
}
err = db.PingSlave()
if err != nil {
fmt.Println(err)
return
}
time.Sleep(1*time.Second)
}
}
// like语句查询
func likeQuery() {
fmt.Println("likeQuery:")
if r, err := db.Table("aa_user").Where("name like ?", "%john%").Select(); err == nil {
fmt.Println(r)
} else {
fmt.Println(err)
}
}
// mapToStruct
func mapToStruct() {
type User struct {
Id int
Name string
Age int
Addr string
}
fmt.Println("mapToStruct:")
if r, err := db.Table("aa_user").Where("id=?", 1).One(); err == nil {
u := User{}
if err := r.ToStruct(&u); err == nil {
fmt.Println(r)
fmt.Println(u)
} else {
fmt.Println(err)
}
} else {
fmt.Println(err)
}
}
// getQueriedSqls
func getQueriedSqls() {
for k, v := range db.GetQueriedSqls() {
fmt.Println(k, ":")
fmt.Println("Sql :", v.Sql)
fmt.Println("Args :", v.Args)
fmt.Println("Error:", v.Error)
fmt.Println("Func :", v.Func)
}
}
func main() {
db.PingMaster()
db.SetDebug(true)
/*err := create()
if err != nil {
return
}*/
//test1
/*for i := 1; i < 5; i++ {
insert(i)
}
query()
*/
//batchInsert()
//query()
//replace()
//save()
//update1()
//update2()
//update3()
/*linkopSelect1()
linkopSelect2()
linkopSelect3()
linkopCount1()
*/
/*linkopUpdate1()
linkopUpdate2()
linkopUpdate3()
linkopUpdate4()
*/
//linkopBatchInsert1()
//linkopBatchInsert2()
//transaction1()
//transaction2()
//
//keepPing()
//likeQuery()
mapToStruct()
//getQueriedSqls()
}

View File

@ -1,5 +1,6 @@
# 模板引擎目录
viewpath = "/home/www/templates/"
test = "v=1"
# MySQL数据库配置
[database]
[[database.default]]

View File

@ -15,7 +15,7 @@ func (c *ControllerTemplate) Info() {
"age" : 18,
"score" : 100,
})
c.View.Display("user/index.tpl")
c.View.Display("view/user/index.tpl")
}
func init() {

View File

@ -1,8 +1,8 @@
package main
import (
"gitee.com/johng/gf/g/frame/gmvc"
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/frame/gmvc"
)
type User struct {
@ -13,8 +13,13 @@ func (c *User) Index() {
c.View.Display("index.html")
}
// 不符合规范,不会被自动注册
func (c *User) Test(value interface{}) {
c.View.Display("index.html")
}
func main() {
g.View().SetPath("C:/www/static")
//g.View().SetPath("C:/www/static")
s := g.Server()
s.BindController("/user", new(User))
s.SetPort(8199)

View File

@ -0,0 +1,20 @@
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/download", func(r *ghttp.Request){
r.Response.Header().Set("Content-Type", "text/html;charset=utf-8");
r.Response.Header().Set("Content-type", "application/force-download");
r.Response.Header().Set("Content-Type", "application/octet-stream");
r.Response.Header().Set("Accept-Ranges", "bytes");
r.Response.Header().Set("Content-Disposition", "attachment;filename=\"下载文件名称.txt\"");
r.Response.ServeFile("text.txt")
})
s.SetPort(8199)
s.Run()
}

View File

@ -0,0 +1 @@
下载文件内容。

View File

@ -0,0 +1,27 @@
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/net/ghttp"
)
func main() {
p := "/"
s := g.Server()
s.BindHandler(p, func(r *ghttp.Request) {
r.Response.Writeln("start")
r.Exit()
r.Response.Writeln("end")
})
s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{
ghttp.HOOK_BEFORE_SERVE : func(r *ghttp.Request){
glog.To(r.Response.Writer).Println("BeforeServe")
},
ghttp.HOOK_AFTER_SERVE : func(r *ghttp.Request){
glog.To(r.Response.Writer).Println("AfterServe")
},
})
s.SetPort(8199)
s.Run()
}

View File

@ -0,0 +1,31 @@
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
)
type User struct {
}
func (c *User) Index(r *ghttp.Request) {
r.Response.Write("Index")
}
// 不符合规范,不会被注册
func (c *User) Test(r *ghttp.Request, value interface{}) {
r.Response.Write("Test")
}
func main() {
s := g.Server()
s.BindObjectMethod("/user", new(User), "Test")
s.SetPort(8199)
s.Run()
}

View File

@ -1,16 +0,0 @@
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request){
content :=`{{if (get "name")}} {{get "name"}} {{else}} NoName {{end}}`
r.Response.WriteTplContent(content, nil)
})
s.SetPort(8199)
s.Run()
}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{.title}}</title>
</head>
<body>
<H1>姓名 {{.name}}</H1>
12
</body>
</html>

View File

@ -0,0 +1,23 @@
package main
import (
"gitee.com/johng/gf/g/frame/gmvc"
"gitee.com/johng/gf/g/net/ghttp"
)
type ControllerIndex struct {
gmvc.Controller
}
func (c *ControllerIndex) Info() {
c.View.Assign("title", "Go Frame 第一个网站")
c.View.Assigns(map[string]interface{}{
"name" : "很开心1",
"score" : 100,
})
c.View.Display("index.html")
}
func main() {
s := ghttp.GetServer()
s.BindController("/", new(ControllerIndex))
s.SetPort(8199)
s.Run()
}

View File

@ -6,6 +6,6 @@ import (
)
func main() {
fmt.Println(g.Config().Get("serverpath"))
fmt.Println(g.Config().GetVar("memcache.0").String())
}

View File

@ -1,22 +1,26 @@
package main
import (
"fmt"
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/os/gcron"
"gitee.com/johng/gf/g/os/glog"
"time"
)
func main() {
gcron.Add("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
gcron.Add("* * * * * *", func() { fmt.Println("Every second") }, "second-cron")
gcron.Add("@hourly", func() { fmt.Println("Every hour") })
gcron.Add("@every 1h30m", func() { fmt.Println("Every hour thirty") })
gcron.Add("0 30 * * * *", func() { glog.Println("Every hour on the half hour") })
gcron.Add("* * * * * *", func() { glog.Println("Every second") }, "second-cron")
gcron.Add("@hourly", func() { glog.Println("Every hour") })
gcron.Add("@every 1h30m", func() { glog.Println("Every hour thirty") })
g.Dump(gcron.Entries())
time.Sleep(3*time.Second)
gcron.Remove("second-cron")
gcron.Stop("second-cron")
time.Sleep(3*time.Second)
gcron.Start("second-cron")
time.Sleep(3*time.Second)
}

View File

@ -12,7 +12,9 @@ func main() {
789
`
gfile.PutContents(path, content)
fmt.Println(gfile.Size(path))
fmt.Println(gfile.GetBinContentsTilCharByPath(path, '\n', 0))
fmt.Println(gfile.GetBinContentsTilCharByPath(path, '\n', 3))
fmt.Println(gfile.GetBinContentsTilCharByPath(path, '\n', 8))
fmt.Println(gfile.GetBinContentsTilCharByPath(path, '\n', 12))
}

20
geg/os/gfpool/gfpool.go Normal file
View File

@ -0,0 +1,20 @@
package main
import (
"fmt"
"gitee.com/johng/gf/g/os/gfpool"
"os"
"time"
)
func main() {
for {
time.Sleep(time.Second)
if f, err := gfpool.Open("/home/john/temp/log.log", os.O_RDONLY, 0666, 60000000*1000); err == nil {
fmt.Println(f.Name())
f.Close()
} else {
fmt.Println(err)
}
}
}

View File

@ -3,6 +3,7 @@ package main
import (
"gitee.com/johng/gf/third/github.com/fsnotify/fsnotify"
"log"
"gitee.com/johng/gf/g/os/glog"
)
func main() {
@ -13,7 +14,7 @@ func main() {
}
defer watch.Close()
//添加要监控的对象,文件或文件夹
err = watch.Add("/home/john/temp")
err = watch.Add("D:\\Workspace\\Go\\GOPATH\\src\\gitee.com\\johng\\gf\\geg\\other\\test.go")
if err != nil {
log.Fatal(err)
}
@ -21,32 +22,12 @@ func main() {
go func() {
for {
select {
case ev := <-watch.Events:
//判断事件发生的类型如下5种
// Create 创建
// Write 写入
// Remove 删除
// Rename 重命名
// Chmod 修改权限
if ev.Op&fsnotify.Create == fsnotify.Create {
log.Println("创建文件 : ", ev.Name)
}
if ev.Op&fsnotify.Write == fsnotify.Write {
log.Println("写入文件 : ", ev.Name)
}
if ev.Op&fsnotify.Remove == fsnotify.Remove {
log.Println("删除文件 : ", ev.Name)
}
if ev.Op&fsnotify.Rename == fsnotify.Rename {
log.Println("重命名文件 : ", ev.Name)
}
if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
log.Println("修改权限 : ", ev.Name)
}
case ev := <-watch.Events:
glog.Println(ev)
case err := <-watch.Errors:
log.Println("error : ", err)
return
case err := <-watch.Errors:
log.Println("error : ", err)
return
}
}

View File

@ -1,30 +1,21 @@
package main
import (
"log"
"gitee.com/johng/gf/g/os/gfsnotify"
"gitee.com/johng/gf/g/os/glog"
)
func main() {
err := gfsnotify.Add("/home/john/temp", func(event *gfsnotify.Event) {
if event.IsCreate() {
log.Println("创建文件 : ", event.Path)
}
if event.IsWrite() {
log.Println("写入文件 : ", event.Path)
}
if event.IsRemove() {
log.Println("删除文件 : ", event.Path)
}
if event.IsRename() {
log.Println("重命名文件 : ", event.Path)
}
if event.IsChmod() {
log.Println("修改权限 : ", event.Path)
}
path := "D:\\Workspace\\Go\\GOPATH\\src\\gitee.com\\johng\\gf\\geg\\other\\test.go"
_, err := gfsnotify.Add(path, func(event *gfsnotify.Event) {
glog.Println(event)
})
// 移除对该path的监听
//gfsnotify.Remove(path)
if err != nil {
log.Fatalln(err)
glog.Fatalln(err)
} else {
select {}
}

View File

@ -0,0 +1,36 @@
package main
import (
"gitee.com/johng/gf/g/os/gfsnotify"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/os/gtime"
"time"
)
func main() {
c1, err := gfsnotify.Add("/home/john/temp/log", func(event *gfsnotify.Event) {
glog.Println("callback1")
})
if err != nil {
panic(err)
}
c2, err := gfsnotify.Add("/home/john/temp/log", func(event *gfsnotify.Event) {
glog.Println("callback2")
})
if err != nil {
panic(err)
}
// 5秒后移除c1的回调函数注册仅剩c2
gtime.SetTimeout(5*time.Second, func() {
gfsnotify.RemoveCallback(c1.Id)
glog.Println("remove callback c1")
})
// 10秒后移除c2的回调函数注册所有的回调都移除不再有任何打印信息输出
gtime.SetTimeout(10*time.Second, func() {
gfsnotify.RemoveCallback(c2.Id)
glog.Println("remove callback c2")
})
select {}
}

View File

@ -0,0 +1,27 @@
package main
import (
"gitee.com/johng/gf/g/os/gfsnotify"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/os/gtime"
"time"
)
func main() {
callback, err := gfsnotify.Add("/home/john/temp", func(event *gfsnotify.Event) {
glog.Println("callback")
})
if err != nil {
panic(err)
}
// 在此期间创建文件、目录、修改文件、删除文件
// 20秒后移除回调函数注册所有的回调都移除不再有任何打印信息输出
gtime.SetTimeout(20*time.Second, func() {
gfsnotify.RemoveCallback(callback.Id)
glog.Println("remove callback")
})
select {}
}

View File

@ -0,0 +1,19 @@
package main
import (
"gitee.com/johng/gf/g/os/gfsnotify"
"gitee.com/johng/gf/g/os/glog"
)
// 对同一个文件多次Add是否超过系统inotify限制
func main() {
path := "/Users/john/temp/log"
for i := 0; i < 9999999; i++ {
_, err := gfsnotify.Add(path, func(event *gfsnotify.Event) {
glog.Println(event)
})
if err != nil {
glog.Fatalln(err)
}
}
}

View File

@ -0,0 +1,27 @@
package main
import (
"fmt"
"gitee.com/johng/gf/g/os/gtime"
)
func main() {
content := `
2018-11-01 14:30:39 -- 67 -- assignDoctor:request -- {"问诊ID":"1017467","操作类型":2,"操作ID":52339491,"医生ID":52339491,"是否主动接单":"是"}
2018-11-01 14:35:55 -- 73 -- throwIntoPool:request -- {"问诊Id":1017474,"当前Id":null,"当前角色":null}
`
if t := gtime.ParseTimeFromContent(content); t != nil {
fmt.Println(t.String())
fmt.Println(t.UTC())
fmt.Println(gtime.Now().UTC())
} else {
panic("cannot parse time from content")
}
//if t := gtime.ParseTimeFromContent(content, "d/M/Y:H:i:s +0800"); t != nil {
// fmt.Println(t.String())
//} else {
// panic("cannot parse time from content")
//}
}

View File

@ -7,7 +7,7 @@ import (
)
func main() {
timeRegex, err := regexp.Compile(gtime.TIME_REAGEX_PATTERN)
timeRegex, err := regexp.Compile(gtime.TIME_REAGEX_PATTERN1)
if err != nil {
panic(err)
}
@ -19,6 +19,7 @@ func main() {
"2018-02-09 20:46:17.897",
"2018-02-09T20:46:17Z",
"2018-02-09 20:46:17",
"2018/10/31 - 16:38:46",
"2018-02-09",
"2017/12/14 04:51:34 +0805 LMT",
"2018/02/09 12:00:15",

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