mirror of
https://gitee.com/johng/gf
synced 2026-06-08 10:37:44 +08:00
Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1dc6c799e1 | |||
| ae1e075696 | |||
| 104613b056 | |||
| 99577ad874 | |||
| f4773ef1e4 | |||
| 0d315218dd | |||
| ec130d0763 | |||
| 30729e3f93 | |||
| 241d7402cc | |||
| 3b14aba1a2 | |||
| b02205f7cd | |||
| c27bc0023f | |||
| 9698a7c5be | |||
| 071e2f8bb4 | |||
| 726d3f7024 | |||
| 3503aa43b4 | |||
| e865b46304 | |||
| 494f96495e | |||
| 7ed2081513 | |||
| 5110313657 | |||
| 24990e26c8 | |||
| 3ca086bcec | |||
| 7d103c4ee8 | |||
| 5fed6f5681 | |||
| 616539ecb0 | |||
| 9e99e88d27 | |||
| 0e39400dd0 | |||
| 2ba796de01 | |||
| efe2535977 | |||
| c17352b8af | |||
| b1fc3ff17a | |||
| 485dafb616 | |||
| bf25a3a601 | |||
| 14fcd0b2f9 | |||
| cb24714faa | |||
| 36199334f0 | |||
| 72c7e65dfa | |||
| a4ad301b44 | |||
| 72569321fa | |||
| 1600a80124 | |||
| 80c1a02377 | |||
| 0c41909454 | |||
| 2b5d889bb9 | |||
| 2c2a71d429 | |||
| f900414e38 | |||
| 1c72766c34 | |||
| 302f3c1467 | |||
| 9415419324 | |||
| 602592a354 | |||
| 1a4cba5fa5 | |||
| 018853e976 | |||
| 651bd33b73 | |||
| f4644ce685 | |||
| 0a422e9a89 | |||
| 432c16c89f | |||
| 241706cbbf | |||
| 33dd0f9922 | |||
| ed8bb354e5 | |||
| e373392f64 | |||
| 292fd2f39e | |||
| 1c9cb8286f | |||
| 8296061b64 | |||
| bb5d84c29c | |||
| eae857bcf7 | |||
| 40b5162fdf | |||
| 7934ad6904 | |||
| 858b010caa | |||
| 37cd2351e2 | |||
| be1e250a6b | |||
| f86896e5af | |||
| a95b1f0dae | |||
| f1c7b95b33 | |||
| e4a7e23c46 | |||
| 6f15adf57f | |||
| 1966b40d01 | |||
| 24ce4d098e | |||
| 98619f9bc9 | |||
| 1efeb2515d | |||
| ccf837b2bf | |||
| e558863743 | |||
| 43f21dfe92 | |||
| f5b2556b70 |
@ -21,10 +21,12 @@ install:
|
||||
- pwd
|
||||
- mkdir -p $GITEE_GF
|
||||
- cp * $GITEE_GF -R
|
||||
- cd $GITEE_GF
|
||||
- cd $GITEE_GF/g
|
||||
|
||||
script:
|
||||
- cd g && go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
|
||||
- GOARCH=386 go test -v ./...
|
||||
- GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
20
README.MD
20
README.MD
@ -1,17 +1,17 @@
|
||||
# GoFrame
|
||||
<img align="right" height="150px" src="https://gfer.me/cover.png">
|
||||
|
||||
[](https://godoc.org/github.com/johng-cn/gf)
|
||||
[](https://travis-ci.org/johng-cn/gf)
|
||||
[](https://goreportcard.com/report/github.com/johng-cn/gf)
|
||||
[](https://godoc.org/github.com/gogf/gf)
|
||||
[](https://travis-ci.org/gogf/gf)
|
||||
[](https://goreportcard.com/report/github.com/gogf/gf)
|
||||
[](https://gfer.me)
|
||||
[](https://github.com/johng-cn/gf)
|
||||
[](https://github.com/johng-cn/gf)
|
||||
[](https://github.com/johng-cn/gf/releases)
|
||||
[](https://github.com/gogf/gf)
|
||||
[](https://github.com/gogf/gf)
|
||||
[](https://github.com/gogf/gf/releases)
|
||||
|
||||
<!--
|
||||
[](https://codecov.io/gh/johng-cn/gf)
|
||||
[](https://www.codetriage.com/johng-cn/gf)
|
||||
[](https://codecov.io/gh/gogf/gf)
|
||||
[](https://www.codetriage.com/gogf/gf)
|
||||
-->
|
||||
|
||||
`GF(GoFrame)` is a modular, lightweight, loosely coupled, high performance application development framework written in Go. Supporting graceful server, hot updates, multi-domain, multi-port, multi-service, HTTP/HTTPS, dynamic/hook routing and many more features. Providing a series of core components and dozens of practical modules.
|
||||
@ -32,7 +32,7 @@ golang version >= 1.9.2
|
||||
|
||||
# Documentation
|
||||
|
||||
* [中文文档](https://gfer.me/)
|
||||
* [中文文档](https://goframe.org)
|
||||
|
||||
# Architecture
|
||||
<div align=center>
|
||||
@ -92,6 +92,8 @@ func main() {
|
||||
|
||||
# Donators
|
||||
|
||||
<a href="https://gitee.com/zhuhuan12" target="_blank" title="zhuhuan12"><img src="https://gitee.com/uploads/39/751839_zhuhuan12.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/hailaz" target="_blank" title="HaiLaz"><img src="https://gitee.com/uploads/87/1273187_hailaz.png" width="60" align="left"></a>
|
||||
|
||||
23
README_ZH.MD
23
README_ZH.MD
@ -1,17 +1,17 @@
|
||||
# GoFrame
|
||||
<img align="right" height="150px" src="https://gfer.me/cover.png">
|
||||
|
||||
[](https://godoc.org/github.com/johng-cn/gf)
|
||||
[](https://travis-ci.org/johng-cn/gf)
|
||||
[](https://goreportcard.com/report/github.com/johng-cn/gf)
|
||||
[](https://godoc.org/github.com/gogf/gf)
|
||||
[](https://travis-ci.org/gogf/gf)
|
||||
[](https://goreportcard.com/report/github.com/gogf/gf)
|
||||
[](https://gfer.me)
|
||||
[](https://github.com/johng-cn/gf)
|
||||
[](https://github.com/johng-cn/gf)
|
||||
[](https://github.com/johng-cn/gf/releases)
|
||||
[](https://github.com/gogf/gf)
|
||||
[](https://github.com/gogf/gf)
|
||||
[](https://github.com/gogf/gf/releases)
|
||||
|
||||
<!--
|
||||
[](https://codecov.io/gh/johng-cn/gf)
|
||||
[](https://www.codetriage.com/johng-cn/gf)
|
||||
[](https://codecov.io/gh/gogf/gf)
|
||||
[](https://www.codetriage.com/gogf/gf)
|
||||
-->
|
||||
|
||||
`GF(Go Frame)`是一款模块化、松耦合、轻量级、高性能的Go应用开发框架。支持热重启、热更新、多域名、多端口、多服务、HTTP/HTTPS、动态路由等特性
|
||||
@ -51,8 +51,10 @@ golang版本 >= 1.9.2
|
||||
|
||||
|
||||
# 文档
|
||||
[https://gfer.me](https://gfer.me)
|
||||
|
||||
开发文档:[https://goframe.org](https://goframe.org)
|
||||
|
||||
接口文档:[https://godoc.org/github.com/gogf/gf](https://godoc.org/github.com/gogf/gf)
|
||||
|
||||
# 使用
|
||||
```go
|
||||
@ -79,6 +81,7 @@ func main() {
|
||||
|
||||
`GF` 使用非常友好的 [MIT](LICENSE) 开源协议进行发布,永久`100%`开源免费。
|
||||
|
||||
|
||||
# 贡献者(TOP 10)
|
||||
|
||||
<a href="https://gitee.com/johng" target="_blank" title="John"><img src="https://gitee.com/uploads/27/1309327_johng.png" width="60" align="left"></a>
|
||||
@ -97,6 +100,8 @@ func main() {
|
||||
|
||||
# 捐赠者
|
||||
|
||||
<a href="https://gitee.com/zhuhuan12" target="_blank" title="zhuhuan12"><img src="https://gitee.com/uploads/39/751839_zhuhuan12.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/hailaz" target="_blank" title="HaiLaz"><img src="https://gitee.com/uploads/87/1273187_hailaz.png" width="60" align="left"></a>
|
||||
|
||||
30
RELEASE.MD
30
RELEASE.MD
@ -1,3 +1,33 @@
|
||||
# `v1.4.6` (2019-01-24)
|
||||
|
||||
## 新特性
|
||||
1. 新增并发安全的高性能任务定时器模块`gtimer`, 类似于Java的`Timer`,但是比较于Java的`Timer`更加强大,内部实现采用灵活高效的`分层时间轮`设计,被设计为可管理维护百万级别以上数量的定时任务。`gtimer`为`GF`框架的核心模块之一,单元测试覆盖率达到`93.6%`:[https://goframe.org/os/gtimer/index](https://goframe.org/os/gtimer/index)
|
||||
1. 采用任务定时器`gtimer`重构`gcron`定时任务模块,去掉第三方`github.com/robfig/cron`包的使用。`gcron`增加单例模式的定时任务:[https://goframe.org/os/gcron/index#](https://goframe.org/os/gcron/index#);
|
||||
1. `gconv`类型转换模块支持对`struct`结构体中的**指针属性**转换:[https://goframe.org/util/gconv/struct](https://goframe.org/util/gconv/struct);
|
||||
1. `gform`增加对数据库类型的自动识别特性,这一特性在需要将查询结果`json`编码返回时非常有用: [https://goframe.org/database/orm/index](https://goframe.org/database/orm/index)
|
||||
1. `Travis CI`增加对`386`架构的自动化测试支持(目前已支持`386`和`amd64`);
|
||||
|
||||
## 新功能
|
||||
1. `ghttp`模块新增`Exit`、`ExitAll`、`ExitHook`方法,用于HTTP请求处理流程控制: [https://goframe.org/net/ghttp/service/object](https://goframe.org/net/ghttp/service/object);
|
||||
1. `grand`模块增加`Meet/MeetProb`方法,用于给定概率的随机满足判断,增加别名方法`N/Str/Digits/Letters`;
|
||||
1. `gvalid`数据/表单校验模块增加`16X`及`19X`手机号的校验支持;
|
||||
|
||||
## 功能改进
|
||||
1. `gform`设置默认的数据库连接池`CONN_MAX_LIFE`参数值为`30`秒;
|
||||
1. 改进`glist`模块,提高约`20%`左右性能,并增加若干链表操作方法;
|
||||
1. 改进`gqueue`模块,提高约`50`左右性能,并增加模块对`select`语法的支持(使用`Queue.C`): [https://goframe.org/container/gqueue/index](https://goframe.org/container/gqueue/index);
|
||||
1. 改进`gmlock`内存锁模块,并完善单元测试用例:[https://goframe.org/os/gmlock/index](https://goframe.org/os/gmlock/index);
|
||||
1. 改进并发安全容器所有的模块,调整并发安全控制非必需参数`safe...bool`为`unsafe...bool`;
|
||||
1. 改进`gpool`对象复用模块,支持并发安全;
|
||||
1. 更新`gkafka`模块的第三方依赖包;
|
||||
1. 完善`ghttp`模块的单元测试用例;
|
||||
|
||||
|
||||
## Bug Fix
|
||||
1. 修复`gmd5`模块操作文件时的文件指针未关闭问题;
|
||||
1. 修复`gcache`缓存项过期删除失效问题;
|
||||
1. 其他修复;
|
||||
|
||||
# `v1.3.8` (2018-12-26)
|
||||
|
||||
## 新特性
|
||||
|
||||
21
TODO.MD
21
TODO.MD
@ -1,8 +1,4 @@
|
||||
# ON THE WAY
|
||||
1. orm增加更多数据库支持;
|
||||
1. 增加对于数据表Model的封装;
|
||||
1. 更多数据库的ORM功能支持;
|
||||
1. 考虑gdb对象管理增加二级连接池特性,提高New&Close性能;
|
||||
1. 增加图形验证码支持,至少支持数字和英文字母;
|
||||
1. 增加热编译工具,提高开发环境的开发/测试效率(媲美PHP开发效率);
|
||||
1. 增加可选择性的orm tag特性,用以数据表记录与struct对象转换的键名属性映射;
|
||||
@ -47,12 +43,10 @@
|
||||
1. 改进WebServer获取POST参数处理逻辑,当提交非form数据时,例如json数据,针对某些方法可以直接解析;
|
||||
1. WebServer增加可选择的路由覆盖配置,默认情况下不覆盖;
|
||||
1. gkafka这个包比较重,未来从框架中剥离出来;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
1. grpool性能压测结果变慢的问题;
|
||||
1. 增加jumplist的数据结构容器;
|
||||
1. DelayQueue/PriorityQueue;
|
||||
1. gconv针对struct的转换增加json tag支持,gconv.Map默认也支持json tag;
|
||||
|
||||
# DONE
|
||||
1. gconv完善针对不同类型的判断,例如:尽量减少sprintf("%v", xxx)来执行string类型的转换;
|
||||
@ -78,7 +72,7 @@
|
||||
21. 改进控制器及执行对象注册,更友好地支持动态路由注册,例如:注册规则为 /channel/:name,现有的控制器及执行对象注册很难友好支持这种动态形式;
|
||||
22. 当前gpage分页包的输出标签不支持li,大多数CSS框架都是li+a标签模式,需要提供可更加灵活的定制化功能实现;
|
||||
23. 平滑重启机制改进,以便于开发阶段调试;
|
||||
24. 对grpool进行优化改进,包括属性原子操作封装采用gtype实现,修正设计BUG:https://github.com/johng-cn/gf/issues/6;
|
||||
24. 对grpool进行优化改进,包括属性原子操作封装采用gtype实现,修正设计BUG:https://github.com/gogf/gf/issues/6;
|
||||
25. gredis增加redis密码支持;
|
||||
26. 改进ghttp.Server平滑重启机制,当新进程接管服务后,再使用进程间通信方式通知父进程销毁;
|
||||
27. gproc进程间通信增加分组特性,不同的进程间可以通过进程ID以及分组名称发送/获取进程消息;
|
||||
@ -108,4 +102,7 @@
|
||||
1. gcfg/gview/ghttp等模块加上对临时文件目录的自动添加监听判断(基本是开发环境下,特别是windows环境),去掉临时文件的监听,避免临时文件过大引起的运行缓慢占用内存问题;
|
||||
1. 改进gfpool在文件指针变化时的更新;
|
||||
1. ghttp hook回调使用方式在注册路由比较多的时候,优先级可能使得开发者混乱,考虑方式便于管理;
|
||||
1. gform对于MySQL字段类型为datetime类型的时区问题分析;
|
||||
1. gform对于MySQL字段类型为datetime类型的时区问题分析;
|
||||
1. 改进证书打开失败时的WebServer错误提示,前置HOOK校验后关闭后续的HOOK逻辑执行;
|
||||
1. 目前WebServer的HOOK是按照优先级执行的,需要增加覆盖特性;
|
||||
|
||||
|
||||
@ -5,9 +5,10 @@
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package garray provides kinds of concurrent-safe(alternative) arrays.
|
||||
//
|
||||
// 并发安全的数组.
|
||||
package garray
|
||||
|
||||
func New(size int, cap int, safe...bool) *Array {
|
||||
return NewArray(size, cap, safe...)
|
||||
func New(size int, cap int, unsafe...bool) *Array {
|
||||
return NewArray(size, cap, unsafe...)
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
package garray
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type IntArray struct {
|
||||
@ -17,9 +17,9 @@ type IntArray struct {
|
||||
array []int // 底层数组
|
||||
}
|
||||
|
||||
func NewIntArray(size int, cap int, safe...bool) *IntArray {
|
||||
func NewIntArray(size int, cap int, unsafe...bool) *IntArray {
|
||||
a := &IntArray{
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
a.size = size
|
||||
if cap > 0 {
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
package garray
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type Array struct {
|
||||
@ -17,9 +17,9 @@ type Array struct {
|
||||
array []interface{} // 底层数组
|
||||
}
|
||||
|
||||
func NewArray(size int, cap int, safe...bool) *Array {
|
||||
func NewArray(size int, cap int, unsafe...bool) *Array {
|
||||
a := &Array{
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
a.size = size
|
||||
if cap > 0 {
|
||||
|
||||
@ -8,7 +8,7 @@ package garray
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
// 默认按照从低到高进行排序
|
||||
@ -21,9 +21,9 @@ type SortedIntArray struct {
|
||||
}
|
||||
|
||||
// 创建一个排序的int数组
|
||||
func NewSortedIntArray(cap int, safe...bool) *SortedIntArray {
|
||||
func NewSortedIntArray(cap int, unsafe...bool) *SortedIntArray {
|
||||
return &SortedIntArray {
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
array : make([]int, 0, cap),
|
||||
unique : gtype.NewBool(),
|
||||
compareFunc : func(v1, v2 int) int {
|
||||
@ -156,10 +156,10 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int)
|
||||
for min <= max {
|
||||
mid = int((min + max) / 2)
|
||||
cmp = a.compareFunc(value, a.array[mid])
|
||||
switch cmp {
|
||||
case -1 : max = mid - 1
|
||||
case 1 : min = mid + 1
|
||||
case 0 :
|
||||
switch {
|
||||
case cmp < 0 : max = mid - 1
|
||||
case cmp > 0 : min = mid + 1
|
||||
default :
|
||||
return mid, cmp
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ package garray
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
// 默认按照从低到高进行排序
|
||||
@ -20,9 +20,9 @@ type SortedArray struct {
|
||||
compareFunc func(v1, v2 interface{}) int // 比较函数,返回值 -1: v1 < v2;0: v1 == v2;1: v1 > v2
|
||||
}
|
||||
|
||||
func NewSortedArray(cap int, compareFunc func(v1, v2 interface{}) int, safe...bool) *SortedArray {
|
||||
func NewSortedArray(cap int, compareFunc func(v1, v2 interface{}) int, unsafe...bool) *SortedArray {
|
||||
return &SortedArray{
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
unique : gtype.NewBool(),
|
||||
array : make([]interface{}, 0, cap),
|
||||
compareFunc : compareFunc,
|
||||
@ -149,10 +149,10 @@ func (a *SortedArray) binSearch(value interface{}, lock bool)(index int, result
|
||||
for min <= max {
|
||||
mid = int((min + max) / 2)
|
||||
cmp = a.compareFunc(value, a.array[mid])
|
||||
switch cmp {
|
||||
case -1 : max = mid - 1
|
||||
case 1 : min = mid + 1
|
||||
case 0 :
|
||||
switch {
|
||||
case cmp < 0 : max = mid - 1
|
||||
case cmp > 0 : min = mid + 1
|
||||
default :
|
||||
return mid, cmp
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ package garray
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"strings"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
// 默认按照从低到高进行排序
|
||||
@ -21,9 +21,9 @@ type SortedStringArray struct {
|
||||
compareFunc func(v1, v2 string) int // 比较函数,返回值 -1: v1 < v2;0: v1 == v2;1: v1 > v2
|
||||
}
|
||||
|
||||
func NewSortedStringArray(cap int, safe...bool) *SortedStringArray {
|
||||
func NewSortedStringArray(cap int, unsafe...bool) *SortedStringArray {
|
||||
return &SortedStringArray {
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
array : make([]string, 0, cap),
|
||||
unique : gtype.NewBool(),
|
||||
compareFunc : func(v1, v2 string) int {
|
||||
@ -150,10 +150,10 @@ func (a *SortedStringArray) binSearch(value string, lock bool) (index int, resul
|
||||
for min <= max {
|
||||
mid = int((min + max) / 2)
|
||||
cmp = a.compareFunc(value, a.array[mid])
|
||||
switch cmp {
|
||||
case -1 : max = mid - 1
|
||||
case 1 : min = mid + 1
|
||||
case 0 :
|
||||
switch {
|
||||
case cmp < 0 : max = mid - 1
|
||||
case cmp > 0 : min = mid + 1
|
||||
default :
|
||||
return mid, cmp
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ package garray
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type StringArray struct {
|
||||
@ -18,9 +18,9 @@ type StringArray struct {
|
||||
array []string // 底层数组
|
||||
}
|
||||
|
||||
func NewStringArray(size int, cap int, safe...bool) *StringArray {
|
||||
func NewStringArray(size int, cap int, unsafe...bool) *StringArray {
|
||||
a := &StringArray{
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
a.size = size
|
||||
if cap > 0 {
|
||||
|
||||
43
g/container/garray/garray_z_bench_test.go
Normal file
43
g/container/garray/garray_z_bench_test.go
Normal file
@ -0,0 +1,43 @@
|
||||
// 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.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/garray"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
sortedIntArray = garray.NewSortedIntArray(0)
|
||||
)
|
||||
|
||||
func BenchmarkSortedIntArray_Add(b *testing.B) {
|
||||
b.N = 1000
|
||||
for i := 0; i < b.N; i++ {
|
||||
sortedIntArray.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSortedIntArray_Search(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sortedIntArray.Search(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSortedIntArray_PopLeft(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sortedIntArray.PopLeft()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSortedIntArray_PopRight(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
sortedIntArray.PopLeft()
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gchan provides graceful operations for channel.
|
||||
//
|
||||
// 优雅的Channel操作.
|
||||
package gchan
|
||||
|
||||
|
||||
@ -1,264 +1,306 @@
|
||||
// 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,
|
||||
// If a copy of the MIT was not distributed with l file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
// Package glist provides a concurrent-safe(alternative) doubly linked list.
|
||||
//
|
||||
// 并发安全的双向链表.
|
||||
package glist
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"container/list"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
// 变长双向链表
|
||||
type List struct {
|
||||
mu *rwmutex.RWMutex
|
||||
list *list.List
|
||||
mu *rwmutex.RWMutex
|
||||
list *list.List
|
||||
}
|
||||
|
||||
type Element = list.Element
|
||||
|
||||
|
||||
// 获得一个变长链表指针
|
||||
func New(safe...bool) *List {
|
||||
func New(unsafe...bool) *List {
|
||||
return &List {
|
||||
mu : rwmutex.New(safe...),
|
||||
list : list.New(),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
list : list.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// 往链表头入栈数据项
|
||||
func (this *List) PushFront(v interface{}) *list.Element {
|
||||
this.mu.Lock()
|
||||
e := this.list.PushFront(v)
|
||||
this.mu.Unlock()
|
||||
return e
|
||||
func (l *List) PushFront(v interface{}) (e *Element) {
|
||||
l.mu.Lock()
|
||||
e = l.list.PushFront(v)
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 往链表尾入栈数据项
|
||||
func (this *List) PushBack(v interface{}) *list.Element {
|
||||
this.mu.Lock()
|
||||
r := this.list.PushBack(v)
|
||||
this.mu.Unlock()
|
||||
return r
|
||||
func (l *List) PushBack(v interface{}) (e *Element) {
|
||||
l.mu.Lock()
|
||||
e = l.list.PushBack(v)
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 在list 中元素mark之后插入一个值为v的元素,并返回该元素,如果mark不是list中元素,则list不改变。
|
||||
func (this *List) InsertAfter(v interface{}, mark *list.Element) *list.Element {
|
||||
this.mu.Lock()
|
||||
r := this.list.InsertAfter(v, mark)
|
||||
this.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// 在list 中元素mark之前插入一个值为v的元素,并返回该元素,如果mark不是list中元素,则list不改变。
|
||||
func (this *List) InsertBefore(v interface{}, mark *list.Element) *list.Element {
|
||||
this.mu.Lock()
|
||||
r := this.list.InsertBefore(v, mark)
|
||||
this.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
|
||||
// 批量往链表头入栈数据项
|
||||
func (this *List) BatchPushFront(vs []interface{}) {
|
||||
this.mu.Lock()
|
||||
for _, item := range vs {
|
||||
this.list.PushFront(item)
|
||||
func (l *List) BatchPushFront(values []interface{}) {
|
||||
l.mu.Lock()
|
||||
for _, v := range values {
|
||||
l.list.PushFront(v)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// 批量往链表尾入栈数据项
|
||||
func (l *List) BatchPushBack(values []interface{}) {
|
||||
l.mu.Lock()
|
||||
for _, v := range values {
|
||||
l.list.PushBack(v)
|
||||
}
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// 从链表尾端出栈数据项(删除)
|
||||
func (this *List) PopBack() interface{} {
|
||||
this.mu.Lock()
|
||||
if elem := this.list.Back(); elem != nil {
|
||||
item := this.list.Remove(elem)
|
||||
this.mu.Unlock()
|
||||
return item
|
||||
func (l *List) PopBack() (value interface{}) {
|
||||
l.mu.Lock()
|
||||
if e := l.list.Back(); e != nil {
|
||||
value = l.list.Remove(e)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return nil
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 从链表头端出栈数据项(删除)
|
||||
func (this *List) PopFront() interface{} {
|
||||
this.mu.Lock()
|
||||
if elem := this.list.Front(); elem != nil {
|
||||
item := this.list.Remove(elem)
|
||||
this.mu.Unlock()
|
||||
return item
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return nil
|
||||
func (l *List) PopFront() (value interface{}) {
|
||||
l.mu.Lock()
|
||||
if e := l.list.Front(); e != nil {
|
||||
value = l.list.Remove(e)
|
||||
}
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 批量从链表尾端出栈数据项(删除)
|
||||
func (this *List) BatchPopBack(max int) []interface{} {
|
||||
this.mu.Lock()
|
||||
count := this.list.Len()
|
||||
if count == 0 {
|
||||
this.mu.Unlock()
|
||||
return []interface{}{}
|
||||
}
|
||||
|
||||
if count > max {
|
||||
count = max
|
||||
}
|
||||
items := make([]interface{}, count)
|
||||
for i := 0; i < count; i++ {
|
||||
items[i] = this.list.Remove(this.list.Back())
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return items
|
||||
func (l *List) BatchPopBack(max int) (values []interface{}) {
|
||||
l.mu.Lock()
|
||||
length := l.list.Len()
|
||||
if length > 0 {
|
||||
if max > 0 && max < length {
|
||||
length = max
|
||||
}
|
||||
tempe := (*Element)(nil)
|
||||
values = make([]interface{}, length)
|
||||
for i := 0; i < length; i++ {
|
||||
tempe = l.list.Back()
|
||||
values[i] = l.list.Remove(tempe)
|
||||
}
|
||||
}
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 批量从链表头端出栈数据项(删除)
|
||||
func (this *List) BatchPopFront(max int) []interface{} {
|
||||
this.mu.Lock()
|
||||
count := this.list.Len()
|
||||
if count == 0 {
|
||||
this.mu.Unlock()
|
||||
return []interface{}{}
|
||||
}
|
||||
|
||||
if count > max {
|
||||
count = max
|
||||
}
|
||||
items := make([]interface{}, count)
|
||||
for i := 0; i < count; i++ {
|
||||
items[i] = this.list.Remove(this.list.Front())
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return items
|
||||
func (l *List) BatchPopFront(max int) (values []interface{}) {
|
||||
l.mu.RLock()
|
||||
length := l.list.Len()
|
||||
if length > 0 {
|
||||
if max > 0 && max < length {
|
||||
length = max
|
||||
}
|
||||
tempe := (*Element)(nil)
|
||||
values = make([]interface{}, length)
|
||||
for i := 0; i < length; i++ {
|
||||
tempe = l.list.Front()
|
||||
values[i] = l.list.Remove(tempe)
|
||||
}
|
||||
}
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 批量从链表尾端依次获取所有数据(删除)
|
||||
func (this *List) PopBackAll() []interface{} {
|
||||
this.mu.Lock()
|
||||
count := this.list.Len()
|
||||
if count == 0 {
|
||||
this.mu.Unlock()
|
||||
return []interface{}{}
|
||||
}
|
||||
items := make([]interface{}, count)
|
||||
for i := 0; i < count; i++ {
|
||||
items[i] = this.list.Remove(this.list.Back())
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return items
|
||||
func (l *List) PopBackAll() []interface{} {
|
||||
return l.BatchPopBack(-1)
|
||||
}
|
||||
|
||||
// 批量从链表头端依次获取所有数据(删除)
|
||||
func (this *List) PopFrontAll() []interface{} {
|
||||
this.mu.Lock()
|
||||
count := this.list.Len()
|
||||
if count == 0 {
|
||||
this.mu.Unlock()
|
||||
return []interface{}{}
|
||||
}
|
||||
items := make([]interface{}, count)
|
||||
for i := 0; i < count; i++ {
|
||||
items[i] = this.list.Remove(this.list.Front())
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return items
|
||||
}
|
||||
|
||||
// 删除数据项
|
||||
func (this *List) Remove(e *list.Element) interface{} {
|
||||
this.mu.Lock()
|
||||
r := this.list.Remove(e)
|
||||
this.mu.Unlock()
|
||||
return r
|
||||
}
|
||||
|
||||
// 删除所有数据项
|
||||
func (this *List) RemoveAll() {
|
||||
this.mu.Lock()
|
||||
this.list = list.New()
|
||||
this.mu.Unlock()
|
||||
func (l *List) PopFrontAll() []interface{} {
|
||||
return l.BatchPopFront(-1)
|
||||
}
|
||||
|
||||
// 从链表头获取所有数据(不删除)
|
||||
func (this *List) FrontAll() []interface{} {
|
||||
this.mu.RLock()
|
||||
count := this.list.Len()
|
||||
if count == 0 {
|
||||
this.mu.RUnlock()
|
||||
return []interface{}{}
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0, count)
|
||||
for e := this.list.Front(); e != nil; e = e.Next() {
|
||||
items = append(items, e.Value)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return items
|
||||
func (l *List) FrontAll() (values []interface{}) {
|
||||
l.mu.RLock()
|
||||
length := l.list.Len()
|
||||
if length > 0 {
|
||||
values = make([]interface{}, length)
|
||||
for i, e := 0, l.list.Front(); i < length; i, e = i + 1, e.Next() {
|
||||
values[i] = e.Value
|
||||
}
|
||||
}
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 从链表尾获取所有数据(不删除)
|
||||
func (this *List) BackAll() []interface{} {
|
||||
this.mu.RLock()
|
||||
count := this.list.Len()
|
||||
if count == 0 {
|
||||
this.mu.RUnlock()
|
||||
return []interface{}{}
|
||||
}
|
||||
|
||||
items := make([]interface{}, 0, count)
|
||||
for e := this.list.Back(); e != nil; e = e.Prev() {
|
||||
items = append(items, e.Value)
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
return items
|
||||
func (l *List) BackAll() (values []interface{}) {
|
||||
l.mu.RLock()
|
||||
length := l.list.Len()
|
||||
if length > 0 {
|
||||
values = make([]interface{}, length)
|
||||
for i, e := 0, l.list.Back(); i < length; i, e = i + 1, e.Prev() {
|
||||
values[i] = e.Value
|
||||
}
|
||||
}
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取链表头值(不删除)
|
||||
func (this *List) FrontItem() interface{} {
|
||||
this.mu.RLock()
|
||||
if f := this.list.Front(); f != nil {
|
||||
this.mu.RUnlock()
|
||||
return f.Value
|
||||
}
|
||||
|
||||
this.mu.RUnlock()
|
||||
return nil
|
||||
func (l *List) FrontItem() (value interface{}) {
|
||||
l.mu.RLock()
|
||||
if e := l.list.Front(); e != nil {
|
||||
value = e.Value
|
||||
}
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取链表尾值(不删除)
|
||||
func (this *List) BackItem() interface{} {
|
||||
this.mu.RLock()
|
||||
if f := this.list.Back(); f != nil {
|
||||
this.mu.RUnlock()
|
||||
return f.Value
|
||||
func (l *List) BackItem() (value interface{}) {
|
||||
l.mu.RLock()
|
||||
if e := l.list.Back(); e != nil {
|
||||
value = e.Value
|
||||
}
|
||||
|
||||
this.mu.RUnlock()
|
||||
return nil
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取表头指针
|
||||
func (this *List) Front() *list.Element {
|
||||
this.mu.RLock()
|
||||
r := this.list.Front()
|
||||
this.mu.RUnlock()
|
||||
return r
|
||||
func (l *List) Front() (e *Element) {
|
||||
l.mu.RLock()
|
||||
e = l.list.Front()
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取表位指针
|
||||
func (this *List) Back() *list.Element {
|
||||
this.mu.RLock()
|
||||
r := this.list.Back()
|
||||
this.mu.RUnlock()
|
||||
return r
|
||||
func (l *List) Back() (e *Element) {
|
||||
l.mu.RLock()
|
||||
e = l.list.Back()
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取链表长度
|
||||
func (this *List) Len() int {
|
||||
this.mu.RLock()
|
||||
length := this.list.Len()
|
||||
this.mu.RUnlock()
|
||||
return length
|
||||
func (l *List) Len() (length int) {
|
||||
l.mu.RLock()
|
||||
length = l.list.Len()
|
||||
l.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (l *List) MoveBefore(e, p *Element) {
|
||||
l.mu.Lock()
|
||||
l.list.MoveBefore(e, p)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
func (l *List) MoveAfter(e, p *Element) {
|
||||
l.mu.Lock()
|
||||
l.list.MoveAfter(e, p)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
func (l *List) MoveToFront(e *Element) {
|
||||
l.mu.Lock()
|
||||
l.list.MoveToFront(e)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
func (l *List) MoveToBack(e *Element) {
|
||||
l.mu.Lock()
|
||||
l.list.MoveToBack(e)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
func (l *List) PushBackList(other *List) {
|
||||
if l != other {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
l.mu.Lock()
|
||||
l.list.PushBackList(other.list)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
func (l *List) PushFrontList(other *List) {
|
||||
if l != other {
|
||||
other.mu.RLock()
|
||||
defer other.mu.RUnlock()
|
||||
}
|
||||
l.mu.Lock()
|
||||
l.list.PushFrontList(other.list)
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// 在list中元素项p之后插入一个值为v的元素,并返回该元素,如果mark不是list中元素,则list不改变。
|
||||
func (l *List) InsertAfter(v interface{}, p *Element) (e *Element) {
|
||||
l.mu.Lock()
|
||||
e = l.list.InsertAfter(v, p)
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 在list中元素项p之前插入一个值为v的元素,并返回该元素,如果mark不是list中元素,则list不改变。
|
||||
func (l *List) InsertBefore(v interface{}, p *Element) (e *Element) {
|
||||
l.mu.Lock()
|
||||
e = l.list.InsertBefore(v, p)
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 删除数据项e, 并返回删除项的元素项
|
||||
func (l *List) Remove(e *Element) (value interface{}) {
|
||||
l.mu.Lock()
|
||||
value = l.list.Remove(e)
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 批量删除数据项
|
||||
func (l *List) BatchRemove(es []*Element) {
|
||||
l.mu.Lock()
|
||||
for _, e := range es {
|
||||
l.list.Remove(e)
|
||||
}
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 删除所有数据项
|
||||
func (l *List) RemoveAll() {
|
||||
l.mu.Lock()
|
||||
l.list = list.New()
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// 读锁操作
|
||||
func (l *List) RLockFunc(f func(list *list.List)) {
|
||||
l.mu.RLock()
|
||||
defer l.mu.RUnlock()
|
||||
f(l.list)
|
||||
}
|
||||
|
||||
// 写锁操作
|
||||
func (l *List) LockFunc(f func(list *list.List)) {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
f(l.list)
|
||||
}
|
||||
@ -12,35 +12,45 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var l = New()
|
||||
var (
|
||||
l = New()
|
||||
bn = 20000000
|
||||
)
|
||||
|
||||
func Benchmark_PushBack(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PushBack(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_PopFront(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PopFront()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_PushFront(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PushFront(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_PopBack(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PopBack()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Len(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.Len()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_PopFront(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PopFront()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_PopBack(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PopBack()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
367
g/container/glist/glist_z_unit_test.go
Normal file
367
g/container/glist/glist_z_unit_test.go
Normal file
@ -0,0 +1,367 @@
|
||||
// Copyright 2019 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 glist
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 检查链表长度
|
||||
func checkListLen(t *testing.T, l *List, len int) bool {
|
||||
if n := l.Len(); n != len {
|
||||
t.Errorf("l.Len() = %d, want %d", n, len)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查指针地址
|
||||
func checkListPointers(t *testing.T, l *List, es []*Element) {
|
||||
if !checkListLen(t, l, len(es)) {
|
||||
return
|
||||
}
|
||||
l.RLockFunc(func(list *list.List) {
|
||||
for i, e := 0, l.list.Front(); i < list.Len(); i, e = i + 1, e.Next() {
|
||||
if e.Prev() != es[i].Prev() {
|
||||
t.Errorf("list[%d].Prev = %p, want %p", i, e.Prev(), es[i].Prev())
|
||||
}
|
||||
if e.Next() != es[i].Next() {
|
||||
t.Errorf("list[%d].Next = %p, want %p", i, e.Next(), es[i].Next())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
l := New()
|
||||
l.PushFront(1)
|
||||
l.PushFront(2)
|
||||
if v := l.PopBack(); v != 1 {
|
||||
t.Errorf("EXPECT %v, GOT %v", 1, v)
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
}
|
||||
if v := l.PopBack(); v != 2 {
|
||||
t.Errorf("EXPECT %v, GOT %v", 2, v)
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
}
|
||||
if v := l.PopBack(); v != nil {
|
||||
t.Errorf("EXPECT %v, GOT %v", nil, v)
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
}
|
||||
l.PushBack(1)
|
||||
l.PushBack(2)
|
||||
if v := l.PopFront(); v != 1 {
|
||||
t.Errorf("EXPECT %v, GOT %v", 1, v)
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
}
|
||||
if v := l.PopFront(); v != 2 {
|
||||
t.Errorf("EXPECT %v, GOT %v", 2, v)
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
}
|
||||
if v := l.PopFront(); v != nil {
|
||||
t.Errorf("EXPECT %v, GOT %v", nil, v)
|
||||
} else {
|
||||
//fmt.Println(v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
l := New()
|
||||
checkListPointers(t, l, []*Element{})
|
||||
|
||||
// Single element list
|
||||
e := l.PushFront("a")
|
||||
checkListPointers(t, l, []*Element{e})
|
||||
l.MoveToFront(e)
|
||||
checkListPointers(t, l, []*Element{e})
|
||||
l.MoveToBack(e)
|
||||
checkListPointers(t, l, []*Element{e})
|
||||
l.Remove(e)
|
||||
checkListPointers(t, l, []*Element{})
|
||||
|
||||
// Bigger list
|
||||
e2 := l.PushFront(2)
|
||||
e1 := l.PushFront(1)
|
||||
e3 := l.PushBack(3)
|
||||
e4 := l.PushBack("banana")
|
||||
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
|
||||
|
||||
l.Remove(e2)
|
||||
checkListPointers(t, l, []*Element{e1, e3, e4})
|
||||
|
||||
l.MoveToFront(e3) // move from middle
|
||||
checkListPointers(t, l, []*Element{e3, e1, e4})
|
||||
|
||||
l.MoveToFront(e1)
|
||||
l.MoveToBack(e3) // move from middle
|
||||
checkListPointers(t, l, []*Element{e1, e4, e3})
|
||||
|
||||
l.MoveToFront(e3) // move from back
|
||||
checkListPointers(t, l, []*Element{e3, e1, e4})
|
||||
l.MoveToFront(e3) // should be no-op
|
||||
checkListPointers(t, l, []*Element{e3, e1, e4})
|
||||
|
||||
l.MoveToBack(e3) // move from front
|
||||
checkListPointers(t, l, []*Element{e1, e4, e3})
|
||||
l.MoveToBack(e3) // should be no-op
|
||||
checkListPointers(t, l, []*Element{e1, e4, e3})
|
||||
|
||||
e2 = l.InsertBefore(2, e1) // insert before front
|
||||
checkListPointers(t, l, []*Element{e2, e1, e4, e3})
|
||||
l.Remove(e2)
|
||||
e2 = l.InsertBefore(2, e4) // insert before middle
|
||||
checkListPointers(t, l, []*Element{e1, e2, e4, e3})
|
||||
l.Remove(e2)
|
||||
e2 = l.InsertBefore(2, e3) // insert before back
|
||||
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
|
||||
l.Remove(e2)
|
||||
|
||||
e2 = l.InsertAfter(2, e1) // insert after front
|
||||
checkListPointers(t, l, []*Element{e1, e2, e4, e3})
|
||||
l.Remove(e2)
|
||||
e2 = l.InsertAfter(2, e4) // insert after middle
|
||||
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
|
||||
l.Remove(e2)
|
||||
e2 = l.InsertAfter(2, e3) // insert after back
|
||||
checkListPointers(t, l, []*Element{e1, e4, e3, e2})
|
||||
l.Remove(e2)
|
||||
|
||||
// Check standard iteration.
|
||||
sum := 0
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
if i, ok := e.Value.(int); ok {
|
||||
sum += i
|
||||
}
|
||||
}
|
||||
if sum != 4 {
|
||||
t.Errorf("sum over l = %d, want 4", sum)
|
||||
}
|
||||
|
||||
// Clear all elements by iterating
|
||||
var next *Element
|
||||
for e := l.Front(); e != nil; e = next {
|
||||
next = e.Next()
|
||||
l.Remove(e)
|
||||
}
|
||||
checkListPointers(t, l, []*Element{})
|
||||
}
|
||||
|
||||
func checkList(t *testing.T, l *List, es []interface{}) {
|
||||
if !checkListLen(t, l, len(es)) {
|
||||
return
|
||||
}
|
||||
|
||||
i := 0
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
le := e.Value.(int)
|
||||
if le != es[i] {
|
||||
t.Errorf("elt[%d].Value() = %v, want %v", i, le, es[i])
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtending(t *testing.T) {
|
||||
l1 := New()
|
||||
l2 := New()
|
||||
|
||||
l1.PushBack(1)
|
||||
l1.PushBack(2)
|
||||
l1.PushBack(3)
|
||||
|
||||
l2.PushBack(4)
|
||||
l2.PushBack(5)
|
||||
|
||||
l3 := New()
|
||||
l3.PushBackList(l1)
|
||||
checkList(t, l3, []interface{}{1, 2, 3})
|
||||
l3.PushBackList(l2)
|
||||
checkList(t, l3, []interface{}{1, 2, 3, 4, 5})
|
||||
|
||||
l3 = New()
|
||||
l3.PushFrontList(l2)
|
||||
checkList(t, l3, []interface{}{4, 5})
|
||||
l3.PushFrontList(l1)
|
||||
checkList(t, l3, []interface{}{1, 2, 3, 4, 5})
|
||||
|
||||
checkList(t, l1, []interface{}{1, 2, 3})
|
||||
checkList(t, l2, []interface{}{4, 5})
|
||||
|
||||
l3 = New()
|
||||
l3.PushBackList(l1)
|
||||
checkList(t, l3, []interface{}{1, 2, 3})
|
||||
l3.PushBackList(l3)
|
||||
checkList(t, l3, []interface{}{1, 2, 3, 1, 2, 3})
|
||||
|
||||
l3 = New()
|
||||
l3.PushFrontList(l1)
|
||||
checkList(t, l3, []interface{}{1, 2, 3})
|
||||
l3.PushFrontList(l3)
|
||||
checkList(t, l3, []interface{}{1, 2, 3, 1, 2, 3})
|
||||
|
||||
l3 = New()
|
||||
l1.PushBackList(l3)
|
||||
checkList(t, l1, []interface{}{1, 2, 3})
|
||||
l1.PushFrontList(l3)
|
||||
checkList(t, l1, []interface{}{1, 2, 3})
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
l := New()
|
||||
e1 := l.PushBack(1)
|
||||
e2 := l.PushBack(2)
|
||||
checkListPointers(t, l, []*Element{e1, e2})
|
||||
//e := l.Front()
|
||||
//l.Remove(e)
|
||||
//checkListPointers(t, l, []*Element{e2})
|
||||
//l.Remove(e)
|
||||
//checkListPointers(t, l, []*Element{e2})
|
||||
}
|
||||
|
||||
func TestIssue4103(t *testing.T) {
|
||||
l1 := New()
|
||||
l1.PushBack(1)
|
||||
l1.PushBack(2)
|
||||
|
||||
l2 := New()
|
||||
l2.PushBack(3)
|
||||
l2.PushBack(4)
|
||||
|
||||
e := l1.Front()
|
||||
l2.Remove(e) // l2 should not change because e is not an element of l2
|
||||
if n := l2.Len(); n != 2 {
|
||||
t.Errorf("l2.Len() = %d, want 2", n)
|
||||
}
|
||||
|
||||
l1.InsertBefore(8, e)
|
||||
if n := l1.Len(); n != 3 {
|
||||
t.Errorf("l1.Len() = %d, want 3", n)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue6349(t *testing.T) {
|
||||
l := New()
|
||||
l.PushBack(1)
|
||||
l.PushBack(2)
|
||||
|
||||
e := l.Front()
|
||||
l.Remove(e)
|
||||
if e.Value != 1 {
|
||||
t.Errorf("e.value = %d, want 1", e.Value)
|
||||
}
|
||||
//if e.Next() != nil {
|
||||
// t.Errorf("e.Next() != nil")
|
||||
//}
|
||||
//if e.Prev() != nil {
|
||||
// t.Errorf("e.Prev() != nil")
|
||||
//}
|
||||
}
|
||||
|
||||
func TestMove(t *testing.T) {
|
||||
l := New()
|
||||
e1 := l.PushBack(1)
|
||||
e2 := l.PushBack(2)
|
||||
e3 := l.PushBack(3)
|
||||
e4 := l.PushBack(4)
|
||||
|
||||
l.MoveAfter(e3, e3)
|
||||
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
|
||||
l.MoveBefore(e2, e2)
|
||||
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
|
||||
|
||||
l.MoveAfter(e3, e2)
|
||||
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
|
||||
l.MoveBefore(e2, e3)
|
||||
checkListPointers(t, l, []*Element{e1, e2, e3, e4})
|
||||
|
||||
l.MoveBefore(e2, e4)
|
||||
checkListPointers(t, l, []*Element{e1, e3, e2, e4})
|
||||
e2, e3 = e3, e2
|
||||
|
||||
l.MoveBefore(e4, e1)
|
||||
checkListPointers(t, l, []*Element{e4, e1, e2, e3})
|
||||
e1, e2, e3, e4 = e4, e1, e2, e3
|
||||
|
||||
l.MoveAfter(e4, e1)
|
||||
checkListPointers(t, l, []*Element{e1, e4, e2, e3})
|
||||
e2, e3, e4 = e4, e2, e3
|
||||
|
||||
l.MoveAfter(e2, e3)
|
||||
checkListPointers(t, l, []*Element{e1, e3, e2, e4})
|
||||
e2, e3 = e3, e2
|
||||
}
|
||||
|
||||
// Test PushFront, PushBack, PushFrontList, PushBackList with uninitialized List
|
||||
func TestZeroList(t *testing.T) {
|
||||
var l1 = New()
|
||||
l1.PushFront(1)
|
||||
checkList(t, l1, []interface{}{1})
|
||||
|
||||
var l2 = New()
|
||||
l2.PushBack(1)
|
||||
checkList(t, l2, []interface{}{1})
|
||||
|
||||
var l3 = New()
|
||||
l3.PushFrontList(l1)
|
||||
checkList(t, l3, []interface{}{1})
|
||||
|
||||
var l4 = New()
|
||||
l4.PushBackList(l2)
|
||||
checkList(t, l4, []interface{}{1})
|
||||
}
|
||||
|
||||
// Test that a list l is not modified when calling InsertBefore with a mark that is not an element of l.
|
||||
func TestInsertBeforeUnknownMark(t *testing.T) {
|
||||
l := New()
|
||||
l.PushBack(1)
|
||||
l.PushBack(2)
|
||||
l.PushBack(3)
|
||||
l.InsertBefore(1, new(Element))
|
||||
checkList(t, l, []interface{}{1, 2, 3})
|
||||
}
|
||||
|
||||
// Test that a list l is not modified when calling InsertAfter with a mark that is not an element of l.
|
||||
func TestInsertAfterUnknownMark(t *testing.T) {
|
||||
l := New()
|
||||
l.PushBack(1)
|
||||
l.PushBack(2)
|
||||
l.PushBack(3)
|
||||
l.InsertAfter(1, new(Element))
|
||||
checkList(t, l, []interface{}{1, 2, 3})
|
||||
}
|
||||
|
||||
// Test that a list l is not modified when calling MoveAfter or MoveBefore with a mark that is not an element of l.
|
||||
func TestMoveUnknownMark(t *testing.T) {
|
||||
l1 := New()
|
||||
e1 := l1.PushBack(1)
|
||||
|
||||
l2 := New()
|
||||
e2 := l2.PushBack(2)
|
||||
|
||||
l1.MoveAfter(e1, e2)
|
||||
checkList(t, l1, []interface{}{1})
|
||||
checkList(t, l2, []interface{}{2})
|
||||
|
||||
l1.MoveBefore(e1, e2)
|
||||
checkList(t, l1, []interface{}{1})
|
||||
checkList(t, l2, []interface{}{2})
|
||||
}
|
||||
|
||||
func TestList_RemoveAll(t *testing.T) {
|
||||
l := New()
|
||||
l.PushBack(1)
|
||||
l.RemoveAll()
|
||||
checkList(t, l, []interface{}{})
|
||||
l.PushBack(2)
|
||||
checkList(t, l, []interface{}{2})
|
||||
}
|
||||
@ -5,6 +5,7 @@
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gmap provides kinds of concurrent-safe(alternative) maps.
|
||||
//
|
||||
// 并发安全的哈希MAP.
|
||||
package gmap
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type IntBoolMap struct {
|
||||
@ -16,10 +16,10 @@ type IntBoolMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
}
|
||||
|
||||
func NewIntBoolMap(safe...bool) *IntBoolMap {
|
||||
func NewIntBoolMap(unsafe...bool) *IntBoolMap {
|
||||
return &IntBoolMap{
|
||||
m : make(map[int]bool),
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type IntIntMap struct {
|
||||
@ -16,10 +16,10 @@ type IntIntMap struct {
|
||||
m map[int]int
|
||||
}
|
||||
|
||||
func NewIntIntMap(safe...bool) *IntIntMap {
|
||||
func NewIntIntMap(unsafe...bool) *IntIntMap {
|
||||
return &IntIntMap{
|
||||
m : make(map[int]int),
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,17 +7,17 @@
|
||||
|
||||
package gmap
|
||||
|
||||
import "gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
import "gitee.com/johng/gf/g/internal/rwmutex"
|
||||
|
||||
type IntInterfaceMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
m map[int]interface{}
|
||||
}
|
||||
|
||||
func NewIntInterfaceMap(safe...bool) *IntInterfaceMap {
|
||||
func NewIntInterfaceMap(unsafe...bool) *IntInterfaceMap {
|
||||
return &IntInterfaceMap{
|
||||
m : make(map[int]interface{}),
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type IntStringMap struct {
|
||||
@ -16,10 +16,10 @@ type IntStringMap struct {
|
||||
m map[int]string
|
||||
}
|
||||
|
||||
func NewIntStringMap(safe...bool) *IntStringMap {
|
||||
func NewIntStringMap(unsafe...bool) *IntStringMap {
|
||||
return &IntStringMap{
|
||||
m : make(map[int]string),
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type InterfaceInterfaceMap struct {
|
||||
@ -16,10 +16,10 @@ type InterfaceInterfaceMap struct {
|
||||
m map[interface{}]interface{}
|
||||
}
|
||||
|
||||
func NewInterfaceInterfaceMap(safe...bool) *InterfaceInterfaceMap {
|
||||
func NewInterfaceInterfaceMap(unsafe...bool) *InterfaceInterfaceMap {
|
||||
return &InterfaceInterfaceMap{
|
||||
m : make(map[interface{}]interface{}),
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type StringBoolMap struct {
|
||||
@ -16,10 +16,10 @@ type StringBoolMap struct {
|
||||
m map[string]bool
|
||||
}
|
||||
|
||||
func NewStringBoolMap(safe...bool) *StringBoolMap {
|
||||
func NewStringBoolMap(unsafe...bool) *StringBoolMap {
|
||||
return &StringBoolMap{
|
||||
m : make(map[string]bool),
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,17 +7,17 @@
|
||||
|
||||
package gmap
|
||||
|
||||
import "gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
import "gitee.com/johng/gf/g/internal/rwmutex"
|
||||
|
||||
type StringIntMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
m map[string]int
|
||||
}
|
||||
|
||||
func NewStringIntMap(safe...bool) *StringIntMap {
|
||||
func NewStringIntMap(unsafe...bool) *StringIntMap {
|
||||
return &StringIntMap{
|
||||
m : make(map[string]int),
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
package gmap
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type StringInterfaceMap struct {
|
||||
@ -16,10 +16,10 @@ type StringInterfaceMap struct {
|
||||
m map[string]interface{}
|
||||
}
|
||||
|
||||
func NewStringInterfaceMap(safe...bool) *StringInterfaceMap {
|
||||
func NewStringInterfaceMap(unsafe...bool) *StringInterfaceMap {
|
||||
return &StringInterfaceMap{
|
||||
m : make(map[string]interface{}),
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -7,17 +7,17 @@
|
||||
|
||||
package gmap
|
||||
|
||||
import "gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
import "gitee.com/johng/gf/g/internal/rwmutex"
|
||||
|
||||
type StringStringMap struct {
|
||||
mu *rwmutex.RWMutex
|
||||
m map[string]string
|
||||
}
|
||||
|
||||
func NewStringStringMap(safe...bool) *StringStringMap {
|
||||
func NewStringStringMap(unsafe...bool) *StringStringMap {
|
||||
return &StringStringMap{
|
||||
m : make(map[string]string),
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,15 +5,17 @@
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gpool provides a object-reusable concurrent-safe pool.
|
||||
//
|
||||
// 对象复用池.
|
||||
package gpool
|
||||
|
||||
import (
|
||||
"time"
|
||||
"errors"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/container/glist"
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/os/gtimer"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 对象池
|
||||
@ -32,27 +34,29 @@ type poolItem struct {
|
||||
value interface{} // 对象值
|
||||
}
|
||||
|
||||
// 对象创建方法类型
|
||||
type NewFunc func() (interface{}, error)
|
||||
|
||||
// 对象过期方法类型
|
||||
type ExpireFunc func(interface{})
|
||||
|
||||
// 创建一个对象池,为保证执行效率,过期时间一旦设定之后无法修改
|
||||
// expire = 0表示不过期,expire < 0表示使用完立即回收,expire > 0表示超时回收
|
||||
// 注意过期时间单位为**毫秒**
|
||||
func New(expire int, newFunc...func() (interface{}, error)) *Pool {
|
||||
func New(expire int, newFunc NewFunc, expireFunc...ExpireFunc) *Pool {
|
||||
r := &Pool {
|
||||
list : glist.New(),
|
||||
closed : gtype.NewBool(),
|
||||
Expire : int64(expire),
|
||||
NewFunc : newFunc,
|
||||
}
|
||||
if len(newFunc) > 0 {
|
||||
r.NewFunc = newFunc[0]
|
||||
if len(expireFunc) > 0 {
|
||||
r.ExpireFunc = expireFunc[0]
|
||||
}
|
||||
go r.expireCheckingLoop()
|
||||
gtimer.AddSingleton(time.Second, r.checkExpire)
|
||||
return r
|
||||
}
|
||||
|
||||
// 设置对象过期销毁时的关闭方法
|
||||
func (p *Pool) SetExpireFunc(expireFunc func(interface{})) {
|
||||
p.ExpireFunc = expireFunc
|
||||
}
|
||||
|
||||
// 放一个临时对象到池中
|
||||
func (p *Pool) Put(value interface{}) {
|
||||
item := &poolItem {
|
||||
@ -100,22 +104,22 @@ func (p *Pool) Close() {
|
||||
}
|
||||
|
||||
// 超时检测循环
|
||||
func (p *Pool) expireCheckingLoop() {
|
||||
for !p.closed.Val() {
|
||||
for {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
item := r.(*poolItem)
|
||||
if item.expire == 0 || item.expire > gtime.Millisecond() {
|
||||
p.list.PushFront(item)
|
||||
break
|
||||
}
|
||||
if p.ExpireFunc != nil {
|
||||
p.ExpireFunc(item.value)
|
||||
}
|
||||
} else {
|
||||
func (p *Pool) checkExpire() {
|
||||
if p.closed.Val() {
|
||||
gtimer.Exit()
|
||||
}
|
||||
for {
|
||||
if r := p.list.PopFront(); r != nil {
|
||||
item := r.(*poolItem)
|
||||
if item.expire == 0 || item.expire > gtime.Millisecond() {
|
||||
p.list.PushFront(item)
|
||||
break
|
||||
}
|
||||
if p.ExpireFunc != nil {
|
||||
p.ExpireFunc(item.value)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var pool = New(99999999)
|
||||
var pool = New(99999999, nil)
|
||||
var syncp = sync.Pool{}
|
||||
|
||||
func BenchmarkGPoolPut(b *testing.B) {
|
||||
|
||||
@ -5,47 +5,54 @@
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gqueue provides a dynamic/static concurrent-safe(alternative) queue.
|
||||
//
|
||||
// 并发安全的动态队列.
|
||||
// 特点:
|
||||
// 1、动态队列初始化速度快;
|
||||
// 2、动态的队列大小(不限大小);
|
||||
// 3、取数据时如果队列为空那么会阻塞等待;
|
||||
//
|
||||
// 特点:
|
||||
// 1. 动态队列初始化速度快;
|
||||
// 2. 动态的队列大小(不限大小);
|
||||
// 3. 取数据时如果队列为空那么会阻塞等待;
|
||||
package gqueue
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/glist"
|
||||
"container/list"
|
||||
"math"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 0、这是一个先进先出的队列(chan <-- list);
|
||||
// 1、当创建Queue对象时限定大小,那么等同于一个同步的chan并发安全队列;
|
||||
// 2、不限制大小时,list链表用以存储数据,临时chan负责为客户端读取数据,当从chan获取数据时,list往chan中不停补充数据;
|
||||
// 3、由于功能主体是chan,那么操作仍然像chan那样具有阻塞效果;
|
||||
// 1、这是一个先进先出的队列(chan <-- list);
|
||||
//
|
||||
// 2、当创建Queue对象时限定大小,那么等同于一个同步的chan并发安全队列;
|
||||
//
|
||||
// 3、不限制大小时,list链表用以存储数据,临时chan负责为客户端读取数据,当从chan获取数据时,list往chan中不停补充数据;
|
||||
//
|
||||
// 4、由于功能主体是chan,那么操作仍然像chan那样具有阻塞效果;
|
||||
type Queue struct {
|
||||
mu sync.Mutex // 底层链表写锁
|
||||
limit int // 队列限制大小
|
||||
queue chan interface{} // 用于队列写入限制
|
||||
list *glist.List // 数据链表
|
||||
events chan struct{} // 通知chan,当不限制队列大小时的写入事件通知
|
||||
closeChan chan struct{} // 关闭channel
|
||||
list *list.List // 底层数据链表
|
||||
events chan struct{} // 写入事件通知
|
||||
closed chan struct{} // 队列关闭通知
|
||||
C chan interface{} // 队列数据读取
|
||||
}
|
||||
|
||||
const (
|
||||
// 动态队列缓冲区大小
|
||||
gQUEUE_SIZE = 10000
|
||||
gDEFAULT_QUEUE_SIZE = 10000
|
||||
)
|
||||
|
||||
// 队列大小为非必须参数,默认不限制
|
||||
func New(limit...int) *Queue {
|
||||
q := &Queue {
|
||||
closeChan : make(chan struct{}, 0),
|
||||
closed : make(chan struct{}, 0),
|
||||
}
|
||||
if len(limit) > 0 {
|
||||
q.limit = limit[0]
|
||||
q.queue = make(chan interface{}, limit[0])
|
||||
q.C = make(chan interface{}, limit[0])
|
||||
} else {
|
||||
q.list = glist.New()
|
||||
q.queue = make(chan interface{}, gQUEUE_SIZE)
|
||||
q.list = list.New()
|
||||
q.events = make(chan struct{}, math.MaxInt32)
|
||||
q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE)
|
||||
go q.startAsyncLoop()
|
||||
}
|
||||
return q
|
||||
@ -55,13 +62,24 @@ func New(limit...int) *Queue {
|
||||
func (q *Queue) startAsyncLoop() {
|
||||
for {
|
||||
select {
|
||||
case <- q.closeChan:
|
||||
case <- q.closed:
|
||||
return
|
||||
case <- q.events:
|
||||
// 循环读取链表,直到为空才跳出
|
||||
for {
|
||||
if v := q.list.PopFront(); v != nil {
|
||||
q.queue <- v
|
||||
if length := q.list.Len(); length > 0 {
|
||||
array := make([]interface{}, length)
|
||||
q.mu.Lock()
|
||||
for i := 0; i < length; i++ {
|
||||
if e := q.list.Front(); e != nil {
|
||||
array[i] = q.list.Remove(e)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
q.mu.Unlock()
|
||||
for _, v := range array {
|
||||
q.C <- v
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
@ -70,34 +88,33 @@ func (q *Queue) startAsyncLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
// 将数据压入队列, 队头
|
||||
// 将数据压入队列, 队尾
|
||||
func (q *Queue) Push(v interface{}) {
|
||||
if q.limit > 0 {
|
||||
q.queue <- v
|
||||
q.C <- v
|
||||
} else {
|
||||
q.mu.Lock()
|
||||
q.list.PushBack(v)
|
||||
if len(q.events) == 0 {
|
||||
q.events <- struct{}{}
|
||||
}
|
||||
q.mu.Unlock()
|
||||
q.events <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// 从队头先进先出地从队列取出一项数据
|
||||
func (q *Queue) Pop() interface{} {
|
||||
return <- q.queue
|
||||
return <- q.C
|
||||
}
|
||||
|
||||
// 关闭队列(通知所有通过Pop*阻塞的协程退出)
|
||||
func (q *Queue) Close() {
|
||||
q.list.RemoveAll()
|
||||
close(q.queue)
|
||||
close(q.C)
|
||||
close(q.events)
|
||||
close(q.closeChan)
|
||||
close(q.closed)
|
||||
}
|
||||
|
||||
// 获取当前队列大小
|
||||
func (q *Queue) Size() int {
|
||||
return len(q.queue) + q.list.Len()
|
||||
return len(q.C) + q.list.Len()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -5,13 +5,14 @@
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gring provides a concurrent-safe(alternative) ring(circular lists).
|
||||
// 并发安全的环.
|
||||
//
|
||||
// 并发安全环.
|
||||
package gring
|
||||
|
||||
import (
|
||||
"container/ring"
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type Ring struct {
|
||||
@ -22,9 +23,9 @@ type Ring struct {
|
||||
dirty *gtype.Bool // 标记环是否脏了(需要重新计算大小,当环大小发生改变时做标记)
|
||||
}
|
||||
|
||||
func New(cap int, safe...bool) *Ring {
|
||||
func New(cap int, unsafe...bool) *Ring {
|
||||
return &Ring {
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
ring : ring.New(cap),
|
||||
len : gtype.NewInt(),
|
||||
cap : gtype.NewInt(cap),
|
||||
|
||||
@ -5,12 +5,13 @@
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gset provides kinds of concurrent-safe(alternative) sets.
|
||||
// 并发安全的集合SET.
|
||||
//
|
||||
// 并发安全集合.
|
||||
package gset
|
||||
|
||||
type Set = InterfaceSet
|
||||
|
||||
// 默认Set类型
|
||||
func New(safe...bool) *Set {
|
||||
return NewInterfaceSet(safe...)
|
||||
func New(unsafe...bool) *Set {
|
||||
return NewInterfaceSet(unsafe...)
|
||||
}
|
||||
@ -10,7 +10,7 @@ package gset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type IntSet struct {
|
||||
@ -18,18 +18,18 @@ type IntSet struct {
|
||||
m map[int]struct{}
|
||||
}
|
||||
|
||||
func NewIntSet(safe...bool) *IntSet {
|
||||
func NewIntSet(unsafe...bool) *IntSet {
|
||||
return &IntSet{
|
||||
m : make(map[int]struct{}),
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *IntSet) Iterator(f func (v int) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, _ := range this.m {
|
||||
func (set *IntSet) Iterator(f func (v int) bool) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
if !f(k) {
|
||||
break
|
||||
}
|
||||
@ -37,80 +37,80 @@ func (this *IntSet) Iterator(f func (v int) bool) {
|
||||
}
|
||||
|
||||
// 设置键
|
||||
func (this *IntSet) Add(item int) *IntSet {
|
||||
this.mu.Lock()
|
||||
this.m[item] = struct{}{}
|
||||
this.mu.Unlock()
|
||||
return this
|
||||
func (set *IntSet) Add(item int) *IntSet {
|
||||
set.mu.Lock()
|
||||
set.m[item] = struct{}{}
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// 批量添加设置键
|
||||
func (this *IntSet) BatchAdd(items []int) *IntSet {
|
||||
this.mu.Lock()
|
||||
func (set *IntSet) BatchAdd(items []int) *IntSet {
|
||||
set.mu.Lock()
|
||||
for _, item := range items {
|
||||
this.m[item] = struct{}{}
|
||||
set.m[item] = struct{}{}
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return this
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// 键是否存在
|
||||
func (this *IntSet) Contains(item int) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[item]
|
||||
this.mu.RUnlock()
|
||||
func (set *IntSet) Contains(item int) bool {
|
||||
set.mu.RLock()
|
||||
_, exists := set.m[item]
|
||||
set.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 删除键值对
|
||||
func (this *IntSet) Remove(key int) {
|
||||
this.mu.Lock()
|
||||
delete(this.m, key)
|
||||
this.mu.Unlock()
|
||||
func (set *IntSet) Remove(key int) {
|
||||
set.mu.Lock()
|
||||
delete(set.m, key)
|
||||
set.mu.Unlock()
|
||||
}
|
||||
|
||||
// 大小
|
||||
func (this *IntSet) Size() int {
|
||||
this.mu.RLock()
|
||||
l := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
func (set *IntSet) Size() int {
|
||||
set.mu.RLock()
|
||||
l := len(set.m)
|
||||
set.mu.RUnlock()
|
||||
return l
|
||||
}
|
||||
|
||||
// 清空set
|
||||
func (this *IntSet) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[int]struct{})
|
||||
this.mu.Unlock()
|
||||
func (set *IntSet) Clear() {
|
||||
set.mu.Lock()
|
||||
set.m = make(map[int]struct{})
|
||||
set.mu.Unlock()
|
||||
}
|
||||
|
||||
// 转换为数组
|
||||
func (this *IntSet) Slice() []int {
|
||||
this.mu.RLock()
|
||||
ret := make([]int, len(this.m))
|
||||
func (set *IntSet) Slice() []int {
|
||||
set.mu.RLock()
|
||||
ret := make([]int, len(set.m))
|
||||
i := 0
|
||||
for item := range this.m {
|
||||
for item := range set.m {
|
||||
ret[i] = item
|
||||
i++
|
||||
}
|
||||
|
||||
this.mu.RUnlock()
|
||||
set.mu.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
// 转换为字符串
|
||||
func (this *IntSet) String() string {
|
||||
return fmt.Sprint(this.Slice())
|
||||
func (set *IntSet) String() string {
|
||||
return fmt.Sprint(set.Slice())
|
||||
}
|
||||
|
||||
func (this *IntSet) LockFunc(f func(m map[int]struct{})) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
func (set *IntSet) LockFunc(f func(m map[int]struct{})) {
|
||||
set.mu.Lock(true)
|
||||
defer set.mu.Unlock(true)
|
||||
f(set.m)
|
||||
}
|
||||
|
||||
func (this *IntSet) RLockFunc(f func(m map[int]struct{})) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
func (set *IntSet) RLockFunc(f func(m map[int]struct{})) {
|
||||
set.mu.RLock(true)
|
||||
defer set.mu.RUnlock(true)
|
||||
f(set.m)
|
||||
}
|
||||
@ -9,7 +9,7 @@ package gset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type InterfaceSet struct {
|
||||
@ -17,18 +17,18 @@ type InterfaceSet struct {
|
||||
m map[interface{}]struct{}
|
||||
}
|
||||
|
||||
func NewInterfaceSet(safe...bool) *InterfaceSet {
|
||||
func NewInterfaceSet(unsafe...bool) *InterfaceSet {
|
||||
return &InterfaceSet{
|
||||
m : make(map[interface{}]struct{}),
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *InterfaceSet) Iterator(f func (v interface{}) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, _ := range this.m {
|
||||
func (set *InterfaceSet) Iterator(f func (v interface{}) bool) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
if !f(k) {
|
||||
break
|
||||
}
|
||||
@ -36,79 +36,79 @@ func (this *InterfaceSet) Iterator(f func (v interface{}) bool) {
|
||||
}
|
||||
|
||||
// 添加
|
||||
func (this *InterfaceSet) Add(item interface{}) *InterfaceSet {
|
||||
this.mu.Lock()
|
||||
this.m[item] = struct{}{}
|
||||
this.mu.Unlock()
|
||||
return this
|
||||
func (set *InterfaceSet) Add(item interface{}) *InterfaceSet {
|
||||
set.mu.Lock()
|
||||
set.m[item] = struct{}{}
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// 批量添加
|
||||
func (this *InterfaceSet) BatchAdd(items []interface{}) *InterfaceSet {
|
||||
this.mu.Lock()
|
||||
func (set *InterfaceSet) BatchAdd(items []interface{}) *InterfaceSet {
|
||||
set.mu.Lock()
|
||||
for _, item := range items {
|
||||
this.m[item] = struct{}{}
|
||||
set.m[item] = struct{}{}
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return this
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// 键是否存在
|
||||
func (this *InterfaceSet) Contains(item interface{}) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[item]
|
||||
this.mu.RUnlock()
|
||||
func (set *InterfaceSet) Contains(item interface{}) bool {
|
||||
set.mu.RLock()
|
||||
_, exists := set.m[item]
|
||||
set.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 删除键值对
|
||||
func (this *InterfaceSet) Remove(key interface{}) {
|
||||
this.mu.Lock()
|
||||
delete(this.m, key)
|
||||
this.mu.Unlock()
|
||||
func (set *InterfaceSet) Remove(key interface{}) {
|
||||
set.mu.Lock()
|
||||
delete(set.m, key)
|
||||
set.mu.Unlock()
|
||||
}
|
||||
|
||||
// 大小
|
||||
func (this *InterfaceSet) Size() int {
|
||||
this.mu.RLock()
|
||||
l := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
func (set *InterfaceSet) Size() int {
|
||||
set.mu.RLock()
|
||||
l := len(set.m)
|
||||
set.mu.RUnlock()
|
||||
return l
|
||||
}
|
||||
|
||||
// 清空set
|
||||
func (this *InterfaceSet) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[interface{}]struct{})
|
||||
this.mu.Unlock()
|
||||
func (set *InterfaceSet) Clear() {
|
||||
set.mu.Lock()
|
||||
set.m = make(map[interface{}]struct{})
|
||||
set.mu.Unlock()
|
||||
}
|
||||
|
||||
// 转换为数组
|
||||
func (this *InterfaceSet) Slice() []interface{} {
|
||||
this.mu.RLock()
|
||||
func (set *InterfaceSet) Slice() []interface{} {
|
||||
set.mu.RLock()
|
||||
i := 0
|
||||
ret := make([]interface{}, len(this.m))
|
||||
for item := range this.m {
|
||||
ret := make([]interface{}, len(set.m))
|
||||
for item := range set.m {
|
||||
ret[i] = item
|
||||
i++
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
set.mu.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
// 转换为字符串
|
||||
func (this *InterfaceSet) String() string {
|
||||
return fmt.Sprint(this.Slice())
|
||||
func (set *InterfaceSet) String() string {
|
||||
return fmt.Sprint(set.Slice())
|
||||
}
|
||||
|
||||
func (this *InterfaceSet) LockFunc(f func(m map[interface{}]struct{})) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
func (set *InterfaceSet) LockFunc(f func(m map[interface{}]struct{})) {
|
||||
set.mu.Lock(true)
|
||||
defer set.mu.Unlock(true)
|
||||
f(set.m)
|
||||
}
|
||||
|
||||
func (this *InterfaceSet) RLockFunc(f func(m map[interface{}]struct{})) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
func (set *InterfaceSet) RLockFunc(f func(m map[interface{}]struct{})) {
|
||||
set.mu.RLock(true)
|
||||
defer set.mu.RUnlock(true)
|
||||
f(set.m)
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ package gset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
)
|
||||
|
||||
type StringSet struct {
|
||||
@ -17,18 +17,18 @@ type StringSet struct {
|
||||
m map[string]struct{}
|
||||
}
|
||||
|
||||
func NewStringSet(safe...bool) *StringSet {
|
||||
return &StringSet{
|
||||
func NewStringSet(unsafe...bool) *StringSet {
|
||||
return &StringSet {
|
||||
m : make(map[string]struct{}),
|
||||
mu : rwmutex.New(safe...),
|
||||
mu : rwmutex.New(unsafe...),
|
||||
}
|
||||
}
|
||||
|
||||
// 给定回调函数对原始内容进行遍历,回调函数返回true表示继续遍历,否则停止遍历
|
||||
func (this *StringSet) Iterator(f func (v string) bool) {
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
for k, _ := range this.m {
|
||||
func (set *StringSet) Iterator(f func (v string) bool) {
|
||||
set.mu.RLock()
|
||||
defer set.mu.RUnlock()
|
||||
for k, _ := range set.m {
|
||||
if !f(k) {
|
||||
break
|
||||
}
|
||||
@ -36,80 +36,80 @@ func (this *StringSet) Iterator(f func (v string) bool) {
|
||||
}
|
||||
|
||||
// 设置键
|
||||
func (this *StringSet) Add(item string) *StringSet {
|
||||
this.mu.Lock()
|
||||
this.m[item] = struct{}{}
|
||||
this.mu.Unlock()
|
||||
return this
|
||||
func (set *StringSet) Add(item string) *StringSet {
|
||||
set.mu.Lock()
|
||||
set.m[item] = struct{}{}
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// 批量添加设置键
|
||||
func (this *StringSet) BatchAdd(items []string) *StringSet {
|
||||
this.mu.Lock()
|
||||
func (set *StringSet) BatchAdd(items []string) *StringSet {
|
||||
set.mu.Lock()
|
||||
for _, item := range items {
|
||||
this.m[item] = struct{}{}
|
||||
set.m[item] = struct{}{}
|
||||
}
|
||||
this.mu.Unlock()
|
||||
return this
|
||||
set.mu.Unlock()
|
||||
return set
|
||||
}
|
||||
|
||||
// 键是否存在
|
||||
func (this *StringSet) Contains(item string) bool {
|
||||
this.mu.RLock()
|
||||
_, exists := this.m[item]
|
||||
this.mu.RUnlock()
|
||||
func (set *StringSet) Contains(item string) bool {
|
||||
set.mu.RLock()
|
||||
_, exists := set.m[item]
|
||||
set.mu.RUnlock()
|
||||
return exists
|
||||
}
|
||||
|
||||
// 删除键值对
|
||||
func (this *StringSet) Remove(key string) {
|
||||
this.mu.Lock()
|
||||
delete(this.m, key)
|
||||
this.mu.Unlock()
|
||||
func (set *StringSet) Remove(key string) {
|
||||
set.mu.Lock()
|
||||
delete(set.m, key)
|
||||
set.mu.Unlock()
|
||||
}
|
||||
|
||||
// 大小
|
||||
func (this *StringSet) Size() int {
|
||||
this.mu.RLock()
|
||||
l := len(this.m)
|
||||
this.mu.RUnlock()
|
||||
func (set *StringSet) Size() int {
|
||||
set.mu.RLock()
|
||||
l := len(set.m)
|
||||
set.mu.RUnlock()
|
||||
return l
|
||||
}
|
||||
|
||||
// 清空set
|
||||
func (this *StringSet) Clear() {
|
||||
this.mu.Lock()
|
||||
this.m = make(map[string]struct{})
|
||||
this.mu.Unlock()
|
||||
func (set *StringSet) Clear() {
|
||||
set.mu.Lock()
|
||||
set.m = make(map[string]struct{})
|
||||
set.mu.Unlock()
|
||||
}
|
||||
|
||||
// 转换为数组
|
||||
func (this *StringSet) Slice() []string {
|
||||
this.mu.RLock()
|
||||
ret := make([]string, len(this.m))
|
||||
func (set *StringSet) Slice() []string {
|
||||
set.mu.RLock()
|
||||
ret := make([]string, len(set.m))
|
||||
i := 0
|
||||
for item := range this.m {
|
||||
for item := range set.m {
|
||||
ret[i] = item
|
||||
i++
|
||||
}
|
||||
|
||||
this.mu.RUnlock()
|
||||
set.mu.RUnlock()
|
||||
return ret
|
||||
}
|
||||
|
||||
// 转换为字符串
|
||||
func (this *StringSet) String() string {
|
||||
return fmt.Sprint(this.Slice())
|
||||
func (set *StringSet) String() string {
|
||||
return fmt.Sprint(set.Slice())
|
||||
}
|
||||
|
||||
func (this *StringSet) LockFunc(f func(m map[string]struct{})) {
|
||||
this.mu.Lock(true)
|
||||
defer this.mu.Unlock(true)
|
||||
f(this.m)
|
||||
func (set *StringSet) LockFunc(f func(m map[string]struct{})) {
|
||||
set.mu.Lock(true)
|
||||
defer set.mu.Unlock(true)
|
||||
f(set.m)
|
||||
}
|
||||
|
||||
func (this *StringSet) RLockFunc(f func(m map[string]struct{})) {
|
||||
this.mu.RLock(true)
|
||||
defer this.mu.RUnlock(true)
|
||||
f(this.m)
|
||||
func (set *StringSet) RLockFunc(f func(m map[string]struct{})) {
|
||||
set.mu.RLock(true)
|
||||
defer set.mu.RUnlock(true)
|
||||
f(set.m)
|
||||
}
|
||||
|
||||
@ -14,9 +14,9 @@ import (
|
||||
"gitee.com/johng/gf/g/container/gset"
|
||||
)
|
||||
|
||||
var intsUnsafe = gset.NewIntSet(false)
|
||||
var itfsUnsafe = gset.NewInterfaceSet(false)
|
||||
var strsUnsafe = gset.NewStringSet(false)
|
||||
var intsUnsafe = gset.NewIntSet(true)
|
||||
var itfsUnsafe = gset.NewInterfaceSet(true)
|
||||
var strsUnsafe = gset.NewStringSet(true)
|
||||
|
||||
func Benchmark_Unsafe_IntSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
@ -4,8 +4,9 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gtype provides kinds of concurrent-safe basic-types.
|
||||
// 并发安全的基本类型.
|
||||
// Package gtype provides kinds of high performance, concurrent-safe basic variable types.
|
||||
//
|
||||
// 并发安全基本类型.
|
||||
package gtype
|
||||
|
||||
type Type = Interface
|
||||
|
||||
@ -34,7 +34,7 @@ func (t *Int) Val() int {
|
||||
return int(atomic.LoadInt64(&t.val))
|
||||
}
|
||||
|
||||
// 数值增加delta,并返回新的数值
|
||||
// 数值增加delta,并返回**新**的数值
|
||||
func (t *Int) Add(delta int) int {
|
||||
return int(atomic.AddInt64(&t.val, int64(delta)))
|
||||
}
|
||||
@ -4,7 +4,8 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gvar provides a universal variable type.
|
||||
// Package gvar provides an universal variable type, like generics.
|
||||
//
|
||||
// 通用动态变量.
|
||||
package gvar
|
||||
|
||||
@ -21,10 +22,10 @@ type Var struct {
|
||||
}
|
||||
|
||||
// 创建一个动态变量,value参数可以为nil
|
||||
func New(value interface{}, safe...bool) *Var {
|
||||
func New(value interface{}, unsafe...bool) *Var {
|
||||
v := &Var{}
|
||||
if len(safe) > 0 && safe[0] {
|
||||
v.safe = safe[0]
|
||||
if len(unsafe) == 0 || !unsafe[0] {
|
||||
v.safe = true
|
||||
v.value = gtype.NewInterface(value)
|
||||
} else {
|
||||
v.value = value
|
||||
@ -33,8 +34,8 @@ func New(value interface{}, safe...bool) *Var {
|
||||
}
|
||||
|
||||
// 创建一个只读动态变量,value参数可以为nil
|
||||
func NewRead(value interface{}, safe...bool) VarRead {
|
||||
return VarRead(New(value, safe...))
|
||||
func NewRead(value interface{}, unsafe...bool) VarRead {
|
||||
return VarRead(New(value, unsafe...))
|
||||
}
|
||||
|
||||
// 返回动态变量的只读接口
|
||||
@ -90,10 +91,16 @@ func (v *Var) Floats() []float64 { return gconv.Floats(v.Val()) }
|
||||
func (v *Var) Strings() []string { return gconv.Strings(v.Val()) }
|
||||
func (v *Var) Interfaces() []interface{} { return gconv.Interfaces(v.Val()) }
|
||||
|
||||
func (v *Var) Time(format...string) time.Time { return gconv.Time(v.Val(), format...) }
|
||||
func (v *Var) TimeDuration() time.Duration { return gconv.TimeDuration(v.Val()) }
|
||||
func (v *Var) Time(format...string) time.Time {
|
||||
return gconv.Time(v.Val(), format...)
|
||||
}
|
||||
func (v *Var) TimeDuration() time.Duration {
|
||||
return gconv.TimeDuration(v.Val())
|
||||
}
|
||||
|
||||
func (v *Var) GTime(format...string) *gtime.Time { return gconv.GTime(v.Val(), format...) }
|
||||
func (v *Var) GTime(format...string) *gtime.Time {
|
||||
return gconv.GTime(v.Val(), format...)
|
||||
}
|
||||
|
||||
// 将变量转换为对象,注意 objPointer 参数必须为struct指针
|
||||
func (v *Var) Struct(objPointer interface{}, attrMapping...map[string]string) error {
|
||||
|
||||
7
g/crypto/crypto.go
Normal file
7
g/crypto/crypto.go
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright 2019 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 crypto
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// AES
|
||||
// Package gaes provides useful API for AES encryption/decryption algorithms.
|
||||
package gaes
|
||||
|
||||
import (
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// CRC32
|
||||
// Package gcrc32 provides useful API for CRC32 encryption/decryption algorithms.
|
||||
package gcrc32
|
||||
|
||||
import (
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// @author: wenzi1<liyz23@qq.com>
|
||||
|
||||
// Package gdes provides useful API for DES encryption/decryption algorithms.
|
||||
package gdes
|
||||
|
||||
import (
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// MD5
|
||||
// Package gmd5 provides useful API for MD5 encryption/decryption algorithms.
|
||||
package gmd5
|
||||
|
||||
import (
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// SHA1
|
||||
// Package gsha1 provides useful API for SHA1 encryption/decryption algorithms.
|
||||
package gsha1
|
||||
|
||||
import (
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gdb provides ORM features for popular relationship databases/数据库ORM.
|
||||
// Package gdb provides ORM features for popular relationship databases.
|
||||
//
|
||||
// 数据库ORM,
|
||||
// 默认内置支持MySQL, 其他数据库需要手动import对应的数据库引擎第三方包.
|
||||
package gdb
|
||||
|
||||
@ -90,7 +92,9 @@ type DB interface {
|
||||
getChars() (charLeft string, charRight string)
|
||||
getDebug() bool
|
||||
filterFields(table string, data map[string]interface{}) map[string]interface{}
|
||||
convertValue(fieldValue interface{}, fieldType string) interface{}
|
||||
getTableFields(table string) (map[string]string, error)
|
||||
rowsToResult(rows *sql.Rows) (Result, error)
|
||||
handleSqlBeforeExec(sql string) string
|
||||
}
|
||||
|
||||
@ -103,15 +107,16 @@ type dbLink interface {
|
||||
|
||||
// 数据库链接对象
|
||||
type dbBase struct {
|
||||
db DB // 数据库对象
|
||||
group string // 配置分组名称
|
||||
debug *gtype.Bool // (默认关闭)是否开启调试模式,当开启时会启用一些调试特性
|
||||
sqls *gring.Ring // (debug=true时有效)已执行的SQL列表
|
||||
cache *gcache.Cache // 数据库缓存,包括底层连接池对象缓存及查询缓存;需要注意的是,事务查询不支持查询缓存
|
||||
schema *gtype.String // 手动切换的数据库名称
|
||||
maxIdleConnCount *gtype.Int // 连接池最大限制的连接数
|
||||
maxOpenConnCount *gtype.Int // 连接池最大打开的连接数
|
||||
maxConnLifetime *gtype.Int // (单位秒)连接对象可重复使用的时间长度
|
||||
db DB // 数据库对象
|
||||
group string // 配置分组名称
|
||||
debug *gtype.Bool // (默认关闭)是否开启调试模式,当开启时会启用一些调试特性
|
||||
sqls *gring.Ring // (debug=true时有效)已执行的SQL列表
|
||||
cache *gcache.Cache // 数据库缓存,包括底层连接池对象缓存及查询缓存;需要注意的是,事务查询不支持查询缓存
|
||||
schema *gtype.String // 手动切换的数据库名称
|
||||
tables map[string]map[string]string // 数据库表结构
|
||||
maxIdleConnCount *gtype.Int // 连接池最大限制的连接数
|
||||
maxOpenConnCount *gtype.Int // 连接池最大打开的连接数
|
||||
maxConnLifetime *gtype.Int // (单位秒)连接对象可重复使用的时间长度
|
||||
}
|
||||
|
||||
// 执行的SQL对象
|
||||
@ -125,7 +130,7 @@ type Sql struct {
|
||||
}
|
||||
|
||||
// 返回数据表记录值
|
||||
type Value = gvar.VarRead
|
||||
type Value = *gvar.Var
|
||||
|
||||
// 返回数据表记录Map
|
||||
type Record map[string]Value
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/gvar"
|
||||
"gitee.com/johng/gf/g/os/gcache"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
@ -149,7 +150,7 @@ func (bs *dbBase) GetAll(query string, args ...interface{}) (Result, error) {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return rowsToResult(rows)
|
||||
return bs.db.rowsToResult(rows)
|
||||
}
|
||||
|
||||
// 数据库查询,获取查询结果记录,以关联数组结构返回
|
||||
@ -434,36 +435,40 @@ func (bs *dbBase) getCache() *gcache.Cache {
|
||||
return bs.cache
|
||||
}
|
||||
|
||||
// 将map的数据按照fields进行过滤,只保留与表字段同名的数据
|
||||
func (bs *dbBase) filterFields(table string, data map[string]interface{}) map[string]interface{} {
|
||||
if fields, err := bs.db.getTableFields(table); err == nil {
|
||||
for k, _ := range data {
|
||||
if _, ok := fields[k]; !ok {
|
||||
delete(data, k)
|
||||
// 将数据查询的列表数据*sql.Rows转换为Result类型
|
||||
func (bs *dbBase) rowsToResult(rows *sql.Rows) (Result, error) {
|
||||
// 列信息列表, 名称与类型
|
||||
types := make([]string, 0)
|
||||
columns := make([]string, 0)
|
||||
columnTypes, _ := rows.ColumnTypes()
|
||||
for _, t := range columnTypes {
|
||||
types = append(types, t.DatabaseTypeName())
|
||||
columns = append(columns, t.Name())
|
||||
}
|
||||
// 返回结构组装
|
||||
values := make([]sql.RawBytes, len(columns))
|
||||
scanArgs := make([]interface{}, len(values))
|
||||
records := make(Result, 0)
|
||||
for i := range values {
|
||||
scanArgs[i] = &values[i]
|
||||
}
|
||||
for rows.Next() {
|
||||
if err := rows.Scan(scanArgs...); err != nil {
|
||||
return records, err
|
||||
}
|
||||
row := make(Record)
|
||||
// 注意col字段是一个[]byte类型(slice类型本身是一个指针),多个记录循环时该变量指向的是同一个内存地址
|
||||
for i, col := range values {
|
||||
if col == nil {
|
||||
row[columns[i]] = gvar.New(nil, true)
|
||||
} else {
|
||||
// 由于 sql.RawBytes 是slice类型, 这里必须使用值复制
|
||||
v := make([]byte, len(col))
|
||||
copy(v, col)
|
||||
row[columns[i]] = gvar.New(bs.db.convertValue(v, types[i]), true)
|
||||
}
|
||||
}
|
||||
records = append(records, row)
|
||||
}
|
||||
return data
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// 获得指定表表的数据结构,构造成map哈希表返回,其中键名为表字段名称,键值暂无用途(默认为字段数据类型).
|
||||
func (bs *dbBase) getTableFields(table string) (fields map[string]string, err error) {
|
||||
// 缓存不存在时会查询数据表结构,缓存后不过期,直至程序重启(重新部署)
|
||||
v := bs.cache.GetOrSetFunc("table_fields_" + table, func() interface{} {
|
||||
result := (Result)(nil)
|
||||
charl, charr := bs.db.getChars()
|
||||
result, err = bs.GetAll(fmt.Sprintf(`SHOW COLUMNS FROM %s%s%s`, charl, table, charr))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields = make(map[string]string)
|
||||
for _, m := range result {
|
||||
fields[m["Field"].String()] = m["Type"].String()
|
||||
}
|
||||
return fields
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]string)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -8,10 +8,8 @@ package gdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/gvar"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
@ -22,41 +20,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 将数据查询的列表数据*sql.Rows转换为Result类型
|
||||
func rowsToResult(rows *sql.Rows) (Result, error) {
|
||||
// 列名称列表
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 返回结构组装
|
||||
values := make([]sql.RawBytes, len(columns))
|
||||
scanArgs := make([]interface{}, len(values))
|
||||
records := make(Result, 0)
|
||||
for i := range values {
|
||||
scanArgs[i] = &values[i]
|
||||
}
|
||||
for rows.Next() {
|
||||
err = rows.Scan(scanArgs...)
|
||||
if err != nil {
|
||||
return records, err
|
||||
}
|
||||
row := make(Record)
|
||||
// 注意col字段是一个[]byte类型(slice类型本身是一个指针),多个记录循环时该变量指向的是同一个内存地址
|
||||
for i, col := range values {
|
||||
if col == nil {
|
||||
row[columns[i]] = gvar.New(nil, false)
|
||||
} else {
|
||||
v := make([]byte, len(col))
|
||||
copy(v, col)
|
||||
row[columns[i]] = gvar.New(v, false)
|
||||
}
|
||||
}
|
||||
records = append(records, row)
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// 格式化SQL查询条件
|
||||
func formatCondition(where interface{}, args []interface{}) (string, []interface{}) {
|
||||
// 条件字符串处理
|
||||
@ -80,7 +43,7 @@ func formatCondition(where interface{}, args []interface{}) (string, []interface
|
||||
buffer.Write(gconv.Bytes(where))
|
||||
}
|
||||
if buffer.Len() == 0 {
|
||||
buffer.WriteString("1")
|
||||
buffer.WriteString("1=1")
|
||||
}
|
||||
// 查询条件处理
|
||||
newWhere := buffer.String()
|
||||
|
||||
@ -21,7 +21,6 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
// 数据库链接对象
|
||||
type dbMssql struct {
|
||||
*dbBase
|
||||
@ -34,7 +33,7 @@ func (db *dbMssql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
source = config.Linkinfo
|
||||
} else {
|
||||
source = fmt.Sprintf("user id=%s;password=%s;server=%s;port=%s;database=%s;encrypt=disable",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name)
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name)
|
||||
}
|
||||
if db, err := sql.Open("sqlserver", source); err == nil {
|
||||
return db, nil
|
||||
@ -44,7 +43,7 @@ func (db *dbMssql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
|
||||
// 获得关键字操作符
|
||||
func (db *dbMssql) getChars () (charLeft string, charRight string) {
|
||||
func (db *dbMssql) getChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
@ -92,7 +91,7 @@ func (db *dbMssql) parseSql(sql string) string {
|
||||
//不含LIMIT则不处理
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//判断SQL中是否含有order by
|
||||
selectStr := ""
|
||||
@ -101,28 +100,26 @@ func (db *dbMssql) parseSql(sql string) string {
|
||||
if haveOrderby {
|
||||
//取order by 前面的字符串
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
|
||||
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "ORDER BY") == false{
|
||||
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "ORDER BY") == false {
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
|
||||
//取order by表达式的值
|
||||
orderbyExpr, _ := gregex.MatchString("((?i)ORDER BY)(.+)((?i)LIMIT)", sql)
|
||||
if len(orderbyExpr) != 4 || strings.EqualFold(orderbyExpr[1], "ORDER BY") == false || strings.EqualFold(orderbyExpr[3], "LIMIT") == false{
|
||||
if len(orderbyExpr) != 4 || strings.EqualFold(orderbyExpr[1], "ORDER BY") == false || strings.EqualFold(orderbyExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
orderbyStr = orderbyExpr[2]
|
||||
} else {
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false{
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
}
|
||||
|
||||
|
||||
|
||||
//取limit后面的取值范围
|
||||
first, limit := 0, 0
|
||||
for i := 1; i < len(res[index]); i++ {
|
||||
@ -150,4 +147,31 @@ func (db *dbMssql) parseSql(sql string) string {
|
||||
default:
|
||||
}
|
||||
return sql
|
||||
}
|
||||
}
|
||||
|
||||
// 获得指定表表的数据结构,构造成map哈希表返回,其中键名为表字段名称,键值暂无用途(默认为字段数据类型).
|
||||
func (db *dbMssql) getTableFields(table string) (fields map[string]string, err error) {
|
||||
// 缓存不存在时会查询数据表结构,缓存后不过期,直至程序重启(重新部署)
|
||||
v := db.cache.GetOrSetFunc("table_fields_"+table, func() interface{} {
|
||||
result := (Result)(nil)
|
||||
result, err = db.GetAll(fmt.Sprintf(`
|
||||
SELECT c.name as FIELD, CASE t.name
|
||||
WHEN 'numeric' THEN t.name + '(' + convert(varchar(20),c.xprec) + ',' + convert(varchar(20),c.xscale) + ')'
|
||||
WHEN 'char' THEN t.name + '(' + convert(varchar(20),c.length)+ ')'
|
||||
WHEN 'varchar' THEN t.name + '(' + convert(varchar(20),c.length)+ ')'
|
||||
ELSE t.name + '(' + convert(varchar(20),c.length)+ ')' END as TYPE
|
||||
FROM systypes t,syscolumns c WHERE t.xtype=c.xtype AND c.id = (SELECT id FROM sysobjects WHERE name='%s') ORDER BY c.colid`, strings.ToUpper(table)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields = make(map[string]string)
|
||||
for _, m := range result {
|
||||
fields[strings.ToLower(m["FIELD"].String())] = strings.ToLower(m["TYPE"].String()) //sqlserver返回的field为大写的需要转为小写的
|
||||
}
|
||||
return fields
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]string)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ func (db *dbOracle) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
|
||||
// 获得关键字操作符
|
||||
func (db *dbOracle) getChars () (charLeft string, charRight string) {
|
||||
func (db *dbOracle) getChars() (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ func (db *dbOracle) parseSql(sql string) string {
|
||||
}
|
||||
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false{
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
|
||||
break
|
||||
}
|
||||
|
||||
@ -141,3 +141,30 @@ func (db *dbOracle) parseSql(sql string) string {
|
||||
}
|
||||
return sql
|
||||
}
|
||||
|
||||
// 获得指定表表的数据结构,构造成map哈希表返回,其中键名为表字段名称,键值暂无用途(默认为字段数据类型).
|
||||
func (db *dbOracle) getTableFields(table string) (fields map[string]string, err error) {
|
||||
// 缓存不存在时会查询数据表结构,缓存后不过期,直至程序重启(重新部署)
|
||||
v := db.cache.GetOrSetFunc("table_fields_"+table, func() interface{} {
|
||||
result := (Result)(nil)
|
||||
result, err = db.GetAll(fmt.Sprintf(`
|
||||
SELECT COLUMN_NAME AS FIELD, CASE DATA_TYPE
|
||||
WHEN 'NUMBER' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
WHEN 'FLOAT' THEN DATA_TYPE||'('||DATA_PRECISION||','||DATA_SCALE||')'
|
||||
ELSE DATA_TYPE||'('||DATA_LENGTH||')' END AS TYPE
|
||||
FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, strings.ToUpper(table)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fields = make(map[string]string)
|
||||
for _, m := range result {
|
||||
fields[strings.ToLower(m["FIELD"].String())] = strings.ToLower(m["TYPE"].String()) //ORACLE返回的值默认都是大写的,需要转为小写
|
||||
}
|
||||
return fields
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]string)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
120
g/database/gdb/gdb_structure.go
Normal file
120
g/database/gdb/gdb_structure.go
Normal file
@ -0,0 +1,120 @@
|
||||
// Copyright 2019 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 gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
"strings"
|
||||
)
|
||||
|
||||
/*
|
||||
// 同步数据库表结构到内存中
|
||||
func (bs *dbBase) syncTableStructure() {
|
||||
bs.tables = make(map[string]map[string]string)
|
||||
for _, table := range bs.db.getTables() {
|
||||
bs.tables[table], _ = bs.db.getTableFields(table)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// 字段类型转换,将数据库字段类型转换为golang变量类型
|
||||
func (bs *dbBase) convertValue(fieldValue interface{}, fieldType string) interface{} {
|
||||
t, _ := gregex.ReplaceString(`\(.+\)`, "", fieldType)
|
||||
t = strings.ToLower(t)
|
||||
switch t {
|
||||
case "binary", "varbinary", "blob", "tinyblob", "mediumblob", "longblob":
|
||||
return gconv.Bytes(fieldValue)
|
||||
|
||||
case "bit", "int", "tinyint", "small_int", "medium_int":
|
||||
return gconv.Int(fieldValue)
|
||||
|
||||
case "big_int":
|
||||
return gconv.Int64(fieldValue)
|
||||
|
||||
case "float", "double", "decimal":
|
||||
return gconv.Float64(fieldValue)
|
||||
|
||||
case "bool":
|
||||
return gconv.Bool(fieldValue)
|
||||
|
||||
default:
|
||||
// 自动识别类型, 以便默认支持更多数据库类型
|
||||
switch {
|
||||
case strings.Contains(t, "int"):
|
||||
return gconv.Int(fieldValue)
|
||||
|
||||
case strings.Contains(t, "text") || strings.Contains(t, "char"):
|
||||
return gconv.String(fieldValue)
|
||||
|
||||
case strings.Contains(t, "float") || strings.Contains(t, "double"):
|
||||
return gconv.Float64(fieldValue)
|
||||
|
||||
case strings.Contains(t, "bool"):
|
||||
return gconv.Bool(fieldValue)
|
||||
|
||||
case strings.Contains(t, "binary") || strings.Contains(t, "blob"):
|
||||
return gconv.Bytes(fieldValue)
|
||||
|
||||
default:
|
||||
return gconv.String(fieldValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将map的数据按照fields进行过滤,只保留与表字段同名的数据
|
||||
func (bs *dbBase) filterFields(table string, data map[string]interface{}) map[string]interface{} {
|
||||
if fields, err := bs.db.getTableFields(table); err == nil {
|
||||
for k, _ := range data {
|
||||
if _, ok := fields[k]; !ok {
|
||||
delete(data, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// 获得指定表表的数据结构,构造成map哈希表返回,其中键名为表字段名称,键值暂无用途(默认为字段数据类型).
|
||||
func (bs *dbBase) getTableFields(table string) (fields map[string]string, err error) {
|
||||
// 缓存不存在时会查询数据表结构,缓存后不过期,直至程序重启(重新部署)
|
||||
v := bs.cache.GetOrSetFunc("table_fields_" + table, func() interface{} {
|
||||
result := (Result)(nil)
|
||||
charl, charr := bs.db.getChars()
|
||||
result, err = bs.GetAll(fmt.Sprintf(`SHOW COLUMNS FROM %s%s%s`, charl, table, charr))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields = make(map[string]string)
|
||||
for _, m := range result {
|
||||
fields[m["Field"].String()] = m["Type"].String()
|
||||
}
|
||||
return fields
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]string)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
// 获取当前数据库所有的表结构
|
||||
func (bs *dbBase) getTables() []string {
|
||||
if result, _ := bs.GetAll(`SHOW TABLES`); result != nil {
|
||||
array := make([]string, len(result))
|
||||
for i, m := range result {
|
||||
for _, v := range m {
|
||||
array[i] = v.String()
|
||||
break
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
return nil
|
||||
}
|
||||
*/
|
||||
@ -51,7 +51,7 @@ func (tx *TX) GetAll(query string, args ...interface{}) (Result, error) {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return rowsToResult(rows)
|
||||
return tx.db.rowsToResult(rows)
|
||||
}
|
||||
|
||||
// 数据库查询,获取查询结果记录,以关联数组结构返回
|
||||
|
||||
@ -27,7 +27,7 @@ func (r Record) ToXml(rootTag...string) string {
|
||||
func (r Record) ToMap() Map {
|
||||
m := make(map[string]interface{})
|
||||
for k, v := range r {
|
||||
m[k] = v.String()
|
||||
m[k] = v.Val()
|
||||
}
|
||||
return m
|
||||
}
|
||||
@ -36,7 +36,7 @@ func (r Record) ToMap() Map {
|
||||
func (r Record) ToStruct(obj interface{}) error {
|
||||
m := make(map[string]interface{})
|
||||
for k, v := range r {
|
||||
m[k] = v.String()
|
||||
m[k] = v.Val()
|
||||
}
|
||||
return gconv.Struct(m, obj)
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ func (r Result) ToJson() string {
|
||||
return string(content)
|
||||
}
|
||||
|
||||
// 将结果集转换为JSON字符串
|
||||
// 将结果集转换为XML字符串
|
||||
func (r Result) ToXml(rootTag...string) string {
|
||||
content, _ := gparser.VarToXml(r.ToList(), rootTag...)
|
||||
return string(content)
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gkafka provides producer and consumer client for kafka server/Kafka客户端.
|
||||
// Package gkafka provides producer and consumer client for kafka server.
|
||||
//
|
||||
// Kafka客户端.
|
||||
package gkafka
|
||||
|
||||
import (
|
||||
|
||||
@ -4,8 +4,10 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gredis provides client for redis server.
|
||||
//
|
||||
// Redis客户端.
|
||||
// Redis中文手册文档请参考:http://redisdoc.com/ ,Redis官方命令请参考:https://redis.io/commands
|
||||
// Redis中文手册文档请参考:http://redisdoc.com/ , Redis官方命令请参考:https://redis.io/commands
|
||||
package gredis
|
||||
|
||||
import (
|
||||
|
||||
@ -4,5 +4,4 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 数据编码/解码.
|
||||
package encoding
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// BASE64
|
||||
// Package gbase64 provides useful API for BASE64 encoding/decoding algorithms.
|
||||
package gbase64
|
||||
|
||||
import (
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 二进制及byte操作
|
||||
// Package gbinary provides useful API for handling binary/bytes data.
|
||||
package gbinary
|
||||
|
||||
import (
|
||||
|
||||
@ -6,7 +6,9 @@
|
||||
// @author wenzi1
|
||||
// @date 20180604
|
||||
|
||||
// 字符集转换方法.
|
||||
// Package gcharset provides converting string to requested character encoding.
|
||||
//
|
||||
// 字符集转换方法,
|
||||
// 使用mahonia实现的字符集转换方法,支持的字符集包括常见的utf8/UTF-16/UTF-16LE/macintosh/big5/gbk/gb18030,支持的全量字符集可以参考mahonia包
|
||||
package gcharset
|
||||
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 数据压缩/解压
|
||||
// Package gcompress provides kinds of compression algorithms for binary/bytes data.
|
||||
//
|
||||
// 数据压缩/解压.
|
||||
package gcompress
|
||||
|
||||
import (
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 常用的hash函数
|
||||
// Package ghash provides some popular hash functions(uint32/uint64) in go.
|
||||
//
|
||||
// 常用的hash函数.
|
||||
package ghash
|
||||
|
||||
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// HTML编码
|
||||
// Package ghtml provides useful API for HTML content handling.
|
||||
//
|
||||
// HTML编码.
|
||||
package ghtml
|
||||
|
||||
import (
|
||||
|
||||
@ -4,12 +4,12 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// JSON解析/封装.
|
||||
// 单元测试请参考gpaser包.
|
||||
// Package gjson provides quite flexible and useful API for JSON/XML/YAML/TOML content handling.
|
||||
package gjson
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
"strings"
|
||||
"strconv"
|
||||
"io/ioutil"
|
||||
@ -21,7 +21,7 @@ import (
|
||||
"gitee.com/johng/gf/g/encoding/gtoml"
|
||||
"gitee.com/johng/gf/g/util/gstr"
|
||||
"time"
|
||||
"gitee.com/johng/gf/g/encoding/gjson/internal/rwmutex"
|
||||
"gitee.com/johng/gf/g/internal/rwmutex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
@ -37,44 +37,46 @@ type Json struct {
|
||||
vc bool // 层级检索是否执行分隔符冲突检测(默认为false,检测会比较影响检索效率)
|
||||
}
|
||||
|
||||
// 将变量转换为Json对象进行处理,该变量至少应当是一个map或者array,否者转换没有意义
|
||||
func New(value interface{}, safe...bool) *Json {
|
||||
// 将变量转换为Json对象进行处理,该变量至少应当是一个map或者slice,否者转换没有意义
|
||||
func New(value interface{}, unsafe...bool) *Json {
|
||||
j := (*Json)(nil)
|
||||
switch value.(type) {
|
||||
case map[string]interface{}, []interface{}, nil:
|
||||
j = &Json{
|
||||
p : &value,
|
||||
c : byte(gDEFAULT_SPLIT_CHAR),
|
||||
vc : false ,
|
||||
}
|
||||
default:
|
||||
v := (interface{})(nil)
|
||||
if m := gconv.Map(value); m != nil {
|
||||
v = m
|
||||
case map[string]interface{}, []interface{}, nil:
|
||||
j = &Json {
|
||||
p : &v,
|
||||
p : &value,
|
||||
c : byte(gDEFAULT_SPLIT_CHAR),
|
||||
vc : false,
|
||||
vc : false ,
|
||||
}
|
||||
} else {
|
||||
v = gconv.Interfaces(value)
|
||||
j = &Json {
|
||||
p : &v,
|
||||
c : byte(gDEFAULT_SPLIT_CHAR),
|
||||
vc : false,
|
||||
case string, []byte:
|
||||
j, _ = LoadContent(gconv.Bytes(value))
|
||||
default:
|
||||
v := (interface{})(nil)
|
||||
if m := gconv.Map(value); m != nil {
|
||||
v = m
|
||||
j = &Json {
|
||||
p : &v,
|
||||
c : byte(gDEFAULT_SPLIT_CHAR),
|
||||
vc : false,
|
||||
}
|
||||
} else {
|
||||
v = gconv.Interfaces(value)
|
||||
j = &Json {
|
||||
p : &v,
|
||||
c : byte(gDEFAULT_SPLIT_CHAR),
|
||||
vc : false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
j.mu = rwmutex.New(safe...)
|
||||
j.mu = rwmutex.New(unsafe...)
|
||||
return j
|
||||
}
|
||||
|
||||
// 创建一个非并发安全的Json对象
|
||||
func NewUnsafe(value...interface{}) *Json {
|
||||
if len(value) > 0 {
|
||||
return New(value[0], false)
|
||||
return New(value[0], true)
|
||||
}
|
||||
return New(nil, false)
|
||||
return New(nil, true)
|
||||
}
|
||||
|
||||
// 编码go变量为json字符串,并返回json字符串指针
|
||||
@ -115,40 +117,47 @@ func Load (path string) (*Json, error) {
|
||||
return LoadContent(data, gfile.Ext(path))
|
||||
}
|
||||
|
||||
// 支持的配置文件格式:xml, json, yaml/yml, toml,默认为json
|
||||
func LoadContent (data []byte, dataType...string) (*Json, error) {
|
||||
// 支持的配置文件格式:xml, json, yaml/yml, toml,
|
||||
// 默认为自动识别,当无法检测成功时使用json解析。
|
||||
func LoadContent(data []byte, dataType...string) (*Json, error) {
|
||||
var err error
|
||||
var result interface{}
|
||||
t := "json"
|
||||
if len(dataType) > 0 {
|
||||
t = dataType[0]
|
||||
} else {
|
||||
if gregex.IsMatch(`<.+>.*</.+>`, data) {
|
||||
t = "xml"
|
||||
} else if gregex.IsMatch(`\w+\s*:\s*\w+`, data) {
|
||||
t = "yml"
|
||||
} else if gregex.IsMatch(`\w+\s*=\s*\w+`, data) {
|
||||
t = "toml"
|
||||
}
|
||||
}
|
||||
switch t {
|
||||
case "xml": fallthrough
|
||||
case ".xml":
|
||||
data, err = gxml.ToJson(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "yml": fallthrough
|
||||
case "yaml": fallthrough
|
||||
case ".yml": fallthrough
|
||||
case ".yaml":
|
||||
data, err = gyaml.ToJson(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "xml", ".xml":
|
||||
data, err = gxml.ToJson(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case "toml": fallthrough
|
||||
case ".toml":
|
||||
data, err = gtoml.ToJson(data)
|
||||
if err != nil {
|
||||
case "yml", "yaml", ".yml", ".yaml":
|
||||
data, err = gyaml.ToJson(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case "toml", ".toml":
|
||||
data, err = gtoml.ToJson(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if result == nil {
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return New(result), nil
|
||||
}
|
||||
|
||||
@ -654,10 +663,10 @@ func (j *Json) ToMap() map[string]interface{} {
|
||||
j.mu.RLock()
|
||||
defer j.mu.RUnlock()
|
||||
switch (*(j.p)).(type) {
|
||||
case map[string]interface{}:
|
||||
return (*(j.p)).(map[string]interface{})
|
||||
default:
|
||||
return nil
|
||||
case map[string]interface{}:
|
||||
return (*(j.p)).(map[string]interface{})
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
package rwmutex
|
||||
|
||||
import "sync"
|
||||
|
||||
// RWMutex的封装,支持对并发安全开启/关闭的控制。
|
||||
// 但是只能初始化时确定并发安全性,不能在运行时动态修改并发安全特性设置。
|
||||
type RWMutex struct {
|
||||
sync.RWMutex
|
||||
safe bool
|
||||
}
|
||||
|
||||
func New(safe...bool) *RWMutex {
|
||||
mu := new(RWMutex)
|
||||
if len(safe) > 0 {
|
||||
mu.safe = safe[0]
|
||||
} else {
|
||||
mu.safe = true
|
||||
}
|
||||
return mu
|
||||
}
|
||||
|
||||
func (mu *RWMutex) IsSafe() bool {
|
||||
return mu.safe
|
||||
}
|
||||
|
||||
func (mu *RWMutex) Lock(force...bool) {
|
||||
if mu.safe || (len(force) > 0 && force[0]) {
|
||||
mu.RWMutex.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mu *RWMutex) Unlock(force...bool) {
|
||||
if mu.safe || (len(force) > 0 && force[0]) {
|
||||
mu.RWMutex.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mu *RWMutex) RLock(force...bool) {
|
||||
if mu.safe || (len(force) > 0 && force[0]) {
|
||||
mu.RWMutex.RLock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mu *RWMutex) RUnlock(force...bool) {
|
||||
if mu.safe || (len(force) > 0 && force[0]) {
|
||||
mu.RWMutex.RUnlock()
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gp.
|
||||
|
||||
// 数据文件编码/解析.
|
||||
// Package gparser provides a flexible and easy way for accessing/converting variable and JSON/XML/YAML/TOML contents.
|
||||
package gparser
|
||||
|
||||
import (
|
||||
@ -18,8 +18,8 @@ type Parser struct {
|
||||
|
||||
// 将变量转换为Parser对象进行处理,该变量至少应当是一个map或者array,否者转换没有意义
|
||||
// value可以传递nil, 表示创建一个空的Parser对象
|
||||
func New (value interface{}, safe...bool) *Parser {
|
||||
return &Parser{gjson.New(value, safe...)}
|
||||
func New (value interface{}, unsafe...bool) *Parser {
|
||||
return &Parser{gjson.New(value, unsafe...)}
|
||||
}
|
||||
|
||||
// 非并发安全Parser对象
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// TOML
|
||||
// Package gtoml provides accessing and converting for TOML content.
|
||||
package gtoml
|
||||
|
||||
import (
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// URL编码
|
||||
// Package gurl provides useful API for URL handling.
|
||||
package gurl
|
||||
|
||||
import "net/url"
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// XML
|
||||
// Package gxml provides accessing and converting for XML content.
|
||||
package gxml
|
||||
|
||||
import (
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// YAML
|
||||
// Package gyaml provides accessing and converting for YAML content.
|
||||
package gyaml
|
||||
|
||||
import "gitee.com/johng/gf/third/github.com/ghodss/yaml"
|
||||
|
||||
@ -4,5 +4,4 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 常用框架管理.
|
||||
package frame
|
||||
|
||||
@ -4,6 +4,8 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gins provides instances management and some core components.
|
||||
//
|
||||
// 单例对象管理.
|
||||
// 框架内置了一些核心对象获取方法,并且可以通过Set和Get方法实现IoC以及对内置核心对象的自定义替换
|
||||
package gins
|
||||
@ -13,9 +15,8 @@ import (
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"gitee.com/johng/gf/g/database/gdb"
|
||||
"gitee.com/johng/gf/g/database/gredis"
|
||||
"gitee.com/johng/gf/g/internal/cmdenv"
|
||||
"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/gfile"
|
||||
"gitee.com/johng/gf/g/os/gfsnotify"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
@ -72,13 +73,7 @@ func View(name...string) *gview.View {
|
||||
}
|
||||
key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_VIEW, group)
|
||||
return instances.GetOrSetFuncLock(key, func() interface{} {
|
||||
path := gcmd.Option.Get("gf.viewpath")
|
||||
if path == "" {
|
||||
path = genv.Get("GF_VIEWPATH")
|
||||
if path == "" {
|
||||
path = gfile.SelfDir()
|
||||
}
|
||||
}
|
||||
path := cmdenv.Get("gf.gview.path", gfile.SelfDir()).String()
|
||||
view := gview.New(path)
|
||||
// 添加基于源码的搜索目录检索地址,常用于开发环境调试,只添加入口文件目录
|
||||
if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) {
|
||||
@ -99,13 +94,7 @@ func Config(file...string) *gcfg.Config {
|
||||
}
|
||||
return instances.GetOrSetFuncLock(fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_CONFIG, configFile),
|
||||
func() interface{} {
|
||||
path := gcmd.Option.Get("gf.cfgpath")
|
||||
if path == "" {
|
||||
path = genv.Get("GF_CFGPATH")
|
||||
if path == "" {
|
||||
path = gfile.SelfDir()
|
||||
}
|
||||
}
|
||||
path := cmdenv.Get("gf.gcfg.path", gfile.SelfDir()).String()
|
||||
config := gcfg.New(path, configFile)
|
||||
// 添加基于源码的搜索目录检索地址,常用于开发环境调试,只添加入口文件目录
|
||||
if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// MVC
|
||||
// Package gmvc provides basic object classes for MVC.
|
||||
package gmvc
|
||||
|
||||
import (
|
||||
|
||||
@ -24,8 +24,8 @@ const (
|
||||
)
|
||||
|
||||
// 动态变量
|
||||
func NewVar(i interface{}, safe...bool) *Var {
|
||||
return gvar.New(i, safe...)
|
||||
func NewVar(i interface{}, unsafe...bool) *Var {
|
||||
return gvar.New(i, unsafe...)
|
||||
}
|
||||
|
||||
// 阻塞等待HTTPServer执行完成(同一进程多HTTPServer情况下)
|
||||
|
||||
@ -7,21 +7,9 @@
|
||||
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)
|
||||
|
||||
34
g/internal/cmdenv/cmdenv.go
Normal file
34
g/internal/cmdenv/cmdenv.go
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2019 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 cmdenv
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/gvar"
|
||||
"gitee.com/johng/gf/g/os/gcmd"
|
||||
"gitee.com/johng/gf/g/os/genv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 获取指定名称的命令行参数,当不存在时获取环境变量参数,皆不存在时,返回给定的默认值。
|
||||
// 规则:
|
||||
// 1、命令行参数以小写字母格式,使用: gf.包名.变量名 传递;
|
||||
// 2、环境变量参数以大写字母格式,使用: GF_包名_变量名 传递;
|
||||
func Get(key string, def...interface{}) *gvar.Var {
|
||||
value := interface{}(nil)
|
||||
if len(def) > 0 {
|
||||
value = def[0]
|
||||
}
|
||||
if v := gcmd.Option.Get(key); v != "" {
|
||||
value = v
|
||||
} else {
|
||||
key = strings.ToUpper(strings.Replace(key, ".", "_", -1))
|
||||
if v := genv.Get(key); v != "" {
|
||||
value = v
|
||||
}
|
||||
}
|
||||
return gvar.New(value, true)
|
||||
}
|
||||
41
g/internal/mutex/mutex.go
Normal file
41
g/internal/mutex/mutex.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2019 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 mutex
|
||||
|
||||
import "sync"
|
||||
|
||||
// Mutex的封装,支持对并发安全开启/关闭的控制。
|
||||
type Mutex struct {
|
||||
sync.Mutex
|
||||
safe bool
|
||||
}
|
||||
|
||||
func New(unsafe...bool) *Mutex {
|
||||
mu := new(Mutex)
|
||||
if len(unsafe) > 0 {
|
||||
mu.safe = !unsafe[0]
|
||||
} else {
|
||||
mu.safe = true
|
||||
}
|
||||
return mu
|
||||
}
|
||||
|
||||
func (mu *Mutex) IsSafe() bool {
|
||||
return mu.safe
|
||||
}
|
||||
|
||||
func (mu *Mutex) Lock(force...bool) {
|
||||
if mu.safe || (len(force) > 0 && force[0]) {
|
||||
mu.Mutex.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mu *Mutex) Unlock(force...bool) {
|
||||
if mu.safe || (len(force) > 0 && force[0]) {
|
||||
mu.Mutex.Unlock()
|
||||
}
|
||||
}
|
||||
@ -1,18 +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.
|
||||
|
||||
package rwmutex
|
||||
|
||||
import "sync"
|
||||
|
||||
// RWMutex的封装,支持对并发安全开启/关闭的控制。
|
||||
// 但是只能初始化时确定并发安全性,不能在运行时动态修改并发安全特性设置。
|
||||
type RWMutex struct {
|
||||
sync.RWMutex
|
||||
safe bool
|
||||
}
|
||||
|
||||
func New(safe...bool) *RWMutex {
|
||||
func New(unsafe...bool) *RWMutex {
|
||||
mu := new(RWMutex)
|
||||
if len(safe) > 0 {
|
||||
mu.safe = safe[0]
|
||||
if len(unsafe) > 0 {
|
||||
mu.safe = !unsafe[0]
|
||||
} else {
|
||||
mu.safe = true
|
||||
}
|
||||
@ -4,5 +4,5 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// HTTP Client & Server.
|
||||
// Package ghttp provides quite powerful HTTP server and simple client implementations.
|
||||
package ghttp
|
||||
|
||||
96
g/net/ghttp/ghttp_client_request_api.go
Normal file
96
g/net/ghttp/ghttp_client_request_api.go
Normal file
@ -0,0 +1,96 @@
|
||||
// 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.
|
||||
|
||||
// HTTP客户端请求.
|
||||
|
||||
package ghttp
|
||||
|
||||
func Get(url string) (*ClientResponse, error) {
|
||||
return DoRequest("GET", url, []byte(""))
|
||||
}
|
||||
|
||||
func Put(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("PUT", url, []byte(data))
|
||||
}
|
||||
|
||||
func Post(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("POST", url, []byte(data))
|
||||
}
|
||||
|
||||
func Delete(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("DELETE", url, []byte(data))
|
||||
}
|
||||
|
||||
func Head(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("HEAD", url, []byte(data))
|
||||
}
|
||||
|
||||
func Patch(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("PATCH", url, []byte(data))
|
||||
}
|
||||
|
||||
func Connect(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("CONNECT", url, []byte(data))
|
||||
}
|
||||
|
||||
func Options(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("OPTIONS", url, []byte(data))
|
||||
}
|
||||
|
||||
func Trace(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("TRACE", url, []byte(data))
|
||||
}
|
||||
|
||||
// 该方法支持二进制提交数据
|
||||
func DoRequest(method, url string, data []byte) (*ClientResponse, error) {
|
||||
return NewClient().DoRequest(method, url, data)
|
||||
}
|
||||
|
||||
// GET请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func GetContent(url string, data...string) string {
|
||||
return RequestContent("GET", url, data...)
|
||||
}
|
||||
|
||||
// PUT请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func PutContent(url string, data...string) string {
|
||||
return RequestContent("PUT", url, data...)
|
||||
}
|
||||
|
||||
// POST请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func PostContent(url string, data...string) string {
|
||||
return RequestContent("POST", url, data...)
|
||||
}
|
||||
|
||||
// DELETE请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func DeleteContent(url string, data...string) string {
|
||||
return RequestContent("DELETE", url, data...)
|
||||
}
|
||||
|
||||
func HeadContent(url string, data...string) string {
|
||||
return RequestContent("HEAD", url, data...)
|
||||
}
|
||||
|
||||
func PatchContent(url string, data...string) string {
|
||||
return RequestContent("PATCH", url, data...)
|
||||
}
|
||||
|
||||
func ConnectContent(url string, data...string) string {
|
||||
return RequestContent("CONNECT", url, data...)
|
||||
}
|
||||
|
||||
func OptionsContent(url string, data...string) string {
|
||||
return RequestContent("OPTIONS", url, data...)
|
||||
}
|
||||
|
||||
func TraceContent(url string, data...string) string {
|
||||
return RequestContent("TRACE", url, data...)
|
||||
}
|
||||
|
||||
// 请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func RequestContent(method string, url string, data...string) string {
|
||||
return NewClient().DoRequestContent(method, url, data...)
|
||||
}
|
||||
|
||||
@ -3,11 +3,13 @@
|
||||
// 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.
|
||||
|
||||
// HTTP客户端请求.
|
||||
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
"time"
|
||||
"bytes"
|
||||
"strings"
|
||||
@ -24,6 +26,7 @@ import (
|
||||
type Client struct {
|
||||
http.Client // 底层http client对象
|
||||
header map[string]string // HEADER信息Map
|
||||
prefix string // 设置请求的URL前缀
|
||||
authUser string // HTTP基本权限设置:名称
|
||||
authPass string // HTTP基本权限设置:密码
|
||||
}
|
||||
@ -40,11 +43,26 @@ func NewClient() (*Client) {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置HTTP Headerss
|
||||
// 设置HTTP Header
|
||||
func (c *Client) SetHeader(key, value string) {
|
||||
c.header[key] = value
|
||||
}
|
||||
|
||||
// 通过字符串设置HTTP Header
|
||||
func (c *Client) SetHeaderRaw(header string) {
|
||||
for _, line := range strings.Split(strings.TrimSpace(header), "\n") {
|
||||
array, _ := gregex.MatchString(`^([\w\-]+):\s*(.+)`, line)
|
||||
if len(array) >= 3 {
|
||||
c.header[array[1]] = array[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 设置请求的URL前缀
|
||||
func (c *Client) SetPrefix(prefix string) {
|
||||
c.prefix = prefix
|
||||
}
|
||||
|
||||
// 设置请求过期时间
|
||||
func (c *Client) SetTimeOut(t time.Duration) {
|
||||
c.Timeout = t
|
||||
@ -70,7 +88,10 @@ func (c *Client) Put(url, data string) (*ClientResponse, error) {
|
||||
// 如果服务端对Content-Type有要求,可使用Client对象进行请求,单独设置相关属性。
|
||||
// 支持文件上传,需要字段格式为:FieldName=@file:
|
||||
func (c *Client) Post(url, data string) (*ClientResponse, error) {
|
||||
var req *http.Request
|
||||
if len(c.prefix) > 0 {
|
||||
url = c.prefix + url
|
||||
}
|
||||
req := (*http.Request)(nil)
|
||||
if strings.Contains(data, "@file:") {
|
||||
buffer := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(buffer)
|
||||
@ -157,11 +178,68 @@ func (c *Client) Trace(url, data string) (*ClientResponse, error) {
|
||||
return c.DoRequest("TRACE", url, []byte(data))
|
||||
}
|
||||
|
||||
// GET请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func (c *Client) GetContent(url string, data...string) string {
|
||||
return c.DoRequestContent("GET", url, data...)
|
||||
}
|
||||
|
||||
// PUT请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func (c *Client) PutContent(url string, data...string) string {
|
||||
return c.DoRequestContent("PUT", url, data...)
|
||||
}
|
||||
|
||||
// POST请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func (c *Client) PostContent(url string, data...string) string {
|
||||
return c.DoRequestContent("POST", url, data...)
|
||||
}
|
||||
|
||||
// DELETE请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func (c *Client) DeleteContent(url string, data...string) string {
|
||||
return c.DoRequestContent("DELETE", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) HeadContent(url string, data...string) string {
|
||||
return c.DoRequestContent("HEAD", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) PatchContent(url string, data...string) string {
|
||||
return c.DoRequestContent("PATCH", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) ConnectContent(url string, data...string) string {
|
||||
return c.DoRequestContent("CONNECT", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) OptionsContent(url string, data...string) string {
|
||||
return c.DoRequestContent("OPTIONS", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) TraceContent(url string, data...string) string {
|
||||
return c.DoRequestContent("TRACE", url, data...)
|
||||
}
|
||||
|
||||
// 请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func (c *Client) DoRequestContent(method string, url string, data...string) string {
|
||||
content := ""
|
||||
if len(data) > 0 {
|
||||
content = data[0]
|
||||
}
|
||||
response, err := c.DoRequest(method, url, []byte(content))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer response.Close()
|
||||
return string(response.ReadAll())
|
||||
}
|
||||
|
||||
// 请求并返回response对象,该方法支持二进制提交数据
|
||||
func (c *Client) DoRequest(method, url string, data []byte) (*ClientResponse, error) {
|
||||
if strings.Compare("POST", strings.ToUpper(method)) == 0 {
|
||||
if strings.EqualFold("POST", method) {
|
||||
return c.Post(url, string(data))
|
||||
}
|
||||
if len(c.prefix) > 0 {
|
||||
url = c.prefix + url
|
||||
}
|
||||
req, err := http.NewRequest(strings.ToUpper(method), url, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -183,43 +261,5 @@ func (c *Client) DoRequest(method, url string, data []byte) (*ClientResponse, er
|
||||
}
|
||||
|
||||
|
||||
func Get(url string) (*ClientResponse, error) {
|
||||
return DoRequest("GET", url, []byte(""))
|
||||
}
|
||||
|
||||
func Put(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("PUT", url, []byte(data))
|
||||
}
|
||||
|
||||
func Post(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("POST", url, []byte(data))
|
||||
}
|
||||
|
||||
func Delete(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("DELETE", url, []byte(data))
|
||||
}
|
||||
|
||||
func Head(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("HEAD", url, []byte(data))
|
||||
}
|
||||
|
||||
func Patch(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("PATCH", url, []byte(data))
|
||||
}
|
||||
|
||||
func Connect(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("CONNECT", url, []byte(data))
|
||||
}
|
||||
|
||||
func Options(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("OPTIONS", url, []byte(data))
|
||||
}
|
||||
|
||||
func Trace(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("TRACE", url, []byte(data))
|
||||
}
|
||||
|
||||
// 该方法支持二进制提交数据
|
||||
func DoRequest(method, url string, data []byte) (*ClientResponse, error) {
|
||||
return NewClient().DoRequest(method, url, data)
|
||||
}
|
||||
@ -36,6 +36,7 @@ type Request struct {
|
||||
params map[string]interface{} // 开发者自定义参数(请求流程中有效)
|
||||
parsedHost string // 解析过后不带端口号的服务器域名名称
|
||||
clientIp string // 解析过后的客户端IP地址
|
||||
rawContent []byte // 客户端提交的原始参数
|
||||
isFileRequest bool // 是否为静态文件请求(非服务请求,当静态文件存在时,优先级会被服务请求高,被识别为文件请求)
|
||||
}
|
||||
|
||||
@ -78,10 +79,12 @@ func (r *Request) GetVar(key string, def ... interface{}) gvar.VarRead {
|
||||
return r.GetRequestVar(key, def...)
|
||||
}
|
||||
|
||||
// 获取原始请求输入字符串,注意:只能获取一次,读完就没了
|
||||
// 获取原始请求输入字符串
|
||||
func (r *Request) GetRaw() []byte {
|
||||
result, _ := ioutil.ReadAll(r.Body)
|
||||
return result
|
||||
if r.rawContent == nil {
|
||||
r.rawContent, _ = ioutil.ReadAll(r.Body)
|
||||
}
|
||||
return r.rawContent
|
||||
}
|
||||
|
||||
// 获取原始json请求输入字符串,并解析为json对象
|
||||
@ -144,12 +147,22 @@ func (r *Request) GetToStruct(object interface{}, mapping...map[string]string) {
|
||||
r.GetRequestToStruct(object, mapping...)
|
||||
}
|
||||
|
||||
// 退出当前请求执行,原理是在Request.exit做标记,由服务逻辑流程做判断,自行停止
|
||||
// 仅退出当前逻辑执行函数, 如:服务函数、HOOK函数
|
||||
func (r *Request) Exit() {
|
||||
r.exit = true
|
||||
panic(gEXCEPTION_EXIT)
|
||||
}
|
||||
|
||||
// 退出当前请求执行,后续所有的服务逻辑流程(包括其他的HOOK)将不会执行
|
||||
func (r *Request) ExitAll() {
|
||||
r.exit = true
|
||||
panic(gEXCEPTION_EXIT_ALL)
|
||||
}
|
||||
|
||||
// 仅针对HOOK执行,默认情况下HOOK会按照优先级进行调用,当使用ExitHook后当前类型的后续HOOK将不会被调用
|
||||
func (r *Request) ExitHook() {
|
||||
panic(gEXCEPTION_EXIT_HOOK)
|
||||
}
|
||||
|
||||
// 判断当前请求是否停止执行
|
||||
func (r *Request) IsExited() bool {
|
||||
return r.exit
|
||||
|
||||
@ -20,9 +20,9 @@ func (r *Request) SetParam(key string, value interface{}) {
|
||||
func (r *Request) GetParam(key string) gvar.VarRead {
|
||||
if r.params != nil {
|
||||
if v, ok := r.params[key]; ok {
|
||||
return gvar.New(v, false)
|
||||
return gvar.New(v, true)
|
||||
}
|
||||
}
|
||||
return gvar.New(nil, false)
|
||||
return gvar.New(nil, true)
|
||||
}
|
||||
|
||||
|
||||
@ -155,15 +155,12 @@ func (r *Request) GetPostInterfaces(key string, def ... []interface{}) []interfa
|
||||
func (r *Request) GetPostMap(def...map[string]string) map[string]string {
|
||||
r.initPost()
|
||||
m := make(map[string]string)
|
||||
if len(def) == 0 {
|
||||
for k, v := range r.PostForm {
|
||||
m[k] = v[0]
|
||||
}
|
||||
} else {
|
||||
for k, v := range r.PostForm {
|
||||
m[k] = v[0]
|
||||
}
|
||||
if len(def) > 0 {
|
||||
for k, v := range def[0] {
|
||||
if v2, ok := r.PostForm[k]; ok {
|
||||
m[k] = v2[0]
|
||||
} else {
|
||||
if _, ok := m[k]; !ok {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,12 +8,21 @@ package ghttp
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 初始化GET请求参数
|
||||
func (r *Request) initGet() {
|
||||
if !r.parsedGet {
|
||||
r.queryVars = r.URL.Query()
|
||||
if strings.EqualFold(r.Method, "GET") {
|
||||
if raw := r.GetRaw(); len(raw) > 0 {
|
||||
for _, item := range strings.Split(string(raw), "&") {
|
||||
array := strings.Split(item, "=")
|
||||
r.queryVars[array[0]] = append(r.queryVars[array[0]], array[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
r.parsedGet = true
|
||||
}
|
||||
}
|
||||
@ -153,17 +162,13 @@ func (r *Request) GetQueryInterfaces(key string, def ... []interface{}) []interf
|
||||
func (r *Request) GetQueryMap(def ... map[string]string) map[string]string {
|
||||
r.initGet()
|
||||
m := make(map[string]string)
|
||||
if len(def) == 0 {
|
||||
for k, v := range r.queryVars {
|
||||
m[k] = v[0]
|
||||
}
|
||||
} else {
|
||||
for k, v := range r.queryVars {
|
||||
m[k] = v[0]
|
||||
}
|
||||
if len(def) > 0 {
|
||||
for k, v := range def[0] {
|
||||
v2 := r.GetQueryArray(k)
|
||||
if v2 == nil {
|
||||
if _, ok := m[k]; !ok {
|
||||
m[k] = v
|
||||
} else {
|
||||
m[k] = v2[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,12 +29,12 @@ func (r *Request) GetRequest(key string, def ... []string) []string {
|
||||
func (r *Request) GetRequestVar(key string, def ... interface{}) gvar.VarRead {
|
||||
value := r.GetRequest(key)
|
||||
if value != nil {
|
||||
return gvar.New(value[0], false)
|
||||
return gvar.New(value[0], true)
|
||||
}
|
||||
if len(def) > 0 {
|
||||
return gvar.New(def[0], false)
|
||||
return gvar.New(def[0], true)
|
||||
}
|
||||
return gvar.New(nil, false)
|
||||
return gvar.New(nil, true)
|
||||
}
|
||||
|
||||
func (r *Request) GetRequestString(key string, def ... string) string {
|
||||
@ -148,18 +148,12 @@ func (r *Request) GetRequestInterfaces(key string, def ... []interface{}) []inte
|
||||
// 需要注意的是,如果其中一个字段为数组形式,那么只会返回第一个元素,如果需要获取全部的元素,请使用GetRequestArray获取特定字段内容
|
||||
func (r *Request) GetRequestMap(def...map[string]string) map[string]string {
|
||||
m := r.GetQueryMap()
|
||||
if len(def) == 0 {
|
||||
for k, v := range r.GetPostMap() {
|
||||
if _, ok := m[k]; !ok {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if len(m) == 0 {
|
||||
m = r.GetPostMap()
|
||||
}
|
||||
if len(def) > 0 {
|
||||
for k, v := range def[0] {
|
||||
v2 := r.GetRequest(k)
|
||||
if v2 != nil {
|
||||
m[k] = v2[0]
|
||||
} else {
|
||||
if _, ok := m[k]; !ok {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
@ -179,6 +173,11 @@ func (r *Request) GetRequestToStruct(object interface{}, mapping...map[string]st
|
||||
for k, v := range r.GetRequestMap() {
|
||||
params[k] = v
|
||||
}
|
||||
if len(params) == 0 {
|
||||
if j := r.GetJson(); j != nil {
|
||||
params = j.ToMap()
|
||||
}
|
||||
}
|
||||
gconv.Struct(params, object, tagmap)
|
||||
}
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ import (
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/os/gproc"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/os/gtimer"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
"gitee.com/johng/gf/third/github.com/gorilla/websocket"
|
||||
@ -118,6 +118,8 @@ const (
|
||||
gROUTE_REGISTER_OBJECT = 2
|
||||
gROUTE_REGISTER_CONTROLLER = 3
|
||||
gEXCEPTION_EXIT = "exit"
|
||||
gEXCEPTION_EXIT_ALL = "exit_all"
|
||||
gEXCEPTION_EXIT_HOOK = "exit_hook"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -249,7 +251,7 @@ func (s *Server) Start() error {
|
||||
|
||||
// 如果是子进程,那么服务开启后通知父进程销毁
|
||||
if gproc.IsChild() {
|
||||
gtime.SetTimeout(2*time.Second, func() {
|
||||
gtimer.SetTimeout(2*time.Second, func() {
|
||||
if err := gproc.Send(gproc.PPid(), []byte("exit"), gADMIN_GPROC_COMM_GROUP); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -269,7 +271,7 @@ func (s *Server) Start() error {
|
||||
func (s *Server) DumpRoutesMap() {
|
||||
if s.config.DumpRouteMap && len(s.routesMap) > 0 {
|
||||
// (等待一定时间后)当所有框架初始化信息打印完毕之后才打印路由表信息
|
||||
gtime.SetTimeout(50*time.Millisecond, func() {
|
||||
gtimer.SetTimeout(50*time.Millisecond, func() {
|
||||
glog.Header(false).Println(fmt.Sprintf("\n%s\n", s.GetRouteMap()))
|
||||
})
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/os/gtimer"
|
||||
"strings"
|
||||
"gitee.com/johng/gf/g/os/gview"
|
||||
"gitee.com/johng/gf/g/os/gproc"
|
||||
@ -251,7 +252,7 @@ func restartWebServers(signal string, newExeFilePath...string) error {
|
||||
forkRestartProcess(newExeFilePath...)
|
||||
} else {
|
||||
// 非终端信号下,异步1秒后再执行重启,目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了)
|
||||
gtime.SetTimeout(time.Second, func() {
|
||||
gtimer.SetTimeout(time.Second, func() {
|
||||
forcedlyCloseWebServers()
|
||||
forkRestartProcess(newExeFilePath...)
|
||||
})
|
||||
@ -284,7 +285,7 @@ func shutdownWebServers(signal string) {
|
||||
} else {
|
||||
glog.Printfln("%d: server shutting down by web admin", gproc.Pid())
|
||||
// 非终端信号下,异步1秒后再执行关闭,目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了)
|
||||
gtime.SetTimeout(time.Second, func() {
|
||||
gtimer.SetTimeout(time.Second, func() {
|
||||
forcedlyCloseWebServers()
|
||||
doneChan <- struct{}{}
|
||||
})
|
||||
|
||||
@ -52,7 +52,7 @@ type ServerConfig struct {
|
||||
ServerRoot string // 服务器服务的本地目录根路径(检索优先级比StaticPaths低)
|
||||
SearchPaths []string // 静态文件搜索目录(包含ServerRoot,按照优先级进行排序)
|
||||
StaticPaths []staticPathItem // 静态文件目录映射(按照优先级进行排序)
|
||||
FileServerEnabled bool // 是否允许静态文件服务(总开关,默认开启)
|
||||
FileServerEnabled bool // 是否允许静态文件服务(通过静态文件服务方法调用自动识别)
|
||||
|
||||
// COOKIE
|
||||
CookieMaxAge int // Cookie有效期
|
||||
@ -99,7 +99,7 @@ var defaultServerConfig = ServerConfig {
|
||||
ServerAgent : "gf",
|
||||
ServerRoot : "",
|
||||
StaticPaths : make([]staticPathItem, 0),
|
||||
FileServerEnabled : true,
|
||||
FileServerEnabled : false,
|
||||
|
||||
CookieMaxAge : gDEFAULT_COOKIE_MAX_AGE,
|
||||
CookiePath : gDEFAULT_COOKIE_PATH,
|
||||
|
||||
@ -64,7 +64,8 @@ func (s *Server)SetServerRoot(root string) {
|
||||
if path == "" {
|
||||
glog.Fatal(fmt.Sprintf(`[ghttp] SetServerRoot failed: path "%s" does not exist`, root))
|
||||
}
|
||||
s.config.SearchPaths = []string{strings.TrimRight(path, gfile.Separator)}
|
||||
s.config.SearchPaths = []string{strings.TrimRight(path, gfile.Separator)}
|
||||
s.config.FileServerEnabled = true
|
||||
}
|
||||
|
||||
// 添加静态文件搜索**目录**,必须给定目录的绝对路径
|
||||
@ -81,7 +82,8 @@ func (s *Server) AddSearchPath(path string) {
|
||||
if realPath == "" {
|
||||
glog.Fatal(fmt.Sprintf(`[ghttp] AddSearchPath failed: path "%s" does not exist`, path))
|
||||
}
|
||||
s.config.SearchPaths = append(s.config.SearchPaths, realPath)
|
||||
s.config.SearchPaths = append(s.config.SearchPaths, realPath)
|
||||
s.config.FileServerEnabled = true
|
||||
}
|
||||
|
||||
// 添加URI与静态**目录**的映射
|
||||
@ -132,5 +134,6 @@ func (s *Server) AddStaticPath(prefix string, path string) {
|
||||
} else {
|
||||
s.config.StaticPaths = []staticPathItem { addItem }
|
||||
}
|
||||
s.config.FileServerEnabled = true
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -92,11 +93,11 @@ func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string) error {
|
||||
if config.NextProtos == nil {
|
||||
config.NextProtos = []string{"http/1.1"}
|
||||
}
|
||||
var err error
|
||||
err := error(nil)
|
||||
config.Certificates = make([]tls.Certificate, 1)
|
||||
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.New(fmt.Sprintf(`open cert file "%s","%s" failed: %s`, certFile, keyFile, err.Error()))
|
||||
}
|
||||
ln, err := s.getNetListener(addr)
|
||||
if err != nil {
|
||||
|
||||
@ -111,26 +111,36 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// 静态目录
|
||||
s.serveFile(request, staticFile)
|
||||
} else {
|
||||
request.Response.WriteStatus(http.StatusNotFound)
|
||||
if len(request.Response.Header()) == 0 &&
|
||||
request.Response.Status == 0 &&
|
||||
request.Response.BufferLength() == 0 {
|
||||
request.Response.WriteStatus(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 事件 - AfterServe
|
||||
s.callHookHandler(HOOK_AFTER_SERVE, request)
|
||||
if !request.IsExited() {
|
||||
s.callHookHandler(HOOK_AFTER_SERVE, request)
|
||||
}
|
||||
|
||||
// 设置请求完成时间
|
||||
request.LeaveTime = gtime.Microsecond()
|
||||
|
||||
// 事件 - BeforeOutput
|
||||
s.callHookHandler(HOOK_BEFORE_OUTPUT, request)
|
||||
if !request.IsExited() {
|
||||
s.callHookHandler(HOOK_BEFORE_OUTPUT, request)
|
||||
}
|
||||
// 输出Cookie
|
||||
request.Cookie.Output()
|
||||
// 输出缓冲区
|
||||
request.Response.OutputBuffer()
|
||||
// 事件 - AfterOutput
|
||||
s.callHookHandler(HOOK_AFTER_OUTPUT, request)
|
||||
if !request.IsExited() {
|
||||
s.callHookHandler(HOOK_AFTER_OUTPUT, request)
|
||||
}
|
||||
}
|
||||
|
||||
// 查找静态文件的绝对路径
|
||||
@ -158,30 +168,36 @@ func (s *Server) searchStaticFile(uri string) (filePath string, isDir bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// 初始化控制器
|
||||
// 调用服务接口
|
||||
func (s *Server) callServeHandler(h *handlerItem, r *Request) {
|
||||
if h.faddr == nil {
|
||||
c := reflect.New(h.ctype)
|
||||
s.niceCall(func() {
|
||||
s.niceCallFunc(func() {
|
||||
c.MethodByName("Init").Call([]reflect.Value{reflect.ValueOf(r)})
|
||||
})
|
||||
s.niceCall(func() {
|
||||
c.MethodByName(h.fname).Call(nil)
|
||||
})
|
||||
s.niceCall(func() {
|
||||
c.MethodByName("Shut").Call(nil)
|
||||
})
|
||||
if !r.IsExited() {
|
||||
s.niceCallFunc(func() {
|
||||
c.MethodByName(h.fname).Call(nil)
|
||||
})
|
||||
}
|
||||
if !r.IsExited() {
|
||||
s.niceCallFunc(func() {
|
||||
c.MethodByName("Shut").Call(nil)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if h.finit != nil {
|
||||
s.niceCall(func() {
|
||||
s.niceCallFunc(func() {
|
||||
h.finit(r)
|
||||
})
|
||||
}
|
||||
s.niceCall(func() {
|
||||
h.faddr(r)
|
||||
})
|
||||
if h.fshut != nil {
|
||||
s.niceCall(func() {
|
||||
if !r.IsExited() {
|
||||
s.niceCallFunc(func() {
|
||||
h.faddr(r)
|
||||
})
|
||||
}
|
||||
if h.fshut != nil && !r.IsExited() {
|
||||
s.niceCallFunc(func() {
|
||||
h.fshut(r)
|
||||
})
|
||||
}
|
||||
@ -189,10 +205,16 @@ func (s *Server) callServeHandler(h *handlerItem, r *Request) {
|
||||
}
|
||||
|
||||
// 友好地调用方法
|
||||
func (s *Server) niceCall(f func()) {
|
||||
func (s *Server) niceCallFunc(f func()) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil && e != gEXCEPTION_EXIT {
|
||||
panic(e)
|
||||
if err := recover(); err != nil {
|
||||
switch err {
|
||||
case gEXCEPTION_EXIT: fallthrough
|
||||
case gEXCEPTION_EXIT_ALL:
|
||||
return
|
||||
default:
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
f()
|
||||
|
||||
@ -69,6 +69,14 @@ func (g *RouterGroup) ALL(pattern string, object interface{}, params...interface
|
||||
g.bind("HANDLER", gDEFAULT_METHOD + ":" + pattern, object, params...)
|
||||
}
|
||||
|
||||
// 绑定常用方法: GET/PUT/POST/DELETE
|
||||
func (g *RouterGroup) COMMON(pattern string, object interface{}, params...interface{}) {
|
||||
g.GET(pattern, object, params...)
|
||||
g.PUT(pattern, object, params...)
|
||||
g.POST(pattern, object, params...)
|
||||
g.DELETE(pattern, object, params...)
|
||||
}
|
||||
|
||||
func (g *RouterGroup) GET(pattern string, object interface{}, params...interface{}) {
|
||||
g.bind("HANDLER", "GET:" + pattern, object, params...)
|
||||
}
|
||||
|
||||
@ -46,11 +46,6 @@ 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 {
|
||||
@ -70,13 +65,32 @@ func (s *Server) callHookHandler(hook string, r *Request) {
|
||||
}
|
||||
// 不使用hook的router对象,保留路由注册服务的router对象,不能覆盖
|
||||
// r.Router = item.handler.router
|
||||
item.handler.faddr(r)
|
||||
if err := s.niceCallHookHandler(item.handler.faddr, r); err != nil {
|
||||
switch err {
|
||||
case gEXCEPTION_EXIT:
|
||||
break
|
||||
case gEXCEPTION_EXIT_ALL: fallthrough
|
||||
case gEXCEPTION_EXIT_HOOK:
|
||||
return
|
||||
default:
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 恢复原有的router变量
|
||||
r.routerVars = oldRouterVars
|
||||
}
|
||||
}
|
||||
|
||||
// 友好地调用方法
|
||||
func (s *Server) niceCallHookHandler(f HandlerFunc, r *Request) (err interface{}) {
|
||||
defer func() {
|
||||
err = recover()
|
||||
}()
|
||||
f(r)
|
||||
return
|
||||
}
|
||||
|
||||
// 查询请求处理方法, 带缓存机制,按照Host、Method、Path进行缓存.
|
||||
func (s *Server) getHookHandlerWithCache(hook string, r *Request) []*handlerParsedItem {
|
||||
cacheItems := ([]*handlerParsedItem)(nil)
|
||||
@ -150,7 +164,7 @@ func (s *Server) searchHookHandler(method, path, domain, hook string) []*handler
|
||||
}
|
||||
|
||||
// 多层链表遍历检索,从数组末尾的链表开始遍历,末尾的深度高优先级也高
|
||||
pushedSet := gset.NewStringSet(false)
|
||||
pushedSet := gset.NewStringSet(true)
|
||||
for i := len(lists) - 1; i >= 0; i-- {
|
||||
for e := lists[i].Front(); e != nil; e = e.Next() {
|
||||
handler := e.Value.(*handlerItem)
|
||||
|
||||
@ -97,7 +97,7 @@ func (s *Session) Get (key string) interface{} {
|
||||
// 获取SESSION,建议都用该方法获取参数
|
||||
func (s *Session) GetVar(key string) gvar.VarRead {
|
||||
s.init()
|
||||
return gvar.NewRead(s.data.Get(key), false)
|
||||
return gvar.NewRead(s.data.Get(key), true)
|
||||
}
|
||||
|
||||
|
||||
|
||||
55
g/net/ghttp/ghttp_unit_1_test.go
Normal file
55
g/net/ghttp/ghttp_unit_1_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
// 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 ghttp_test
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gtest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
func Test_Router_Basic(t *testing.T) {
|
||||
s := g.Server(gtime.Nanosecond())
|
||||
s.BindHandler("/:name", func(r *ghttp.Request){
|
||||
r.Response.Write("/:name")
|
||||
})
|
||||
s.BindHandler("/:name/update", func(r *ghttp.Request){
|
||||
r.Response.Write(r.Get("name"))
|
||||
})
|
||||
s.BindHandler("/:name/:action", func(r *ghttp.Request){
|
||||
r.Response.Write(r.Get("action"))
|
||||
})
|
||||
s.BindHandler("/:name/*any", func(r *ghttp.Request){
|
||||
r.Response.Write(r.Get("any"))
|
||||
})
|
||||
s.BindHandler("/user/list/{field}.html", func(r *ghttp.Request){
|
||||
r.Response.Write(r.Get("field"))
|
||||
})
|
||||
s.SetPort(8100)
|
||||
s.SetDumpRouteMap(false)
|
||||
go s.Run()
|
||||
defer func() {
|
||||
s.Shutdown()
|
||||
time.Sleep(time.Second)
|
||||
}()
|
||||
// 等待启动完成
|
||||
time.Sleep(time.Second)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix("http://127.0.0.1:8100")
|
||||
|
||||
gtest.Assert(client.GetContent("/john"), "")
|
||||
gtest.Assert(client.GetContent("/john/update"), "john")
|
||||
gtest.Assert(client.GetContent("/john/edit"), "edit")
|
||||
gtest.Assert(client.GetContent("/user/list/100.html"), "100")
|
||||
})
|
||||
}
|
||||
126
g/net/ghttp/ghttp_unit_2_test.go
Normal file
126
g/net/ghttp/ghttp_unit_2_test.go
Normal file
@ -0,0 +1,126 @@
|
||||
// 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 ghttp_test
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/frame/gmvc"
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gtest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 执行对象
|
||||
type Object struct {}
|
||||
|
||||
func (o *Object) Show(r *ghttp.Request) {
|
||||
r.Response.Write("Object Show")
|
||||
}
|
||||
|
||||
func (o *Object) Delete(r *ghttp.Request) {
|
||||
r.Response.Write("Object REST Delete")
|
||||
}
|
||||
|
||||
// 控制器
|
||||
type Controller struct {
|
||||
gmvc.Controller
|
||||
}
|
||||
|
||||
func (c *Controller) Show() {
|
||||
c.Response.Write("Controller Show")
|
||||
}
|
||||
|
||||
func (c *Controller) Post() {
|
||||
c.Response.Write("Controller REST Post")
|
||||
}
|
||||
|
||||
func Handler(r *ghttp.Request) {
|
||||
r.Response.Write("Handler")
|
||||
}
|
||||
|
||||
func Test_Router_Group1(t *testing.T) {
|
||||
s := g.Server(gtime.Nanosecond())
|
||||
obj := new(Object)
|
||||
ctl := new(Controller)
|
||||
// 分组路由方法注册
|
||||
g := s.Group("/api")
|
||||
g.ALL ("/handler", Handler)
|
||||
g.ALL ("/ctl", ctl)
|
||||
g.GET ("/ctl/my-show", ctl, "Show")
|
||||
g.REST("/ctl/rest", ctl)
|
||||
g.ALL ("/obj", obj)
|
||||
g.GET ("/obj/my-show", obj, "Show")
|
||||
g.REST("/obj/rest", obj)
|
||||
s.SetPort(8200)
|
||||
s.SetDumpRouteMap(false)
|
||||
go s.Run()
|
||||
defer func() {
|
||||
s.Shutdown()
|
||||
time.Sleep(time.Second)
|
||||
}()
|
||||
time.Sleep(time.Second)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix("http://127.0.0.1:8200")
|
||||
|
||||
gtest.Assert(client.GetContent ("/api/handler"), "Handler")
|
||||
|
||||
gtest.Assert(client.GetContent ("/api/ctl/my-show"), "Controller Show")
|
||||
gtest.Assert(client.GetContent ("/api/ctl/post"), "Controller REST Post")
|
||||
gtest.Assert(client.GetContent ("/api/ctl/show"), "Controller Show")
|
||||
gtest.Assert(client.PostContent("/api/ctl/rest"), "Controller REST Post")
|
||||
|
||||
gtest.Assert(client.GetContent ("/api/obj/delete"), "Object REST Delete")
|
||||
gtest.Assert(client.GetContent ("/api/obj/my-show"), "Object Show")
|
||||
gtest.Assert(client.GetContent ("/api/obj/show"), "Object Show")
|
||||
gtest.Assert(client.DeleteContent("/api/obj/rest"), "Object REST Delete")
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_Group2(t *testing.T) {
|
||||
s := g.Server(gtime.Nanosecond())
|
||||
obj := new(Object)
|
||||
ctl := new(Controller)
|
||||
// 分组路由批量注册
|
||||
s.Group("/api").Bind("/api", []ghttp.GroupItem{
|
||||
{"ALL", "/handler", Handler},
|
||||
{"ALL", "/ctl", ctl},
|
||||
{"GET", "/ctl/my-show", ctl, "Show"},
|
||||
{"REST", "/ctl/rest", ctl},
|
||||
{"ALL", "/obj", obj},
|
||||
{"GET", "/obj/my-show", obj, "Show"},
|
||||
{"REST", "/obj/rest", obj},
|
||||
})
|
||||
s.SetPort(8300)
|
||||
s.SetDumpRouteMap(false)
|
||||
go s.Run()
|
||||
defer func() {
|
||||
s.Shutdown()
|
||||
time.Sleep(time.Second)
|
||||
}()
|
||||
time.Sleep(time.Second)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix("http://127.0.0.1:8300")
|
||||
|
||||
gtest.Assert(client.GetContent ("/api/handler"), "Handler")
|
||||
|
||||
gtest.Assert(client.GetContent ("/api/ctl/my-show"), "Controller Show")
|
||||
gtest.Assert(client.GetContent ("/api/ctl/post"), "Controller REST Post")
|
||||
gtest.Assert(client.GetContent ("/api/ctl/show"), "Controller Show")
|
||||
gtest.Assert(client.PostContent("/api/ctl/rest"), "Controller REST Post")
|
||||
|
||||
gtest.Assert(client.GetContent ("/api/obj/delete"), "Object REST Delete")
|
||||
gtest.Assert(client.GetContent ("/api/obj/my-show"), "Object Show")
|
||||
gtest.Assert(client.GetContent ("/api/obj/show"), "Object Show")
|
||||
gtest.Assert(client.DeleteContent("/api/obj/rest"), "Object REST Delete")
|
||||
})
|
||||
}
|
||||
149
g/net/ghttp/ghttp_unit_3_test.go
Normal file
149
g/net/ghttp/ghttp_unit_3_test.go
Normal file
@ -0,0 +1,149 @@
|
||||
// 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 ghttp_test
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gtest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_Params(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
Pass1 string `params:"password1"`
|
||||
Pass2 string `params:"password2"`
|
||||
}
|
||||
s := g.Server(gtime.Nanosecond())
|
||||
s.BindHandler("/get", func(r *ghttp.Request){
|
||||
if r.GetQuery("slice") != nil {
|
||||
r.Response.Write(r.GetQuery("slice"))
|
||||
}
|
||||
if r.GetQuery("bool") != nil {
|
||||
r.Response.Write(r.GetQueryBool("bool"))
|
||||
}
|
||||
if r.GetQuery("float32") != nil {
|
||||
r.Response.Write(r.GetQueryFloat32("float32"))
|
||||
}
|
||||
if r.GetQuery("float64") != nil {
|
||||
r.Response.Write(r.GetQueryFloat64("float64"))
|
||||
}
|
||||
if r.GetQuery("int") != nil {
|
||||
r.Response.Write(r.GetQueryInt("int"))
|
||||
}
|
||||
if r.GetQuery("uint") != nil {
|
||||
r.Response.Write(r.GetQueryUint("uint"))
|
||||
}
|
||||
if r.GetQuery("string") != nil {
|
||||
r.Response.Write(r.GetQueryString("string"))
|
||||
}
|
||||
})
|
||||
s.BindHandler("/post", func(r *ghttp.Request){
|
||||
if r.GetPost("slice") != nil {
|
||||
r.Response.Write(r.GetPost("slice"))
|
||||
}
|
||||
if r.GetPost("bool") != nil {
|
||||
r.Response.Write(r.GetPostBool("bool"))
|
||||
}
|
||||
if r.GetPost("float32") != nil {
|
||||
r.Response.Write(r.GetPostFloat32("float32"))
|
||||
}
|
||||
if r.GetPost("float64") != nil {
|
||||
r.Response.Write(r.GetPostFloat64("float64"))
|
||||
}
|
||||
if r.GetPost("int") != nil {
|
||||
r.Response.Write(r.GetPostInt("int"))
|
||||
}
|
||||
if r.GetPost("uint") != nil {
|
||||
r.Response.Write(r.GetPostUint("uint"))
|
||||
}
|
||||
if r.GetPost("string") != nil {
|
||||
r.Response.Write(r.GetPostString("string"))
|
||||
}
|
||||
})
|
||||
s.BindHandler("/map", func(r *ghttp.Request){
|
||||
if m := r.GetQueryMap(); len(m) > 0 {
|
||||
r.Response.Write(m["name"])
|
||||
}
|
||||
if m := r.GetPostMap(); len(m) > 0 {
|
||||
r.Response.Write(m["name"])
|
||||
}
|
||||
})
|
||||
s.BindHandler("/raw", func(r *ghttp.Request){
|
||||
r.Response.Write(r.GetRaw())
|
||||
})
|
||||
s.BindHandler("/json", func(r *ghttp.Request){
|
||||
r.Response.Write(r.GetJson().Get("name"))
|
||||
})
|
||||
s.BindHandler("/struct", func(r *ghttp.Request){
|
||||
if m := r.GetQueryMap(); len(m) > 0 {
|
||||
user := new(User)
|
||||
r.GetQueryToStruct(user)
|
||||
r.Response.Write(user.Id, user.Name, user.Pass1, user.Pass2)
|
||||
}
|
||||
if m := r.GetPostMap(); len(m) > 0 {
|
||||
user := new(User)
|
||||
r.GetPostToStruct(user)
|
||||
r.Response.Write(user.Id, user.Name, user.Pass1, user.Pass2)
|
||||
}
|
||||
})
|
||||
s.SetPort(8400)
|
||||
s.SetDumpRouteMap(false)
|
||||
go s.Run()
|
||||
defer func() {
|
||||
s.Shutdown()
|
||||
time.Sleep(time.Second)
|
||||
}()
|
||||
// 等待启动完成
|
||||
time.Sleep(time.Second)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix("http://127.0.0.1:8400")
|
||||
// GET
|
||||
gtest.Assert(client.GetContent("/get", "slice=1&slice=2"), `["1","2"]`)
|
||||
gtest.Assert(client.GetContent("/get", "bool=1"), `true`)
|
||||
gtest.Assert(client.GetContent("/get", "bool=0"), `false`)
|
||||
gtest.Assert(client.GetContent("/get", "float32=0.11"), `0.11`)
|
||||
gtest.Assert(client.GetContent("/get", "float64=0.22"), `0.22`)
|
||||
gtest.Assert(client.GetContent("/get", "int=-10000"), `-10000`)
|
||||
gtest.Assert(client.GetContent("/get", "int=10000"), `10000`)
|
||||
gtest.Assert(client.GetContent("/get", "uint=-10000"), `10000`)
|
||||
gtest.Assert(client.GetContent("/get", "uint=9"), `9`)
|
||||
gtest.Assert(client.GetContent("/get", "string=key"), `key`)
|
||||
|
||||
// POST
|
||||
gtest.Assert(client.PostContent("/post", "slice=1&slice=2"), `["1","2"]`)
|
||||
gtest.Assert(client.PostContent("/post", "bool=1"), `true`)
|
||||
gtest.Assert(client.PostContent("/post", "bool=0"), `false`)
|
||||
gtest.Assert(client.PostContent("/post", "float32=0.11"), `0.11`)
|
||||
gtest.Assert(client.PostContent("/post", "float64=0.22"), `0.22`)
|
||||
gtest.Assert(client.PostContent("/post", "int=-10000"), `-10000`)
|
||||
gtest.Assert(client.PostContent("/post", "int=10000"), `10000`)
|
||||
gtest.Assert(client.PostContent("/post", "uint=-10000"), `10000`)
|
||||
gtest.Assert(client.PostContent("/post", "uint=9"), `9`)
|
||||
gtest.Assert(client.PostContent("/post", "string=key"), `key`)
|
||||
|
||||
// Map
|
||||
gtest.Assert(client.GetContent ("/map", "id=1&name=john"), `john`)
|
||||
gtest.Assert(client.PostContent("/map", "id=1&name=john"), `john`)
|
||||
|
||||
// Raw
|
||||
gtest.Assert(client.PutContent("/raw", "id=1&name=john"), `id=1&name=john`)
|
||||
|
||||
// Json
|
||||
gtest.Assert(client.PostContent("/json", `{"id":1, "name":"john"}`), `john`)
|
||||
|
||||
// Struct
|
||||
gtest.Assert(client.GetContent("/struct", `id=1&name=john&password1=123&password2=456`), `1john123456`)
|
||||
gtest.Assert(client.PostContent("/struct", `id=1&name=john&password1=123&password2=456`), `1john123456`)
|
||||
})
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user