Compare commits

...

205 Commits

Author SHA1 Message Date
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
d604d198ab hot fix 2019-01-04 15:43:56 +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
36791d2f48 README updates 2019-01-02 21:43:17 +08:00
08f9cffed9 TODO++ 2019-01-02 21:38:26 +08:00
783c0ba846 updates kafka dependences 2019-01-02 21:35:48 +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
7ad4f61564 revert hot fix codes, waiting for next release to fix 2019-01-02 11:41:22 +08:00
adf06a2b0d thirdparty package kafka updated to date 2019-01-02 11:02:03 +08:00
d6aa2b2512 hot fix for gcache 2019-01-02 10:30:27 +08:00
a95b1f0dae add more unit test cases 2019-01-02 10:18:00 +08:00
0a8af94610 !15 打开文件没关闭
Merge pull request !15 from hello/master
2019-01-01 19:45:46 +08:00
f1c7b95b33 up 2019-01-01 19:43:31 +08:00
wgb
2c27c0f58a close file 2019-01-01 15:35:25 +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
4172eae87e update default ConnMaxLifeTime to 30 seconds in gdb package 2018-12-28 22:02:21 +08:00
26f2c61068 update default ConnMaxLifeTime to 30 seconds in gdb package 2018-12-28 22:00:49 +08:00
f97bed2607 update default ConnMaxLifeTime to 10 seconds in gdb package 2018-12-28 21:56:27 +08:00
8ef7155c70 hot fix 2018-12-28 21:46:01 +08:00
f5b2556b70 up 2018-12-28 21:44:36 +08:00
2c6e8f88fb README updates, TODO++ 2018-12-27 20:47:13 +08:00
25068b1e83 README updates 2018-12-27 13:27:57 +08:00
1f36eb3a9a README updates 2018-12-27 13:27:04 +08:00
a9ed577d05 README updates 2018-12-27 09:59:56 +08:00
782d614082 README updates 2018-12-27 09:57:54 +08:00
0629c00b07 README updates 2018-12-27 09:49:50 +08:00
b90d5bb205 README updates 2018-12-27 09:46:53 +08:00
cbc824c80a README updates 2018-12-27 09:46:35 +08:00
0c9be40b86 README updates 2018-12-27 09:46:18 +08:00
c96abd706d README updates 2018-12-27 09:45:04 +08:00
0ae5872783 README updates 2018-12-27 09:22:07 +08:00
2cff10e0d2 fix issue in controller interface definition 2018-12-26 10:17:24 +08:00
cab78f557d fix issue in controller detection for object parameter, in router group of web server 2018-12-25 23:20:43 +08:00
04353aa1a5 RELEASE updates 2018-12-25 13:54:36 +08:00
35121a66e9 README updates 2018-12-22 21:50:47 +08:00
e726ed2c19 gdb.Model updates 2018-12-22 21:03:03 +08:00
503446afc7 fix issue of ghttp.Request.GetVar 2018-12-22 11:52:12 +08:00
2063f662d3 fix silly issue in binary search of garray package, and add unit-test file for garray 2018-12-20 21:55:05 +08:00
d7381399aa fix issue of grand.intn in x86 arch; add router group feature for WebServer 2018-12-20 21:04:43 +08:00
d05b497cdb Merge branch 'master' into qiangg_router_group 2018-12-19 18:58:47 +08:00
ef919be587 g.DB can use gdb's configurations, not to force using config.toml 2018-12-19 18:35:44 +08:00
fff31e0f4f add Charset support for mysql of gdb package; fix issue for glog for log writing failed when the folder path wa deleted 2018-12-19 18:15:22 +08:00
cdd6fc7c1e extend pid length from 16bit to 24bit in process communication of gproc package 2018-12-19 16:17:54 +08:00
74bc36a2dc remove gfile.MainPkgPath check in gcfg/gview default path 2018-12-19 14:51:09 +08:00
48328ae52c router group developing 2018-12-19 14:45:39 +08:00
a86f4f8e23 disable auto adding temp directory to gview/gcfg search path; disable backtrace feature in normal log print with glog; fix issue caused by fmt.Fprintf in gfsnotify 2018-12-18 20:03:23 +08:00
0a1e048268 add Model.Clone support for gdb package 2018-12-18 10:10:14 +08:00
6fc5efd6ba README updates 2018-12-17 20:51:49 +08:00
2d795b593d README updates 2018-12-17 20:44:38 +08:00
20628ec75c README updates 2018-12-17 19:50:35 +08:00
10d1ccb009 README updates 2018-12-17 19:41:08 +08:00
fcc37c9581 CI updates 2018-12-17 19:36:34 +08:00
43cd391543 CI updates 2018-12-17 19:29:03 +08:00
18d2df33f7 CI updates 2018-12-17 19:26:59 +08:00
a85daa5617 CI updates 2018-12-17 18:35:29 +08:00
48dc4ce3e2 travis updates 2018-12-17 14:01:43 +08:00
d07bac89a0 travis updates 2018-12-17 13:59:00 +08:00
5d32ad6bc4 travis updates 2018-12-17 13:57:15 +08:00
397b0a3e7e travis updates 2018-12-17 13:50:17 +08:00
259961632d travis updates 2018-12-17 13:46:06 +08:00
cb1d6382ec travis updates 2018-12-17 13:38:35 +08:00
8714a69a13 travis updates 2018-12-17 13:36:38 +08:00
3ae0ea2de7 travis updates 2018-12-17 13:32:35 +08:00
1879a9f4c7 README updates 2018-12-17 13:28:19 +08:00
3938717b04 travis updates 2018-12-17 13:24:53 +08:00
1208b688f1 add code helper 2018-12-17 13:08:32 +08:00
0ad7ee5a32 add code helper 2018-12-17 13:07:01 +08:00
7a4e68e6b9 add code helper 2018-12-17 13:06:58 +08:00
71222b247f add travis/goreport/golint/govet 2018-12-17 13:02:55 +08:00
95db811943 add travis/goreport/golint/govet 2018-12-17 13:02:18 +08:00
2dbc817132 VERSION updates 2018-12-17 11:24:58 +08:00
7a8bd96edc gdb: add support for slice argument in where statement 2018-12-17 10:52:44 +08:00
c5e9686a95 gdb updates, make priority=1 when no priority set 2018-12-16 23:11:15 +08:00
c914edf616 gdb comment updates 2018-12-16 22:27:04 +08:00
656bfcb6bd Merge branch 'qiangg_db2' into develop 2018-12-16 22:22:33 +08:00
7434dfe6fa done refacting gdb package 2018-12-16 22:22:07 +08:00
e67aa63a50 refract gdb package, add complete unit test cases, almost there 2018-12-15 15:50:39 +08:00
06fc786416 fix issue of gcache 2018-12-14 18:38:29 +08:00
d5e46f2b42 refracting gdb package 2018-12-14 18:35:51 +08:00
c003a92408 remove HANGUP signal handle of Web Server admin 2018-12-14 10:11:05 +08:00
09e6f10b60 new version of gdb developing 2018-12-14 10:09:45 +08:00
c961c22cd7 update gview: show empty string when the variable does not exist 2018-12-13 18:24:22 +08:00
105a821069 fix issue: database connection pool does not work expectly 2018-12-12 20:01:10 +08:00
670993f769 remove auto-adding main package path to search path of WebServer in develop environment 2018-12-11 19:30:38 +08:00
60a571f291 Fix issue: add mutex lock for method Remove of garray. 2018-12-11 09:56:58 +08:00
20a0cb2cd9 Merge branch 'master' of https://gitee.com/johng/gf 2018-12-10 20:24:24 +08:00
51e70be04d Add rewrite feature to WebServer; Add Unique for un sorted array of garray; Fix map conversion issue of gjson. 2018-12-10 20:24:20 +08:00
ac65b808c6 !14 修复mssql的OPEN方法BUG
Merge pull request !14 from 蚊子/master
2018-12-10 18:43:56 +08:00
eb9ddf3c47 修改mssql的open方法连接串 2018-12-10 18:14:51 +08:00
80993e9f77 Fix issue of gjson package in map conversion 2018-12-09 22:30:10 +08:00
b7a6d257d5 Fix binary decoding issue of gbinary package. 2018-12-08 18:18:05 +08:00
7022486e93 Package gview: build-in function 'date' supports printing current datetime. 2018-12-05 15:52:38 +08:00
83f5a9d34e Package gview: build-in function 'date' supports printing current datetime. 2018-12-05 15:48:15 +08:00
431e1051b8 Fix gdb.Model.Count method: Count with no fields checking 2018-12-05 13:52:34 +08:00
f8ab5c3842 VERSION updates 2018-12-04 20:29:32 +08:00
101d095f45 GF greets you. 2018-12-04 20:28:06 +08:00
8481de2b47 Add method for gcache package, to show all map datas of the cache. 2018-12-04 20:23:48 +08:00
c973f133de hot fix 2018-12-04 19:50:24 +08:00
d23cdcbe57 改进gcache,完善功能及基准测试;改进gconv.Map对struct转换时默认使用json tag作为键名 2018-12-04 19:26:46 +08:00
9a52175bd6 gredis增加GetConn方法获取原生redis连接对象 2018-12-01 11:28:47 +08:00
4275218841 README updates 2018-11-30 20:43:08 +08:00
663a2c2a16 README updates 2018-11-30 20:40:23 +08:00
da58a60ad5 README updates 2018-11-30 20:38:53 +08:00
2c26063f4b README updates 2018-11-30 20:37:28 +08:00
b19e47783b ghttp增加静态文件目录映射功能;改进gspath目录管理功能;改进gconv的slice转换功能,并增加gconv.Map方法 2018-11-30 09:48:57 +08:00
aee266eea0 WebServer改进 2018-11-28 20:19:28 +08:00
8304769953 代码修正 2018-11-27 20:37:57 +08:00
914a74bca9 完善代码示例 2018-11-26 09:33:45 +08:00
b965dbff70 gvar调用端改进,去掉不必要的并发安全参数;错误提示细节改进 2018-11-25 22:18:36 +08:00
9f9bcd2467 RELEASE updates 2018-11-24 17:21:30 +08:00
b3353afe3c WebServer添加RouterCacheExpire配置参数 2018-11-24 11:55:57 +08:00
4e3081afee ORM增加mysql datetime参数写入示例 2018-11-24 09:42:21 +08:00
578a6a2df3 改进随机数生成缓冲区 2018-11-24 08:43:39 +08:00
aa42ddd3f1 改进随机数生成缓冲区 2018-11-23 21:39:05 +08:00
69738c337f VERSION updates 2018-11-23 16:50:58 +08:00
12f099fd54 VERSION updates, TODO++ 2018-11-23 16:47:03 +08:00
38932f306d 改进gspath缓存数据结构 2018-11-23 16:45:30 +08:00
5e7e1077a1 性能改进 2018-11-23 09:20:45 +08:00
8f85311332 完善获取数据库配置失败时的错误提示 2018-11-21 00:02:29 +08:00
6eb2887a5a README updates 2018-11-20 23:26:58 +08:00
54f4fd3101 README updates 2018-11-20 10:15:23 +08:00
64a22acf84 WebServer允许同一HOOK事件被多次绑定注册,先注册的回调函数优先级更高 2018-11-19 23:13:12 +08:00
4e5877923d dev 2018-11-19 21:49:43 +08:00
ceaa1a4dd1 Merge branch 'develop' of https://gitee.com/johng/gf into develop 2018-11-19 21:12:21 +08:00
9e1ad46c90 改进ghttp.Request,增加SetParam/GetParam请求流程自定义变量方法;gvar模块增加VarRead只读接口 2018-11-19 21:11:43 +08:00
d5a3fefd8b !13 ORM新增对MSSQL的支持
Merge pull request !13 from 蚊子/master
2018-11-19 11:48:18 +08:00
d85332aca1 ORM新增对MSSQL的支持 2018-11-19 11:38:57 +08:00
10c3f6d85a 完善程序细节和测试 2018-11-18 22:22:44 +08:00
ea4764f1f9 当前工作目录为系统临时目录时,gcfg/gview/ghttp模块默认不添加工作目录到搜索路径 2018-11-18 19:45:04 +08:00
fe753b0bc8 当前工作目录为系统临时目录时,gcfg/gview/ghttp模块默认不添加工作目录到搜索路径 2018-11-18 19:37:42 +08:00
04608269fe 修复gspath模块是windows下搜索失效问题 2018-11-18 19:14:17 +08:00
6addd64cf0 glog模块日志前缀输出改进 2018-11-17 22:12:41 +08:00
520 changed files with 76626 additions and 14994 deletions

1
.gitignore vendored
View File

@ -16,3 +16,4 @@ cbuild
**/.DS_Store
.vscode/
go.sum

35
.travis.yml Normal file
View File

@ -0,0 +1,35 @@
language: go
go:
- "1.11.x"
branches:
only:
- master
- develop
env:
- GITEE_GF=$GOPATH/src/gitee.com/johng/gf GO111MODULE=on
services:
- mysql
before_install:
- pwd
install:
- pwd
- mkdir -p $GITEE_GF
- cp * $GITEE_GF -R
- cd $GITEE_GF/g
script:
- 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

@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

223
README.MD
View File

@ -1,49 +1,46 @@
<div align=center>
<img src="http://cover.kancloud.cn/johng/gf" width="150"/>
</div>
# GoFrame
<img align="right" height="150px" src="https://gfer.me/cover.png">
[![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/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)
GF(Go Frame)是一款模块化、松耦合、轻量级、高性能的Go语言Web开发框架。支持热重启、热更新、多域名、多端口、多服务、HTTP/HTTPS、动态路由等特性
并提供了Web服务开发的系列核心组件Router、Cookie、Session、服务注册、配置管理、模板引擎、数据校验、分页管理、数据库ORM等等等等
并且提供了数十个实用开发模块集缓存、日志、时间、命令行、二进制、文件锁、对象池、连接池、数据编码、进程管理、进程通信、TCP/UDP组件、
并发安全容器、Goroutine池等等等等等等。
<!--
[![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)
-->
开源项目地址(仓库保持实时同步)
[Gitee](https://gitee.com/johng/gf)[Github](https://github.com/johng-cn/gf)。
使用中有任何问题/建议欢迎加入技术QQ群交流**116707870**。
如有优秀的框架使用案例,欢迎联系作者将地址展示到项目库中,您的牛逼将被世人所瞻仰。
`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.
# 安装
```html
# Installation
```
go get -u gitee.com/johng/gf
```
# 限制
```shell
golang版本 >= 1.9.2
or use `go.mod`
```
require gitee.com/johng/gf latest
```
# 特点
1. 轻量级、高性能,模块化、松耦合设计,丰富的开发模块;
1. 热重启、热更新特性并支持Web界面及命令行管理接口
1. 专业的技术交流群,完善的开发文档及示例代码,良好的中文化支持;
1. 支持多种形式的服务注册特性,灵活高效的路由控制管理;
1. 支持服务事件回调注册功能可供选择的pprof性能分析模块
1. 支持配置文件及模板文件的自动检测更新机制,即修改即生效;
1. 支持自定义日期时间格式的时间模块类似PHP日期时间格式化
1. 强大的数据/表单校验模块支持常用的40种及自定义校验规则
1. 强大的网络通信TCP/UDP组件并提供TCP连接池特性简便高效
1. 提供了对基本数据类型的并发安全封装,提供了常用的数据结构容器;
1. 支持Go变量/Json/Xml/Yml/Toml任意数据格式之间的相互转换及创建
1. 强大的数据库ORM支持应用层级的集群管理、读写分离、负载均衡查询缓存、方法及链式ORM操作
1. 更多特点请查阅框架[手册](https://gfer.me)和[源码](https://godoc.org/github.com/johng-cn/gf)
# Limitation
```
golang version >= 1.9.2
```
# 文档
GoFrame开发文档[gfer.me](https://gfer.me)
# Documentation
* [中文文档](https://goframe.org)
# Architecture
<div align=center>
<img src="https://gfer.me/images/arch.png"/>
</div>
# Quick Start
# 使用
## Hello World
```go
package main
@ -60,122 +57,50 @@ func main() {
s.Run()
}
```
## 多域名支持
```go
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.Domain("localhost1,localhost2").BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("localhostx")
})
s.Run()
}
```
## 多端口支持
```go
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Writeln("go frame!")
})
s.SetPort(8080, 8081, 8082)
s.Run()
}
```
## 路由控制
```go
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/order/:action/{page}.html", func(r *ghttp.Request){
r.Response.Writef("action:%s, page:%s", r.Get("action"), r.Get("page"))
})
s.SetPort(8199)
s.Run()
}
```
## 数据库ORM
### ORM创建/关闭
```go
// 获取默认配置的单例数据库对象(配置名称为"default")
db, err := gdb.DB()
// 获取配置分组名称为"user-center"的单例数据库对象
db, err := gdb.DB("user-center")
// 无须显示Close数据库引擎底层采用了链接池设计当链接不再使用时会自动关闭
```
### 单表/联表查询
```go
// 查询多条记录并使用Limit分页
r, err := db.Table("user").Where("u.uid > ?", 1).Limit(0, 10).Select()
// 查询符合条件的单条记录(第一条)
r, err := db.Table("user u").LeftJoin("user_detail ud", "u.uid=ud.uid").Fields("u.*,ud.site").Where("u.uid=?", 1).One()
// 查询指定字段值
r, err := db.Table("user u").RightJoin("user_detail ud", "u.uid=ud.uid").Fields("ud.site").Where("u.uid=?", 1).Value()
// 分组及排序
r, err := db.Table("user u").InnerJoin("user_detail ud", "u.uid=ud.uid").Fields("u.*,ud.city").GroupBy("city").OrderBy("register_time asc").Select()
// 不使用john的联表查询
r, err := db.Table("user u,user_detail ud").Where("u.uid=ud.uid").Fields("u.*,ud.city").All()
// 不使用Fields方法指定查询字段时默认查询为"*"
r, err := db.Table("user").Where("u.uid=1",).One()
```
### 更新/删除
```go
// 更新
r, err := db.Table("user").Data(gdb.Map{"name" : "john2"}).Where("name=?", "john").Update()
r, err := db.Table("user").Data("name='john3'").Where("name=?", "john2").Update()
// 删除
r, err := db.Table("user").Where("uid=?", 10).Delete()
// Data数值方法的参数形式比较灵活
r, err := db.Table("user").Data(`name="john"`).Update()
r, err := db.Table("user").Data("name", "john").Update()
r, err := db.Table("user").Data(g.Map{"name" : "john"}).Update()
```
### 写入/保存
```go
r, err := db.Table("user").Data(gdb.Map{"name": "john"}).Insert()
r, err := db.Table("user").Data(gdb.Map{"uid": 10000, "name": "john"}).Replace()
r, err := db.Table("user").Data(gdb.Map{"uid": 10001, "name": "john"}).Save()
```
### 事务操作
```go
if tx, err := db.Begin(); err == nil {
if r, err := tx.Table("user").Data(gdb.Map{"uid":1, "name": "john"}).Save(); err == nil {
tx.Commit()
} else {
tx.Rollback()
}
fmt.Println(r, err)
}
```
[View More..](https://gfer.me/start/index)
# License
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
# Contributors(TOP 10)
<a href="https://gitee.com/johng" target="_blank" title="John"><img src="https://gitee.com/uploads/27/1309327_johng.png?1530630243" width="60" align="left"></a>
<a href="https://gitee.com/wenzi1" target="_blank" title="蚊子"><img src="https://images.gitee.com/uploads/22/1923122_wenzi1.png" width="60" align="left"></a>
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://gfer.me/images/contributors/zseeker.png" width="60" align="left"></a>
<a href="https://gitee.com/ymrjqyy" target="_blank" title="一墨染尽青衣颜"><img src="https://images.gitee.com/uploads/27/876827_ymrjqyy.png" width="60" align="left"></a>
<a href="https://github.com/chenyang351" target="_blank" title="chenyang351"><img src="https://avatars1.githubusercontent.com/u/30063958?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/wxkj" target="_blank" title="wxkj"><img src="https://gitee.com/uploads/56/91356_wxkj.png" width="60" align="left"></a>
<a href="https://github.com/wxkj001" target="_blank" title="3wxkj001
"><img src="https://avatars0.githubusercontent.com/u/7794279?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/zhangjinfu" target="_blank" title="张金富"><img src="https://images.gitee.com/uploads/63/356163_zhangjinfu.png" width="60" align="left"></a>
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://gfer.me/images/contributors/garfieldkwong.png" width="60" align="left"></a>
<a href="https://gitee.com/qq1054000800" target="_blank" title="hello"><img src="https://gitee.com/uploads/9/2209_qq1054000800.jpg" width="60" align="left"></a>
<br /><br /><br />
# 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>
<a href="https://gitee.com/mg91" target="_blank" title="mg91"><img src="https://images.gitee.com/uploads/30/1410930_mg91.png" width="60" align="left"></a>
...
更多特性及示例请查看官方开发文档:[gfer.me](https://gfer.me)

109
README_ZH.MD Normal file
View File

@ -0,0 +1,109 @@
# GoFrame
<img align="right" height="150px" src="https://gfer.me/cover.png">
[![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/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/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、动态路由等特性
并提供了Web服务开发的系列核心组件Router、Cookie、Session、服务注册、配置管理、模板引擎、数据校验、分页管理、数据库ORM等等等等
并且提供了数十个内置核心开发模块集缓存、日志、时间、命令行、二进制、文件锁、内存锁、对象池、连接池、数据编码、进程管理、进程通信、文件监控、定时任务、TCP/UDP组件、
并发安全容器等等等等等等。
# 特点
* 模块化、松耦合设计;
* 丰富实用的开发模块;
* 详尽的开发文档及示例;
* 完善的本地中文化支持;
* 致力于项目的通用方案;
* 更适合企业及团队使用;
* 更多请查阅文档及源码;
# 安装
```html
go get -u gitee.com/johng/gf
```
或者
`go.mod`
```
require gitee.com/johng/gf latest
```
# 限制
```shell
golang版本 >= 1.9.2
```
# 架构
<div align=center>
<img src="https://gfer.me/images/arch.png"/>
</div>
# 文档
开发文档:[https://goframe.org](https://goframe.org)
接口文档:[https://godoc.org/github.com/gogf/gf](https://godoc.org/github.com/gogf/gf)
# 使用
```go
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("Hello World")
})
s.Run()
}
```
[更多..](https://gfer.me/start/index)
# 协议
`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>
<a href="https://gitee.com/wenzi1" target="_blank" title="蚊子"><img src="https://images.gitee.com/uploads/22/1923122_wenzi1.png" width="60" align="left"></a>
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://gfer.me/images/contributors/zseeker.png" width="60" align="left"></a>
<a href="https://gitee.com/ymrjqyy" target="_blank" title="一墨染尽青衣颜"><img src="https://images.gitee.com/uploads/27/876827_ymrjqyy.png" width="60" align="left"></a>
<a href="https://github.com/chenyang351" target="_blank" title="chenyang351"><img src="https://avatars1.githubusercontent.com/u/30063958?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/wxkj" target="_blank" title="wxkj"><img src="https://gitee.com/uploads/56/91356_wxkj.png" width="60" align="left"></a>
<a href="https://github.com/wxkj001" target="_blank" title="3wxkj001
"><img src="https://avatars0.githubusercontent.com/u/7794279?s=60&v=4" width="60" align="left"></a>
<a href="https://gitee.com/zhangjinfu" target="_blank" title="张金富"><img src="https://images.gitee.com/uploads/63/356163_zhangjinfu.png" width="60" align="left"></a>
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://gfer.me/images/contributors/garfieldkwong.png" width="60" align="left"></a>
<a href="https://gitee.com/qq1054000800" target="_blank" title="hello"><img src="https://gitee.com/uploads/9/2209_qq1054000800.jpg" width="60" align="left"></a>
<br /><br /><br />
# 捐赠者
<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>
<a href="https://gitee.com/mg91" target="_blank" title="mg91"><img src="https://images.gitee.com/uploads/30/1410930_mg91.png" width="60" align="left"></a>

