Compare commits

..

82 Commits

Author SHA1 Message Date
1dc6c799e1 version updates 2019-02-01 17:48:48 +08:00
ae1e075696 fix issue in binSearch of garray 2019-02-01 17:44:58 +08:00
104613b056 VERSION up 2019-01-31 13:58:35 +08:00
99577ad874 hot fix gfcache issue 2019-01-31 13:55:53 +08:00
f4773ef1e4 add func ghttp.Group.Common for common http methods(GET/PUT/POST/DELETE) binding; fix issue #IRHB3,#IRHSJ; add more unit test cases for gvalid 2019-01-29 23:01:14 +08:00
0d315218dd README updates 2019-01-25 12:48:22 +08:00
ec130d0763 RELEASE updates 2019-01-23 21:30:02 +08:00
30729e3f93 README updates 2019-01-23 18:34:52 +08:00
241d7402cc README updates 2019-01-23 18:30:04 +08:00
3b14aba1a2 README updates 2019-01-23 17:33:51 +08:00
b02205f7cd gpool updates 2019-01-23 17:27:30 +08:00
c27bc0023f Merge branch 'master' of https://gitee.com/johng/gf 2019-01-23 15:01:29 +08:00
9698a7c5be gpool updates 2019-01-23 15:01:21 +08:00
071e2f8bb4 !17 修复where为空时不是1=1的bug
Merge pull request !17 from 张金富/master
2019-01-23 13:46:41 +08:00
726d3f7024 update unit tests of gtimer 2019-01-23 13:36:39 +08:00
3503aa43b4 gtimer, gmlock updates 2019-01-23 13:30:46 +08:00
e865b46304 add console and env values to change the default values of gtimer 2019-01-23 13:01:58 +08:00
494f96495e fulfil unit test cases of gtimer 2019-01-23 11:28:57 +08:00
7ed2081513 gcron, gtimer updates 2019-01-22 22:07:46 +08:00
5110313657 gcron, gtimer updates 2019-01-22 13:50:10 +08:00
24990e26c8 gcron, gtimer updates 2019-01-21 22:09:51 +08:00
3ca086bcec 修复where为空时不是1=1的bug 2019-01-21 16:26:47 +08:00
7d103c4ee8 gcron updates 2019-01-18 22:02:17 +08:00
5fed6f5681 update unit test case of gmlock 2019-01-18 15:14:05 +08:00
616539ecb0 update unit test cases of gmlock, concurrent safe reason 2019-01-18 15:03:45 +08:00
9e99e88d27 gmlock updates, add more unit test cases for gmlock 2019-01-18 11:30:52 +08:00
0e39400dd0 johng-cn/gf -> gogf/gf 2019-01-17 22:20:37 +08:00
2ba796de01 gconv examples update 2019-01-17 20:32:02 +08:00
efe2535977 README updates 2019-01-17 20:04:17 +08:00
c17352b8af travis updates 2019-01-17 16:37:26 +08:00
b1fc3ff17a travis updates 2019-01-17 16:06:56 +08:00
485dafb616 update grand.MeetProb, change param type from float64 to float32 2019-01-17 14:20:18 +08:00
bf25a3a601 gtimer updates; grand updates, change buffer slice type from uint64 to uint32 2019-01-17 14:15:23 +08:00
14fcd0b2f9 move gtime.SetTimeout&gtime.SetInterval to gtimer 2019-01-16 23:23:53 +08:00
cb24714faa gcron updates 2019-01-16 22:59:26 +08:00
36199334f0 gcron updates 2019-01-16 22:34:22 +08:00
72c7e65dfa gcron updates 2019-01-16 21:06:35 +08:00
a4ad301b44 fulfil logics of Exit* funcs of ghttp 2019-01-16 20:27:58 +08:00
72569321fa package comments update 2019-01-16 13:35:16 +08:00
1600a80124 comment updates 2019-01-16 13:02:59 +08:00
80c1a02377 pakage comments update 2019-01-16 09:00:23 +08:00
0c41909454 pakage comments updates 2019-01-15 23:27:47 +08:00
2b5d889bb9 fulfil unit test cases of gconv 2019-01-15 21:54:34 +08:00
2c2a71d429 fix issue in concurrent safe handling of gregex 2019-01-15 20:20:34 +08:00
f900414e38 add surport for field type auto detection in gform 2019-01-14 22:55:43 +08:00
1c72766c34 add new func Meet/MeetProb and alias N/Str/Digits/Letters for grand 2019-01-14 20:12:44 +08:00
302f3c1467 optimized package gconv, supported struct attr with ptr 2019-01-14 13:55:07 +08:00
9415419324 fix issue in gconv.Struct when given atrr of struct is nil time.Time 2019-01-14 09:00:31 +08:00
602592a354 TODO-- 2019-01-13 17:02:32 +08:00
1a4cba5fa5 VERSION updates 2019-01-13 00:47:23 +08:00
018853e976 optimize WebServer in hook priority handler and request exit mechanism 2019-01-13 00:43:36 +08:00
651bd33b73 add new 16x and 19x phone number validation for gvalid 2019-01-12 23:45:45 +08:00
f4644ce685 change func(xxx, safe...bool) to func(xxx, unsafe...bool); add new internal package mutex 2019-01-12 23:36:22 +08:00
0a422e9a89 rename go test funcs for gtimer 2019-01-12 22:47:07 +08:00
432c16c89f complete pckage gtimer development, a levelled timing wheel for interval/delayed jobs running and management 2019-01-12 22:41:12 +08:00
241706cbbf up 2019-01-12 20:20:30 +08:00
33dd0f9922 timing wheel up 2019-01-11 13:46:40 +08:00
ed8bb354e5 up 2019-01-09 18:47:11 +08:00
e373392f64 up 2019-01-09 13:26:59 +08:00
292fd2f39e up 2019-01-09 13:06:15 +08:00
1c9cb8286f up 2019-01-09 12:54:37 +08:00
8296061b64 up 2019-01-06 16:43:42 +08:00
bb5d84c29c up 2019-01-06 11:09:50 +08:00
eae857bcf7 merge master 2019-01-04 18:39:40 +08:00
40b5162fdf up 2019-01-04 15:32:16 +08:00
7934ad6904 Merge branch 'develop' of https://gitee.com/johng/gf into develop 2019-01-03 19:12:08 +08:00
858b010caa up 2019-01-03 19:11:54 +08:00
37cd2351e2 !16 增加ORACLE,SQL SERVER中获取指定表的所有字段名的方法
Merge pull request !16 from 蚊子/master
2019-01-02 21:11:30 +08:00
be1e250a6b 增加ORACLE,SQL SERVER中获取指定表的所有字段名的方法 2019-01-02 18:17:01 +08:00
f86896e5af Merge branch 'master' into develop 2019-01-02 12:42:36 +08:00
a95b1f0dae add more unit test cases 2019-01-02 10:18:00 +08:00
f1c7b95b33 up 2019-01-01 19:43:31 +08:00
e4a7e23c46 VERSION updates 2018-12-31 17:56:06 +08:00
6f15adf57f unit test cases++ 2018-12-31 17:46:04 +08:00
1966b40d01 ghttp.Client updates; add more unit test cases for web server 2018-12-31 00:50:55 +08:00
24ce4d098e ghttp.Client updates; add more unit test cases for web server 2018-12-31 00:22:18 +08:00
98619f9bc9 add more unit test cases for web server 2018-12-30 22:02:46 +08:00
1efeb2515d refract package 'gcron'; add package 'gtimew' for Time Wheel style job management 2018-12-30 18:56:21 +08:00
ccf837b2bf up 2018-12-30 14:53:16 +08:00
e558863743 up 2018-12-30 11:08:07 +08:00
43f21dfe92 Merge branch 'master' into qiangg_unit_test1 2018-12-29 13:07:32 +08:00
f5b2556b70 up 2018-12-28 21:44:36 +08:00
240 changed files with 7777 additions and 11357 deletions

