mirror of
https://gitee.com/johng/gf
synced 2026-06-23 16:34:17 +08:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4a483c08fd | |||
| 2b1db2350b | |||
| 6c49132d41 | |||
| 9186c209d9 | |||
| 92f46f7306 | |||
| 5903e82e40 | |||
| 7c1796bf8e | |||
| 2590c4385c | |||
| 619c27cdf9 | |||
| 9c66426cd0 | |||
| ceaf8314b9 | |||
| 5602454af9 | |||
| 5a68608693 | |||
| 1e60c35496 | |||
| 02f6bd8a17 | |||
| df571adfc1 | |||
| 4af6b653ad | |||
| 806bd63ba6 | |||
| 52a182b7e0 | |||
| c66300fe11 | |||
| c27aa61e02 | |||
| 7fa564a8f0 | |||
| 0e60e61d41 | |||
| 58ad87e4c3 | |||
| 61d02f8a94 | |||
| 08929c9830 | |||
| 81edda7508 | |||
| 105e492b65 | |||
| 80f32c7f18 | |||
| afbae75934 | |||
| 89f9397703 | |||
| 944bc3182f | |||
| f79c70d77d | |||
| 9a85c5eb53 | |||
| bb309a5ed0 | |||
| 4b8aa1522c | |||
| b456d94ccc | |||
| 853c723202 | |||
| 6e1c541f57 | |||
| bbf7ede777 | |||
| 0b04e9430a | |||
| 383026f356 | |||
| 44b804ec7d | |||
| bba2aa9a63 | |||
| 72046a48db | |||
| 1b11416521 | |||
| 846db9c5fd | |||
| cf5929c641 | |||
| 1a99a395fc | |||
| ebcfb6e1ff | |||
| 198e7854e3 | |||
| a9644abceb | |||
| b6f86de8f9 | |||
| 32e32993d3 | |||
| bf82381b9e | |||
| 6fba59be4b | |||
| 4263465345 | |||
| 77b8584809 | |||
| e4086ad9a1 | |||
| 74dd93a934 | |||
| 2c071579a6 | |||
| c7af9e5007 | |||
| 95b7a013e7 | |||
| 7a6dbf159c | |||
| 35fa199c5a | |||
| cdd3d507b8 | |||
| 6fcf17140d | |||
| 371ab48d7a | |||
| ee9362666e | |||
| 293cf81cf1 | |||
| 6e4ef6d0b5 | |||
| 4bbaf6db1b | |||
| ff126dfbaa | |||
| 28813577e1 | |||
| 21b6f169bb | |||
| c653ffa7a5 | |||
| f4f57e7a91 | |||
| 827c66a702 | |||
| 7582a604af | |||
| b109845d0e | |||
| c4ff01702c | |||
| 2ffebf2500 | |||
| 2b4bc53d71 | |||
| fe2bd315db | |||
| b98560c894 | |||
| bdf714825e | |||
| 8c459d499a | |||
| 6597ca3ad1 | |||
| 8baf0b5619 | |||
| 9534334714 | |||
| c4172fba9b | |||
| 44575bdef3 |
@ -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
86
TODO
@ -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实现,修正设计BUG:https://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
100
TODO.MD
Normal 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实现,修正设计BUG:https://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信息;
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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{} {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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{} {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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查询条件
|
||||
|
||||
@ -12,6 +12,10 @@ import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// MySQL接口对象
|
||||
var linkMysql = &dbmysql{}
|
||||
|
||||
|
||||
// 数据库链接对象
|
||||
type dbmysql struct {
|
||||
Db
|
||||
|
||||
149
g/database/gdb/gdb_oracle.go
Normal file
149
g/database/gdb/gdb_oracle.go
Normal 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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
|
||||
|
||||
20
g/g_func.go
20
g/g_func.go
@ -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...)
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -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")
|
||||
|
||||
@ -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...)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
// 获取
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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] == '*' {
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
@ -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{} {
|
||||
|
||||
@ -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
142
g/os/gcron/gcron_cron.go
Normal 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
21
g/os/gcron/gcron_entry.go
Normal 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()
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
// 是否删除
|
||||
|
||||
@ -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 ""
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
96
g/os/gfsnotify/gfsnotify_filefunc.go
Normal file
96
g/os/gfsnotify/gfsnotify_filefunc.go
Normal 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
|
||||
}
|
||||
284
g/os/gfsnotify/gfsnotify_watcher.go
Normal file
284
g/os/gfsnotify/gfsnotify_watcher.go
Normal 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
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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目录,
|
||||
// 这是一个链式操作,可以设置多个分类,将会创建层级的日志分类目录。
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
|
||||
// 格式化,使用标准库格式
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
137
g/util/gconv/gconv_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
45
g/util/grand/grand_intn.go
Normal file
45
g/util/grand/grand_intn.go
Normal 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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
23
g/util/gutil/gutil_test.go
Normal file
23
g/util/gutil/gutil_test.go
Normal 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{}) {
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
580
geg/database/orm/oracle/gdb.go
Normal file
580
geg/database/orm/oracle/gdb.go
Normal 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()
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
# 模板引擎目录
|
||||
viewpath = "/home/www/templates/"
|
||||
test = "v=1"
|
||||
# MySQL数据库配置
|
||||
[database]
|
||||
[[database.default]]
|
||||
@ -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() {
|
||||
|
||||
@ -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)
|
||||
|
||||
20
geg/net/ghttp/server/download/download.go
Normal file
20
geg/net/ghttp/server/download/download.go
Normal 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()
|
||||
}
|
||||
1
geg/net/ghttp/server/download/text.txt
Normal file
1
geg/net/ghttp/server/download/text.txt
Normal file
@ -0,0 +1 @@
|
||||
下载文件内容。
|
||||
27
geg/net/ghttp/server/exit.go
Normal file
27
geg/net/ghttp/server/exit.go
Normal 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()
|
||||
}
|
||||
31
geg/net/ghttp/server/object/user.go
Normal file
31
geg/net/ghttp/server/object/user.go
Normal 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()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
11
geg/net/ghttp/server/template/tpl1/index.html
Normal file
11
geg/net/ghttp/server/template/tpl1/index.html
Normal 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>
|
||||
23
geg/net/ghttp/server/template/tpl1/tpl1.go
Normal file
23
geg/net/ghttp/server/template/tpl1/tpl1.go
Normal 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()
|
||||
}
|
||||
@ -6,6 +6,6 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(g.Config().Get("serverpath"))
|
||||
fmt.Println(g.Config().GetVar("memcache.0").String())
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
@ -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
20
geg/os/gfpool/gfpool.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {}
|
||||
}
|
||||
|
||||
36
geg/os/gfsnotify/gfsnotify_callback.go
Normal file
36
geg/os/gfsnotify/gfsnotify_callback.go
Normal 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 {}
|
||||
|
||||
}
|
||||
27
geg/os/gfsnotify/gfsnotify_callback_folder.go
Normal file
27
geg/os/gfsnotify/gfsnotify_callback_folder.go
Normal 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 {}
|
||||
}
|
||||
19
geg/os/gfsnotify/gfsnotify_limit.go
Normal file
19
geg/os/gfsnotify/gfsnotify_limit.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
27
geg/os/gtime/gtime_parsertime.go
Normal file
27
geg/os/gtime/gtime_parsertime.go
Normal 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")
|
||||
//}
|
||||
}
|
||||
@ -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
Reference in New Issue
Block a user