View File

@ -1,136 +1,116 @@
# `v0.97.399 beta` (2018-04-23)
1、 增加gfsnotify文件监控模块
2、 配置管理模块增加配置文件自动检测更新机制;
3、 模板引擎增加对模板文件的自动检测更新机制;
4、 改进gconv包基本类型转换功能提高转换性能
5、 增加gpage分页管理包支持动态分页、静态分页以及自定义分页样式特性
6、 ghttp.Request增加Exit方法用以标记服务退出当在服务执行前调用后服务将不再执行
7、 ghttp.Response去掉WriteString方法统一使用Write方法返回数据流是使用灵活的参数形式
8、 模板引擎增加模板变量暴露接口LockFunc/RLockFunc以便支持开发者灵活处理模板变量
9、 ghttp.Server增加access & error log功能并支持开发者自定义日志处理回调函数注册
10、增加gredis包支持对redis的客户端操作封装并将gredis.Redis对象加入到gins单例管理器中进行统一配置管理维护
11、gins单例管理器增加对单例对象配置文件的自动检测更新机制当配置文件在外部发生变更时自动刷新单例管理器中的单例对象
12、gdb数据库ORM包增加And/Or条件链式方法并改进Where/Data方法参数灵活性
13、对于新增加的模块同时也增加了对应的开发文档并梳理完善了现有的其他模块开发文档
14、修复ISSUE:
#IISWI gitee.com/johng/gf/issues/IISWI,
#IISMY gitee.com/johng/gf/issues/IISMY,
反馈并跟踪完成第三方依赖mxj包的ISSUE修复(github.com/clbanning/mxj/issues/48)
# `v1.4.6` (2019-01-24)
# `v0.98.503 beta` (2018-05-21)
## 新特性
1、平滑重启特性( http://gf.johng.cn/625833 )
2、gflock文件锁模块( http://gf.johng.cn/626062 )
3、gproc进程管理及通信模块( http://gf.johng.cn/626063 )
4、gpage分页管理模块强大的动态分页及静态分页功能并为开发者自定义分页样式提供了极高的灵活度( http://gf.johng.cn/597431 )
5、ghttp.Server增加多端口监听特性并支持HTTP/HTTPS( http://gf.johng.cn/494366 , http://gf.johng.cn/598802 )
6、增加gspath目录检索包管理工具支持对多目录下的文件检索特性
7、ghttp包控制器及执行对象注册增加更灵活的动态路由特性路由规则增加{method}变量支持;
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、gutil包增加MapToStruct方法支持将map数据类型映射为struct对象
2、gconv
1)、gconv包增加按照类型名称字符串进行类型转换
2)、gconv包新增Time/TimeDuration类型转换方法
3、ghttp
1)、增加Web Server目录安全访问控制机制
2)、ghttp.Server增加自定义状态码回调函数注册处理
4、gdb
1)、gdb包增加gdb.GetStruct/gdb.Model.Struct方法获取查询结果记录自动转换为指定对象
2)、gdb增加Value/Record/Result类型增加对Value类型的系列类型转换方法
3)、gdb包增加db.GetCount,tx.GetCount,model.Count数量查询方法
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、改进gredis客户端功能封装
2、改进grand包随机数生成性能
3、grand/gdb/gredis包增加benchmark性能测试脚本
4、改进gjson/gparser包的ToStruct方法实现
5、gdb 改进gdb.New获取ORM操作对象性能
6、gcfg :改进配置文件检索功能
7、gview模板引擎增加多目录检索功能
8、gfile增加源码main包目录获取方法MainPkgPath
9、ghttp
1)、ghttp.Request增加请求进入和完成时间记录并增加到默认日志内容中
2)、ghttp.Server事件回调之间支持通过ghttp.Request.Param自定义参数进行流程传参
10、gdb
1)、改进gdb.Result与gdb.List, gdb.Record与gdb.Map之间的类型转换便于业务层数据编码处理(如json/xml)
2)、改进gdb.Tx.GetValue返回值类型
3)、gdb.Model.Data参数支持更加灵活的map参数
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`模块的单元测试用例
## 问题修复
1、ghttp
1)、修复ghttp包路由缓存问题
2)、修复服务注册时的控制器及执行对象方法丢失问题;
2、gconv
1)、修正gconv.Float64方法位大小设置问题
2)、修复gconv.Int64(float64(xxx))问题;
2、gdb
1)、修复gdb.GetAll针对返回数据列表的for..range...的返回结果slice相同指针问题
2)、修复gdb.Delete方法错误
3)、修复gdb.Model.And/Or方法
4)、修复gdb.Model.Where方法参数处理问题
3、garray修复garray包Remove方法锁机制问题
4、gtype 修复gtype.Float32/gtype.Float64对象类型的方法逻辑错误
5、gfsnotify修复在windows下文件参数中不同文件分隔符引起的热更新机制失效问题
6、修复gvalid包验证问题如果值为nil并且不需要require*验证时,其他验证失效。并增加单元测试项,测试通过。
# `v0.99.682 beta` (2018-08-07)
## Bug Fix
1. 修复`gmd5`模块操作文件时的文件指针未关闭问题;
1. 修复`gcache`缓存项过期删除失效问题;
1. 其他修复;
# `v1.3.8` (2018-12-26)
## 新特性
1、新增gdes包用于DES加密/加密算法处理
2、新增gkafka包kafka的golang客户端
3、新增gpool对象复用池比较于标准库的sync.Pool更加灵活强大可自定义对象的缓存时间、创建方法、销毁方法(http://gf.johng.cn/686654)
4、完成网络通信gtcp/gudp包的重构并进行了大量的改进工作新增了详尽的开发文档及示例代码(http://gf.johng.cn/494382)
5、增加gring并发安全环标准库container/ring包的并发安全版本并做了易用性的封装(http://gf.johng.cn/686655)
6、gtime包新增了自定义日期格式话的支持格式化语法类似PHP的date语法(http://gf.johng.cn/494387)
7、gdb增加调试模式特性使用SetDebug方法实现在调试模式下可以获取详细的SQL执行记录增加了详细的开发文档及示例代码(http://gf.johng.cn/702801)
8、gdb增加查询缓存特性使用Cache方法实现增加了详细的开发文档及示例代码(http://gf.johng.cn/702801)
9、ghttp.Server路由功能增加字段匹配规则特性支持如/order/list/{page}.html 动态路由规则特性(http://gf.johng.cn/702766)
10、gpage分页包增加分页URL规则生成模板特性内部可使用{.page}变量指定页码位置(http://gf.johng.cn/716438)
11、增加gmap.Map对象这是gmap.InterfaceInterfaceMap的别名
1. 对`gform`完成重构,以提高扩展性,并修复部分细节问题、完善单元测试用例([https://gfer.me/database/orm/index](https://gfer.me/database/orm/index))
1. `WebServer`路由注册新增分组路由特性([https://gfer.me/net/ghttp/group](https://gfer.me/net/ghttp/group));
1. `WebServer`新增`Rewrite`路由重写特性([https://gfer.me/net/ghttp/static](https://gfer.me/net/ghttp/static));
1. 增加框架运行时对开发环境的自动识别
1. 增加了`Travis CI`自动化构建/测试
## 新功能
1、gdb增加MaxIdleConnCount/MaxOpenConnCount/MaxConnLifetime三项配置并增加SetMaxConnLifetime方法
2、ghttp.Client增加HTTP账号密码设置功能(SetBasicAuth)
3、glog新增对系统换行符号的自适配调整(\n|\r\n)
4、增加glog控制台调试模式打印开关(SetDebug)
5、gcfg增加SetFileName方法设置默认读取的配置文件名称
6、gcfg/gjson/gparser包新增Int8/16/32/64,Uint8/16/32/64方法
7、增加gzip方法的封装(Zip/Unzip)
8、gview增加模板变量分隔符设置方法SetDelimiters
9、ghttp.Response增加Writef、Writefln方法
1. 改进`WebServer`静态文件服务功能,增加`SetStaticPath`/`AddStaticPath`方法([https://gfer.me/net/ghttp/static](https://gfer.me/net/ghttp/static))
1. `gform`新增`Filter`链式操作方法,用于过滤参数中的非表字段键值对([https://gfer.me/database/orm/linkop](https://gfer.me/database/orm/linkop)
1. `gcache`新增`Data`方法,用以获取所有的缓存数据项
1. `gredis`增加`GetConn`方法获取原生redis连接对象
## 功能改进
1、改进gfilepool文件指针池设计改进gfile文本内容写入增加指针池使用
2、gdb包增加调试模式特性并支持在调试模式下获得已执行的SQL列表结果
3、改进gproc进程间通信机制增加进程消息分组特性并限定队列大小
4、gdb结果方法处理增加ToXml/ToJson方法
5、gregx包名修改为gregex
6、改进gtime.StrToTime方法新增对常见标准时间日期的自动转换以及对时区的自动识别支持并调整gconv,gvalid对该包的引用
7、增加对字符集转换的封装gxml包中使用新增的字符集转换包来做处理
8、ghttp.Server.EnableAdmin页面Restart接口支持GET参数newExeFilePath支持
9、ghttp.Server平滑重启机制增加可自定义重启可执行文件路径特别是针对windows系统特别有用(因为windows下不支持可执行文件覆盖更新)
10、改进ghttp.Server静态文件检索设计增加开发环境时的main包源码目录查找机制改进gcfg/gview的main包源码目录查找机制
11、优化gcache设计LRU特性非默认开启优化gtype/gcache基准测试脚本新增gregx基准测试脚本改进设计提升性能
12、gfile包增加GoRootOfBuild方法用于获取编译时的GOROOT数值并改进glog包中backtrace的GOROOT路径过滤处理
13、改进grpool代码质量并改进对池化goroutine数量的限制设计
14、改进gdb.Map/List及g.Map/List的类型定义改用别名特性以便支持原生类型输入(map/slice)并修复gdb.Model.Update方法参数处理问题
15、调整ghttp包示例代码目录结构增加ghttp.Client自定义Header方法ghttp.Cookie增加Map方法用于获得客户端提交的所有cookie值构造成map返回
16、删除gcharset中的getcharset方法
17、去掉gmap中常用的基本数据类型转换获取方法
18、改进gconv.String方法当无法使用基本类型进行字符串转换时使用json.Marshal进行转换
19、gvalid.CheckObject方法名称修改为gvalid.CheckStruct
1. 改进`gform`的`Where`方法,支持`slice`类型的参数,并更方便地支持`in`操作查询([https://gfer.me/database/orm/linkop](https://gfer.me/database/orm/linkop)
1. 改进`gproc`进程间通信数据结构,将`pid`字段从`16bit`扩展为`24bit`
1. 改进`gconv`/`gmap`/`garray`,增加若干操作方法;
1. 改进`gview`模板引擎中的`date`内置函数,当给定的时间戳为空时打印当前的系统时间;
1. 改进`gview`模板引擎中,当打印的变量不存在时,显示为空(标准库默认显示为`<no value>`
1. 改进`WebServer`,去掉`HANGUP`的信号监听,避免程序通过`nohup`运行时产生异常退出问题;
1. 改进`gcache`性能,并完善基准测试;
## Bug Fix
1. 修复`gcache`在非LRU特性开启时的缓存关闭资源竞争问题并修复`doSetWithLockCheck`内部方法的返回值问题;
1. 修复`grand.intn`内部方法在`x86`架构下的随机数位溢出问题;
1. 修复`gbinary`中`Int`方法针对`[]byte`参数长度自动匹配造成的字节长度溢出问题;
1. 修复`gjson`由于官方标准库`json`不支持`map[interface{}]*`类型造成的Go变量编码问题
1. 修复`garray`中部分方法的数据竞争问题,修复二分查找排序问题;
1. 修复`ghttp.Request.GetVar`方法获取参数问题;
1. 修复`gform`的数据库连接池不起作用的问题;
# `v1.2.11` (2018-11-26)
## 新特性
1. `ORM`新增对`SQLServer`及`Oracle`的支持([https://gfer.me/database/orm/database](https://gfer.me/database/orm/database))
1. 完成`gvalid`模块校验结果的顺序特性([https://gfer.me/util/gvalid/checkmap](https://gfer.me/util/gvalid/checkmap));
1. 改进`ghttp.Request.Exit`,使得调用该方法时立即退出业务执行,开发者无需调用`Exit`方法时再使用`return`返回([https://gfer.me/net/ghttp/service/object](https://gfer.me/net/ghttp/service/object))
1. 模板引擎新增若干内置函数:`text/html/htmldecode/url/urldecode/date/compare/substr/strlimit/hidestr/highlight/toupper/tolower/nl2br` ([https://gfer.me/os/gview/funcs](https://gfer.me/os/gview/funcs));
1. 模板引擎新增内置变量`Config` ([https://gfer.me/os/gview/vars](https://gfer.me/os/gview/vars));
1. 改进`gconv.Struct`转换默认规则,支持不区分大小写的键名与属性名称匹配;
1. `gform`配置文件支持`linkinfo`自定义数据库连接字段([https://gfer.me/database/orm/config](https://gfer.me/database/orm/config))
1. `gfsnotify`模块增加对特定回调的取消注册功能([https://gfer.me/os/gfsnotify/index](https://gfer.me/os/gfsnotify/index)
## 新功能
1. 改进`ghttp.Request`,增加`SetParam/GetParam`请求流程自定义变量设置/获取方法,用于在请求流程中的回调函数共享变量([https://gfer.me/net/ghttp/request](https://gfer.me/net/ghttp/request);
1. 改进`ghttp.Response`,增加`ServeFileDownload`方法用于WebServer引导客户端下载文件([https://gfer.me/net/ghttp/response](https://gfer.me/net/ghttp/response));
1. `gvar`模块新增`gvar.VarRead`只读接口,用于控制对外只暴露数据读取功能;
1. 增加`g.Throw`抛异常方法,`g.TryCatch`异常捕获方法封装;
1. 改进`gcron`模块增加自定义的Cron管理对象增加`New/Start/Stop`方法;
## 功能改进
1. WebServer添加`RouterCacheExpire`配置参数,用于设置路由检索缓存过期时间;
1. WebServer允许同一`HOOK`事件被多次绑定注册,先注册的回调函数优先级更高([https://gfer.me/net/ghttp/service/hook](https://gfer.me/net/ghttp/service/hook));
1. 当前工作目录为系统临时目录时,`gcfg`/`gview`/`ghttp`模块默认不添加工作目录到搜索路径;
1. 改进`WebSocket`默认支持跨域请求([https://gfer.me/net/ghttp/websocket](https://gfer.me/net/ghttp/websocket));
1. 改进`gtime.Format`支持中文;
1. 改进`gfsnotify`,支持编辑器对文件非执行标准编辑时(RENAME+CHMOD)的热更新问题;
1. 改进`gtype.Set`方法增加Set原子操作返回旧的变量值;
1. `gfile.ScanDir`增加支持`pattern`多个文件模式匹配,使用'`,`'符号分隔多个匹配模式;
1. `gcfg`模块增加获取配置变量为`*gvar.Var`;
1. `gstr`模块增加对中文截取方法;
1. 改进`gtime.StrToTime`对常用时间格式匹配模式,新增`gtime.ParseTimeFromContent`方法;
1. 修改配置管理、模板引擎、调试模式的环境变量名称为大写下划线标准格式;
1. 改进`grand`模块随机数生成设计,底层使用`crypto/rand`+缓冲区实现高速的随机数生成([https://gfer.me/util/grand/index](https://gfer.me/util/grand/index));
## 问题修复
1、修正gstr.IsNumeric错误
2、修复当xml中encoding字符集为非UTF-8字符集时报错的问题
3、修正gconv包float32->float64精度问题
4、修复gpage包分页计数问题
5、修复gdb批量数据Save错误
6、去掉gpool中math.MAXINT64常量的使用以修复int64到int类型的转换错误兼容32位系统
7、修正ghttp包没有使用Server仍然初始化相关异步goroutine的问题
1. 修复`gspath`模块在`windows`下搜索失效问题;
1. 修复`gspath`模块Search时带有indexFiles的检索问题;
1. bug fix INZS1([https://gitee.com/johng/gf/issues/INZS1](https://gitee.com/johng/gf/issues/INZS1));
1. 修复`gproc.ShellRun`在windows下的执行问题;
# `v1.0.898 stable` (2018-10-24)
@ -227,3 +207,145 @@
1. 其他一些改动;
# `v0.99.682 beta` (2018-08-07)
## 新特性
1、新增gdes包用于DES加密/加密算法处理;
2、新增gkafka包kafka的golang客户端
3、新增gpool对象复用池比较于标准库的sync.Pool更加灵活强大可自定义对象的缓存时间、创建方法、销毁方法(http://gf.johng.cn/686654)
4、完成网络通信gtcp/gudp包的重构并进行了大量的改进工作新增了详尽的开发文档及示例代码(http://gf.johng.cn/494382)
5、增加gring并发安全环标准库container/ring包的并发安全版本并做了易用性的封装(http://gf.johng.cn/686655)
6、gtime包新增了自定义日期格式话的支持格式化语法类似PHP的date语法(http://gf.johng.cn/494387)
7、gdb增加调试模式特性使用SetDebug方法实现在调试模式下可以获取详细的SQL执行记录增加了详细的开发文档及示例代码(http://gf.johng.cn/702801)
8、gdb增加查询缓存特性使用Cache方法实现增加了详细的开发文档及示例代码(http://gf.johng.cn/702801)
9、ghttp.Server路由功能增加字段匹配规则特性支持如/order/list/{page}.html 动态路由规则特性(http://gf.johng.cn/702766)
10、gpage分页包增加分页URL规则生成模板特性内部可使用{.page}变量指定页码位置(http://gf.johng.cn/716438)
11、增加gmap.Map对象这是gmap.InterfaceInterfaceMap的别名
## 新功能
1、gdb增加MaxIdleConnCount/MaxOpenConnCount/MaxConnLifetime三项配置并增加SetMaxConnLifetime方法
2、ghttp.Client增加HTTP账号密码设置功能(SetBasicAuth)
3、glog新增对系统换行符号的自适配调整(\n|\r\n)
4、增加glog控制台调试模式打印开关(SetDebug)
5、gcfg增加SetFileName方法设置默认读取的配置文件名称
6、gcfg/gjson/gparser包新增Int8/16/32/64,Uint8/16/32/64方法
7、增加gzip方法的封装(Zip/Unzip)
8、gview增加模板变量分隔符设置方法SetDelimiters
9、ghttp.Response增加Writef、Writefln方法
## 功能改进
1、改进gfilepool文件指针池设计改进gfile文本内容写入增加指针池使用
2、gdb包增加调试模式特性并支持在调试模式下获得已执行的SQL列表结果
3、改进gproc进程间通信机制增加进程消息分组特性并限定队列大小
4、gdb结果方法处理增加ToXml/ToJson方法
5、gregx包名修改为gregex
6、改进gtime.StrToTime方法新增对常见标准时间日期的自动转换以及对时区的自动识别支持并调整gconv,gvalid对该包的引用
7、增加对字符集转换的封装gxml包中使用新增的字符集转换包来做处理
8、ghttp.Server.EnableAdmin页面Restart接口支持GET参数newExeFilePath支持
9、ghttp.Server平滑重启机制增加可自定义重启可执行文件路径特别是针对windows系统特别有用(因为windows下不支持可执行文件覆盖更新)
10、改进ghttp.Server静态文件检索设计增加开发环境时的main包源码目录查找机制改进gcfg/gview的main包源码目录查找机制
11、优化gcache设计LRU特性非默认开启优化gtype/gcache基准测试脚本新增gregx基准测试脚本改进设计提升性能
12、gfile包增加GoRootOfBuild方法用于获取编译时的GOROOT数值并改进glog包中backtrace的GOROOT路径过滤处理
13、改进grpool代码质量并改进对池化goroutine数量的限制设计
14、改进gdb.Map/List及g.Map/List的类型定义改用别名特性以便支持原生类型输入(map/slice)并修复gdb.Model.Update方法参数处理问题
15、调整ghttp包示例代码目录结构增加ghttp.Client自定义Header方法ghttp.Cookie增加Map方法用于获得客户端提交的所有cookie值构造成map返回
16、删除gcharset中的getcharset方法
17、去掉gmap中常用的基本数据类型转换获取方法
18、改进gconv.String方法当无法使用基本类型进行字符串转换时使用json.Marshal进行转换
19、gvalid.CheckObject方法名称修改为gvalid.CheckStruct
## 问题修复
1、修正gstr.IsNumeric错误
2、修复当xml中encoding字符集为非UTF-8字符集时报错的问题
3、修正gconv包float32->float64精度问题
4、修复gpage包分页计数问题
5、修复gdb批量数据Save错误
6、去掉gpool中math.MAXINT64常量的使用以修复int64到int类型的转换错误兼容32位系统
7、修正ghttp包没有使用Server仍然初始化相关异步goroutine的问题
# `v0.98.503 beta` (2018-05-21)
## 新特性
1、平滑重启特性( http://gf.johng.cn/625833 )
2、gflock文件锁模块( http://gf.johng.cn/626062 )
3、gproc进程管理及通信模块( http://gf.johng.cn/626063 )
4、gpage分页管理模块强大的动态分页及静态分页功能并为开发者自定义分页样式提供了极高的灵活度( http://gf.johng.cn/597431 )
5、ghttp.Server增加多端口监听特性并支持HTTP/HTTPS( http://gf.johng.cn/494366 , http://gf.johng.cn/598802 )
6、增加gspath目录检索包管理工具支持对多目录下的文件检索特性
7、ghttp包控制器及执行对象注册增加更灵活的动态路由特性路由规则增加{method}变量支持;
## 新功能
1、gutil包增加MapToStruct方法支持将map数据类型映射为struct对象
2、gconv
1)、gconv包增加按照类型名称字符串进行类型转换
2)、gconv包新增Time/TimeDuration类型转换方法
3、ghttp
1)、增加Web Server目录安全访问控制机制
2)、ghttp.Server增加自定义状态码回调函数注册处理
4、gdb
1)、gdb包增加gdb.GetStruct/gdb.Model.Struct方法获取查询结果记录自动转换为指定对象
2)、gdb增加Value/Record/Result类型增加对Value类型的系列类型转换方法
3)、gdb包增加db.GetCount,tx.GetCount,model.Count数量查询方法
## 功能改进
1、改进gredis客户端功能封装
2、改进grand包随机数生成性能
3、grand/gdb/gredis包增加benchmark性能测试脚本
4、改进gjson/gparser包的ToStruct方法实现
5、gdb 改进gdb.New获取ORM操作对象性能
6、gcfg :改进配置文件检索功能;
7、gview模板引擎增加多目录检索功能
8、gfile增加源码main包目录获取方法MainPkgPath
9、ghttp
1)、ghttp.Request增加请求进入和完成时间记录并增加到默认日志内容中
2)、ghttp.Server事件回调之间支持通过ghttp.Request.Param自定义参数进行流程传参
10、gdb
1)、改进gdb.Result与gdb.List, gdb.Record与gdb.Map之间的类型转换便于业务层数据编码处理(如json/xml)
2)、改进gdb.Tx.GetValue返回值类型
3)、gdb.Model.Data参数支持更加灵活的map参数
## 问题修复
1、ghttp
1)、修复ghttp包路由缓存问题
2)、修复服务注册时的控制器及执行对象方法丢失问题;
2、gconv
1)、修正gconv.Float64方法位大小设置问题
2)、修复gconv.Int64(float64(xxx))问题;
2、gdb
1)、修复gdb.GetAll针对返回数据列表的for..range...的返回结果slice相同指针问题
2)、修复gdb.Delete方法错误
3)、修复gdb.Model.And/Or方法
4)、修复gdb.Model.Where方法参数处理问题
3、garray修复garray包Remove方法锁机制问题
4、gtype 修复gtype.Float32/gtype.Float64对象类型的方法逻辑错误
5、gfsnotify修复在windows下文件参数中不同文件分隔符引起的热更新机制失效问题
6、修复gvalid包验证问题如果值为nil并且不需要require*验证时,其他验证失效。并增加单元测试项,测试通过。
# `v0.97.399 beta` (2018-04-23)
1、 增加gfsnotify文件监控模块
2、 配置管理模块增加配置文件自动检测更新机制;
3、 模板引擎增加对模板文件的自动检测更新机制;
4、 改进gconv包基本类型转换功能提高转换性能
5、 增加gpage分页管理包支持动态分页、静态分页以及自定义分页样式特性
6、 ghttp.Request增加Exit方法用以标记服务退出当在服务执行前调用后服务将不再执行
7、 ghttp.Response去掉WriteString方法统一使用Write方法返回数据流是使用灵活的参数形式
8、 模板引擎增加模板变量暴露接口LockFunc/RLockFunc以便支持开发者灵活处理模板变量
9、 ghttp.Server增加access & error log功能并支持开发者自定义日志处理回调函数注册
10、增加gredis包支持对redis的客户端操作封装并将gredis.Redis对象加入到gins单例管理器中进行统一配置管理维护
11、gins单例管理器增加对单例对象配置文件的自动检测更新机制当配置文件在外部发生变更时自动刷新单例管理器中的单例对象
12、gdb数据库ORM包增加And/Or条件链式方法并改进Where/Data方法参数灵活性
13、对于新增加的模块同时也增加了对应的开发文档并梳理完善了现有的其他模块开发文档
14、修复ISSUE:
#IISWI gitee.com/johng/gf/issues/IISWI,
#IISMY gitee.com/johng/gf/issues/IISMY,
反馈并跟踪完成第三方依赖mxj包的ISSUE修复(github.com/clbanning/mxj/issues/48)

27
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对象转换的键名属性映射
@ -34,17 +30,23 @@
- glog分类&日志等级&链式操作、gdb debug自动输出调试信息、gmlock内存锁、
1. 服务注册域名增加对泛域名的支持;
1. Cookie设置中文失效问题
1. ghttp hook回调使用方式在注册路由比较多的时候优先级可能使得开发者混乱考虑方式便于管理
1. 使用gconv将slice映射到struct属性上例如redis hscan的结果集
1. 项目参考:
- https://github.com/namreg/godown
- https://github.com/Masterminds/sprig
1. gform参考 https://gohouse.github.io/gorose/dist/index.html 进行改进
1. 模板引擎增加对对象的支持(参考https://segmentfault.com/q/1010000016829214)
1. 改进gfpool在文件指针变化时的更新
1. gtcp提供简便的包发送/接收方法(SendPkg/RecvPkg)以解决常见的TCP通信粘包问题并完善文档参考https://www.cnblogs.com/kex1n/p/6502002.html
1. gfile对于文件的读写强行使用了gfpool在某些场景下不合适需要考虑剥离开并为开发者提供单独的指针池文件操作特性
1. 路由增加不区分大小写得匹配方式;
1. str_ireplace: http://php.net/manual/en/function.str-ireplace.php
1. strpos/stripos/strrpos/strripos: http://php.net/manual/en/function.stripos.php
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类型的转换
@ -70,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以及分组名称发送/获取进程消息;
@ -96,4 +98,11 @@
1. `gfsnotify`增加添加监听文件时的监听ID返回以便调用端删除监听时只删除自己添加的监听而不影响其他对该同一文件的监听回调
1. `gfsnotify`针对添加目录监听时无法使用多个`Watcher`,考虑改进,并考虑动态扩容全局`Watcher`方案;
1. 由于系统对inotify实例数量(`fs.inotify.max_user_instances`)以及队列大小(`fs.inotify.max_user_watches`)有限制,需要改进`gfsnotify`
1. WebServer事件回调允许对同一个路由规则绑定多个事件回调
1. gcfg/gview/ghttp等模块加上对临时文件目录的自动添加监听判断基本是开发环境下特别是windows环境去掉临时文件的监听避免临时文件过大引起的运行缓慢占用内存问题
1. 改进gfpool在文件指针变化时的更新
1. ghttp hook回调使用方式在注册路由比较多的时候优先级可能使得开发者混乱考虑方式便于管理
1. gform对于MySQL字段类型为datetime类型的时区问题分析
1. 改进证书打开失败时的WebServer错误提示前置HOOK校验后关闭后续的HOOK逻辑执行
1. 目前WebServer的HOOK是按照优先级执行的需要增加覆盖特性

View File

@ -4,9 +4,11 @@
// If a copy of the MIT was not distributed with this file,
// 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 {
@ -69,6 +69,17 @@ func (a *IntArray) InsertAfter(index int, value int) {
func (a *IntArray) Remove(index int) int {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
@ -136,6 +147,20 @@ func (a *IntArray) Search(value int) int {
return result
}
// 清理数组中重复的元素项
func (a *IntArray) Unique() *IntArray {
a.mu.Lock()
for i := 0; i < len(a.array) - 1; i++ {
for j := i + 1; j < len(a.array); j++ {
if a.array[i] == a.array[j] {
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
}
}
}
a.mu.Unlock()
return a
}
// 使用自定义方法执行加锁修改操作
func (a *IntArray) LockFunc(f func(array []int)) {
a.mu.Lock(true)

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 {
@ -68,11 +68,41 @@ func (a *Array) InsertAfter(index int, value interface{}) {
func (a *Array) Remove(index int) interface{} {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
}
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
func (a *Array) PopLeft() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
value := a.array[0]
a.array = a.array[1 : ]
return value
}
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
func (a *Array) PopRight() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[: index]
return value
}
// 追加数据项
func (a *Array) Append(value...interface{}) {
a.mu.Lock()
@ -135,6 +165,20 @@ func (a *Array) Search(value interface{}) int {
return result
}
// 清理数组中重复的元素项
func (a *Array) Unique() *Array {
a.mu.Lock()
for i := 0; i < len(a.array) - 1; i++ {
for j := i + 1; j < len(a.array); j++ {
if a.array[i] == a.array[j] {
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
}
}
}
a.mu.Unlock()
return a
}
// 使用自定义方法执行加锁修改操作
func (a *Array) LockFunc(f func(array []interface{})) {
a.mu.Lock(true)

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"
)
// 默认按照从低到高进行排序
@ -16,14 +16,14 @@ type SortedIntArray struct {
mu *rwmutex.RWMutex // 互斥锁
cap int // 初始化设置的数组容量
array []int // 底层数组
unique *gtype.Bool // 是否要求不能重复
unique *gtype.Bool // 是否要求不能重复(默认false)
compareFunc func(v1, v2 int) int // 比较函数,返回值 -1: v1 < v20: v1 == v21: v1 > v2
}
// 创建一个排序的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 {
@ -153,16 +153,14 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int)
max := len(a.array) - 1
mid := 0
cmp := -2
for {
for min <= max {
mid = int((min + max) / 2)
cmp = a.compareFunc(value, a.array[mid])
switch cmp {
case -1 : max = mid - 1
case 0 :
case 1 : min = mid + 1
}
if cmp == 0 || min > max {
break
case 0 :
return mid, cmp
}
}
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,
@ -146,16 +146,14 @@ func (a *SortedArray) binSearch(value interface{}, lock bool)(index int, result
max := len(a.array) - 1
mid := 0
cmp := -2
for {
for min <= max {
mid = int((min + max) / 2)
cmp = a.compareFunc(value, a.array[mid])
switch cmp {
case -1 : max = mid - 1
case 0 :
case 1 : min = mid + 1
}
if cmp == 0 || min > max {
break
case 0 :
return mid, cmp
}
}
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 {
@ -147,16 +147,14 @@ func (a *SortedStringArray) binSearch(value string, lock bool) (index int, resul
max := len(a.array) - 1
mid := 0
cmp := -2
for {
for min <= max {
mid = int((min + max) / 2)
cmp = a.compareFunc(value, a.array[mid])
switch cmp {
case -1 : max = mid - 1
case 0 :
case 1 : min = mid + 1
}
if cmp == 0 || min > max {
break
case 0 :
return mid, cmp
}
}
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 {
@ -68,7 +68,18 @@ func (a *StringArray) InsertAfter(index int, value string) {
// 删除指定索引的数据项, 调用方注意判断数组边界
func (a *StringArray) Remove(index int) string {
a.mu.Lock()
defer a.mu.RUnlock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
@ -135,6 +146,20 @@ func (a *StringArray) Search(value string) int {
return result
}
// 清理数组中重复的元素项
func (a *StringArray) Unique() *StringArray {
a.mu.Lock()
for i := 0; i < len(a.array) - 1; i++ {
for j := i + 1; j < len(a.array); j++ {
if a.array[i] == a.array[j] {
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
}
}
}
a.mu.Unlock()
return a
}
// 使用自定义方法执行加锁修改操作
func (a *StringArray) LockFunc(f func(array []string)) {
a.mu.Lock(true)

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

@ -0,0 +1,84 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// go test *.go
package garray_test
import (
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/util/gtest"
"strings"
"testing"
)
func Test_IntArray_Unique(t *testing.T) {
expect := []int{1, 2, 3, 4, 5, 6}
array := garray.NewIntArray(0, 0)
array.Append(1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6)
array.Unique()
gtest.Assert(array.Slice(), expect)
}
func Test_SortedIntArray1(t *testing.T) {
expect := []int{0,1,2,3,4,5,6,7,8,9,10}
array := garray.NewSortedIntArray(0)
for i := 10; i > -1; i-- {
array.Add(i)
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedIntArray2(t *testing.T) {
expect := []int{0,1,2,3,4,5,6,7,8,9,10}
array := garray.NewSortedIntArray(0)
for i := 0; i <= 10; i++ {
array.Add(i)
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedStringArray1(t *testing.T) {
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
array := garray.NewSortedStringArray(0)
for i := 10; i > -1; i-- {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedStringArray2(t *testing.T) {
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
array := garray.NewSortedStringArray(0)
for i := 0; i <= 10; i++ {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedArray1(t *testing.T) {
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
array := garray.NewSortedArray(0, func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
})
for i := 10; i > -1; i-- {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}
func Test_SortedArray2(t *testing.T) {
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
array := garray.NewSortedArray(0, func(v1, v2 interface{}) int {
return strings.Compare(gconv.String(v1), gconv.String(v2))
})
for i := 0; i <= 10; i++ {
array.Add(gconv.String(i))
}
gtest.Assert(array.Slice(), expect)
}

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 gchan provides graceful operations for channel.
//
// 优雅的Channel操作.
package gchan
@ -40,8 +42,7 @@ func (q *Chan) Pop() interface{} {
// 关闭队列(通知所有通过Pop阻塞的协程退出)
func (q *Chan) Close() {
if !q.closed.Val() {
q.closed.Set(true)
if !q.closed.Set(true) {
close(q.list)
}
}

View File

@ -1,263 +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 {
return &List{
mu : rwmutex.New(safe...),
list : list.New(),
func New(unsafe...bool) *List {
return &List {
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 BenchmarkPushBack(b *testing.B) {
func Benchmark_PushBack(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
l.PushBack(i)
}
}
func BenchmarkPopFront(b *testing.B) {
for i := 0; i < b.N; i++ {
l.PopFront()
}
}
func BenchmarkPushFront(b *testing.B) {
func Benchmark_PushFront(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
l.PushFront(i)
}
}
func BenchmarkPopBack(b *testing.B) {
for i := 0; i < b.N; i++ {
l.PopBack()
}
}
func BenchmarkLen(b *testing.B) {
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

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

@ -4,15 +4,18 @@
// If a copy of the MIT was not distributed with this file,
// 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"
)
// 对象池
@ -31,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 {
@ -99,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

@ -4,51 +4,55 @@
// If a copy of the MIT was not distributed with this file,
// 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 (
// 默认临时队列大小,注意是临时的
// 动态队列缓冲区大小
gDEFAULT_QUEUE_SIZE = 10000
)
// 队列大小为非必须参数,默认不限制
func New(limit...int) *Queue {
size := gDEFAULT_QUEUE_SIZE
if len(limit) > 0 {
size = limit[0]
}
q := &Queue {
list : glist.New(),
queue : make(chan interface{}, size),
events : make(chan struct{}, math.MaxInt32),
closeChan : make(chan struct{}, 0),
closed : make(chan struct{}, 0),
}
if len(limit) > 0 {
q.limit = size
q.limit = limit[0]
q.C = make(chan interface{}, limit[0])
} else {
// 如果是动态队列大小那么额外会运行一个goroutine
q.list = list.New()
q.events = make(chan struct{}, math.MaxInt32)
q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE)
go q.startAsyncLoop()
}
return q
@ -58,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
}
@ -73,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

@ -13,36 +13,38 @@ import (
"gitee.com/johng/gf/g/container/gqueue"
)
var length = 10000000
var bn = 20000000
var length = 1000000
var qstatic = gqueue.New(length)
var qdynamic = gqueue.New()
var cany = make(chan interface{}, length)
var cint = make(chan int, length)
func Benchmark_GqueueStaticPushAndPop(b *testing.B) {
func Benchmark_Gqueue_StaticPushAndPop(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
qstatic.Push(i)
qstatic.Pop()
}
}
func Benchmark_GqueueDynamicPush(b *testing.B) {
func Benchmark_Gqueue_DynamicPush(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
qdynamic.Push(i)
}
}
func Benchmark_ChannelInterfacePushAndPop(b *testing.B) {
func Benchmark_Gqueue_DynamicPop(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
qdynamic.Pop()
}
}
func Benchmark_Channel_PushAndPop(b *testing.B) {
b.N = bn
for i := 0; i < b.N; i++ {
cany <- i
<- cany
}
}
func Benchmark_ChannelIntPushAndPop(b *testing.B) {
for i := 0; i < b.N; i++ {
cint <- i
<- cint
}
}

View File

@ -4,13 +4,15 @@
// If a copy of the MIT was not distributed with this file,
// 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 {
@ -21,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

@ -4,12 +4,14 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// 并发安全的集合SET.
// Package gset provides kinds of concurrent-safe(alternative) sets.
//
// 并发安全集合.
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

@ -18,55 +18,55 @@ var ints = gset.NewIntSet()
var itfs = gset.NewInterfaceSet()
var strs = gset.NewStringSet()
func BenchmarkIntSet_Add(b *testing.B) {
func Benchmark_IntSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Add(i)
}
}
func BenchmarkIntSet_Contains(b *testing.B) {
func Benchmark_IntSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Contains(i)
}
}
func BenchmarkIntSet_Remove(b *testing.B) {
func Benchmark_IntSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
ints.Remove(i)
}
}
func BenchmarkInterfaceSet_Add(b *testing.B) {
func Benchmark_InterfaceSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Add(i)
}
}
func BenchmarkInterfaceSet_Contains(b *testing.B) {
func Benchmark_InterfaceSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Contains(i)
}
}
func BenchmarkInterfaceSet_Remove(b *testing.B) {
func Benchmark_InterfaceSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
itfs.Remove(i)
}
}
func BenchmarkStringSet_Add(b *testing.B) {
func Benchmark_StringSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Add(strconv.Itoa(i))
}
}
func BenchmarkStringSet_Contains(b *testing.B) {
func Benchmark_StringSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Contains(strconv.Itoa(i))
}
}
func BenchmarkStringSet_Remove(b *testing.B) {
func Benchmark_StringSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
strs.Remove(strconv.Itoa(i))
}

View File

@ -0,0 +1,73 @@
// 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=".*"
package gset_test
import (
"testing"
"strconv"
"gitee.com/johng/gf/g/container/gset"
)
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++ {
intsUnsafe.Add(i)
}
}
func Benchmark_Unsafe_IntSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
intsUnsafe.Contains(i)
}
}
func Benchmark_Unsafe_IntSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
intsUnsafe.Remove(i)
}
}
func Benchmark_Unsafe_InterfaceSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Add(i)
}
}
func Benchmark_Unsafe_InterfaceSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Contains(i)
}
}
func Benchmark_Unsafe_InterfaceSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
itfsUnsafe.Remove(i)
}
}
func Benchmark_Unsafe_StringSet_Add(b *testing.B) {
for i := 0; i < b.N; i++ {
strsUnsafe.Add(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringSet_Contains(b *testing.B) {
for i := 0; i < b.N; i++ {
strsUnsafe.Contains(strconv.Itoa(i))
}
}
func Benchmark_Unsafe_StringSet_Remove(b *testing.B) {
for i := 0; i < b.N; i++ {
strsUnsafe.Remove(strconv.Itoa(i))
}
}

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 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,13 +4,16 @@
// 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 an universal variable type, like generics.
//
// 通用动态变量.
package gvar
import (
"time"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/util/gconv"
"time"
)
type Var struct {
@ -19,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
@ -30,6 +33,16 @@ func New(value interface{}, safe...bool) *Var {
return v
}
// 创建一个只读动态变量value参数可以为nil
func NewRead(value interface{}, unsafe...bool) VarRead {
return VarRead(New(value, unsafe...))
}
// 返回动态变量的只读接口
func (v *Var) ReadOnly() VarRead {
return VarRead(v)
}
func (v *Var) Set(value interface{}) (old interface{}) {
if v.safe {
old = v.value.(*gtype.Interface).Set(value)
@ -48,6 +61,7 @@ func (v *Var) Val() interface{} {
}
}
// Val() 别名
func (v *Var) Interface() interface{} {
return v.Val()
}
@ -77,8 +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...)
}
// 将变量转换为对象,注意 objPointer 参数必须为struct指针
func (v *Var) Struct(objPointer interface{}, attrMapping...map[string]string) error {

View File

@ -0,0 +1,42 @@
// 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 gvar
import (
"gitee.com/johng/gf/g/os/gtime"
"time"
)
// 只读变量接口
type VarRead interface {
Val() interface{}
IsNil() bool
Bytes() []byte
String() string
Bool() bool
Int() int
Int8() int8
Int16() int16
Int32() int32
Int64() int64
Uint() uint
Uint8() uint8
Uint16() uint16
Uint32() uint32
Uint64() uint64
Float32() float32
Float64() float64
Interface() interface{}
Ints() []int
Floats() []float64
Strings() []string
Interfaces() []interface{}
Time(format ...string) time.Time
TimeDuration() time.Duration
GTime(format...string) *gtime.Time
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

@ -1,11 +1,11 @@
package gdes
package gdes_test
import (
"testing"
"bytes"
"encoding/hex"
"fmt"
"gitee.com/johng/gf/g/encoding/gdes"
"gitee.com/johng/gf/g/crypto/gdes"
)
func TestDesECB(t *testing.T){

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 (
@ -35,6 +35,7 @@ func EncryptFile(path string) string {
if e != nil {
return ""
}
defer f.Close()
h := md5.New()
_, e = io.Copy(h, f)
if e != nil {

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.
// 数据库ORM.
// Package gdb provides ORM features for popular relationship databases.
//
// 数据库ORM,
// 默认内置支持MySQL, 其他数据库需要手动import对应的数据库引擎第三方包.
package gdb
@ -12,7 +14,6 @@ import (
"database/sql"
"errors"
"fmt"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/container/gring"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/container/gvar"
@ -22,39 +23,42 @@ import (
"time"
)
const (
OPTION_INSERT = 0
OPTION_REPLACE = 1
OPTION_SAVE = 2
OPTION_IGNORE = 3
)
// 数据库操作接口
type Link interface {
// 打开数据库连接,建立数据库操作对象
Open(c *ConfigNode) (*sql.DB, error)
type DB interface {
// 建立数据库连接方法(开发者一般不需要直接调用)
Open(config *ConfigNode) (*sql.DB, error)
// SQL操作方法
Query(q string, args ...interface{}) (*sql.Rows, error)
Exec(q string, args ...interface{}) (sql.Result, error)
Prepare(q string) (*sql.Stmt, error)
// SQL操作方法 API
Query(query string, args ...interface{}) (*sql.Rows, error)
Exec(sql string, args ...interface{}) (sql.Result, error)
Prepare(sql string, execOnMaster...bool) (*sql.Stmt, error)
// 内部实现API的方法(不同数据库可覆盖这些方法实现自定义的操作)
doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error)
doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error)
doPrepare(link dbLink, query string) (*sql.Stmt, error)
doInsert(link dbLink, table string, data Map, option int) (result sql.Result, err error)
doBatchInsert(link dbLink, table string, list List, batch int, option int) (result sql.Result, err error)
doUpdate(link dbLink, table string, data interface{}, condition interface{}, args ...interface{}) (result sql.Result, err error)
doDelete(link dbLink, table string, condition interface{}, args ...interface{}) (result sql.Result, err error)
// 数据库查询
GetAll(q string, args ...interface{}) (Result, error)
GetOne(q string, args ...interface{}) (Record, error)
GetValue(q string, args ...interface{}) (Value, error)
GetAll(query string, args ...interface{}) (Result, error)
GetOne(query string, args ...interface{}) (Record, error)
GetValue(query string, args ...interface{}) (Value, error)
GetCount(query string, args ...interface{}) (int, error)
GetStruct(obj interface{}, query string, args ...interface{}) error
// Ping
// 创建底层数据库master/slave链接对象
Master() (*sql.DB, error)
Slave() (*sql.DB, error)
// Ping
PingMaster() error
PingSlave() error
// 连接属性设置
SetMaxIdleConns(n int)
SetMaxOpenConns(n int)
SetConnMaxLifetime(n int)
// 开启事务操作
Begin() (*Tx, error)
Begin() (*TX, error)
// 数据表插入/更新/保存操作
Insert(table string, data Map) (sql.Result, error)
@ -74,27 +78,45 @@ type Link interface {
Table(tables string) *Model
From(tables string) *Model
// 内部方法
insert(table string, data Map, option uint8) (sql.Result, error)
batchInsert(table string, list List, batch int, option uint8) (sql.Result, error)
// 设置管理
SetDebug(debug bool)
SetSchema(schema string)
GetQueriedSqls() []*Sql
PrintQueriedSqls()
SetMaxIdleConns(n int)
SetMaxOpenConns(n int)
SetConnMaxLifetime(n int)
getQuoteCharLeft() string
getQuoteCharRight() string
handleSqlBeforeExec(q *string) *string
// 内部方法接口
getCache() (*gcache.Cache)
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
}
// 执行底层数据库操作的核心接口
type dbLink interface {
Query(query string, args ...interface{}) (*sql.Rows, error)
Exec(sql string, args ...interface{}) (sql.Result, error)
Prepare(sql string) (*sql.Stmt, error)
}
// 数据库链接对象
type Db struct {
link Link // 底层数据库类型管理对象
group string // 配置分组名称
charl string // SQL安全符号(左)
charr string // SQL安全符号(右)
debug *gtype.Bool // (默认关闭)是否开启调试模式,当开启时会启用一些调试特性
sqls *gring.Ring // (debug=true时有效)已执行的SQL列表
cache *gcache.Cache // 查询缓存,需要注意的是,事务查询不支持缓存
maxIdleConnCount *gtype.Int // 连接池最大限制的连接数
maxOpenConnCount *gtype.Int // 连接池最大打开的连接数
maxConnLifetime *gtype.Int // (单位秒)连接对象可重复使用的时间长度
type dbBase struct {
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对象
@ -104,7 +126,7 @@ type Sql struct {
Error error // 执行结果(nil为成功)
Start int64 // 执行开始时间(毫秒)
End int64 // 执行结束时间(毫秒)
Func string // 执行方法名称
Func string // 执行方法
}
// 返回数据表记录值
@ -117,26 +139,22 @@ type Record map[string]Value
type Result []Record
// 关联数组,绑定一条数据表记录(使用别名)
type Map = map[string]interface{}
type Map = map[string]interface{}
// 关联数组列表(索引从0开始的数组),绑定多条记录(使用别名)
type List = []Map
var (
// 支持的数据库类型map
driverMap = make(map[string]interface{})
// 数据库查询缓存对象map使用数据库连接名称作为键名键值为查询缓存对象
dbCaches = gmap.NewStringInterfaceMap()
const (
OPTION_INSERT = 0
OPTION_REPLACE = 1
OPTION_SAVE = 2
OPTION_IGNORE = 3
// 默认的连接池连接存活时间(秒)
gDEFAULT_CONN_MAX_LIFE_TIME = 30
)
func init() {
driverMap["mysql"] = linkMysql
driverMap["oracle"] = linkOracle
driverMap["sqlite"] = linkSqlite
driverMap["pgsql"] = linkPgsql
}
// 使用默认/指定分组配置进行连接数据库集群配置项default
func New(groupName ...string) (*Db, error) {
func New(groupName ...string) (db DB, err error) {
group := config.d
if len(groupName) > 0 {
group = groupName[0]
@ -149,24 +167,30 @@ func New(groupName ...string) (*Db, error) {
}
if _, ok := config.c[group]; ok {
if node, err := getConfigNodeByGroup(group, true); err == nil {
link, err := getLinkByType(node.Type)
if err != nil {
return nil, err
}
db := &Db {
link : link,
base := &dbBase {
group : group,
charl : link.getQuoteCharLeft(),
charr : link.getQuoteCharRight(),
debug : gtype.NewBool(),
cache : gcache.New(),
schema : gtype.NewString(),
maxIdleConnCount : gtype.NewInt(),
maxOpenConnCount : gtype.NewInt(),
maxConnLifetime : gtype.NewInt(),
maxConnLifetime : gtype.NewInt(gDEFAULT_CONN_MAX_LIFE_TIME),
}
db.cache = dbCaches.GetOrSetFuncLock(group, func() interface{} {
return gcache.New()
}).(*gcache.Cache)
return db, nil
switch node.Type {
case "mysql":
base.db = &dbMysql{dbBase : base}
case "pgsql":
base.db = &dbPgsql{dbBase : base}
case "mssql":
base.db = &dbMssql{dbBase : base}
case "sqlite":
base.db = &dbSqlite{dbBase : base}
case "oracle":
base.db = &dbOracle{dbBase : base}
default:
return nil, errors.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type))
}
return base.db, nil
} else {
return nil, err
}
@ -218,6 +242,13 @@ func getConfigNodeByPriority(cg ConfigGroup) *ConfigNode {
for i := 0; i < len(cg); i++ {
total += cg[i].Priority * 100
}
// 如果total为0表示所有连接都没有配置priority属性那么默认都是1
if total == 0 {
for i := 0; i < len(cg); i++ {
cg[i].Priority = 1
total += cg[i].Priority * 100
}
}
// 不能取到末尾的边界点
r := grand.Rand(0, total)
if r > 0 {
@ -237,58 +268,63 @@ func getConfigNodeByPriority(cg ConfigGroup) *ConfigNode {
return nil
}
// 根据配置的数据库类型获得Link接口对象
func getLinkByType(dbType string) (Link, error) {
if dblink, ok := driverMap[dbType]; ok == false {
return nil, errors.New(fmt.Sprintf("unsupported db type '%s'", dbType))
} else {
return dblink.(Link), nil
}
// 获得底层数据库链接对象
func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) {
// 负载均衡
node, err := getConfigNodeByGroup(bs.group, master)
if err != nil {
return nil, err
}
// 默认值设定
if node.Charset == "" {
node.Charset = "utf8"
}
v := bs.cache.GetOrSetFuncLock(node.String(), func() interface{} {
sqlDb, err = bs.db.Open(node)
if err != nil {
return nil
}
if n := bs.maxIdleConnCount.Val(); n > 0 {
sqlDb.SetMaxIdleConns(n)
} else if node.MaxIdleConnCount > 0 {
sqlDb.SetMaxIdleConns(node.MaxIdleConnCount)
}
if n := bs.maxOpenConnCount.Val(); n > 0 {
sqlDb.SetMaxOpenConns(n)
} else if node.MaxOpenConnCount > 0 {
sqlDb.SetMaxOpenConns(node.MaxOpenConnCount)
}
if n := bs.maxConnLifetime.Val(); n > 0 {
sqlDb.SetConnMaxLifetime(time.Duration(n) * time.Second)
} else if node.MaxConnLifetime > 0 {
sqlDb.SetConnMaxLifetime(time.Duration(node.MaxConnLifetime) * time.Second)
}
return sqlDb
}, 0)
if v != nil && sqlDb == nil {
sqlDb = v.(*sql.DB)
}
// 是否手动选择数据库
if v := bs.schema.Val(); v != "" {
sqlDb.Exec("USE " + v)
}
return
}
// 获得底层数据库链接对象
func (db *Db) getSqlDb(master bool) (*sql.DB, error) {
node, err := getConfigNodeByGroup(db.group, master)
if err != nil {
return nil, err
}
link, err := getLinkByType(node.Type)
if err != nil {
return nil, err
}
sqlDb, err := link.Open(node)
if err != nil {
return nil, err
}
if node.MaxIdleConnCount > 0 {
sqlDb.SetMaxIdleConns(node.MaxIdleConnCount)
}
if n := db.maxIdleConnCount.Val(); n > 0 {
sqlDb.SetMaxIdleConns(n)
}
if node.MaxOpenConnCount > 0 {
sqlDb.SetMaxOpenConns(node.MaxOpenConnCount)
}
if n := db.maxOpenConnCount.Val(); n > 0 {
sqlDb.SetMaxOpenConns(n)
}
if node.MaxConnLifetime > 0 {
sqlDb.SetConnMaxLifetime(time.Duration(node.MaxConnLifetime) * time.Second)
}
if n := db.maxConnLifetime.Val(); n > 0 {
sqlDb.SetConnMaxLifetime(time.Duration(n) * time.Second)
}
return sqlDb, nil
// 切换操作的数据库(注意该切换是全局的)
func (bs *dbBase) SetSchema(schema string) {
bs.schema.Set(schema)
}
// 创建底层数据库master链接对象
func (db *Db) Master() (*sql.DB, error) {
return db.getSqlDb(true)
func (bs *dbBase) Master() (*sql.DB, error) {
return bs.getSqlDb(true)
}
// 创建底层数据库slave链接对象
func (db *Db) Slave() (*sql.DB, error) {
return db.getSqlDb(false)
func (bs *dbBase) Slave() (*sql.DB, error) {
return bs.getSqlDb(false)
}

View File

@ -8,39 +8,30 @@
package gdb
import (
"fmt"
"errors"
"strings"
"reflect"
"database/sql"
"gitee.com/johng/gf/g/util/gstr"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/container/gring"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/os/glog"
"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"
"gitee.com/johng/gf/g/util/gregex"
"reflect"
"strings"
)
const (
gDEFAULT_DEBUG_SQL_LENGTH = 1000 // 默认调试模式下记录的SQL条数
)
// 是否开启调试服务
func (db *Db) SetDebug(debug bool) {
db.debug.Set(debug)
if debug && db.sqls == nil {
db.sqls = gring.New(gDEFAULT_DEBUG_SQL_LENGTH)
}
}
// 获取已经执行的SQL列表(仅在debug=true时有效)
func (db *Db) GetQueriedSqls() []*Sql {
if db.sqls == nil {
func (bs *dbBase) GetQueriedSqls() []*Sql {
if bs.sqls == nil {
return nil
}
sqls := make([]*Sql, 0)
db.sqls.Prev()
db.sqls.RLockIteratorPrev(func(value interface{}) bool {
bs.sqls.Prev()
bs.sqls.RLockIteratorPrev(func(value interface{}) bool {
if value == nil {
return false
}
@ -51,8 +42,8 @@ func (db *Db) GetQueriedSqls() []*Sql {
}
// 打印已经执行的SQL列表(仅在debug=true时有效)
func (db *Db) PrintQueriedSqls() {
sqls := db.GetQueriedSqls()
func (bs *dbBase) PrintQueriedSqls() {
sqls := bs.GetQueriedSqls()
for k, v := range sqls {
fmt.Println(len(sqls) - k, ":")
fmt.Println(" Sql :", v.Sql)
@ -61,145 +52,110 @@ func (db *Db) PrintQueriedSqls() {
fmt.Println(" Start:", gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u"))
fmt.Println(" End :", gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u"))
fmt.Println(" Cost :", v.End - v.Start, "ms")
fmt.Println(" Func :", v.Func)
}
}
// 打印SQL对象(仅在debug=true时有效)
func (db *Db) printSql(v *Sql) {
s := fmt.Sprintf("%s, %v, %s, %s, %d ms, %s", v.Sql, v.Args,
gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u"),
gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u"),
v.End - v.Start, v.Func,
)
if v.Error != nil {
s += "\nError: " + v.Error.Error()
glog.Backtrace(true, 2).Error(s)
} else {
glog.Debug(s)
}
}
// 数据库sql查询操作主要执行查询
func (db *Db) Query(query string, args ...interface{}) (*sql.Rows, error) {
var err error
var rows *sql.Rows
var slave *sql.DB
slave, err = db.Slave();
func (bs *dbBase) Query(query string, args ...interface{}) (rows *sql.Rows, err error) {
link, err := bs.db.Slave()
if err != nil {
return nil,err
}
defer slave.Close()
p := db.link.handleSqlBeforeExec(&query)
if db.debug.Val() {
militime1 := gtime.Millisecond()
rows, err = slave.Query(*p, args ...)
militime2 := gtime.Millisecond()
s := &Sql{
Sql : *p,
return bs.db.doQuery(link, query, args...)
}
// 数据库sql查询操作主要执行查询
func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error) {
query = bs.db.handleSqlBeforeExec(query)
if bs.db.getDebug() {
mTime1 := gtime.Millisecond()
rows, err = link.Query(query, args...)
mTime2 := gtime.Millisecond()
s := &Sql {
Sql : query,
Args : args,
Error : err,
Start : militime1,
End : militime2,
Func : "DB:Query",
Start : mTime1,
End : mTime2,
}
db.sqls.Put(s)
db.printSql(s)
bs.sqls.Put(s)
printSql(s)
} else {
rows, err = slave.Query(*p, args ...)
rows, err = link.Query(query, args ...)
}
if err == nil {
return rows, nil
} else {
err = db.formatError(err, p, args...)
err = formatError(err, query, args...)
}
return nil, err
}
// 执行一条sql并返回执行情况主要用于非查询操作
func (db *Db) Exec(query string, args ...interface{}) (sql.Result, error) {
var err error
var result sql.Result
var master *sql.DB
master, err = db.Master();
func (bs *dbBase) Exec(query string, args ...interface{}) (result sql.Result, err error) {
link, err := bs.db.Master()
if err != nil {
return nil,err
}
defer master.Close()
p := db.link.handleSqlBeforeExec(&query)
if db.debug.Val() {
militime1 := gtime.Millisecond()
result, err = master.Exec(*p, args ...)
militime2 := gtime.Millisecond()
s := &Sql{
Sql : *p,
return bs.db.doExec(link, query, args...)
}
// 执行一条sql并返回执行情况主要用于非查询操作
func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error) {
query = bs.db.handleSqlBeforeExec(query)
if bs.db.getDebug() {
mTime1 := gtime.Millisecond()
result, err = link.Exec(query, args ...)
mTime2 := gtime.Millisecond()
s := &Sql{
Sql : query,
Args : args,
Error : err,
Start : militime1,
End : militime2,
Func : "DB:Exec",
Start : mTime1,
End : mTime2,
}
db.sqls.Put(s)
db.printSql(s)
bs.sqls.Put(s)
printSql(s)
} else {
result, err = master.Exec(*p, args ...)
result, err = link.Exec(query, args ...)
}
return result, db.formatError(err, p, args...)
return result, formatError(err, query, args...)
}
// 格式化错误信息
func (db *Db) formatError(err error, query *string, args ...interface{}) error {
if err != nil {
errstr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
errstr += fmt.Sprintf("DB QUERY: %s\n", *query)
if len(args) > 0 {
errstr += fmt.Sprintf("DB PARAM: %v\n", args)
// SQL预处理执行完成后调用返回值sql.Stmt.Exec完成sql操作; 默认执行在Slave上, 通过第二个参数指定执行在Master上
func (bs *dbBase) Prepare(query string, execOnMaster...bool) (*sql.Stmt, error) {
err := (error)(nil)
link := (dbLink)(nil)
if len(execOnMaster) > 0 && execOnMaster[0] {
if link, err = bs.db.Master(); err != nil {
return nil, err
}
} else {
if link, err = bs.db.Slave(); err != nil {
return nil, err
}
err = errors.New(errstr)
}
return err
return bs.db.doPrepare(link, query)
}
// SQL预处理执行完成后调用返回值sql.Stmt.Exec完成sql操作
func (bs *dbBase) doPrepare(link dbLink, query string) (*sql.Stmt, error) {
return link.Prepare(query)
}
// 数据库查询,获取查询结果集,以列表结构返回
func (db *Db) GetAll(query string, args ...interface{}) (Result, error) {
// 执行sql
rows, err := db.Query(query, args ...)
func (bs *dbBase) GetAll(query string, args ...interface{}) (Result, error) {
rows, err := bs.Query(query, args ...)
if err != nil || rows == nil {
return nil, err
}
// 列名称列表
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 {
v := make([]byte, len(col))
copy(v, col)
row[columns[i]] = gvar.New(v)
}
records = append(records, row)
}
return records, nil
defer rows.Close()
return bs.db.rowsToResult(rows)
}
// 数据库查询,获取查询结果记录,以关联数组结构返回
func (db *Db) GetOne(query string, args ...interface{}) (Record, error) {
list, err := db.GetAll(query, args ...)
func (bs *dbBase) GetOne(query string, args ...interface{}) (Record, error) {
list, err := bs.GetAll(query, args ...)
if err != nil {
return nil, err
}
@ -210,18 +166,17 @@ func (db *Db) GetOne(query string, args ...interface{}) (Record, error) {
}
// 数据库查询获取查询结果记录自动映射数据到给定的struct对象中
func (db *Db) GetStruct(obj interface{}, query string, args ...interface{}) error {
one, err := db.GetOne(query, args...)
func (bs *dbBase) GetStruct(obj interface{}, query string, args ...interface{}) error {
one, err := bs.GetOne(query, args...)
if err != nil {
return err
}
return one.ToStruct(obj)
}
// 数据库查询,获取查询字段值
func (db *Db) GetValue(query string, args ...interface{}) (Value, error) {
one, err := db.GetOne(query, args ...)
func (bs *dbBase) GetValue(query string, args ...interface{}) (Value, error) {
one, err := bs.GetOne(query, args ...)
if err != nil {
return nil, err
}
@ -232,72 +187,44 @@ func (db *Db) GetValue(query string, args ...interface{}) (Value, error) {
}
// 数据库查询,获取查询数量
func (db *Db) GetCount(query string, args ...interface{}) (int, error) {
val, err := db.GetValue(query, args ...)
func (bs *dbBase) GetCount(query string, args ...interface{}) (int, error) {
if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, query) {
query, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, query)
}
value, err := bs.GetValue(query, args ...)
if err != nil {
return 0, err
}
return gconv.Int(val), nil
}
// 数据表查询其中tables可以是多个联表查询语句这种查询方式较复杂建议使用链式操作
func (db *Db) Select(tables, fields string, condition interface{}, groupBy, orderBy string, first, limit int, args ... interface{}) (Result, error) {
s := fmt.Sprintf("SELECT %s FROM %s ", fields, tables)
if condition != nil {
s += fmt.Sprintf("WHERE %s ", db.formatCondition(condition))
}
if len(groupBy) > 0 {
s += fmt.Sprintf("GROUP BY %s ", groupBy)
}
if len(orderBy) > 0 {
s += fmt.Sprintf("ORDER BY %s ", orderBy)
}
if limit > 0 {
s += fmt.Sprintf("LIMIT %d,%d ", first, limit)
}
return db.GetAll(s, args ... )
}
// sql预处理执行完成后调用返回值sql.Stmt.Exec完成sql操作
// 记得调用sql.Stmt.Close关闭操作对象
func (db *Db) Prepare(query string) (*sql.Stmt, error) {
if master, err := db.Master(); err != nil {
return nil, err
} else {
defer master.Close()
return master.Prepare(query)
}
return value.Int(), nil
}
// ping一下判断或保持数据库链接(master)
func (db *Db) PingMaster() error {
if master, err := db.Master(); err != nil {
func (bs *dbBase) PingMaster() error {
if master, err := bs.db.Master(); err != nil {
return err
} else {
defer master.Close()
return master.Ping()
}
}
// ping一下判断或保持数据库链接(slave)
func (db *Db) PingSlave() error {
if slave, err := db.Slave(); err != nil {
func (bs *dbBase) PingSlave() error {
if slave, err := bs.db.Slave(); err != nil {
return err
} else {
defer slave.Close()
return slave.Ping()
}
}
// 事务操作,开启,会返回一个底层的事务操作对象链接如需要嵌套事务,那么可以使用该对象,否则请忽略
// 只有在tx.Commit/tx.Rollback时链接会自动Close
func (db *Db) Begin() (*Tx, error) {
if master, err := db.Master(); err != nil {
func (bs *dbBase) Begin() (*TX, error) {
if master, err := bs.db.Master(); err != nil {
return nil, err
} else {
if tx, err := master.Begin(); err == nil {
return &Tx {
db : db,
return &TX {
db : bs.db,
tx : tx,
master : master,
}, nil
@ -307,17 +234,19 @@ func (db *Db) Begin() (*Tx, error) {
}
}
// 根据insert选项获得操作名称
func (db *Db) getInsertOperationByOption(option uint8) string {
oper := "INSERT"
switch option {
case OPTION_REPLACE:
oper = "REPLACE"
case OPTION_SAVE:
case OPTION_IGNORE:
oper = "INSERT IGNORE"
}
return oper
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
func (bs *dbBase) Insert(table string, data Map) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_INSERT)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (bs *dbBase) Replace(table string, data Map) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_REPLACE)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (bs *dbBase) Save(table string, data Map) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_SAVE)
}
// insert、replace, save ignore操作
@ -325,95 +254,102 @@ func (db *Db) getInsertOperationByOption(option uint8) string {
// 1: replace: 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
// 2: save: 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
// 3: ignore: 如果数据存在(主键或者唯一索引),那么什么也不做
func (db *Db) insert(table string, data Map, option uint8) (sql.Result, error) {
func (bs *dbBase) doInsert(link dbLink, table string, data Map, option int) (result sql.Result, err error) {
var fields []string
var values []string
var params []interface{}
charl, charr := bs.db.getChars()
for k, v := range data {
fields = append(fields, db.charl + k + db.charr)
fields = append(fields, charl + k + charr)
values = append(values, "?")
params = append(params, v)
}
operation := db.getInsertOperationByOption(option)
operation := getInsertOperationByOption(option)
updatestr := ""
if option == OPTION_SAVE {
var updates []string
for k, _ := range data {
updates = append(updates,
fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
db.charl, k, db.charr,
db.charl, k, db.charr,
charl, k, charr,
charl, k, charr,
),
)
}
updatestr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
}
return db.Exec(
fmt.Sprintf("%s INTO %s(%s) VALUES(%s) %s",
operation, table, strings.Join(fields, ","),
strings.Join(values, ","),
updatestr),
params...
)
if link == nil {
if link, err = bs.db.Master(); err != nil {
return nil, err
}
}
return bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES(%s) %s",
operation, table, strings.Join(fields, ","),
strings.Join(values, ","), updatestr),
params...)
}
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
func (db *Db) Insert(table string, data Map) (sql.Result, error) {
return db.insert(table, data, OPTION_INSERT)
// CURD操作:批量数据指定批次量写入
func (bs *dbBase) BatchInsert(table string, list List, batch int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, batch, OPTION_INSERT)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (db *Db) Replace(table string, data Map) (sql.Result, error) {
return db.insert(table, data, OPTION_REPLACE)
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (bs *dbBase) BatchReplace(table string, list List, batch int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, batch, OPTION_REPLACE)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (db *Db) Save(table string, data Map) (sql.Result, error) {
return db.insert(table, data, OPTION_SAVE)
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (bs *dbBase) BatchSave(table string, list List, batch int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, batch, OPTION_SAVE)
}
// 批量写入数据
func (db *Db) batchInsert(table string, list List, batch int, option uint8) (sql.Result, error) {
func (bs *dbBase) doBatchInsert(link dbLink, table string, list List, batch int, option int) (result sql.Result, err error) {
var keys []string
var values []string
var bvalues []string
var params []interface{}
var result sql.Result
var size = len(list)
// 判断长度
if size < 1 {
if len(list) < 1 {
return result, errors.New("empty data list")
}
if link == nil {
if link, err = bs.db.Master(); err != nil {
return
}
}
// 首先获取字段名称及记录长度
for k, _ := range list[0] {
keys = append(keys, k)
values = append(values, "?")
}
keyStr := db.charl + strings.Join(keys, db.charl + "," + db.charr) + db.charr
charl, charr := bs.db.getChars()
keyStr := charl + strings.Join(keys, charl + "," + charr) + charr
valueHolderStr := "(" + strings.Join(values, ",") + ")"
// 操作判断
operation := db.getInsertOperationByOption(option)
operation := getInsertOperationByOption(option)
updatestr := ""
if option == OPTION_SAVE {
var updates []string
for _, k := range keys {
updates = append(updates,
fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
db.charl, k, db.charr,
db.charl, k, db.charr,
charl, k, charr,
charl, k, charr,
),
)
}
updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
}
// 构造批量写入数据格式(注意map的遍历是无序的)
for i := 0; i < size; i++ {
for i := 0; i < len(list); i++ {
for _, k := range keys {
params = append(params, list[i][k])
}
bvalues = append(bvalues, valueHolderStr)
if len(bvalues) == batch {
r, err := db.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
r, err := bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
updatestr),
params...)
@ -427,7 +363,7 @@ func (db *Db) batchInsert(table string, list List, batch int, option uint8) (sql
}
// 处理最后不构成指定批量的数据
if len(bvalues) > 0 {
r, err := db.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
r, err := bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
updatestr),
params...)
@ -439,32 +375,28 @@ func (db *Db) batchInsert(table string, list List, batch int, option uint8) (sql
return result, nil
}
// CURD操作:批量数据指定批次量写入
func (db *Db) BatchInsert(table string, list List, batch int) (sql.Result, error) {
return db.batchInsert(table, list, batch, OPTION_INSERT)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (db *Db) BatchReplace(table string, list List, batch int) (sql.Result, error) {
return db.batchInsert(table, list, batch, OPTION_REPLACE)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (db *Db) BatchSave(table string, list List, batch int) (sql.Result, error) {
return db.batchInsert(table, list, batch, OPTION_SAVE)
// CURD操作:数据更新统一采用sql预处理
// data参数支持字符串或者关联数组类型内部会自行做判断处理
func (bs *dbBase) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
link, err := bs.db.Master()
if err != nil {
return nil, err
}
return bs.db.doUpdate(link, table, data, condition, args ...)
}
// CURD操作:数据更新统一采用sql预处理
// data参数支持字符串或者关联数组类型内部会自行做判断处理
func (db *Db) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
var params []interface{}
var updates string
refValue := reflect.ValueOf(data)
func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, condition interface{}, args ...interface{}) (result sql.Result, err error) {
params := ([]interface{})(nil)
updates := ""
charl, charr := bs.db.getChars()
refValue := reflect.ValueOf(data)
if refValue.Kind() == reflect.Map {
var fields []string
keys := refValue.MapKeys()
for _, k := range keys {
fields = append(fields, fmt.Sprintf("%s%s%s=?", db.charl, k, db.charr))
fields = append(fields, fmt.Sprintf("%s%s%s=?", charl, k, charr))
params = append(params, gconv.String(refValue.MapIndex(k).Interface()))
}
updates = strings.Join(fields, ",")
@ -474,34 +406,69 @@ func (db *Db) Update(table string, data interface{}, condition interface{}, args
for _, v := range args {
params = append(params, gconv.String(v))
}
return db.Exec(fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, db.formatCondition(condition)), params...)
if link == nil {
if link, err = bs.db.Master(); err != nil {
return nil, err
}
}
newWhere, newArgs := formatCondition(condition, params)
return bs.db.doExec(link, fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, newWhere), newArgs...)
}
// CURD操作:删除数据
func (db *Db) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
return db.Exec(fmt.Sprintf("DELETE FROM %s WHERE %s", table, db.formatCondition(condition)), args...)
func (bs *dbBase) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
link, err := bs.db.Master()
if err != nil {
return nil, err
}
return bs.db.doDelete(link, table, condition, args ...)
}
// 格式化SQL查询条件
func (db *Db) formatCondition(condition interface{}) (where string) {
if reflect.ValueOf(condition).Kind() == reflect.Map {
ks := reflect.ValueOf(condition).MapKeys()
vs := reflect.ValueOf(condition)
for _, k := range ks {
key := gconv.String(k.Interface())
value := gconv.String(vs.MapIndex(k).Interface())
isNum := gstr.IsNumeric(value)
if len(where) > 0 {
where += " AND "
}
if isNum || value == "?" {
where += key + "=" + value
// CURD操作:删除数据
func (bs *dbBase) doDelete(link dbLink, table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
newWhere, newArgs := formatCondition(condition, args)
return bs.db.doExec(link, fmt.Sprintf("DELETE FROM %s WHERE %s", table, newWhere), newArgs...)
}
// 获得缓存对象
func (bs *dbBase) getCache() *gcache.Cache {
return bs.cache
}
// 将数据查询的列表数据*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 {
where += key + "='" + value + "'"
// 由于 sql.RawBytes 是slice类型, 这里必须使用值复制
v := make([]byte, len(col))
copy(v, col)
row[columns[i]] = gvar.New(bs.db.convertValue(v, types[i]), true)
}
}
} else {
where += gconv.String(condition)
records = append(records, row)
}
return
}
return records, nil
}

View File

@ -8,6 +8,8 @@
package gdb
import (
"fmt"
"gitee.com/johng/gf/g/container/gring"
"sync"
)
@ -113,6 +115,13 @@ func AddDefaultConfigGroup (nodes ConfigGroup) {
AddConfigGroup(DEFAULT_GROUP_NAME, nodes)
}
// 添加一台数据库服务器配置
func GetConfig (group string) ConfigGroup {
config.RLock()
defer config.RUnlock()
return config.c[group]
}
// 设置默认链接的数据库链接配置项(默认是 default)
func SetDefaultGroup (groupName string) {
config.Lock()
@ -121,17 +130,41 @@ func SetDefaultGroup (groupName string) {
}
// 设置数据库连接池中空闲链接的大小
func (db *Db) SetMaxIdleConns(n int) {
db.maxIdleConnCount.Set(n)
func (bs *dbBase) SetMaxIdleConns(n int) {
bs.maxIdleConnCount.Set(n)
}
// 设置数据库连接池最大打开的链接数量
func (db *Db) SetMaxOpenConns(n int) {
db.maxOpenConnCount.Set(n)
func (bs *dbBase) SetMaxOpenConns(n int) {
bs.maxOpenConnCount.Set(n)
}
// 设置数据库连接可重复利用的时间,超过该时间则被关闭废弃
// 如果 d <= 0 表示该链接会一直重复利用
func (db *Db) SetConnMaxLifetime(n int) {
db.maxConnLifetime.Set(n)
func (bs *dbBase) SetConnMaxLifetime(n int) {
bs.maxConnLifetime.Set(n)
}
// 节点配置转换为字符串
func (node *ConfigNode) String() string {
if node.Linkinfo != "" {
return node.Linkinfo
}
return fmt.Sprintf(`%s@%s:%s,%s,%s,%s,%s,%d-%d-%d`, node.User, node.Host, node.Port,
node.Name, node.Type, node.Role, node.Charset,
node.MaxIdleConnCount, node.MaxOpenConnCount, node.MaxConnLifetime,
)
}
// 是否开启调试服务
func (bs *dbBase) SetDebug(debug bool) {
bs.debug.Set(debug)
if debug && bs.sqls == nil {
bs.sqls = gring.New(gDEFAULT_DEBUG_SQL_LENGTH)
}
}
// 获取是否开启调试服务
func (bs *dbBase) getDebug() bool {
return bs.debug.Val()
}

121
g/database/gdb/gdb_func.go Normal file
View File

@ -0,0 +1,121 @@
// Copyright 2017-2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gdb
import (
"bytes"
"errors"
"fmt"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/util/gregex"
"gitee.com/johng/gf/g/util/gstr"
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
"reflect"
"strings"
)
// 格式化SQL查询条件
func formatCondition(where interface{}, args []interface{}) (string, []interface{}) {
// 条件字符串处理
buffer := bytes.NewBuffer(nil)
if reflect.ValueOf(where).Kind() == reflect.Map {
ks := reflect.ValueOf(where).MapKeys()
vs := reflect.ValueOf(where)
for _, k := range ks {
key := gconv.String(k.Interface())
value := gconv.String(vs.MapIndex(k).Interface())
if buffer.Len() > 0 {
buffer.WriteString(" AND ")
}
if gstr.IsNumeric(value) || value == "?" {
buffer.WriteString(key + "=" + value)
} else {
buffer.WriteString(key + "='" + value + "'")
}
}
} else {
buffer.Write(gconv.Bytes(where))
}
if buffer.Len() == 0 {
buffer.WriteString("1=1")
}
// 查询条件处理
newWhere := buffer.String()
newArgs := make([]interface{}, 0)
if len(args) > 0 {
for index, arg := range args {
rv := reflect.ValueOf(arg)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Slice: fallthrough
case reflect.Array:
for i := 0; i < rv.Len(); i++ {
newArgs = append(newArgs, rv.Index(i).Interface())
}
counter := 0
newWhere, _ = gregex.ReplaceStringFunc(`\?`, newWhere, func(s string) string {
counter++
if counter == index + 1 {
return "?" + strings.Repeat(",?", rv.Len() - 1)
}
return s
})
default:
newArgs = append(newArgs, arg)
}
}
}
return newWhere, newArgs
}
// 打印SQL对象(仅在debug=true时有效)
func printSql(v *Sql) {
s := fmt.Sprintf("%s, %v, %s, %s, %d ms, %s", v.Sql, v.Args,
gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u"),
gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u"),
v.End - v.Start,
v.Func,
)
if v.Error != nil {
s += "\nError: " + v.Error.Error()
glog.Backtrace(true, 2).Error(s)
} else {
glog.Debug(s)
}
}
// 格式化错误信息
func formatError(err error, query string, args ...interface{}) error {
if err != nil {
errstr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
errstr += fmt.Sprintf("DB QUERY: %s\n", query)
if len(args) > 0 {
errstr += fmt.Sprintf("DB PARAM: %v\n", args)
}
err = errors.New(errstr)
}
return err
}
// 根据insert选项获得操作名称
func getInsertOperationByOption(option int) string {
oper := "INSERT"
switch option {
case OPTION_REPLACE:
oper = "REPLACE"
case OPTION_SAVE:
case OPTION_IGNORE:
oper = "INSERT IGNORE"
}
return oper
}

View File

@ -12,12 +12,15 @@ import (
"database/sql"
"gitee.com/johng/gf/g/util/gconv"
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
"reflect"
"strings"
)
// 数据库链式操作模型对象
type Model struct {
tx *Tx // 数据库事务对象
db *Db // 数据库操作对象
db DB // 数据库操作对象
tx *TX // 数据库事务对象
tablesInit string // 初始化Model时的表名称(可以是多个)
tables string // 数据库操作表
fields string // 操作字段
where string // 操作条件
@ -28,123 +31,213 @@ type Model struct {
limit int // 分页条数
data interface{} // 操作记录(支持Map/List/string类型)
batch int // 批量操作条数
filter bool // 是否按照表字段过滤data参数
cacheEnabled bool // 当前SQL操作是否开启查询缓存功能
cacheTime int // 查询缓存时间
cacheName string // 查询缓存名称
}
// 链式操作,数据表字段,可支持多个表,以半角逗号连接
func (db *Db) Table(tables string) (*Model) {
return &Model{
db: db,
tables: tables,
fields: "*",
func (bs *dbBase) Table(tables string) (*Model) {
return &Model {
db : bs.db,
tablesInit : tables,
tables : tables,
fields : "*",
}
}
// 链式操作,数据表字段,可支持多个表,以半角逗号连接
func (db *Db) From(tables string) (*Model) {
return db.Table(tables)
func (bs *dbBase) From(tables string) (*Model) {
return bs.db.Table(tables)
}
// (事务)链式操作,数据表字段,可支持多个表,以半角逗号连接
func (tx *Tx) Table(tables string) (*Model) {
func (tx *TX) Table(tables string) (*Model) {
return &Model{
db: tx.db,
tx: tx,
tables: tables,
db : tx.db,
tx : tx,
tablesInit : tables,
tables : tables,
}
}
// (事务)链式操作,数据表字段,可支持多个表,以半角逗号连接
func (tx *Tx) From(tables string) (*Model) {
func (tx *TX) From(tables string) (*Model) {
return tx.Table(tables)
}
// 克隆一个当前对象
func (md *Model) Clone() *Model {
newModel := (*Model)(nil)
if md.tx != nil {
newModel = md.tx.Table(md.tablesInit)
} else {
newModel = md.db.Table(md.tablesInit)
}
*newModel = *md
return newModel
}
// 链式操作,左联表
func (md *Model) LeftJoin(joinTable string, on string) (*Model) {
md.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", joinTable, on)
return md
model := md.Clone()
model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", joinTable, on)
return model
}
// 链式操作,右联表
func (md *Model) RightJoin(joinTable string, on string) (*Model) {
md.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", joinTable, on)
return md
model := md.Clone()
model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", joinTable, on)
return model
}
// 链式操作,内联表
func (md *Model) InnerJoin(joinTable string, on string) (*Model) {
md.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", joinTable, on)
return md
model := md.Clone()
model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", joinTable, on)
return model
}
// 链式操作,查询字段
func (md *Model) Fields(fields string) (*Model) {
md.fields = fields
return md
model := md.Clone()
model.fields = fields
return model
}
// 链式操作,过滤字段
func (md *Model) Filter() (*Model) {
model := md.Clone()
model.filter = true
return model
}
// 链式操作condition支持string & gdb.Map
func (md *Model) Where(where interface{}, args ...interface{}) (*Model) {
md.where = md.db.formatCondition(where)
md.whereArgs = append(md.whereArgs, args...)
return md
model := md.Clone()
newWhere, newArgs := formatCondition(where, args)
model.where = newWhere
model.whereArgs = append(model.whereArgs, newArgs...)
// 支持 Where("uid", 1)这种格式
if len(args) == 1 && strings.Index(model.where , "?") < 0 {
model.where += "=?"
}
return model
}
// 链式操作添加AND条件到Where中
func (md *Model) And(where interface{}, args ...interface{}) (*Model) {
md.where += " AND " + md.db.formatCondition(where)
md.whereArgs = append(md.whereArgs, args...)
return md
model := md.Clone()
newWhere, newArgs := formatCondition(where, args)
model.where += " AND " + newWhere
model.whereArgs = append(model.whereArgs, newArgs...)
return model
}
// 链式操作添加OR条件到Where中
func (md *Model) Or(where interface{}, args ...interface{}) (*Model) {
md.where += " OR " + md.db.formatCondition(where)
md.whereArgs = append(md.whereArgs, args...)
return md
model := md.Clone()
newWhere, newArgs := formatCondition(where, args)
model.where += " OR " + newWhere
model.whereArgs = append(model.whereArgs, newArgs...)
return model
}
// 链式操作group by
func (md *Model) GroupBy(groupBy string) (*Model) {
md.groupBy = groupBy
return md
model := md.Clone()
model.groupBy = groupBy
return model
}
// 链式操作order by
func (md *Model) OrderBy(orderBy string) (*Model) {
md.orderBy = orderBy
return md
model := md.Clone()
model.orderBy = orderBy
return model
}
// 链式操作limit
func (md *Model) Limit(start int, limit int) (*Model) {
md.start = start
md.limit = limit
return md
model := md.Clone()
model.start = start
model.limit = limit
return model
}
// 链式操作,翻页
// @author ymrjqyy
func (md *Model) ForPage(page, limit int) (*Model) {
md.start = (page - 1) * limit
md.limit = limit
return md
model := md.Clone()
model.start = (page - 1) * limit
model.limit = limit
return model
}
// 设置批处理的大小
func (md *Model) Batch(batch int) *Model {
model := md.Clone()
model.batch = batch
return model
}
// 查询缓存/清除缓存操作,需要注意的是,事务查询不支持缓存。
// 当time < 0时表示清除缓存 time=0时表示不过期, time > 0时表示过期时间time过期时间单位
// name表示自定义的缓存名称便于业务层精准定位缓存项(如果业务层需要手动清理时,必须指定缓存名称)
// 例如:查询缓存时设置名称,清理缓存时可以给定清理的缓存名称进行精准清理。
func (md *Model) Cache(time int, name ... string) *Model {
model := md.Clone()
model.cacheTime = time
if len(name) > 0 {
model.cacheName = name[0]
}
// 查询缓存特性不支持事务操作
if model.tx == nil {
model.cacheEnabled = true
}
return model
}
// 链式操作操作数据记录项可以是string/Map, 也可以是key,value,key,value,...
func (md *Model) Data(data ...interface{}) (*Model) {
model := md.Clone()
if len(data) > 1 {
m := make(map[string]interface{})
for i := 0; i < len(data); i += 2 {
m[gconv.String(data[i])] = data[i+1]
}
md.data = m
model.data = m
} else {
md.data = data[0]
switch data[0].(type) {
case List:
model.data = data[0]
case Map:
model.data = data[0]
default:
rv := reflect.ValueOf(data[0])
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Slice: fallthrough
case reflect.Array:
list := make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
list[i] = gconv.Map(rv.Index(i).Interface())
}
model.data = list
case reflect.Map:
model.data = gconv.Map(data[0])
default:
model.data = data[0]
}
}
}
return md
return model
}
// 链式操作, CURD - Insert/BatchInsert
@ -163,16 +256,24 @@ func (md *Model) Insert() (result sql.Result, err error) {
if md.batch > 0 {
batch = md.batch
}
if md.filter {
for k, m := range list {
list[k] = md.db.filterFields(md.tables, m)
}
}
if md.tx == nil {
return md.db.BatchInsert(md.tables, list, batch)
} else {
return md.tx.BatchInsert(md.tables, list, batch)
}
} else if dataMap, ok := md.data.(Map); ok {
} else if data, ok := md.data.(Map); ok {
if md.filter {
data = md.db.filterFields(md.tables, data)
}
if md.tx == nil {
return md.db.Insert(md.tables, dataMap)
return md.db.Insert(md.tables, data)
} else {
return md.tx.Insert(md.tables, dataMap)
return md.tx.Insert(md.tables, data)
}
}
return nil, errors.New("inserting into table with invalid data type")
@ -194,16 +295,24 @@ func (md *Model) Replace() (result sql.Result, err error) {
if md.batch > 0 {
batch = md.batch
}
if md.filter {
for k, m := range list {
list[k] = md.db.filterFields(md.tables, m)
}
}
if md.tx == nil {
return md.db.BatchReplace(md.tables, list, batch)
} else {
return md.tx.BatchReplace(md.tables, list, batch)
}
} else if dataMap, ok := md.data.(Map); ok {
} else if data, ok := md.data.(Map); ok {
if md.filter {
data = md.db.filterFields(md.tables, data)
}
if md.tx == nil {
return md.db.Insert(md.tables, dataMap)
return md.db.Replace(md.tables, data)
} else {
return md.tx.Insert(md.tables, dataMap)
return md.tx.Replace(md.tables, data)
}
}
return nil, errors.New("replacing into table with invalid data type")
@ -225,16 +334,24 @@ func (md *Model) Save() (result sql.Result, err error) {
if md.batch > 0 {
batch = md.batch
}
if md.filter {
for k, m := range list {
list[k] = md.db.filterFields(md.tables, m)
}
}
if md.tx == nil {
return md.db.BatchSave(md.tables, list, batch)
} else {
return md.tx.BatchSave(md.tables, list, batch)
}
} else if dataMap, ok := md.data.(Map); ok {
} else if data, ok := md.data.(Map); ok {
if md.filter {
data = md.db.filterFields(md.tables, data)
}
if md.tx == nil {
return md.db.Save(md.tables, dataMap)
return md.db.Save(md.tables, data)
} else {
return md.tx.Save(md.tables, dataMap)
return md.tx.Save(md.tables, data)
}
}
return nil, errors.New("saving into table with invalid data type")
@ -250,6 +367,13 @@ func (md *Model) Update() (result sql.Result, err error) {
if md.data == nil {
return nil, errors.New("updating table with empty data")
}
if md.filter {
if data, ok := md.data.(Map); ok {
if md.filter {
md.data = md.db.filterFields(md.tables, data)
}
}
}
if md.tx == nil {
return md.db.Update(md.tables, md.data, md.where, md.whereArgs ...)
} else {
@ -264,9 +388,6 @@ func (md *Model) Delete() (result sql.Result, err error) {
md.checkAndRemoveCache()
}
}()
if md.where == "" {
return nil, errors.New("where is required while deleting")
}
if md.tx == nil {
return md.db.Delete(md.tables, md.where, md.whereArgs...)
} else {
@ -274,36 +395,14 @@ func (md *Model) Delete() (result sql.Result, err error) {
}
}
// 设置批处理的大小
func (md *Model) Batch(batch int) *Model {
md.batch = batch
return md
}
// 查询缓存/清除缓存操作,需要注意的是,事务查询不支持缓存。
// 当time < 0时表示清除缓存 time=0时表示不过期, time > 0时表示过期时间time过期时间单位
// name表示自定义的缓存名称便于业务层精准定位缓存项(如果业务层需要手动清理时,必须指定缓存名称)
// 例如:查询缓存时设置名称,清理缓存时可以给定清理的缓存名称进行精准清理。
func (md *Model) Cache(time int, name ... string) *Model {
md.cacheTime = time
if len(name) > 0 {
md.cacheName = name[0]
}
// 查询缓存特性不支持事务操作
if md.tx == nil {
md.cacheEnabled = true
}
return md
}
// 链式操作select
func (md *Model) Select() (Result, error) {
return md.getAll(md.getFormattedSql(), md.whereArgs...)
return md.All()
}
// 链式操作,查询所有记录
func (md *Model) All() (Result, error) {
return md.Select()
return md.getAll(md.getFormattedSql(), md.whereArgs...)
}
// 链式操作,查询单条记录
@ -342,8 +441,13 @@ func (md *Model) Struct(obj interface{}) error {
// 链式操作查询数量fields可以为空也可以自定义查询字段
// 当给定自定义查询字段时该字段必须为数量结果否则会引起歧义使用如md.Fields("COUNT(id)")
func (md *Model) Count() (int, error) {
defer func(fields string) {
md.fields = fields
}(md.fields)
if md.fields == "" || md.fields == "*" {
md.fields = "COUNT(1)"
} else {
md.fields = fmt.Sprintf(`COUNT(%s)`, md.fields)
}
s := md.getFormattedSql()
if len(md.groupBy) > 0 {
@ -362,29 +466,30 @@ func (md *Model) Count() (int, error) {
}
// 查询操作对底层SQL操作的封装
func (md *Model) getAll(sql string, args ...interface{}) (result Result, err error) {
var cacheKey string
func (md *Model) getAll(query string, args ...interface{}) (result Result, err error) {
cacheKey := ""
// 查询缓存查询处理
if md.cacheEnabled {
cacheKey = md.cacheName
if len(cacheKey) == 0 {
cacheKey = sql + "/" + gconv.String(args)
cacheKey = query + "/" + gconv.String(args)
}
if v := md.db.cache.Get(cacheKey); v != nil {
if v := md.db.getCache().Get(cacheKey); v != nil {
return v.(Result), nil
}
}
if md.tx == nil {
result, err = md.db.GetAll(sql, args...)
result, err = md.db.GetAll(query, args...)
} else {
result, err = md.tx.GetAll(sql, args...)
result, err = md.tx.GetAll(query, args...)
}
// 查询缓存保存处理
if len(cacheKey) > 0 && err == nil {
if md.cacheTime < 0 {
md.db.cache.Remove(cacheKey)
md.db.getCache().Remove(cacheKey)
} else {
md.db.cache.Set(cacheKey, result, md.cacheTime*1000)
md.db.getCache().Set(cacheKey, result, md.cacheTime*1000)
}
}
return result, err
@ -393,7 +498,7 @@ func (md *Model) getAll(sql string, args ...interface{}) (result Result, err err
// 检查是否需要查询查询缓存
func (md *Model) checkAndRemoveCache() {
if md.cacheEnabled && md.cacheTime < 0 && len(md.cacheName) > 0 {
md.db.cache.Remove(md.cacheName)
md.db.getCache().Remove(md.cacheName)
}
}
@ -422,11 +527,10 @@ func (md *Model) getFormattedSql() string {
// @author ymrjqyy
// @author 2018-08-15
func (md *Model) Chunk(limit int, callback func(result Result, err error) bool) {
var page = 1
page := 1
for {
md.ForPage(page, limit)
sqls := md.getFormattedSql()
data, err := md.getAll(sqls, md.whereArgs...)
data, err := md.getAll(md.getFormattedSql(), md.whereArgs...)
if err != nil {
callback(nil, err)
break

177
g/database/gdb/gdb_mssql.go Normal file
View File

@ -0,0 +1,177 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
/*
@author wenzi1<liyz23@qq.com>
@date 20181109
说明:
1.需要导入sqlserver驱动 github.com/denisenkom/go-mssqldb
2.不支持save/replace方法
3.不支持LastInsertId方法
*/
package gdb
import (
"database/sql"
"fmt"
"gitee.com/johng/gf/g/util/gregex"
"strconv"
"strings"
)
// 数据库链接对象
type dbMssql struct {
*dbBase
}
// 创建SQL操作对象
func (db *dbMssql) Open(config *ConfigNode) (*sql.DB, error) {
source := ""
if config.Linkinfo != "" {
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)
}
if db, err := sql.Open("sqlserver", source); err == nil {
return db, nil
} else {
return nil, err
}
}
// 获得关键字操作符
func (db *dbMssql) getChars() (charLeft string, charRight string) {
return "\"", "\""
}
// 在执行sql之前对sql进行进一步处理
func (db *dbMssql) handleSqlBeforeExec(query string) string {
index := 0
str, _ := gregex.ReplaceStringFunc("\\?", query, func(s string) string {
index++
return fmt.Sprintf("@p%d", index)
})
str, _ = gregex.ReplaceString("\"", "", str)
return db.parseSql(str)
}
//将MYSQL的SQL语法转换为MSSQL的语法
//1.由于mssql不支持limit写法所以需要对mysql中的limit用法做转换
func (db *dbMssql) parseSql(sql string) string {
//下面的正则表达式匹配出SELECT和INSERT的关键字后分别做不同的处理如有LIMIT则将LIMIT的关键字也匹配出
patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
if gregex.IsMatchString(patten, sql) == false {
fmt.Println("not matched..")
return sql
}
res, err := gregex.MatchAllString(patten, sql)
if err != nil {
fmt.Println("MatchString error.", err)
return ""
}
index := 0
keyword := strings.TrimSpace(res[index][0])
keyword = strings.ToUpper(keyword)
index++
switch keyword {
case "SELECT":
//不含LIMIT关键字则不处理
if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false && strings.HasPrefix(res[index][0], "limit") == false) {
break
}
//不含LIMIT则不处理
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
break
}
//判断SQL中是否含有order by
selectStr := ""
orderbyStr := ""
haveOrderby := gregex.IsMatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
if haveOrderby {
//取order by 前面的字符串
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "ORDER BY") == false {
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 {
break
}
orderbyStr = orderbyExpr[2]
} else {
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
break
}
selectStr = queryExpr[2]
}
//取limit后面的取值范围
first, limit := 0, 0
for i := 1; i < len(res[index]); i++ {
if len(strings.TrimSpace(res[index][i])) == 0 {
continue
}
if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
first, _ = strconv.Atoi(res[index][i+1])
limit, _ = strconv.Atoi(res[index][i+2])
break
}
}
if haveOrderby {
sql = fmt.Sprintf("SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROWNUMBER_, %s ) as TMP_ WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d", orderbyStr, selectStr, first, limit)
} else {
if first == 0 {
first = limit
} else {
first = limit - first
}
sql = fmt.Sprintf("SELECT * FROM (SELECT TOP %d * FROM (SELECT TOP %d %s) as TMP1_ ) as TMP2_ ", first, limit, selectStr)
}
default:
}
return sql
}
// 获得指定表表的数据结构构造成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

@ -12,22 +12,19 @@ import (
"database/sql"
)
// MySQL接口对象
var linkMysql = &dbmysql{}
// 数据库链接对象
type dbmysql struct {
Db
type dbMysql struct {
*dbBase
}
// 创建SQL操作对象内部采用了lazy link处理
func (db *dbmysql) Open (c *ConfigNode) (*sql.DB, error) {
func (db *dbMysql) Open (config *ConfigNode) (*sql.DB, error) {
var source string
if c.Linkinfo != "" {
source = c.Linkinfo
if config.Linkinfo != "" {
source = config.Linkinfo
} else {
source = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", c.User, c.Pass, c.Host, c.Port, c.Name)
source = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true",
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset)
}
if db, err := sql.Open("mysql", source); err == nil {
return db, nil
@ -36,17 +33,12 @@ func (db *dbmysql) Open (c *ConfigNode) (*sql.DB, error) {
}
}
// 获得关键字操作符 - 左
func (db *dbmysql) getQuoteCharLeft () string {
return "`"
}
// 获得关键字操作符 - 右
func (db *dbmysql) getQuoteCharRight () string {
return "`"
// 获得关键字操作符
func (db *dbMysql) getChars () (charLeft string, charRight string) {
return "`", "`"
}
// 在执行sql之前对sql进行进一步处理
func (db *dbmysql) handleSqlBeforeExec(q *string) *string {
return q
func (db *dbMysql) handleSqlBeforeExec(query string) string {
return query
}

View File

@ -21,20 +21,18 @@ import (
"strings"
)
var linkOracle = &dboracle{}
// 数据库链接对象
type dboracle struct {
Db
type dbOracle struct {
*dbBase
}
// 创建SQL操作对象
func (db *dboracle) Open(c *ConfigNode) (*sql.DB, error) {
func (db *dbOracle) Open(config *ConfigNode) (*sql.DB, error) {
var source string
if c.Linkinfo != "" {
source = c.Linkinfo
if config.Linkinfo != "" {
source = config.Linkinfo
} else {
source = fmt.Sprintf("%s/%s@%s", c.User, c.Pass, c.Name)
source = fmt.Sprintf("%s/%s@%s", config.User, config.Pass, config.Name)
}
if db, err := sql.Open("oci8", source); err == nil {
return db, nil
@ -43,42 +41,37 @@ func (db *dboracle) Open(c *ConfigNode) (*sql.DB, error) {
}
}
// 获得关键字操作符 - 左
func (db *dboracle) getQuoteCharLeft() string {
return "\""
}
// 获得关键字操作符 - 右
func (db *dboracle) getQuoteCharRight() string {
return "\""
// 获得关键字操作符
func (db *dbOracle) getChars() (charLeft string, charRight string) {
return "\"", "\""
}
// 在执行sql之前对sql进行进一步处理
func (db *dboracle) handleSqlBeforeExec(q *string) *string {
func (db *dbOracle) handleSqlBeforeExec(query string) string {
index := 0
str, _ := gregex.ReplaceStringFunc("\\?", *q, func(s string) string {
str, _ := gregex.ReplaceStringFunc("\\?", query, func(s string) string {
index++
return fmt.Sprintf(":%d", index)
})
str, _ = gregex.ReplaceString("\"", "", str)
return db.parseSql(&str)
return db.parseSql(str)
}
//由于ORACLE中对LIMIT和批量插入的语法与MYSQL不一致所以这里需要对LIMIT和批量插入做语法上的转换
func (db *dboracle) parseSql(sql *string) *string {
func (db *dbOracle) parseSql(sql string) string {
//下面的正则表达式匹配出SELECT和INSERT的关键字后分别做不同的处理如有LIMIT则将LIMIT的关键字也匹配出
patten := `^\s*(?i)(SELECT)|(INSERT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
if gregex.IsMatchString(patten, *sql) == false {
if gregex.IsMatchString(patten, sql) == false {
fmt.Println("not matched..")
return sql
}
res, err := gregex.MatchAllString(patten, *sql)
res, err := gregex.MatchAllString(patten, sql)
if err != nil {
fmt.Println("MatchString error.", err)
return nil
return ""
}
index := 0
@ -94,13 +87,14 @@ func (db *dboracle) parseSql(sql *string) *string {
}
//取limit前面的字符串
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", *sql) == false {
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
break
}
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", *sql)
queryExpr[0] = strings.TrimRight(queryExpr[0], "LIMIT")
queryExpr[0] = strings.TrimRight(queryExpr[0], "limit")
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false {
break
}
//取limit后面的取值范围
first, limit := 0, 0
@ -117,10 +111,10 @@ func (db *dboracle) parseSql(sql *string) *string {
}
//也可以使用between,据说这种写法的性能会比between好点,里层SQL中的ROWNUM_ >= limit可以缩小查询后的数据集规模
*sql = fmt.Sprintf("SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d", queryExpr[0], limit, first)
sql = fmt.Sprintf("SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d", queryExpr[1], queryExpr[2], limit, first)
case "INSERT":
//获取VALUE的值匹配所有带括号的值,会将INSERT INTO后的值匹配到所以下面的判断语句会判断数组长度是否小于3
valueExpr, err := gregex.MatchAllString(`(\s*\(([^\(\)]*)\))`, *sql)
valueExpr, err := gregex.MatchAllString(`(\s*\(([^\(\)]*)\))`, sql)
if err != nil {
return sql
}
@ -131,19 +125,46 @@ func (db *dboracle) parseSql(sql *string) *string {
}
//获取INTO后面的值
tableExpr, err := gregex.MatchString(`(?i)\s*(INTO\s+\w+\(([^\(\)]*)\))`, *sql)
tableExpr, err := gregex.MatchString(`(?i)\s*(INTO\s+\w+\(([^\(\)]*)\))`, sql)
if err != nil {
return sql
}
tableExpr[0] = strings.TrimSpace(tableExpr[0])
*sql = "INSERT ALL"
sql = "INSERT ALL"
for i := 1; i < len(valueExpr); i++ {
*sql += fmt.Sprintf(" %s VALUES%s", tableExpr[0], strings.TrimSpace(valueExpr[i][0]))
sql += fmt.Sprintf(" %s VALUES%s", tableExpr[0], strings.TrimSpace(valueExpr[i][0]))
}
*sql += " SELECT 1 FROM DUAL"
sql += " SELECT 1 FROM DUAL"
default:
}
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

@ -18,22 +18,18 @@ import (
// _ "gitee.com/johng/gf/third/github.com/lib/pq"
// @todo 需要完善replace和save的操作覆盖
// PostgreSQL接口对象
var linkPgsql = &dbpgsql{}
// 数据库链接对象
type dbpgsql struct {
Db
type dbPgsql struct {
*dbBase
}
// 创建SQL操作对象内部采用了lazy link处理
func (db *dbpgsql) Open (c *ConfigNode) (*sql.DB, error) {
func (db *dbPgsql) Open (config *ConfigNode) (*sql.DB, error) {
var source string
if c.Linkinfo != "" {
source = c.Linkinfo
if config.Linkinfo != "" {
source = config.Linkinfo
} else {
source = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s", c.User, c.Pass, c.Host, c.Port, c.Name)
source = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s", config.User, config.Pass, config.Host, config.Port, config.Name)
}
if db, err := sql.Open("postgres", source); err == nil {
return db, nil
@ -42,23 +38,18 @@ func (db *dbpgsql) Open (c *ConfigNode) (*sql.DB, error) {
}
}
// 获得关键字操作符 - 左
func (db *dbpgsql) getQuoteCharLeft () string {
return "\""
}
// 获得关键字操作符 - 右
func (db *dbpgsql) getQuoteCharRight () string {
return "\""
// 获得关键字操作符
func (db *dbPgsql) getChars () (charLeft string, charRight string) {
return "\"", "\""
}
// 在执行sql之前对sql进行进一步处理
func (db *dbpgsql) handleSqlBeforeExec(q *string) *string {
func (db *dbPgsql) handleSqlBeforeExec(query string) string {
reg := regexp.MustCompile("\\?")
index := 0
str := reg.ReplaceAllStringFunc(*q, func (s string) string {
str := reg.ReplaceAllStringFunc(query, func (s string) string {
index ++
return fmt.Sprintf("$%d", index)
})
return &str
return str
}

View File

@ -16,20 +16,18 @@ import (
// Sqlite接口对象
// @author wxkj<wxscz@qq.com>
var linkSqlite = &dbsqlite{}
// 数据库链接对象
type dbsqlite struct {
Db
type dbSqlite struct {
*dbBase
}
func (db *dbsqlite) Open(c *ConfigNode) (*sql.DB, error) {
func (db *dbSqlite) Open(config *ConfigNode) (*sql.DB, error) {
var source string
if c.Linkinfo != "" {
source = c.Linkinfo
if config.Linkinfo != "" {
source = config.Linkinfo
} else {
source = c.Name
source = config.Name
}
if db, err := sql.Open("sqlite3", source); err == nil {
return db, nil
@ -38,20 +36,14 @@ func (db *dbsqlite) Open(c *ConfigNode) (*sql.DB, error) {
}
}
// 获得关键字操作符 - 左
func (db *dbsqlite) getQuoteCharLeft() string {
return "`"
}
// 获得关键字操作符 - 右
func (db *dbsqlite) getQuoteCharRight() string {
return "`"
// 获得关键字操作符
func (db *dbSqlite) getChars () (charLeft string, charRight string) {
return "`", "`"
}
// 在执行sql之前对sql进行进一步处理
// @todo 需要增加对Save方法的支持可使用正则来实现替换
// @todo 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE)
func (db *dbsqlite) handleSqlBeforeExec(q *string) *string {
return q
func (db *dbSqlite) handleSqlBeforeExec(query string) string {
return query
}

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

@ -7,132 +7,55 @@
package gdb
import (
"fmt"
"errors"
"strings"
"reflect"
"database/sql"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/util/gregex"
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
"gitee.com/johng/gf/g/container/gvar"
)
// 数据库事务对象
type Tx struct {
db *Db
type TX struct {
db DB
tx *sql.Tx
master *sql.DB
}
// 事务操作,提交
func (tx *Tx) Commit() error {
err := tx.tx.Commit()
tx.master.Close()
return err
func (tx *TX) Commit() error {
return tx.tx.Commit()
}
// 事务操作,回滚
func (tx *Tx) Rollback() error {
err := tx.tx.Rollback()
tx.master.Close()
return err
func (tx *TX) Rollback() error {
return tx.tx.Rollback()
}
// (事务)数据库sql查询操作主要执行查询
func (tx *Tx) Query(query string, args ...interface{}) (*sql.Rows, error) {
var err error
var rows *sql.Rows
p := tx.db.link.handleSqlBeforeExec(&query)
if tx.db.debug.Val() {
militime1 := gtime.Millisecond()
rows, err = tx.tx.Query(*p, args ...)
militime2 := gtime.Millisecond()
s := &Sql{
Sql : *p,
Args : args,
Error : err,
Start : militime1,
End : militime2,
Func : "TX:Query",
}
tx.db.sqls.Put(s)
tx.db.printSql(s)
} else {
rows, err = tx.tx.Query(*p, args ...)
}
if err == nil {
return rows, nil
} else {
err = tx.db.formatError(err, p, args...)
}
return nil, err
func (tx *TX) Query(query string, args ...interface{}) (rows *sql.Rows, err error) {
return tx.db.doQuery(tx.tx, query, args...)
}
// (事务)执行一条sql并返回执行情况主要用于非查询操作
func (tx *Tx) Exec(query string, args ...interface{}) (sql.Result, error) {
var err error
var result sql.Result
p := tx.db.link.handleSqlBeforeExec(&query)
if tx.db.debug.Val() {
militime1 := gtime.Millisecond()
result, err = tx.tx.Exec(*p, args ...)
militime2 := gtime.Millisecond()
s := &Sql{
Sql : *p,
Args : args,
Error : err,
Start : militime1,
End : militime2,
Func : "TX:Exec",
}
tx.db.sqls.Put(s)
tx.db.printSql(s)
} else {
result, err = tx.tx.Exec(*p, args ...)
}
return result, tx.db.formatError(err, p, args...)
func (tx *TX) Exec(query string, args ...interface{}) (sql.Result, error) {
return tx.db.doExec(tx.tx, query, args...)
}
// sql预处理执行完成后调用返回值sql.Stmt.Exec完成sql操作
func (tx *TX) Prepare(query string) (*sql.Stmt, error) {
return tx.db.doPrepare(tx.tx, query)
}
// 数据库查询,获取查询结果集,以列表结构返回
func (tx *Tx) GetAll(query string, args ...interface{}) (Result, error) {
// 执行sql
func (tx *TX) GetAll(query string, args ...interface{}) (Result, error) {
rows, err := tx.Query(query, args ...)
if err != nil || rows == nil {
return nil, err
}
// 列名称列表
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 {
v := make([]byte, len(col))
copy(v, col)
row[columns[i]] = gvar.New(v)
}
//fmt.Printf("%p\n", row["typeid"])
records = append(records, row)
}
return records, nil
defer rows.Close()
return tx.db.rowsToResult(rows)
}
// 数据库查询,获取查询结果记录,以关联数组结构返回
func (tx *Tx) GetOne(query string, args ...interface{}) (Record, error) {
func (tx *TX) GetOne(query string, args ...interface{}) (Record, error) {
list, err := tx.GetAll(query, args ...)
if err != nil {
return nil, err
@ -144,7 +67,7 @@ func (tx *Tx) GetOne(query string, args ...interface{}) (Record, error) {
}
// 数据库查询获取查询结果记录自动映射数据到给定的struct对象中
func (tx *Tx) GetStruct(obj interface{}, query string, args ...interface{}) error {
func (tx *TX) GetStruct(obj interface{}, query string, args ...interface{}) error {
one, err := tx.GetOne(query, args...)
if err != nil {
return err
@ -152,9 +75,8 @@ func (tx *Tx) GetStruct(obj interface{}, query string, args ...interface{}) erro
return one.ToStruct(obj)
}
// 数据库查询,获取查询字段值
func (tx *Tx) GetValue(query string, args ...interface{}) (Value, error) {
func (tx *TX) GetValue(query string, args ...interface{}) (Value, error) {
one, err := tx.GetOne(query, args ...)
if err != nil {
return nil, err
@ -166,187 +88,55 @@ func (tx *Tx) GetValue(query string, args ...interface{}) (Value, error) {
}
// 数据库查询,获取查询数量
func (tx *Tx) GetCount(query string, args ...interface{}) (int, error) {
val, err := tx.GetValue(query, args ...)
func (tx *TX) GetCount(query string, args ...interface{}) (int, error) {
if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, query) {
query, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, query)
}
value, err := tx.GetValue(query, args ...)
if err != nil {
return 0, err
}
return gconv.Int(val), nil
}
// 数据表查询其中tables可以是多个联表查询语句这种查询方式较复杂建议使用链式操作
func (tx *Tx) Select(tables, fields string, condition interface{}, groupBy, orderBy string, first, limit int, args ... interface{}) (Result, error) {
s := fmt.Sprintf("SELECT %s FROM %s ", fields, tables)
if condition != nil {
s += fmt.Sprintf("WHERE %s ", tx.db.formatCondition(condition))
}
if len(groupBy) > 0 {
s += fmt.Sprintf("GROUP BY %s ", groupBy)
}
if len(orderBy) > 0 {
s += fmt.Sprintf("ORDER BY %s ", orderBy)
}
if limit > 0 {
s += fmt.Sprintf("LIMIT %d,%d ", first, limit)
}
return tx.GetAll(s, args ... )
}
// sql预处理执行完成后调用返回值sql.Stmt.Exec完成sql操作
// 记得调用sql.Stmt.Close关闭操作对象
func (tx *Tx) Prepare(query string) (*sql.Stmt, error) {
return tx.tx.Prepare(query)
}
// insert、replace, save ignore操作
// 0: insert: 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
// 1: replace: 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
// 2: save: 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
// 3: ignore: 如果数据存在(主键或者唯一索引),那么什么也不做
func (tx *Tx) insert(table string, data Map, option uint8) (sql.Result, error) {
var keys []string
var values []string
var params []interface{}
for k, v := range data {
keys = append(keys, tx.db.charl + k + tx.db.charr)
values = append(values, "?")
params = append(params, v)
}
operation := tx.db.getInsertOperationByOption(option)
updatestr := ""
if option == OPTION_SAVE {
var updates []string
for k, _ := range data {
updates = append(updates, fmt.Sprintf("%s%s%s=VALUES(%s)", tx.db.charl, k, tx.db.charr, k))
}
updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
}
return tx.Exec(
fmt.Sprintf("%s INTO %s(%s) VALUES(%s) %s",
operation, table, strings.Join(keys, ","),
strings.Join(values, ","),
updatestr),
params...
)
return value.Int(), nil
}
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
func (tx *Tx) Insert(table string, data Map) (sql.Result, error) {
return tx.insert(table, data, OPTION_INSERT)
func (tx *TX) Insert(table string, data Map) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_INSERT)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (tx *Tx) Replace(table string, data Map) (sql.Result, error) {
return tx.insert(table, data, OPTION_REPLACE)
func (tx *TX) Replace(table string, data Map) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_REPLACE)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (tx *Tx) Save(table string, data Map) (sql.Result, error) {
return tx.insert(table, data, OPTION_SAVE)
}
// 批量写入数据
func (tx *Tx) batchInsert(table string, list List, batch int, option uint8) (sql.Result, error) {
var keys []string
var values []string
var bvalues []string
var params []interface{}
var result sql.Result
var size = len(list)
// 判断长度
if size < 1 {
return result, errors.New("empty data list")
}
// 首先获取字段名称及记录长度
for k, _ := range list[0] {
keys = append(keys, k)
values = append(values, "?")
}
keyStr := tx.db.charl + strings.Join(keys, tx.db.charl + "," + tx.db.charr) + tx.db.charr
valueHolderStr := "(" + strings.Join(values, ",") + ")"
// 操作判断
operation := tx.db.getInsertOperationByOption(option)
updatestr := ""
if option == OPTION_SAVE {
var updates []string
for _, k := range keys {
updates = append(updates, fmt.Sprintf("%s%s%s=VALUES(%s)", tx.db.charl, k, tx.db.charr, k))
}
updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
}
// 构造批量写入数据格式(注意map的遍历是无序的)
for i := 0; i < size; i++ {
for _, k := range keys {
params = append(params, list[i][k])
}
bvalues = append(bvalues, valueHolderStr)
if len(bvalues) == batch {
r, err := tx.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
updatestr),
params...)
if err != nil {
return result, err
}
result = r
params = params[:0]
bvalues = bvalues[:0]
}
}
// 处理最后不构成指定批量的数据
if len(bvalues) > 0 {
r, err := tx.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
updatestr),
params...)
if err != nil {
return result, err
}
result = r
}
return result, nil
func (tx *TX) Save(table string, data Map) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_SAVE)
}
// CURD操作:批量数据指定批次量写入
func (tx *Tx) BatchInsert(table string, list List, batch int) (sql.Result, error) {
return tx.batchInsert(table, list, batch, OPTION_INSERT)
func (tx *TX) BatchInsert(table string, list List, batch int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, batch, OPTION_INSERT)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (tx *Tx) BatchReplace(table string, list List, batch int) (sql.Result, error) {
return tx.batchInsert(table, list, batch, OPTION_REPLACE)
func (tx *TX) BatchReplace(table string, list List, batch int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, batch, OPTION_REPLACE)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (tx *Tx) BatchSave(table string, list List, batch int) (sql.Result, error) {
return tx.batchInsert(table, list, batch, OPTION_SAVE)
func (tx *TX) BatchSave(table string, list List, batch int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, batch, OPTION_SAVE)
}
// CURD操作:数据更新统一采用sql预处理
// data参数支持字符串或者关联数组类型内部会自行做判断处理
func (tx *Tx) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
var params []interface{}
var updates string
refValue := reflect.ValueOf(data)
if refValue.Kind() == reflect.Map {
var fields []string
keys := refValue.MapKeys()
for _, k := range keys {
fields = append(fields, fmt.Sprintf("%s%s%s=?", tx.db.charl, k, tx.db.charr))
params = append(params, gconv.String(refValue.MapIndex(k).Interface()))
updates = strings.Join(fields, ",")
}
} else {
updates = gconv.String(data)
}
for _, v := range args {
params = append(params, gconv.String(v))
}
return tx.Exec(fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, tx.db.formatCondition(condition)), params...)
func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
return tx.db.doUpdate(tx.tx, table, data, condition, args ...)
}
// CURD操作:删除数据
func (tx *Tx) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
return tx.Exec(fmt.Sprintf("DELETE FROM %s WHERE %s", table, tx.db.formatCondition(condition)), args...)
func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
return tx.db.doDelete(tx.tx, table, condition, args ...)
}

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

@ -0,0 +1,52 @@
package gdb_test
import (
"gitee.com/johng/gf/g/database/gdb"
"gitee.com/johng/gf/g/util/gtest"
)
var (
// 数据库对象/接口
db gdb.DB
)
// 初始化连接参数。
// 测试前需要修改连接参数。
func init() {
gdb.AddDefaultConfigNode(gdb.ConfigNode{
Host: "127.0.0.1",
Port: "3306",
User: "root",
Pass: "",
Name: "",
Type: "mysql",
Role: "master",
Charset: "utf8",
Priority: 1,
})
if r, err := gdb.New(); err != nil {
gtest.Fatal(err)
} else {
db = r
}
// 准备测试数据结构
if _, err := db.Exec("CREATE DATABASE IF NOT EXISTS `test` CHARACTER SET UTF8"); err != nil {
gtest.Fatal(err)
}
db.SetSchema("test")
if _, err := db.Exec("DROP TABLE IF EXISTS `user`"); err != nil {
gtest.Fatal(err)
}
if _, err := db.Exec(`
CREATE TABLE user (
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID',
passport varchar(45) NOT NULL COMMENT '账号',
password char(32) NOT NULL COMMENT '密码',
nickname varchar(45) NOT NULL COMMENT '昵称',
create_time timestamp NOT NULL COMMENT '创建时间/注册时间',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`); err != nil {
gtest.Fatal(err)
}
}

View File

@ -0,0 +1,172 @@
package gdb_test
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/util/gtest"
"testing"
)
func TestDbBase_Query(t *testing.T) {
if _, err := db.Query("SELECT ?", 1); err != nil {
gtest.Fatal(err)
}
if _, err := db.Query("ERROR"); err == nil {
gtest.Fatal("FAIL")
}
}
func TestDbBase_Exec(t *testing.T) {
if _, err := db.Exec("SELECT ?", 1); err != nil {
gtest.Fatal(err)
}
if _, err := db.Exec("ERROR"); err == nil {
gtest.Fatal("FAIL")
}
}
func TestDbBase_Prepare(t *testing.T) {
st, err := db.Prepare("SELECT 100")
if err != nil {
gtest.Fatal(err)
}
rows, err := st.Query()
if err != nil {
gtest.Fatal(err)
}
array, err := rows.Columns()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(array[0], "100")
if err := rows.Close(); err != nil {
gtest.Fatal(err)
}
}
func TestDbBase_Insert(t *testing.T) {
if _, err := db.Insert("user", g.Map{
"id" : 1,
"passport" : "t1",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T1",
"create_time" : gtime.Now().String(),
}); err != nil {
gtest.Fatal(err)
}
}
func TestDbBase_BatchInsert(t *testing.T) {
if _, err := db.BatchInsert("user", g.List {
{
"id" : 2,
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
},
{
"id" : 3,
"passport" : "t3",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T3",
"create_time" : gtime.Now().String(),
},
}, 10); err != nil {
gtest.Fatal(err)
}
}
func TestDbBase_Save(t *testing.T) {
if _, err := db.Save("user", g.Map{
"id" : 1,
"passport" : "t1",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T11",
"create_time" : gtime.Now().String(),
}); err != nil {
gtest.Fatal(err)
}
}
func TestDbBase_Replace(t *testing.T) {
if _, err := db.Save("user", g.Map{
"id" : 1,
"passport" : "t1",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T111",
"create_time" : gtime.Now().String(),
}); err != nil {
gtest.Fatal(err)
}
}
func TestDbBase_Update(t *testing.T) {
if result, err := db.Update("user", "create_time='2010-10-10 00:00:01'", "id=3"); err != nil {
gtest.Fatal(err)
} else {
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
}
}
func TestDbBase_GetAll(t *testing.T) {
if result, err := db.GetAll("SELECT * FROM user WHERE id=?", 1); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(len(result), 1)
}
}
func TestDbBase_GetOne(t *testing.T) {
if record, err := db.GetOne("SELECT * FROM user WHERE passport=?", "t1"); err != nil {
gtest.Fatal(err)
} else {
if record == nil {
gtest.Fatal("FAIL")
}
gtest.Assert(record["nickname"].String(), "T111")
}
}
func TestDbBase_GetValue(t *testing.T) {
if value, err := db.GetValue("SELECT id FROM user WHERE passport=?", "t3"); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(value.Int(), 3)
}
}
func TestDbBase_GetCount(t *testing.T) {
if count, err := db.GetCount("SELECT * FROM user"); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(count, 3)
}
}
func TestDbBase_GetStruct(t *testing.T) {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
if err := db.GetStruct(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
}
}
func TestDbBase_Delete(t *testing.T) {
if result, err := db.Delete("user", nil); err != nil {
gtest.Fatal(err)
} else {
n, _ := result.RowsAffected()
gtest.Assert(n, 3)
}
}

View File

@ -0,0 +1,220 @@
package gdb_test
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/util/gtest"
"testing"
)
func TestModel_Insert(t *testing.T) {
result, err := db.Table("user").Filter().Data(g.Map{
"id" : 1,
"uid" : 1,
"passport" : "t1",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T1",
"create_time" : gtime.Now().String(),
}).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ := result.LastInsertId()
gtest.Assert(n, 1)
}
func TestModel_Batch(t *testing.T) {
result, err := db.Table("user").Filter().Data(g.List{
{
"id" : 2,
"uid" : 2,
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
},
{
"id" : 3,
"uid" : 3,
"passport" : "t3",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T3",
"create_time" : gtime.Now().String(),
},
}).Batch(10).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 2)
}
func TestModel_Replace(t *testing.T) {
result, err := db.Table("user").Data(g.Map{
"id" : 1,
"passport" : "t11",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T11",
"create_time" : gtime.Now().String(),
}).Replace()
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 2)
}
func TestModel_Save(t *testing.T) {
result, err := db.Table("user").Data(g.Map{
"id" : 1,
"passport" : "t111",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T111",
"create_time" : gtime.Now().String(),
}).Save()
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 2)
}
func TestModel_Update(t *testing.T) {
result, err := db.Table("user").Data("passport", "t22").Where("passport=?", "t2").Update()
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
}
func TestModel_Clone(t *testing.T) {
md := db.Table("user").Where("id IN(?)", g.Slice{1,3})
count, err := md.Count()
if err != nil {
gtest.Fatal(err)
}
record, err := md.OrderBy("id DESC").One()
if err != nil {
gtest.Fatal(err)
}
result, err := md.OrderBy("id ASC").All()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 2)
gtest.Assert(record["id"].Int(), 3)
gtest.Assert(len(result), 2)
gtest.Assert(result[0]["id"].Int(), 1)
gtest.Assert(result[1]["id"].Int(), 3)
}
func TestModel_All(t *testing.T) {
result, err := db.Table("user").All()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(result), 3)
}
func TestModel_One(t *testing.T) {
record, err := db.Table("user").Where("id", 1).One()
if err != nil {
gtest.Fatal(err)
}
if record == nil {
gtest.Fatal("FAIL")
}
gtest.Assert(record["nickname"].String(), "T111")
}
func TestModel_Value(t *testing.T) {
value, err := db.Table("user").Fields("nickname").Where("id", 1).Value()
if err != nil {
gtest.Fatal(err)
}
if value == nil {
gtest.Fatal("FAIL")
}
gtest.Assert(value.String(), "T111")
}
func TestModel_Count(t *testing.T) {
count, err := db.Table("user").Count()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 3)
}
func TestModel_Select(t *testing.T) {
result, err := db.Table("user").Select()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(result), 3)
}
func TestModel_Struct(t *testing.T) {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
err := db.Table("user").Where("id=1").Struct(user)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T111")
}
func TestModel_OrderBy(t *testing.T) {
result, err := db.Table("user").OrderBy("id DESC").Select()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["nickname"].String(), "T3")
}
func TestModel_GroupBy(t *testing.T) {
result, err := db.Table("user").GroupBy("id").Select()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["nickname"].String(), "T111")
}
func TestModel_Where1(t *testing.T) {
result, err := db.Table("user").Where("id IN(?)", g.Slice{1,3}).OrderBy("id ASC").All()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(result), 2)
gtest.Assert(result[0]["id"].Int(), 1)
gtest.Assert(result[1]["id"].Int(), 3)
}
func TestModel_Where2(t *testing.T) {
result, err := db.Table("user").Where("nickname=? AND id IN(?)", "T3", g.Slice{1,3}).OrderBy("id ASC").All()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 3)
}
func TestModel_Delete(t *testing.T) {
result, err := db.Table("user").Delete()
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 3)
}

View File

@ -0,0 +1,372 @@
package gdb_test
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/util/gtest"
"testing"
)
func TestTX_Query(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if rows, err := tx.Query("SELECT ?", 1); err != nil {
gtest.Fatal(err)
} else {
rows.Close()
}
if _, err := tx.Query("ERROR"); err == nil {
gtest.Fatal("FAIL")
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
}
func TestTX_Exec(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if _, err := tx.Exec("SELECT ?", 1); err != nil {
gtest.Fatal(err)
}
if _, err := tx.Exec("ERROR"); err == nil {
gtest.Fatal("FAIL")
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
}
func TestTX_Commit(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
}
func TestTX_Rollback(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if err := tx.Rollback(); err != nil {
gtest.Fatal(err)
}
}
func TestTX_Prepare(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
st, err := tx.Prepare("SELECT 100")
if err != nil {
gtest.Fatal(err)
}
rows, err := st.Query()
if err != nil {
gtest.Fatal(err)
}
array, err := rows.Columns()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(array[0], "100")
if err := rows.Close(); err != nil {
gtest.Fatal(err)
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
}
func TestTX_Insert(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if _, err := tx.Insert("user", g.Map {
"id" : 1,
"passport" : "t1",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T1",
"create_time" : gtime.Now().String(),
}); err != nil {
gtest.Fatal(err)
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
if n, err := db.Table("user").Count(); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(n, 1)
}
}
func TestTX_BatchInsert(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if _, err := tx.BatchInsert("user", g.List {
{
"id" : 2,
"passport" : "t",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
},
{
"id" : 3,
"passport" : "t3",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T3",
"create_time" : gtime.Now().String(),
},
}, 10); err != nil {
gtest.Fatal(err)
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
if n, err := db.Table("user").Count(); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(n, 3)
}
}
func TestTX_BatchReplace(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if _, err := tx.BatchReplace("user", g.List {
{
"id" : 2,
"passport" : "t2",
"password" : "p2",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
},
{
"id" : 4,
"passport" : "t4",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T4",
"create_time" : gtime.Now().String(),
},
}, 10); err != nil {
gtest.Fatal(err)
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
// 数据数量
if n, err := db.Table("user").Count(); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(n, 4)
}
// 检查replace后的数值
if value, err := db.Table("user").Fields("password").Where("id", 2).Value(); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(value.String(), "p2")
}
}
func TestTX_BatchSave(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if _, err := tx.BatchSave("user", g.List {
{
"id" : 4,
"passport" : "t4",
"password" : "p4",
"nickname" : "T4",
"create_time" : gtime.Now().String(),
},
}, 10); err != nil {
gtest.Fatal(err)
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
// 数据数量
if n, err := db.Table("user").Count(); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(n, 4)
}
// 检查replace后的数值
if value, err := db.Table("user").Fields("password").Where("id", 4).Value(); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(value.String(), "p4")
}
}
func TestTX_Replace(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if _, err := tx.Replace("user", g.Map {
"id" : 1,
"passport" : "t11",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T11",
"create_time" : gtime.Now().String(),
}); err != nil {
gtest.Fatal(err)
}
if err := tx.Rollback(); err != nil {
gtest.Fatal(err)
}
if value, err := db.Table("user").Fields("nickname").Where("id", 1).Value(); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(value.String(), "T1")
}
}
func TestTX_Save(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if _, err := tx.Save("user", g.Map {
"id" : 1,
"passport" : "t11",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T11",
"create_time" : gtime.Now().String(),
}); err != nil {
gtest.Fatal(err)
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
if value, err := db.Table("user").Fields("nickname").Where("id", 1).Value(); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(value.String(), "T11")
}
}
func TestTX_GetAll(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if result, err := tx.GetAll("SELECT * FROM user WHERE id=?", 1); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(len(result), 1)
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
}
func TestTX_GetOne(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if record, err := tx.GetOne("SELECT * FROM user WHERE passport=?", "t2"); err != nil {
gtest.Fatal(err)
} else {
if record == nil {
gtest.Fatal("FAIL")
}
gtest.Assert(record["nickname"].String(), "T2")
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
}
func TestTX_GetValue(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if value, err := tx.GetValue("SELECT id FROM user WHERE passport=?", "t3"); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(value.Int(), 3)
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
}
func TestTX_GetCount(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if count, err := tx.GetCount("SELECT * FROM user"); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(count, 4)
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
}
func TestTX_GetStruct(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
if err := tx.GetStruct(user, "SELECT * FROM user WHERE id=?", 1); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(user.NickName, "T11")
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
}
func TestTX_Delete(t *testing.T) {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
if _, err := tx.Delete("user", nil); err != nil {
gtest.Fatal(err)
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
if n, err := db.Table("user").Count(); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(n, 0)
}
}

View File

@ -4,16 +4,17 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// Kafka Client.
// Package gkafka provides producer and consumer client for kafka server.
//
// Kafka客户端.
package gkafka
import (
"gitee.com/johng/gf/g/os/glog"
"time"
"strings"
"gitee.com/johng/gf/third/github.com/Shopify/sarama"
"gitee.com/johng/gf/third/github.com/johng-cn/sarama-cluster"
"errors"
"strings"
"time"
)
var (
@ -177,8 +178,6 @@ func (client *Client) Receive() (*Message, error) {
case <-notifyChan:
}
}
return nil, errors.New("unknown error")
}
// Send data to kafka in synchronized way.

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 (
@ -87,6 +89,12 @@ func (r *Redis) Close() error {
return r.pool.Close()
}
// 获得一个原生的redis连接对象用于自定义连接操作
// 但是需要注意的是如果不再使用该连接对象时需要手动Close连接否则会造成连接数超限。
func (r *Redis) GetConn() redis.Conn {
return r.pool.Get()
}
// 设置属性 - MaxIdle
func (r *Redis) SetMaxIdle(value int) {
r.pool.MaxIdle = value

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 (
@ -174,24 +174,28 @@ func EncodeFloat64(f float64) []byte {
// 当b位数不够时进行高位补0
func fillUpSize(b []byte, l int) []byte {
if len(b) >= l {
return b
}
c := make([]byte, 0)
c = append(c, b...)
for i := 0; i <= l - len(b); i++ {
for i := 0; i < l - len(b); i++ {
c = append(c, 0x00)
}
return c
}
// 将二进制解析为int类型根据[]byte的长度进行自动转换
// 将二进制解析为int类型根据[]byte的长度进行自动转换.
// 注意内部使用的是uint*使用int会造成位丢失。
func DecodeToInt(b []byte) int {
if len(b) < 2 {
return int(DecodeToInt8(b))
return int(DecodeToUint8(b))
} else if len(b) < 3 {
return int(DecodeToInt16(b))
return int(DecodeToUint16(b))
} else if len(b) < 5 {
return int(DecodeToInt32(b))
return int(DecodeToUint32(b))
} else {
return int(DecodeToInt64(b))
return int(DecodeToUint64(b))
}
}

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

@ -9,103 +9,106 @@
package ghash_test
import (
"testing"
"gitee.com/johng/gf/g/encoding/ghash"
"gitee.com/johng/gf/g/encoding/gbinary"
"testing"
)
var (
str = []byte("This is the test string for hash.")
)
func BenchmarkBKDRHash(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.BKDRHash(gbinary.EncodeInt(i))
ghash.BKDRHash(str)
}
}
func BenchmarkBKDRHash64(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.BKDRHash64(gbinary.EncodeInt(i))
ghash.BKDRHash64(str)
}
}
func BenchmarkSDBMHash(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.SDBMHash(gbinary.EncodeInt(i))
ghash.SDBMHash(str)
}
}
func BenchmarkSDBMHash64(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.SDBMHash64(gbinary.EncodeInt(i))
ghash.SDBMHash64(str)
}
}
func BenchmarkRSHash(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.RSHash(gbinary.EncodeInt(i))
ghash.RSHash(str)
}
}
func BenchmarkSRSHash64(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.RSHash64(gbinary.EncodeInt(i))
ghash.RSHash64(str)
}
}
func BenchmarkJSHash(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.JSHash(gbinary.EncodeInt(i))
ghash.JSHash(str)
}
}
func BenchmarkJSHash64(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.JSHash64(gbinary.EncodeInt(i))
ghash.JSHash64(str)
}
}
func BenchmarkPJWHash(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.PJWHash(gbinary.EncodeInt(i))
ghash.PJWHash(str)
}
}
func BenchmarkPJWHash64(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.PJWHash64(gbinary.EncodeInt(i))
ghash.PJWHash64(str)
}
}
func BenchmarkELFHash(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.ELFHash(gbinary.EncodeInt(i))
ghash.ELFHash(str)
}
}
func BenchmarkELFHash64(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.ELFHash64(gbinary.EncodeInt(i))
ghash.ELFHash64(str)
}
}
func BenchmarkDJBHash(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.DJBHash(gbinary.EncodeInt(i))
ghash.DJBHash(str)
}
}
func BenchmarkDJBHash64(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.DJBHash64(gbinary.EncodeInt(i))
ghash.DJBHash64(str)
}
}
func BenchmarkAPHash(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.APHash(gbinary.EncodeInt(i))
ghash.APHash(str)
}
}
func BenchmarkAPHash64(b *testing.B) {
for i := 0; i < b.N; i++ {
ghash.APHash64(gbinary.EncodeInt(i))
ghash.APHash64(str)
}
}

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,36 +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{
j = &Json {
p : &value,
c : byte(gDEFAULT_SPLIT_CHAR),
vc : false ,
}
case string, []byte:
j, _ = LoadContent(gconv.Bytes(value))
default:
// 这里效率会比较低
b, _ := Encode(value)
v, _ := Decode(b)
j = &Json{
p : &v,
c : byte(gDEFAULT_SPLIT_CHAR),
vc : false,
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字符串指针
@ -107,39 +117,46 @@ 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":
case "xml", ".xml":
data, err = gxml.ToJson(data)
if err != nil {
return nil, err
}
case "yml": fallthrough
case "yaml": fallthrough
case ".yml": fallthrough
case ".yaml":
case "yml", "yaml", ".yml", ".yaml":
data, err = gyaml.ToJson(data)
if err != nil {
return nil, err
}
case "toml": fallthrough
case ".toml":
case "toml", ".toml":
data, err = gtoml.ToJson(data)
if err != nil {
return nil, err
}
}
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
if result == nil {
if err := json.Unmarshal(data, &result); err != nil {
return nil, err
}
}
return New(result), nil
}
@ -328,136 +345,136 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
defer j.mu.Unlock()
for i:= 0; i < length; i++ {
switch (*pointer).(type) {
case map[string]interface{}:
if i == length - 1 {
case map[string]interface{}:
if i == length - 1 {
if removed && value == nil {
// 删除map元素
delete((*pointer).(map[string]interface{}), array[i])
} else {
(*pointer).(map[string]interface{})[array[i]] = value
}
} else {
// 当键名不存在的情况这里会进行处理
if v, ok := (*pointer).(map[string]interface{})[array[i]]; !ok {
if removed && value == nil {
// 删除map元素
delete((*pointer).(map[string]interface{}), array[i])
} else {
(*pointer).(map[string]interface{})[array[i]] = value
goto done
}
} else {
// 当键名不存在的情况这里会进行处理
if v, ok := (*pointer).(map[string]interface{})[array[i]]; !ok {
if removed && value == nil {
goto done
}
// 创建新节点
if gstr.IsNumeric(array[i + 1]) {
// 创建array节点
n, _ := strconv.Atoi(array[i + 1])
var v interface{} = make([]interface{}, n + 1)
pparent = j.setPointerWithValue(pointer, array[i], v)
pointer = &v
} else {
// 创建map节点
var v interface{} = make(map[string]interface{})
pparent = j.setPointerWithValue(pointer, array[i], v)
pointer = &v
}
} else {
pparent = pointer
pointer = &v
}
}
case []interface{}:
// 键名与当前指针类型不符合,需要执行**覆盖操作**
if !gstr.IsNumeric(array[i]) {
if i == length - 1 {
*pointer = map[string]interface{}{ array[i] : value }
} else {
var v interface{} = make(map[string]interface{})
*pointer = v
pparent = pointer
pointer = &v
}
continue
}
valn, err := strconv.Atoi(array[i])
if err != nil {
return err
}
// 叶子节点
if i == length - 1 {
if len((*pointer).([]interface{})) > valn {
if removed && value == nil {
// 删除数据元素
j.setPointerWithValue(pparent, array[i - 1], append((*pointer).([]interface{})[ : valn], (*pointer).([]interface{})[valn + 1 : ]...))
} else {
(*pointer).([]interface{})[valn] = value
}
} else {
if removed && value == nil {
goto done
}
if pparent == nil {
// 表示根节点
j.setPointerWithValue(pointer, array[i], value)
} else {
// 非根节点
s := make([]interface{}, valn + 1)
copy(s, (*pointer).([]interface{}))
s[valn] = value
j.setPointerWithValue(pparent, array[i - 1], s)
}
}
} else {
// 创建新节点
if gstr.IsNumeric(array[i + 1]) {
// 创建array节点
n, _ := strconv.Atoi(array[i + 1])
if len((*pointer).([]interface{})) > valn {
(*pointer).([]interface{})[valn] = make([]interface{}, n + 1)
pparent = pointer
pointer = &(*pointer).([]interface{})[valn]
} else {
if removed && value == nil {
goto done
}
var v interface{} = make([]interface{}, n + 1)
pparent = j.setPointerWithValue(pointer, array[i], v)
pointer = &v
}
var v interface{} = make([]interface{}, n + 1)
pparent = j.setPointerWithValue(pointer, array[i], v)
pointer = &v
} else {
// 创建map节点
var v interface{} = make(map[string]interface{})
pparent = j.setPointerWithValue(pointer, array[i], v)
pointer = &v
}
} else {
pparent = pointer
pointer = &v
}
}
// 如果当前指针指向的变量不是引用类型的,
// 那么修改变量必须通过父级进行修改,即 pparent
default:
if removed && value == nil {
goto done
case []interface{}:
// 键名与当前指针类型不符合,需要执行**覆盖操作**
if !gstr.IsNumeric(array[i]) {
if i == length - 1 {
*pointer = map[string]interface{}{ array[i] : value }
} else {
var v interface{} = make(map[string]interface{})
*pointer = v
pparent = pointer
pointer = &v
}
if gstr.IsNumeric(array[i]) {
n, _ := strconv.Atoi(array[i])
s := make([]interface{}, n + 1)
if i == length - 1 {
s[n] = value
}
if pparent != nil {
pparent = j.setPointerWithValue(pparent, array[i - 1], s)
continue
}
valn, err := strconv.Atoi(array[i])
if err != nil {
return err
}
// 叶子节点
if i == length - 1 {
if len((*pointer).([]interface{})) > valn {
if removed && value == nil {
// 删除数据元素
j.setPointerWithValue(pparent, array[i - 1], append((*pointer).([]interface{})[ : valn], (*pointer).([]interface{})[valn + 1 : ]...))
} else {
*pointer = s
pparent = pointer
(*pointer).([]interface{})[valn] = value
}
} else {
if removed && value == nil {
goto done
}
if pparent == nil {
// 表示根节点
j.setPointerWithValue(pointer, array[i], value)
} else {
// 非根节点
s := make([]interface{}, valn + 1)
copy(s, (*pointer).([]interface{}))
s[valn] = value
j.setPointerWithValue(pparent, array[i - 1], s)
}
}
} else {
if gstr.IsNumeric(array[i + 1]) {
n, _ := strconv.Atoi(array[i + 1])
if len((*pointer).([]interface{})) > valn {
(*pointer).([]interface{})[valn] = make([]interface{}, n + 1)
pparent = pointer
pointer = &(*pointer).([]interface{})[valn]
} else {
if removed && value == nil {
goto done
}
var v interface{} = make([]interface{}, n + 1)
pparent = j.setPointerWithValue(pointer, array[i], v)
pointer = &v
}
} else {
var v interface{} = make(map[string]interface{})
if i == length - 1 {
v = map[string]interface{}{
array[i] : value,
}
}
if pparent != nil {
pparent = j.setPointerWithValue(pparent, array[i - 1], v)
} else {
*pointer = v
pparent = pointer
}
pparent = j.setPointerWithValue(pointer, array[i], v)
pointer = &v
}
}
// 如果当前指针指向的变量不是引用类型的,
// 那么修改变量必须通过父级进行修改,即 pparent
default:
if removed && value == nil {
goto done
}
if gstr.IsNumeric(array[i]) {
n, _ := strconv.Atoi(array[i])
s := make([]interface{}, n + 1)
if i == length - 1 {
s[n] = value
}
if pparent != nil {
pparent = j.setPointerWithValue(pparent, array[i - 1], s)
} else {
*pointer = s
pparent = pointer
}
} else {
var v interface{} = make(map[string]interface{})
if i == length - 1 {
v = map[string]interface{}{
array[i] : value,
}
}
if pparent != nil {
pparent = j.setPointerWithValue(pparent, array[i - 1], v)
} else {
*pointer = v
pparent = pointer
}
pointer = &v
}
}
}
@ -468,41 +485,40 @@ done:
// 数据结构转换map参数必须转换为map[string]interface{},数组参数必须转换为[]interface{}
func (j *Json) convertValue(value interface{}) interface{} {
switch value.(type) {
case map[string]interface{}:
return value
case []interface{}:
return value
default:
// 这里效率会比较低,当然比直接用反射也不会差到哪儿去
// 为了操作的灵活性,牺牲了一定的效率
b, _ := Encode(value)
v, _ := Decode(b)
return v
case map[string]interface{}:
return value
case []interface{}:
return value
default:
// 这里效率会比较低,当然比直接用反射也不会差到哪儿去
// 为了操作的灵活性,牺牲了一定的效率
b, _ := Encode(value)
v, _ := Decode(b)
return v
}
return value
}
// 用于Set方法中对指针指向的内存地址进行赋值
// 返回修改后的父级指针
func (j *Json) setPointerWithValue(pointer *interface{}, key string, value interface{}) *interface{} {
switch (*pointer).(type) {
case map[string]interface{}:
(*pointer).(map[string]interface{})[key] = value
return &value
case []interface{}:
n, _ := strconv.Atoi(key)
if len((*pointer).([]interface{})) > n {
(*pointer).([]interface{})[n] = value
return &(*pointer).([]interface{})[n]
} else {
s := make([]interface{}, n + 1)
copy(s, (*pointer).([]interface{}))
s[n] = value
*pointer = s
return &s[n]
}
default:
*pointer = value
case map[string]interface{}:
(*pointer).(map[string]interface{})[key] = value
return &value
case []interface{}:
n, _ := strconv.Atoi(key)
if len((*pointer).([]interface{})) > n {
(*pointer).([]interface{})[n] = value
return &(*pointer).([]interface{})[n]
} else {
s := make([]interface{}, n + 1)
copy(s, (*pointer).([]interface{}))
s[n] = value
*pointer = s
return &s[n]
}
default:
*pointer = value
}
return pointer
}
@ -535,12 +551,12 @@ func (j *Json) Len(pattern string) int {
p := j.getPointerByPattern(pattern)
if p != nil {
switch (*p).(type) {
case map[string]interface{}:
return len((*p).(map[string]interface{}))
case []interface{}:
return len((*p).([]interface{}))
default:
return -1
case map[string]interface{}:
return len((*p).(map[string]interface{}))
case []interface{}:
return len((*p).([]interface{}))
default:
return -1
}
}
return -1
@ -627,17 +643,17 @@ func (j *Json) getPointerByPatternWithoutSplitCharViolenceCheck(pattern string)
// 注意这里返回的指针都是临时变量的内存地址
func (j *Json) checkPatternByPointer(key string, pointer *interface{}) *interface{} {
switch (*pointer).(type) {
case map[string]interface{}:
if v, ok := (*pointer).(map[string]interface{})[key]; ok {
return &v
}
case []interface{}:
if gstr.IsNumeric(key) {
n, err := strconv.Atoi(key)
if err == nil && len((*pointer).([]interface{})) > n {
return &(*pointer).([]interface{})[n]
}
case map[string]interface{}:
if v, ok := (*pointer).(map[string]interface{})[key]; ok {
return &v
}
case []interface{}:
if gstr.IsNumeric(key) {
n, err := strconv.Atoi(key)
if err == nil && len((*pointer).([]interface{})) > n {
return &(*pointer).([]interface{})[n]
}
}
}
return nil
}
@ -659,10 +675,10 @@ func (j *Json) ToArray() []interface{} {
j.mu.RLock()
defer j.mu.RUnlock()
switch (*(j.p)).(type) {
case []interface{}:
return (*(j.p)).([]interface{})
default:
return nil
case []interface{}:
return (*(j.p)).([]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,23 +4,24 @@
// 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
import (
"fmt"
"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"
"gitee.com/johng/gf/g/os/gview"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/database/gdb"
"gitee.com/johng/gf/g/os/gfsnotify"
"fmt"
"gitee.com/johng/gf/g/database/gredis"
"gitee.com/johng/gf/g/util/gregex"
)
@ -72,14 +73,8 @@ 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()
}
}
view := gview.Get(path)
path := cmdenv.Get("gf.gview.path", gfile.SelfDir()).String()
view := gview.New(path)
// 添加基于源码的搜索目录检索地址,常用于开发环境调试,只添加入口文件目录
if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) {
view.AddPath(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) {
@ -116,7 +105,7 @@ func Config(file...string) *gcfg.Config {
}
// 数据库操作对象,使用了连接池
func Database(name...string) *gdb.Db {
func Database(name...string) gdb.DB {
config := Config()
group := gdb.DEFAULT_GROUP_NAME
if len(name) > 0 {
@ -124,64 +113,67 @@ func Database(name...string) *gdb.Db {
}
key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_DATABASE, group)
db := instances.GetOrSetFuncLock(key, func() interface{} {
m := config.GetMap("database")
if m == nil {
glog.Errorfln(`incomplete configuration for database: "database" node not found in config file "%s"`, config.GetFilePath())
}
for group, v := range m {
cg := gdb.ConfigGroup{}
if list, ok := v.([]interface{}); ok {
for _, nodev := range list {
node := gdb.ConfigNode{}
nodem := nodev.(map[string]interface{})
if value, ok := nodem["host"]; ok {
node.Host = gconv.String(value)
}
if value, ok := nodem["port"]; ok {
node.Port = gconv.String(value)
}
if value, ok := nodem["user"]; ok {
node.User = gconv.String(value)
}
if value, ok := nodem["pass"]; ok {
node.Pass = gconv.String(value)
}
if value, ok := nodem["name"]; ok {
node.Name = gconv.String(value)
}
if value, ok := nodem["type"]; ok {
node.Type = gconv.String(value)
}
if value, ok := nodem["role"]; ok {
node.Role = gconv.String(value)
}
if value, ok := nodem["charset"]; ok {
node.Charset = gconv.String(value)
}
if value, ok := nodem["priority"]; ok {
node.Priority = gconv.Int(value)
}
if value, ok := nodem["linkinfo"]; ok {
node.Linkinfo = gconv.String(value)
}
if value, ok := nodem["max-idle"]; ok {
node.MaxIdleConnCount = gconv.Int(value)
}
if value, ok := nodem["max-open"]; ok {
node.MaxOpenConnCount = gconv.Int(value)
}
if value, ok := nodem["max-lifetime"]; ok {
node.MaxConnLifetime = gconv.Int(value)
}
cg = append(cg, node)
}
if gdb.GetConfig(group) == nil {
m := config.GetMap("database")
if m == nil {
glog.Error(`database init failed: "database" node not found, is config file or configuration missing?`)
return nil
}
gdb.AddConfigGroup(group, cg)
for group, v := range m {
cg := gdb.ConfigGroup{}
if list, ok := v.([]interface{}); ok {
for _, nodev := range list {
node := gdb.ConfigNode{}
nodem := nodev.(map[string]interface{})
if value, ok := nodem["host"]; ok {
node.Host = gconv.String(value)
}
if value, ok := nodem["port"]; ok {
node.Port = gconv.String(value)
}
if value, ok := nodem["user"]; ok {
node.User = gconv.String(value)
}
if value, ok := nodem["pass"]; ok {
node.Pass = gconv.String(value)
}
if value, ok := nodem["name"]; ok {
node.Name = gconv.String(value)
}
if value, ok := nodem["type"]; ok {
node.Type = gconv.String(value)
}
if value, ok := nodem["role"]; ok {
node.Role = gconv.String(value)
}
if value, ok := nodem["charset"]; ok {
node.Charset = gconv.String(value)
}
if value, ok := nodem["priority"]; ok {
node.Priority = gconv.Int(value)
}
if value, ok := nodem["linkinfo"]; ok {
node.Linkinfo = gconv.String(value)
}
if value, ok := nodem["max-idle"]; ok {
node.MaxIdleConnCount = gconv.Int(value)
}
if value, ok := nodem["max-open"]; ok {
node.MaxOpenConnCount = gconv.Int(value)
}
if value, ok := nodem["max-lifetime"]; ok {
node.MaxConnLifetime = gconv.Int(value)
}
cg = append(cg, node)
}
}
gdb.AddConfigGroup(group, cg)
}
// 使用gfsnotify进行文件监控当配置文件有任何变化时清空数据库配置缓存
gfsnotify.Add(config.GetFilePath(), func(event *gfsnotify.Event) {
instances.Remove(key)
})
}
// 使用gfsnotify进行文件监控当配置文件有任何变化时清空数据库配置缓存
gfsnotify.Add(config.GetFilePath(), func(event *gfsnotify.Event) {
instances.Remove(key)
})
if db, err := gdb.New(name...); err == nil {
return db
} else {
@ -190,7 +182,7 @@ func Database(name...string) *gdb.Db {
return nil
})
if db != nil {
return db.(*gdb.Db)
return db.(gdb.DB)
}
return nil
}

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 (
@ -32,7 +32,7 @@ func (c *Controller) Init(r *ghttp.Request) {
}
// 控制器结束请求接口方法
func (c *Controller) Shut(r *ghttp.Request) {
func (c *Controller) Shut() {
}

23
g/g.go
View File

@ -10,14 +10,27 @@ package g
import "gitee.com/johng/gf/g/container/gvar"
// 框架动态变量可以用该类型替代interface{}类型
type Var = gvar.Var
type Var = gvar.Var
// 常用map数据结构(使用别名)
type Map = map[string]interface{}
type Map = map[string]interface{}
type MapStrStr = map[string]string
type MapStrInt = map[string]int
type MapIntStr = map[int]string
type MapIntInt = map[int]int
// 常用list数据结构(使用别名)
type List = []Map
type List = []Map
type ListStrStr = []map[string]string
type ListStrInt = []map[string]int
type ListIntStr = []map[int]string
type ListIntInt = []map[int]int
// 常用slice数据结构(使用别名)
type Slice = []interface{}
type Array = Slice
type Slice = []interface{}
type SliceStr = []string
type SliceInt = []int
type Array = Slice
type ArrayStr = SliceStr
type ArrayInt = SliceInt

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

@ -23,12 +23,12 @@ func Server(name...interface{}) *ghttp.Server {
}
// TCPServer单例对象
func TcpServer(name...interface{}) *gtcp.Server {
func TCPServer(name...interface{}) *gtcp.Server {
return gtcp.GetServer(name...)
}
// UDPServer单例对象
func UdpServer(name...interface{}) *gudp.Server {
func UDPServer(name...interface{}) *gudp.Server {
return gudp.GetServer(name...)
}
@ -44,12 +44,12 @@ func Config(file...string) *gcfg.Config {
}
// 数据库操作对象,使用了连接池
func Database(name...string) *gdb.Db {
func Database(name...string) gdb.DB {
return gins.Database(name...)
}
// (别名)Database
func DB(name...string) *gdb.Db {
func DB(name...string) gdb.DB {
return gins.Database(name...)
}

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

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