View File

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

View File

@ -1,17 +1,17 @@
# GoFrame
<img align="right" height="150px" src="https://gfer.me/cover.png">
[![Go Doc](https://godoc.org/github.com/johng-cn/gf?status.svg)](https://godoc.org/github.com/johng-cn/gf)
[![Build Status](https://travis-ci.org/johng-cn/gf.svg?branch=master)](https://travis-ci.org/johng-cn/gf)
[![Go Report](https://goreportcard.com/badge/github.com/johng-cn/gf)](https://goreportcard.com/report/github.com/johng-cn/gf)
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf)
[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf)](https://goreportcard.com/report/github.com/gogf/gf)
[![Documents](https://img.shields.io/badge/docs-100%25-green.svg)](https://gfer.me)
[![License](https://img.shields.io/github/license/johng-cn/gf.svg?style=flat)](https://github.com/johng-cn/gf)
[![Language](https://img.shields.io/badge/language-go-blue.svg)](https://github.com/johng-cn/gf)
[![Release](https://img.shields.io/github/release/johng-cn/gf.svg?style=flat)](https://github.com/johng-cn/gf/releases)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
[![Language](https://img.shields.io/badge/language-go-blue.svg)](https://github.com/gogf/gf)
[![Release](https://img.shields.io/github/release/gogf/gf.svg?style=flat)](https://github.com/gogf/gf/releases)
<!--
[![Code Coverage](https://codecov.io/gh/johng-cn/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/johng-cn/gf)
[![Code Helper](https://www.codetriage.com/johng-cn/gf/badges/users.svg)](https://www.codetriage.com/johng-cn/gf)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf)
[![Code Helper](https://www.codetriage.com/gogf/gf/badges/users.svg)](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>

View File

@ -1,17 +1,17 @@
# GoFrame
<img align="right" height="150px" src="https://gfer.me/cover.png">
[![Go Doc](https://godoc.org/github.com/johng-cn/gf?status.svg)](https://godoc.org/github.com/johng-cn/gf)
[![Build Status](https://travis-ci.org/johng-cn/gf.svg?branch=master)](https://travis-ci.org/johng-cn/gf)
[![Go Report](https://goreportcard.com/badge/github.com/johng-cn/gf)](https://goreportcard.com/report/github.com/johng-cn/gf)
[![Go Doc](https://godoc.org/github.com/gogf/gf?status.svg)](https://godoc.org/github.com/gogf/gf)
[![Build Status](https://travis-ci.org/gogf/gf.svg?branch=master)](https://travis-ci.org/gogf/gf)
[![Go Report](https://goreportcard.com/badge/github.com/gogf/gf)](https://goreportcard.com/report/github.com/gogf/gf)
[![Documents](https://img.shields.io/badge/docs-100%25-green.svg)](https://gfer.me)
[![License](https://img.shields.io/github/license/johng-cn/gf.svg?style=flat)](https://github.com/johng-cn/gf)
[![Language](https://img.shields.io/badge/language-go-blue.svg)](https://github.com/johng-cn/gf)
[![Release](https://img.shields.io/github/release/johng-cn/gf.svg?style=flat)](https://github.com/johng-cn/gf/releases)
[![License](https://img.shields.io/github/license/gogf/gf.svg?style=flat)](https://github.com/gogf/gf)
[![Language](https://img.shields.io/badge/language-go-blue.svg)](https://github.com/gogf/gf)
[![Release](https://img.shields.io/github/release/gogf/gf.svg?style=flat)](https://github.com/gogf/gf/releases)
<!--
[![Code Coverage](https://codecov.io/gh/johng-cn/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/johng-cn/gf)
[![Code Helper](https://www.codetriage.com/johng-cn/gf/badges/users.svg)](https://www.codetriage.com/johng-cn/gf)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf)
[![Code Helper](https://www.codetriage.com/gogf/gf/badges/users.svg)](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>

View File

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

@ -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实现修正设计BUGhttps://github.com/johng-cn/gf/issues/6
24. 对grpool进行优化改进包括属性原子操作封装采用gtype实现修正设计BUGhttps://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是按照优先级执行的需要增加覆盖特性

View File

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

View File

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

View File

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

View File

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

View File

@ -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 < v20: v1 == v21: 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
}
}

View File

@ -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 < v20: v1 == v21: 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
}
}

View File

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

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

View File

@ -5,6 +5,7 @@
// You can obtain one at https://gitee.com/johng/gf.
// Package gchan provides graceful operations for channel.
//
// 优雅的Channel操作.
package gchan

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ import (
"sync"
)
var pool = New(99999999)
var pool = New(99999999, nil)
var syncp = sync.Pool{}
func BenchmarkGPoolPut(b *testing.B) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
}
*/

View File

@ -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)
}
// 数据库查询,获取查询结果记录,以关联数组结构返回

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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情况下)

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View 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