Compare commits

...

106 Commits

Author SHA1 Message Date
62580b5719 add examples for gredis 2019-03-21 23:51:57 +08:00
15cfd5ce5c add g.Export 2019-03-21 18:21:53 +08:00
11c89c4090 fix issue in GetOrSetFuncLock of gmap/gcache; add gutil.IsEmpty/Export functions 2019-03-21 18:20:20 +08:00
4a12cb9f27 version updates 2019-03-21 10:52:12 +08:00
32f575eddd update unit test of gins 2019-03-21 10:36:24 +08:00
fbb4cb3b1c add more unit test cases for gredis 2019-03-21 10:04:53 +08:00
c9d2d5e8ab add configuration support for maxIdle/maxActive/idleTimeout/maxConnLifetime of gredis; update gjson.Append/Len functions; add more unit test cases for gredis/gins/gstr/gjson 2019-03-21 00:14:23 +08:00
93763192f2 version updates 2019-03-19 17:55:02 +08:00
80e0eae6b0 add TLSConfig support for ghttp.Server 2019-03-19 17:48:37 +08:00
4e3d735b90 add logging for gcron 2019-03-19 13:58:18 +08:00
60e5a7da28 add more unit test cases for grand/gstr 2019-03-18 23:52:25 +08:00
997b5ba889 README updates 2019-03-18 14:10:30 +08:00
b3e7ca1963 travis updates 2019-03-18 14:05:46 +08:00
5a82d695c1 mv greuseport to a new repo 2019-03-18 13:56:16 +08:00
64a0427150 travis updates 2019-03-18 13:34:51 +08:00
bca5532df8 version updates 2019-03-17 22:36:58 +08:00
72eeadd9aa Merge branch 'master' into develop 2019-03-17 22:31:15 +08:00
0af55794f6 add more unit test cases for gdb 2019-03-17 22:26:41 +08:00
25a6c53533 add unit test cases for gfsnotify 2019-03-15 14:54:01 +08:00
9f9172c775 update unit test cases of package gconv 2019-03-15 09:05:56 +08:00
320e0db417 fix int-overflow issue in gconv.String when converting int64 to string; add more unit test cases for package gconv 2019-03-15 00:22:39 +08:00
cb8362d447 update unit test cases of package gins 2019-03-14 23:28:56 +08:00
45a83fc53c unit test cases update for package gins 2019-03-14 00:23:46 +08:00
3411bd1c1d merge master 2019-03-13 22:41:00 +08:00
6ab0a77364 add more defaulr searching paths for g.Config() 2019-03-13 22:12:59 +08:00
281bae4116 unit test cases update for package gins 2019-03-13 09:11:50 +08:00
218c692fe0 update unit test cases 2019-03-12 23:56:09 +08:00
fa69b581e1 update unit test cases 2019-03-12 23:50:30 +08:00
bd0baceeca update unit test cases 2019-03-12 23:47:27 +08:00
e71c837472 update unit test cases 2019-03-12 23:45:44 +08:00
782aaabd07 add more unit test cases for gins; update text/template for gview 2019-03-12 23:26:10 +08:00
8ae9276732 add disable cache feature option for package gspath 2019-03-12 00:24:31 +08:00
79a3aa5916 add more unit test cases for package gvalid 2019-03-11 22:58:21 +08:00
733c5db228 version updates 2019-03-11 22:33:43 +08:00
6171c621a7 fix issue in missing customed error messages in package gvalid 2019-03-11 22:33:20 +08:00
802568856c version updates 2019-03-11 16:35:44 +08:00
127fb67185 add session id check in session object initialzing 2019-03-11 16:33:51 +08:00
5db039bbce gdb.Model updates, rename Alterable function to Safe, change gdb.Model alterable in default, update Where function to support more cases when using map as its param, add more unit test cases for gdb.Model 2019-03-11 16:14:55 +08:00
8a3365d18e ghttp comment updates 2019-03-10 00:39:34 +08:00
2ae5b1a4f8 add more unit test cases for ghttp.Server 2019-03-10 00:35:03 +08:00
f9515d7126 version updates 2019-03-09 10:20:11 +08:00
b56679a97c revert g.Map to map[string]interface{}, add g.MapAnyAny 2019-03-09 10:17:21 +08:00
d1b123964a README updates 2019-03-08 21:07:18 +08:00
991f7c4958 ghttp.Response updates 2019-03-08 19:03:22 +08:00
770619c39e update unit test cases og gcron 2019-03-08 18:27:11 +08:00
4ad5450b80 gtest updates; remove one unit test case of gconv.Struct 2019-03-08 18:18:29 +08:00
49ce7fe885 fix data race issue of gqueue 2019-03-08 17:54:50 +08:00
e66f63262b add more unit test cases for ghttp.Server 2019-03-08 17:31:30 +08:00
94bd5da68a add map key operator support in Where function for gdb 2019-03-08 11:12:52 +08:00
3eee95caf2 VERSION updates 2019-03-08 10:35:35 +08:00
5c638c630a add select in support for slice type of arguments in Where function of gdb 2019-03-08 10:33:36 +08:00
a6ec9d7a1c update gdb.Model.Where, orverwrite args if multiple calls Where function 2019-03-08 10:18:38 +08:00
374c70c0e3 README updates 2019-03-08 09:03:32 +08:00
40771066d4 travis updates 2019-03-08 08:48:53 +08:00
22a7ef43ce Merge branch 'master' into develop 2019-03-08 00:21:52 +08:00
05f22d1cee README updates 2019-03-08 00:15:57 +08:00
ebf56a86ab add Alterable function for gdb.Model 2019-03-07 23:53:56 +08:00
2ba59e8943 fix issue in gtime convert in package gconv; add Structs/Scan functions for gdb.Model; add GetStructs/GetScan functions for gdb.Base and gdb.TX 2019-03-07 23:36:45 +08:00
83be1de04c remove error returns from router registry functions of WebServer; add more unit test cases for WebServer 2019-03-06 15:21:00 +08:00
c8251ed82f g.Map updates 2019-03-06 10:38:50 +08:00
2335ea0c4d merge master 2019-03-05 21:07:54 +08:00
5d874e9063 add example code for gconv.Map; comment updates of gdb 2019-03-05 17:52:34 +08:00
f2c080d25f update ghttp.Response 2019-03-05 17:16:21 +08:00
975da97b4a fix issue in empty check for nil attribute of struct in package 'empty';add set-cookie support for 302 status in ghttp.Server 2019-03-05 17:06:37 +08:00
37617589a6 add more unit test cases for ghttp.Server 2019-03-04 23:51:44 +08:00
5c9f0db903 version updates 2019-03-04 23:37:01 +08:00
28abf0c175 add ReplaceI/ReplaceIByArray/ReplaceIByMap case-insensetive replacing functions for gstr 2019-03-04 23:35:06 +08:00
13749feab4 add json features '-' and 'omitempty' for gconv.Map, like package 'json' from stdlib; add internal package 'empty', to check empty variable 2019-03-04 22:59:29 +08:00
c1e77b7e09 update example of gtcp 2019-03-03 20:40:36 +08:00
962a5e93f7 update example of gtcp 2019-03-03 20:20:31 +08:00
c3b9b8d5ae gconv updates 2019-03-03 00:53:35 +08:00
1ad076c522 travis updates 2019-03-03 00:31:16 +08:00
b01777fcd1 travis updates 2019-03-03 00:28:32 +08:00
55a5532c2e gdb updates 2019-03-03 00:14:20 +08:00
adb928941a issue template updates 2019-03-02 00:02:05 +08:00
f92c1fc527 update CORS feature of ghttp.Response; add more unit cases for ghttp.Server 2019-03-01 23:45:55 +08:00
3ae7279ebc issue template updates 2019-03-01 14:01:05 +08:00
66287c2d0e update the admin feature and unit test cases of ghttp 2019-02-28 23:57:20 +08:00
5d37626981 hot fix issue in router registry 2019-02-28 14:07:00 +08:00
d0ed3b979d release updates 2019-02-28 10:28:09 +08:00
fa256aec9f add example for layout using template engine; fix issue with config error output in gview when no config used 2019-02-27 22:53:39 +08:00
86834c5a15 version and comment updates 2019-02-27 22:17:09 +08:00
c2046157d6 add ghttp.Request.GetRawString function 2019-02-27 21:17:56 +08:00
cdb2cc89c0 remove password for unit test of gdb 2019-02-27 12:51:48 +08:00
4964c09a77 add more unit test cases for gdb 2019-02-27 12:38:12 +08:00
ef34b2c9ce gdb updates, add batch operation support for Insert/Save/Replace, change list param type from List to interface{} for Batch* functions 2019-02-27 09:38:10 +08:00
9afe242293 Merge branch 'master' into qiangg_gdb_map 2019-02-27 08:52:50 +08:00
136d93d373 add donate for gitee 2019-02-26 23:39:09 +08:00
3102cec5b8 issue template updates 2019-02-26 23:06:14 +08:00
e28eb9da04 add issue template 2019-02-26 22:58:52 +08:00
754ed86dfb add issue template 2019-02-26 22:56:52 +08:00
e352b07055 add issue_template 2019-02-26 22:51:30 +08:00
fdea242b50 Merge branch 'master' into develop 2019-02-26 22:23:24 +08:00
7058e4f2c4 fix issue of router group in auto-adding 'index' router for controller and object 2019-02-26 22:21:57 +08:00
704a5dbd73 fix issue of "memory leaks" in gfpool 2019-02-26 17:52:50 +08:00
fbd4ce8c2e fulfil unit cases of ghttp.Cookie/Session 2019-02-26 17:17:11 +08:00
cb3ce71cdc fix issue in ghttp.Session 2019-02-26 14:33:01 +08:00
7f44f2f5e4 gdb updates 2019-02-26 14:23:29 +08:00
66efbe63f0 add struct support for *Insert/*Save/*Replace/*Update/Where/Data functions 2019-02-26 01:19:01 +08:00
49a1308875 ghttp.Session updates 2019-02-25 23:08:26 +08:00
4332580c01 disable build-in session variable in template parsing, when it's not necessary 2019-02-25 15:20:57 +08:00
72ecf2d2af TODO++ 2019-02-25 12:39:07 +08:00
0f854e46d8 refact Merge function for garray; add more frequently-used type alias for gf 2019-02-22 09:08:46 +08:00
4564f38e1a add PopRands/Rands functions for garray 2019-02-20 19:06:08 +08:00
7e06bf6705 Merge branch 'master' into qiangg_garray2 2019-02-20 16:28:24 +08:00
d780cf64c2 garray updates 2019-02-20 14:18:11 +08:00
203 changed files with 10091 additions and 3057 deletions

34
.gitee/ISSUE_TEMPLATE.MD Normal file
View File

@ -0,0 +1,34 @@
<!-- 为更高效率地交流并解决问题请按照以下模板提交issue感谢 -->
### 1. 您当前使用的`Go`版本,及系统版本、系统架构?
<!-- 使用 `go version` 命令查看,期望的结果如:`go 1.12, linux/amd64` -->
### 2. 您当前使用的`GoFrame`框架版本?
<!-- 框架版本可以查看自己项目下的 `go.mod`,或者框架文件 `version.go` -->
### 3. 更新到最新的框架版本是否能够解决问题?
<!-- 务必检查是否相同问题已在新版本中已修复 -->
### 4. 问题描述?
<!--
请您尽可能地提供一份最短的,可复现问题的代码。
代码尽可能地完整,最好是可以直接编译运行。
-->
### 5. 您期望得到的结果?
### 6. 您实际得到的结果?

36
.github/ISSUE_TEMPLATE.MD vendored Normal file
View File

@ -0,0 +1,36 @@
<!-- Please answer these questions before submitting your issue. Thanks! -->
### 1. What version of `Go` and system type/arch are you using?
<!--
Please paste the output of command `go version` from your terminal.
What expect to see is like: `go 1.12, linux/amd64`
-->
### 2. What version of `GoFrame` are you using?
<!-- You can find the GF version from your `go.mod`, or from the `version.go` in `GF` -->
### 3. Can this issue be reproduced with the latest release?
### 4. What did you do?
<!--
If possible, provide a copy of shortest codes for reproducing the error.
A complete runnable program is best.
-->
### 5. What did you expect to see?
### 6. What did you see instead?

View File

@ -1,7 +1,9 @@
language: go
go:
- "1.11.x"
- "1.10.x"
- "1.11.x"
- "1.12.x"
branches:
only:
@ -9,25 +11,29 @@ branches:
- develop
env:
- GO111MODULE=on
- GO111MODULE=on
services:
- mysql
- mysql
- redis-server
addons:
hosts:
- local
before_install:
- pwd
- pwd
install:
- pwd
- cat /etc/hosts
script:
- cd g
- GOARCH=386 go test -v ./...
- GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
- cd g
- 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)
- bash <(curl -s https://codecov.io/bash)

View File

@ -4,17 +4,16 @@
[![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://goframe.org)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master)
[![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)
-->
GoFrame is a modular, loose-coupled, production-ready and most-powerful application development framework of golang. Providing a series of core components and dozens of practical modules, such as: cache, logging, containers, timer, validator, database orm, etc. Supporting web server integrated with router, cookie, session, logger, configure, template, https, hooks, rewrites and many more features.
-->
`GF(GoFrame)` is a modular, loose-coupled and production-ready application development framework written in Go. Providing a series of core components and dozens of practical modules, such as: cache, logging, array/queue/set/map, timer/timing tasks, file/memory lock, object pool, validator, database ORM, etc. Supporting web server with graceful server, hot updates, multi-domain, multi-port, multi-service, HTTP/HTTPS, dynamic/hook routing, rewrite rules and many more features.
`GF(GoFrame)` is a modular, loose-coupled, production-ready and most-powerful application development framework of golang. Providing a series of core components and dozens of practical modules, such as: memcache, configure, validator, logging, array/queue/set/map containers, timer/timing tasks, file/memory lock, object pool, database ORM, etc. Supporting web server integrated with router, cookie, session, logger, template, https, hooks, rewrites and many more features.
# Installation
```
@ -66,42 +65,30 @@ func main() {
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
# Contributors(TOP 10)
# Contributors
<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://goframe.org/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://goframe.org/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 />
- [johng](https://gitee.com/johng)
- [zhaopengme](https://github.com/zhaopengme)
- [wenzi1](https://gitee.com/wenzi1)
- [zseeker](https://gitee.com/zseeker)
- [ymrjqyy](https://gitee.com/ymrjqyy)
- [chenyang351](https://github.com/chenyang351)
- [wxkj](https://gitee.com/wxkj)
- [wxkj001](https://github.com/wxkj001)
- [zhangjinfu](https://gitee.com/zhangjinfu)
- [garfieldkwong](https://gitee.com/garfieldkwong)
- [qq1054000800](https://gitee.com/qq1054000800)
# Donators
<a href="https://gitee.com/tiangenglan" target="_blank" title="zhuhuan12"><img src="https://images.gitee.com/uploads/99/1167099_tiangenglan.png" width="60" align="left"></a>
<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>
- [tiangenglan](https://gitee.com/tiangenglan)
- [zhuhuan12](https://gitee.com/zhuhuan12)
- [zfan_codes](https://gitee.com/zfan_codes)
- [hailaz](https://gitee.com/hailaz)
- [mg91](https://gitee.com/mg91)
- [wxkj](https://gitee.com/wxkj)
- [pibigstar](https://github.com/pibigstar)
- [flyke-xu](https://gitee.com/flyke-xu)

View File

@ -4,16 +4,11 @@
[![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://goframe.org)
[![Code Coverage](https://codecov.io/gh/gogf/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/gogf/gf/branch/master)
[![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应用开发框架。提供了常用的核心开发组件缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、数据校验、数据编码、文件监控、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、
并发安全容器等等。并提供了Web服务开发的系列核心组件Router、Cookie、Session、服务注册、配置管理、模板引擎等等支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。
@ -81,31 +76,36 @@ func main() {
`GF` 使用非常友好的 [MIT](LICENSE) 开源协议进行发布,永久`100%`开源免费。
# 捐赠
# 贡献者(TOP 10)
捐赠支持`GF`框架的研发,
请在捐赠时备注您的`github`/`gitee`账号名称。
<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://goframe.org/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://goframe.org/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>
<a href="https://goframe.org/images/donate.png" target="_blank">
<img src="https://goframe.org/images/donate.png" width="300"/>
</a>
<br /><br /><br />
# 贡献者
- [johng](https://gitee.com/johng)
- [zhaopengme](https://github.com/zhaopengme)
- [wenzi1](https://gitee.com/wenzi1)
- [zseeker](https://gitee.com/zseeker)
- [ymrjqyy](https://gitee.com/ymrjqyy)
- [chenyang351](https://github.com/chenyang351)
- [wxkj](https://gitee.com/wxkj)
- [wxkj001](https://github.com/wxkj001)
- [zhangjinfu](https://gitee.com/zhangjinfu)
- [garfieldkwong](https://gitee.com/garfieldkwong)
- [qq1054000800](https://gitee.com/qq1054000800)
# 捐赠者
<a href="https://gitee.com/tiangenglan" target="_blank" title="zhuhuan12"><img src="https://images.gitee.com/uploads/99/1167099_tiangenglan.png" width="60" align="left"></a>
<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>
- [tiangenglan](https://gitee.com/tiangenglan)
- [zhuhuan12](https://gitee.com/zhuhuan12)
- [zfan_codes](https://gitee.com/zfan_codes)
- [hailaz](https://gitee.com/hailaz)
- [mg91](https://gitee.com/mg91)
- [wxkj](https://gitee.com/wxkj)
- [pibigstar](https://github.com/pibigstar)
- [flyke-xu](https://gitee.com/flyke-xu)

View File

@ -1,3 +1,38 @@
# `v1.5.8` (2019-02-28)
## 新特性
1. 主库从`gitee`迁移到了`github`( https://github.com/gogf/gf )`gitee`作为镜像站用于国内的代码贡献及ISSUE提交迁移说明详见https://goframe.org/upgradeto150
1. 对常用的`container`数组模块: `garray`做了大量改进/完善工作新增大量常用方法并完善单元测试用例及方法注释详见API文档https://godoc.org/github.com/gogf/gf/g/container/garray
1. 对常用的`container`集合模块: `gset`做了大量改进/完善工作新增大量常用方法并完善单元测试用例及方法注释详见API文档https://godoc.org/github.com/gogf/gf/g/container/gset
1. 对常用的`container`MAP模块: `gmap`做了大量改进/完善工作新增大量常用方法并完善单元测试用例及方法注释详见API文档https://godoc.org/github.com/gogf/gf/g/container/gmap
1. 对常用的字符串模块: `gstr`做了大量改进/完善工作新增大量常用方法并完善单元测试用例及方法注释详见API文档https://godoc.org/github.com/gogf/gf/g/text/gstr
1. 改进`gform`中对`struct`/`*struct`参数的支持,`*Insert/*Save/*Replace/*Update/Where/Data`方法的参数调整为`interface{}`类型,并支持任意类型的: `string/map/slice/struct/*struct`参数传递具体请参考https://goframe.org/database/orm/chaining
1. 新增/完善若干模块的单元测试用例, 包括:`gvalid`/`gregex`/`garray`/`gset`/`gmap`/`gstr`/`gconv`/`ghttp`/`gdb`
1. 由于`gkafka`模块比较重且不是框架核心模块因此将该模块迁移到新的仓库中独立管理并去掉相关依赖包https://github.com/gogf/gkafka
1. 新增`greuseport`模块用以实现TCP的`REUSEPORT`特性https://godoc.org/github.com/gogf/gf/g/net/greuseport
## 新功能/改进
1. 去掉模板引擎内置变量中自动初始化`session`对象带来的内存占用问题;
1. `ghttp.Client`改进增加若干方法详见https://goframe.org/net/ghttp/client
1. `ghttp`分组路由增加`COMMON`方法,用以注册常用的`HTTP METHOD`(`GET/PUT/POST/DELETE`)路由;
1. 更新框架依赖的`golang.org/x/sys`模块;
1. 改进`gform`的批量操作(`Batch*`操作)返回结果对象,可以通过该结果对象获得批量操作准确的受影响记录行数;
1. 将`gstr`/`gregex`模块从`util`分类迁移到了`text`分类目录下;
1. 将`gtest`模块从`util`分类迁移到了`test`分类目录下;
1. 完善`glog`方法注释;
## Bug Fix
1. 修复带点的邮件格式,用`gvalid.Check`的"`email`"规则不能匹配成功;
1. 修复`gvalid.Check`在`regex`规则下的检查失败问题;
1. 修复`gcron`模块定时规则中天和周不允许`?`符号的问题;
1. 修复`ghttp.Server`在部分异常情况下仍然返回`200`状态码的问题;
1. 修复`gfpool`模块中由于原子操作问题造成的高并发"内存泄露"问题;
1. 修复分组路由注册对象/控制时,方法`Index`的路由仅能通过`/xxx/index`访问的问题;
1. 修复模板引擎使用中,当不存在`config.toml`(即使没使用)配置文件时的报错问题;
1. 其他一些修复;
# `v1.4.6` (2019-01-24)
## 新特性

View File

@ -52,8 +52,12 @@
1. 从ghttp中剥离SESSION功能构成单独的模块gsession
1. 改进gproc进程间通信处理逻辑提高稳定性以应对进程间大批量的数据发送/接收;
1. gdb的Data方法支持struct参数传入
1. gfcache依旧使用gcache作为缓存控制对象不要使用gmap
1. 增加对ghttp路由注册的{.struct}/{.method}单元测试;
1. 更新跨域请求CORS相关功能文档
1. ghttp的热重启的本地进程端口监听在不使用该特性时默认关闭掉
1. gcfg包目前允许添加重复的目录路径需要在SetPath/AddPath时判断重复性不能添加重复的路径
1. gdb执行数据写入时如果参数为struct/[]struct自动映射与表字段对应关系不再使用gconv标签标识

View File

@ -6,3 +6,15 @@
package garray
type apiSliceInterface interface {
Slice() []interface{}
}
type apiSliceInt interface {
Slice() []int
}
type apiSliceString interface {
Slice() []string
}

View File

@ -7,12 +7,12 @@
package garray
import (
"bytes"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
"strings"
)
type IntArray struct {
@ -53,6 +53,20 @@ func NewIntArrayFrom(array []int, unsafe...bool) *IntArray {
}
}
// Create an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice拷贝创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewIntArrayFromCopy(array []int, unsafe...bool) *IntArray {
newArray := make([]int, len(array))
copy(newArray, array)
return &IntArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Get value by index.
//
// 获取指定索引的数据项, 调用方注意判断数组边界。
@ -233,13 +247,31 @@ func (a *IntArray) PopRight() int {
return value
}
// Pop an random item from array.
// PopRand picks an random item out of array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *IntArray) PopRand() int {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands picks <size> items out of array.
//
// 随机将size个数据项移出数组并返回该数据项。
func (a *IntArray) PopRands(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]int, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项。
@ -409,8 +441,8 @@ func (a *IntArray) Unique() *IntArray {
//
// 使用自定义方法执行加锁修改操作。
func (a *IntArray) LockFunc(f func(array []int)) *IntArray {
a.mu.Lock(true)
defer a.mu.Unlock(true)
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
@ -419,23 +451,28 @@ func (a *IntArray) LockFunc(f func(array []int)) *IntArray {
//
// 使用自定义方法执行加锁读取操作。
func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge two arrays.
// Merge two arrays. The parameter <array> can be any garray type or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more variable types.
//
// 合并两个数组.
func (a *IntArray) Merge(array *IntArray) *IntArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
// 合并两个数组, 支持任意的garray数组类型及slice类型.
func (a *IntArray) Merge(array interface{}) *IntArray {
switch v := array.(type) {
case *Array: a.Append(gconv.Ints(v.Slice())...)
case *IntArray: a.Append(gconv.Ints(v.Slice())...)
case *StringArray: a.Append(gconv.Ints(v.Slice())...)
case *SortedArray: a.Append(gconv.Ints(v.Slice())...)
case *SortedIntArray: a.Append(gconv.Ints(v.Slice())...)
case *SortedStringArray: a.Append(gconv.Ints(v.Slice())...)
default:
a.Append(gconv.Ints(array)...)
}
a.array = append(a.array, array.array...)
return a
}
@ -538,11 +575,19 @@ func (a *IntArray) SubSlice(offset, size int) []int {
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
// Rand gets one random entry from array.
//
// 从数组中随机取出size个元素项构成slice返回
func (a *IntArray) Rand(size int) []int {
// 从数组中随机获得1个元素项(不删除)
func (a *IntArray) Rand() int {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands gets one or more random entries from array(a copy).
//
// 从数组中随机拷贝size个元素项构成slice返回。
func (a *IntArray) Rands(size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
@ -588,5 +633,12 @@ func (a *IntArray) Reverse() *IntArray {
func (a *IntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(gconv.Strings(a.array), glue)
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}

View File

@ -7,12 +7,13 @@
package garray
import (
"bytes"
"fmt"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
"strings"
)
type Array struct {
@ -30,8 +31,6 @@ func New(unsafe...bool) *Array {
}
// See New.
//
// 同New方法。
func NewArray(unsafe...bool) *Array {
return NewArraySize(0, 0, unsafe...)
}
@ -48,6 +47,16 @@ func NewArraySize(size int, cap int, unsafe...bool) *Array {
}
}
// See NewArrayFrom.
func NewFrom(array []interface{}, unsafe...bool) *Array {
return NewArrayFrom(array, unsafe...)
}
// See NewArrayFromCopy.
func NewFromCopy(array []interface{}, unsafe...bool) *Array {
return NewArrayFromCopy(array, unsafe...)
}
// Create an array with given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
@ -60,6 +69,20 @@ func NewArrayFrom(array []interface{}, unsafe...bool) *Array {
}
}
// Create an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice拷贝创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewArrayFromCopy(array []interface{}, unsafe...bool) *Array {
newArray := make([]interface{}, len(array))
copy(newArray, array)
return &Array{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Get value by index.
//
// 获取指定索引的数据项, 调用方注意判断数组边界
@ -196,13 +219,31 @@ func (a *Array) PushRight(value...interface{}) *Array {
return a
}
// Pop an random item from array.
// PopRand picks an random item out of array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *Array) PopRand() interface{} {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands picks <size> items out of array.
//
// 随机将size个数据项移出数组并返回该数据项。
func (a *Array) PopRands(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]interface{}, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// Pop an item from the beginning of array.
//
// 将最左端(索引为0)的数据项移出数组,并返回该数据项。
@ -392,8 +433,8 @@ func (a *Array) Unique() *Array {
//
// 使用自定义方法执行加锁修改操作
func (a *Array) LockFunc(f func(array []interface{})) *Array {
a.mu.Lock(true)
defer a.mu.Unlock(true)
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
@ -402,23 +443,28 @@ func (a *Array) LockFunc(f func(array []interface{})) *Array {
//
// 使用自定义方法执行加锁读取操作
func (a *Array) RLockFunc(f func(array []interface{})) *Array {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge two arrays.
// Merge two arrays. The parameter <array> can be any garray type or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more variable types.
//
// 合并两个数组.
func (a *Array) Merge(array *Array) *Array {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
// 合并两个数组, 支持任意的garray数组类型及slice类型.
func (a *Array) Merge(array interface{}) *Array {
switch v := array.(type) {
case *Array: a.Append(gconv.Interfaces(v.Slice())...)
case *IntArray: a.Append(gconv.Interfaces(v.Slice())...)
case *StringArray: a.Append(gconv.Interfaces(v.Slice())...)
case *SortedArray: a.Append(gconv.Interfaces(v.Slice())...)
case *SortedIntArray: a.Append(gconv.Interfaces(v.Slice())...)
case *SortedStringArray: a.Append(gconv.Interfaces(v.Slice())...)
default:
a.Append(gconv.Interfaces(array)...)
}
a.array = append(a.array, array.array...)
return a
}
@ -520,11 +566,19 @@ func (a *Array) SubSlice(offset, size int) []interface{} {
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
// Rand gets one random entry from array.
//
// 从数组中随机取出size个元素项构成slice返回
func (a *Array) Rand(size int) []interface{} {
// 从数组中随机获得1个元素项(不删除)
func (a *Array) Rand() interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands gets one or more random entries from array(a copy).
//
// 从数组中随机拷贝size个元素项构成slice返回。
func (a *Array) Rands(size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
@ -570,7 +624,14 @@ func (a *Array) Reverse() *Array {
func (a *Array) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(gconv.Strings(a.array), glue)
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}
// Counts all the values of an array.
@ -584,4 +645,13 @@ func (a *Array) CountValues() map[interface{}]int {
m[v]++
}
return m
}
// String returns current array as a string.
//
// 将当前数组转换为字符串返回。
func (a *Array) String() string {
a.mu.RLock()
defer a.mu.RUnlock()
return fmt.Sprint(a.array)
}

View File

@ -7,6 +7,7 @@
package garray
import (
"bytes"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
@ -53,6 +54,20 @@ func NewStringArrayFrom(array []string, unsafe...bool) *StringArray {
}
}
// Create an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice拷贝创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewStringArrayFromCopy(array []string, unsafe...bool) *StringArray {
newArray := make([]string, len(array))
copy(newArray, array)
return &StringArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Get value by index.
//
// 获取指定索引的数据项, 调用方注意判断数组边界。
@ -233,13 +248,31 @@ func (a *StringArray) PopRight() string {
return value
}
// Pop an random item from array.
// PopRand picks an random item out of array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *StringArray) PopRand() string {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands picks <size> items out of array.
//
// 随机将size个数据项移出数组并返回该数据项。
func (a *StringArray) PopRands(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]string, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项
@ -407,8 +440,8 @@ func (a *StringArray) Unique() *StringArray {
//
// 使用自定义方法执行加锁修改操作。
func (a *StringArray) LockFunc(f func(array []string)) *StringArray {
a.mu.Lock(true)
defer a.mu.Unlock(true)
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
@ -417,23 +450,28 @@ func (a *StringArray) LockFunc(f func(array []string)) *StringArray {
//
// 使用自定义方法执行加锁读取操作。
func (a *StringArray) RLockFunc(f func(array []string)) *StringArray {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge two arrays.
// Merge two arrays. The parameter <array> can be any garray type or slice type.
// The difference between Merge and Append is Append supports only specified slice type,
// but Merge supports more variable types.
//
// 合并两个数组.
func (a *StringArray) Merge(array *StringArray) *StringArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
// 合并两个数组, 支持任意的garray数组类型及slice类型.
func (a *StringArray) Merge(array interface{}) *StringArray {
switch v := array.(type) {
case *Array: a.Append(gconv.Strings(v.Slice())...)
case *IntArray: a.Append(gconv.Strings(v.Slice())...)
case *StringArray: a.Append(gconv.Strings(v.Slice())...)
case *SortedArray: a.Append(gconv.Strings(v.Slice())...)
case *SortedIntArray: a.Append(gconv.Strings(v.Slice())...)
case *SortedStringArray: a.Append(gconv.Strings(v.Slice())...)
default:
a.Append(gconv.Strings(array)...)
}
a.array = append(a.array, array.array...)
return a
}
@ -537,11 +575,19 @@ func (a *StringArray) SubSlice(offset, size int) []string {
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
// Rand gets one random entry from array.
//
// 从数组中随机取出size个元素项构成slice返回
func (a *StringArray) Rand(size int) []string {
// 从数组中随机获得1个元素项(不删除)
func (a *StringArray) Rand() string {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands gets one or more random entries from array(a copy).
//
// 从数组中随机拷贝size个元素项构成slice返回。
func (a *StringArray) Rands(size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
@ -587,6 +633,13 @@ func (a *StringArray) Reverse() *StringArray {
func (a *StringArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(a.array, glue)
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}

View File

@ -7,13 +7,13 @@
package garray
import (
"bytes"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
"strings"
)
// 默认按照从小到大进行排序
@ -67,6 +67,20 @@ func NewSortedIntArrayFrom(array []int, unsafe...bool) *SortedIntArray {
return a
}
// Create an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice拷贝创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewSortedIntArrayFromCopy(array []int, unsafe...bool) *SortedIntArray {
newArray := make([]int, len(array))
copy(newArray, array)
return &SortedIntArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Set the underlying slice array with the given <array> param.
//
// 设置底层数组变量.
@ -172,13 +186,31 @@ func (a *SortedIntArray) PopRight() int {
return value
}
// Pop an random item from array.
// PopRand picks an random item out of array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *SortedIntArray) PopRand() int {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands picks <size> items out of array.
//
// 随机将size个数据项移出数组并返回该数据项。
func (a *SortedIntArray) PopRands(size int) []int {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]int, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项
@ -383,8 +415,8 @@ func (a *SortedIntArray) Clear() *SortedIntArray {
//
// 使用自定义方法执行加锁修改操作。
func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray {
a.mu.Lock(true)
defer a.mu.Unlock(true)
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
@ -393,24 +425,28 @@ func (a *SortedIntArray) LockFunc(f func(array []int)) *SortedIntArray {
//
// 使用自定义方法执行加锁读取操作。
func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge two arrays.
// Merge two arrays. The parameter <array> can be any garray type or slice type.
// The difference between Merge and Add is Add supports only specified slice type,
// but Merge supports more variable types.
//
// 合并两个数组.
func (a *SortedIntArray) Merge(array *SortedIntArray) *SortedIntArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
// 合并两个数组, 支持任意的garray数组类型及slice类型.
func (a *SortedIntArray) Merge(array interface{}) *SortedIntArray {
switch v := array.(type) {
case *Array: a.Add(gconv.Ints(v.Slice())...)
case *IntArray: a.Add(gconv.Ints(v.Slice())...)
case *StringArray: a.Add(gconv.Ints(v.Slice())...)
case *SortedArray: a.Add(gconv.Ints(v.Slice())...)
case *SortedIntArray: a.Add(gconv.Ints(v.Slice())...)
case *SortedStringArray: a.Add(gconv.Ints(v.Slice())...)
default:
a.Add(gconv.Ints(array)...)
}
a.array = append(a.array, array.array...)
sort.Ints(a.array)
return a
}
@ -462,11 +498,19 @@ func (a *SortedIntArray) SubSlice(offset, size int) []int {
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
// Rand gets one random entry from array.
//
// 从数组中随机取出size个元素项构成slice返回
func (a *SortedIntArray) Rand(size int) []int {
// 从数组中随机获得1个元素项(不删除)
func (a *SortedIntArray) Rand() int {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands gets one or more random entries from array(a copy).
//
// 从数组中随机拷贝size个元素项构成slice返回。
func (a *SortedIntArray) Rands(size int) []int {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
@ -488,5 +532,12 @@ func (a *SortedIntArray) Rand(size int) []int {
func (a *SortedIntArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(gconv.Strings(a.array), glue)
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}

View File

@ -7,13 +7,13 @@
package garray
import (
"bytes"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/util/grand"
"math"
"sort"
"strings"
)
// 默认按照从小到大进行排序
@ -69,6 +69,20 @@ func NewSortedArrayFrom(array []interface{}, compareFunc func(v1, v2 interface{}
return a
}
// Create an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice拷贝创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewSortedArrayFromCopy(array []interface{}, unsafe...bool) *SortedArray {
newArray := make([]interface{}, len(array))
copy(newArray, array)
return &SortedArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Set the underlying slice array with the given <array> param.
//
// 设置底层数组变量.
@ -178,13 +192,31 @@ func (a *SortedArray) PopRight() interface{} {
return value
}
// Pop an random item from array.
// PopRand picks an random item out of array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *SortedArray) PopRand() interface{} {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands picks <size> items out of array.
//
// 随机将size个数据项移出数组并返回该数据项。
func (a *SortedArray) PopRands(size int) []interface{} {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]interface{}, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项
@ -390,8 +422,8 @@ func (a *SortedArray) Clear() *SortedArray {
//
// 使用自定义方法执行加锁修改操作。
func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
a.mu.Lock(true)
defer a.mu.Unlock(true)
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
@ -400,26 +432,28 @@ func (a *SortedArray) LockFunc(f func(array []interface{})) *SortedArray {
//
// 使用自定义方法执行加锁读取操作。
func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge two arrays.
// Merge two arrays. The parameter <array> can be any garray type or slice type.
// The difference between Merge and Add is Add supports only specified slice type,
// but Merge supports more variable types.
//
// 合并两个数组.
func (a *SortedArray) Merge(array *SortedArray) *SortedArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
// 合并两个数组, 支持任意的garray数组类型及slice类型.
func (a *SortedArray) Merge(array interface{}) *SortedArray {
switch v := array.(type) {
case *Array: a.Add(gconv.Interfaces(v.Slice())...)
case *IntArray: a.Add(gconv.Interfaces(v.Slice())...)
case *StringArray: a.Add(gconv.Interfaces(v.Slice())...)
case *SortedArray: a.Add(gconv.Interfaces(v.Slice())...)
case *SortedIntArray: a.Add(gconv.Interfaces(v.Slice())...)
case *SortedStringArray: a.Add(gconv.Interfaces(v.Slice())...)
default:
a.Add(gconv.Interfaces(array)...)
}
a.array = append(a.array, array.array...)
sort.Slice(a.array, func(i, j int) bool {
return a.compareFunc(a.array[i], a.array[j]) < 0
})
return a
}
@ -471,11 +505,19 @@ func (a *SortedArray) SubSlice(offset, size int) []interface{} {
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
// Rand gets one random entry from array.
//
// 从数组中随机取出size个元素项构成slice返回
func (a *SortedArray) Rand(size int) []interface{} {
// 从数组中随机获得1个元素项(不删除)
func (a *SortedArray) Rand() interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands gets one or more random entries from array(a copy).
//
// 从数组中随机拷贝size个元素项构成slice返回。
func (a *SortedArray) Rands(size int) []interface{} {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
@ -497,5 +539,12 @@ func (a *SortedArray) Rand(size int) []interface{} {
func (a *SortedArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(gconv.Strings(a.array), glue)
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}

View File

@ -7,6 +7,7 @@
package garray
import (
"bytes"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/rwmutex"
"github.com/gogf/gf/g/util/gconv"
@ -61,6 +62,20 @@ func NewSortedStringArrayFrom(array []string, unsafe...bool) *SortedStringArray
return a
}
// Create an array from a copy of given slice <array>.
// The param <unsafe> used to specify whether using array with un-concurrent-safety,
// which is false in default, means concurrent-safe in default.
//
// 通过给定的slice拷贝创建数组对象参数unsafe用于指定是否用于非并发安全场景默认为false表示并发安全。
func NewSortedStringArrayFromCopy(array []string, unsafe...bool) *SortedStringArray {
newArray := make([]string, len(array))
copy(newArray, array)
return &SortedStringArray{
mu : rwmutex.New(unsafe...),
array : newArray,
}
}
// Set the underlying slice array with the given <array> param.
//
// 设置底层数组变量.
@ -166,13 +181,31 @@ func (a *SortedStringArray) PopRight() string {
return value
}
// Pop an random item from array.
// PopRand picks an random item out of array.
//
// 随机将一个数据项移出数组,并返回该数据项。
func (a *SortedStringArray) PopRand() string {
return a.Remove(grand.Intn(len(a.array)))
}
// PopRands picks <size> items out of array.
//
// 随机将size个数据项移出数组并返回该数据项。
func (a *SortedStringArray) PopRands(size int) []string {
a.mu.Lock()
defer a.mu.Unlock()
if size > len(a.array) {
size = len(a.array)
}
array := make([]string, size)
for i := 0; i < size; i++ {
index := grand.Intn(len(a.array))
array[i] = a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
return array
}
// Pop <size> items from the beginning of array.
//
// 将最左端(首部)的size个数据项移出数组并返回该数据项
@ -377,8 +410,8 @@ func (a *SortedStringArray) Clear() *SortedStringArray {
//
// 使用自定义方法执行加锁修改操作。
func (a *SortedStringArray) LockFunc(f func(array []string)) *SortedStringArray {
a.mu.Lock(true)
defer a.mu.Unlock(true)
a.mu.Lock()
defer a.mu.Unlock()
f(a.array)
return a
}
@ -387,24 +420,28 @@ func (a *SortedStringArray) LockFunc(f func(array []string)) *SortedStringArray
//
// 使用自定义方法执行加锁读取操作。
func (a *SortedStringArray) RLockFunc(f func(array []string)) *SortedStringArray {
a.mu.RLock(true)
defer a.mu.RUnlock(true)
a.mu.RLock()
defer a.mu.RUnlock()
f(a.array)
return a
}
// Merge two arrays.
// Merge two arrays. The parameter <array> can be any garray type or slice type.
// The difference between Merge and Add is Add supports only specified slice type,
// but Merge supports more variable types.
//
// 合并两个数组.
func (a *SortedStringArray) Merge(array *SortedStringArray) *SortedStringArray {
a.mu.Lock()
defer a.mu.Unlock()
if a != array {
array.mu.RLock()
defer array.mu.RUnlock()
// 合并两个数组, 支持任意的garray数组类型及slice类型.
func (a *SortedStringArray) Merge(array interface{}) *SortedStringArray {
switch v := array.(type) {
case *Array: a.Add(gconv.Strings(v.Slice())...)
case *IntArray: a.Add(gconv.Strings(v.Slice())...)
case *StringArray: a.Add(gconv.Strings(v.Slice())...)
case *SortedArray: a.Add(gconv.Strings(v.Slice())...)
case *SortedIntArray: a.Add(gconv.Strings(v.Slice())...)
case *SortedStringArray: a.Add(gconv.Strings(v.Slice())...)
default:
a.Add(gconv.Strings(array)...)
}
a.array = append(a.array, array.array...)
sort.Strings(a.array)
return a
}
@ -456,11 +493,19 @@ func (a *SortedStringArray) SubSlice(offset, size int) []string {
}
}
// Picks one or more random entries out of an array(a copy),
// and returns the key (or keys) of the random entries.
// Rand gets one random entry from array.
//
// 从数组中随机取出size个元素项构成slice返回
func (a *SortedStringArray) Rand(size int) []string {
// 从数组中随机获得1个元素项(不删除)
func (a *SortedStringArray) Rand() string {
a.mu.RLock()
defer a.mu.RUnlock()
return a.array[grand.Intn(len(a.array))]
}
// Rands gets one or more random entries from array(a copy).
//
// 从数组中随机拷贝size个元素项构成slice返回。
func (a *SortedStringArray) Rands(size int) []string {
a.mu.RLock()
defer a.mu.RUnlock()
if size > len(a.array) {
@ -482,5 +527,12 @@ func (a *SortedStringArray) Rand(size int) []string {
func (a *SortedStringArray) Join(glue string) string {
a.mu.RLock()
defer a.mu.RUnlock()
return strings.Join(a.array, glue)
buffer := bytes.NewBuffer(nil)
for k, v := range a.array {
buffer.WriteString(gconv.String(v))
if k != len(a.array) - 1 {
buffer.WriteString(glue)
}
}
return buffer.String()
}

View File

@ -0,0 +1,111 @@
// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package garray_test
import (
"fmt"
"github.com/gogf/gf/g/container/garray"
)
func Example_basic() {
// 创建普通的数组,默认并发安全(带锁)
a := garray.New()
// 添加数据项
for i := 0; i < 10; i++ {
a.Append(i)
}
// 获取当前数组长度
fmt.Println(a.Len())
// 获取当前数据项列表
fmt.Println(a.Slice())
// 获取指定索引项
fmt.Println(a.Get(6))
// 查找指定数据项是否存在
fmt.Println(a.Contains(6))
fmt.Println(a.Contains(100))
// 在指定索引前插入数据项
a.InsertAfter(9, 11)
// 在指定索引后插入数据项
a.InsertBefore(10, 10)
fmt.Println(a.Slice())
// 修改指定索引的数据项
a.Set(0, 100)
fmt.Println(a.Slice())
// 搜索数据项,返回搜索到的索引位置
fmt.Println(a.Search(5))
// 删除指定索引的数据项
a.Remove(0)
fmt.Println(a.Slice())
// 清空数组
fmt.Println(a.Slice())
a.Clear()
fmt.Println(a.Slice())
// Output:
// 10
// [0 1 2 3 4 5 6 7 8 9]
// 6
// true
// false
// [0 1 2 3 4 5 6 7 8 9 10 11]
// [100 1 2 3 4 5 6 7 8 9 10 11]
// 5
// [1 2 3 4 5 6 7 8 9 10 11]
// [1 2 3 4 5 6 7 8 9 10 11]
// []
}
func Example_rand() {
array := garray.NewFrom([]interface{}{1,2,3,4,5,6,7,8,9})
// 随机返回两个数据项(不删除)
fmt.Println(array.Rands(2))
fmt.Println(array.PopRand())
}
func Example_pop() {
array := garray.NewFrom([]interface{}{1,2,3,4,5,6,7,8,9})
fmt.Println(array.PopLeft())
fmt.Println(array.PopLefts(2))
fmt.Println(array.PopRight())
fmt.Println(array.PopRights(2))
// Output:
// 1
// [2 3]
// 9
// [7 8]
}
func Example_merge() {
array1 := garray.NewFrom([]interface{}{1,2})
array2 := garray.NewFrom([]interface{}{3,4})
slice1 := []interface{}{5,6}
slice2 := []int{7,8}
slice3 := []string{"9","0"}
fmt.Println(array1.Slice())
array1.Merge(array1)
array1.Merge(array2)
array1.Merge(slice1)
array1.Merge(slice2)
array1.Merge(slice3)
fmt.Println(array1.Slice())
// Output:
// [1 2]
// [1 2 1 2 3 4 5 6 7 8 9 0]
}

View File

@ -161,9 +161,18 @@ func TestIntArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []int{0,1,2,3,4,5,6}
array1 := garray.NewIntArrayFrom(a1)
gtest.Assert(len(array1.Rand(2)), 2)
gtest.Assert(len(array1.Rand(10)), 7)
gtest.AssertIN(array1.Rand(1)[0], a1)
gtest.Assert(len(array1.Rands(2)), 2)
gtest.Assert(len(array1.Rands(10)), 7)
gtest.AssertIN(array1.Rands(1)[0], a1)
gtest.AssertIN(array1.Rand(), a1)
})
}
func TestIntArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{100, 200, 300, 400, 500, 600}
array := garray.NewFromCopy(a1)
gtest.AssertIN(array.PopRands(2), a1)
})
}

View File

@ -80,6 +80,14 @@ func TestArray_PushAndPop(t *testing.T) {
})
}
func TestArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{100, 200, 300, 400, 500, 600}
array := garray.NewFromCopy(a1)
gtest.AssertIN(array.PopRands(2), a1)
})
}
func TestArray_PopLeftsAndPopRights(t *testing.T) {
gtest.Case(t, func() {
value1 := []interface{}{0,1,2,3,4,5,6}
@ -165,9 +173,9 @@ func TestArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{0,1,2,3,4,5,6}
array1 := garray.NewArrayFrom(a1)
gtest.Assert(len(array1.Rand(2)), 2)
gtest.Assert(len(array1.Rand(10)), 7)
gtest.AssertIN(array1.Rand(1)[0], a1)
gtest.Assert(len(array1.Rands(2)), 2)
gtest.Assert(len(array1.Rands(10)), 7)
gtest.AssertIN(array1.Rands(1)[0], a1)
})
}

View File

@ -162,9 +162,17 @@ func TestStringArray_Rand(t *testing.T) {
gtest.Case(t, func() {
a1 := []string{"0","1","2","3","4","5","6"}
array1 := garray.NewStringArrayFrom(a1)
gtest.Assert(len(array1.Rand(2)), "2")
gtest.Assert(len(array1.Rand(10)), "7")
gtest.AssertIN(array1.Rand(1)[0], a1)
gtest.Assert(len(array1.Rands(2)), "2")
gtest.Assert(len(array1.Rands(10)), "7")
gtest.AssertIN(array1.Rands(1)[0], a1)
})
}
func TestStringArray_PopRands(t *testing.T) {
gtest.Case(t, func() {
a1 := []interface{}{"100", "200", "300", "400", "500", "600"}
array := garray.NewFromCopy(a1)
gtest.AssertIN(array.PopRands(2), a1)
})
}

View File

@ -291,8 +291,8 @@ func (gm *Map) Clear() {
//
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (gm *Map) LockFunc(f func(m map[interface{}]interface{})) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
gm.mu.Lock()
defer gm.mu.Unlock()
f(gm.m)
}
@ -300,8 +300,8 @@ func (gm *Map) LockFunc(f func(m map[interface{}]interface{})) {
//
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (gm *Map) RLockFunc(f func(m map[interface{}]interface{})) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
gm.mu.RLock()
defer gm.mu.RUnlock()
f(gm.m)
}

View File

@ -238,15 +238,15 @@ func (gm *IntBoolMap) Clear() {
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (gm *IntBoolMap) LockFunc(f func(m map[int]bool)) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
gm.mu.Lock()
defer gm.mu.Unlock()
f(gm.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (gm *IntBoolMap) RLockFunc(f func(m map[int]bool)) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
gm.mu.RLock()
defer gm.mu.RUnlock()
f(gm.m)
}

View File

@ -238,15 +238,15 @@ func (gm *IntIntMap) Clear() {
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (gm *IntIntMap) LockFunc(f func(m map[int]int)) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
gm.mu.Lock()
defer gm.mu.Unlock()
f(gm.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (gm *IntIntMap) RLockFunc(f func(m map[int]int)) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
gm.mu.RLock()
defer gm.mu.RUnlock()
f(gm.m)
}

View File

@ -109,7 +109,9 @@ func (gm *IntInterfaceMap) doSetWithLockCheck(key int, value interface{}) interf
if f, ok := value.(func() interface {}); ok {
value = f()
}
gm.m[key] = value
if value != nil {
gm.m[key] = value
}
return value
}
@ -231,8 +233,8 @@ func (gm *IntInterfaceMap) LockFunc(f func(m map[int]interface{})) {
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (gm *IntInterfaceMap) RLockFunc(f func(m map[int]interface{})) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
gm.mu.RLock()
defer gm.mu.RUnlock()
f(gm.m)
}

View File

@ -239,15 +239,15 @@ func (gm *IntStringMap) Clear() {
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (gm *IntStringMap) LockFunc(f func(m map[int]string)) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
gm.mu.Lock()
defer gm.mu.Unlock()
f(gm.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (gm *IntStringMap) RLockFunc(f func(m map[int]string)) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
gm.mu.RLock()
defer gm.mu.RUnlock()
f(gm.m)
}

View File

@ -238,15 +238,15 @@ func (gm *StringBoolMap) Clear() {
// 并发安全锁操作,使用自定义方法执行加锁修改操作
func (gm *StringBoolMap) LockFunc(f func(m map[string]bool)) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
gm.mu.Lock()
defer gm.mu.Unlock()
f(gm.m)
}
// 并发安全锁操作,使用自定义方法执行加锁读取操作
func (gm *StringBoolMap) RLockFunc(f func(m map[string]bool)) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
gm.mu.RLock()
defer gm.mu.RUnlock()
f(gm.m)
}

View File

@ -239,15 +239,15 @@ func (gm *StringIntMap) Clear() {
// 并发安全写锁操作,使用自定义方法执行加锁修改操作
func (gm *StringIntMap) LockFunc(f func(m map[string]int)) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
gm.mu.Lock()
defer gm.mu.Unlock()
f(gm.m)
}
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
func (gm *StringIntMap) RLockFunc(f func(m map[string]int)) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
gm.mu.RLock()
defer gm.mu.RUnlock()
f(gm.m)
}

View File

@ -109,7 +109,9 @@ func (gm *StringInterfaceMap) doSetWithLockCheck(key string, value interface{})
if f, ok := value.(func() interface {}); ok {
value = f()
}
gm.m[key] = value
if value != nil {
gm.m[key] = value
}
return value
}
@ -224,15 +226,15 @@ func (gm *StringInterfaceMap) Clear() {
// 并发安全写锁操作,使用自定义方法执行加锁修改操作
func (gm *StringInterfaceMap) LockFunc(f func(m map[string]interface{})) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
gm.mu.Lock()
defer gm.mu.Unlock()
f(gm.m)
}
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
func (gm *StringInterfaceMap) RLockFunc(f func(m map[string]interface{})) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
gm.mu.RLock()
defer gm.mu.RUnlock()
f(gm.m)
}

View File

@ -238,15 +238,15 @@ func (gm *StringStringMap) Clear() {
// 并发安全写锁操作,使用自定义方法执行加锁修改操作
func (gm *StringStringMap) LockFunc(f func(m map[string]string)) {
gm.mu.Lock(true)
defer gm.mu.Unlock(true)
gm.mu.Lock()
defer gm.mu.Unlock()
f(gm.m)
}
// 并发安全读锁操作,使用自定义方法执行加锁读取操作
func (gm *StringStringMap) RLockFunc(f func(m map[string]string)) {
gm.mu.RLock(true)
defer gm.mu.RUnlock(true)
gm.mu.RLock()
defer gm.mu.RUnlock()
f(gm.m)
}

View File

@ -15,9 +15,8 @@
package gqueue
import (
"container/list"
"github.com/gogf/gf/g/container/glist"
"math"
"sync"
)
// 1、这是一个先进先出的队列(chan <-- list)
@ -28,9 +27,8 @@ import (
//
// 4、由于功能主体是chan那么操作仍然像chan那样具有阻塞效果
type Queue struct {
mu sync.Mutex // 底层链表写锁
limit int // 队列限制大小
list *list.List // 底层数据链表
list *glist.List // 底层数据链表
events chan struct{} // 写入事件通知
closed chan struct{} // 队列关闭通知
C chan interface{} // 队列数据读取
@ -50,7 +48,7 @@ func New(limit...int) *Queue {
q.limit = limit[0]
q.C = make(chan interface{}, limit[0])
} else {
q.list = list.New()
q.list = glist.New()
q.events = make(chan struct{}, math.MaxInt32)
q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE)
go q.startAsyncLoop()
@ -68,7 +66,6 @@ func (q *Queue) startAsyncLoop() {
for {
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)
@ -76,7 +73,6 @@ func (q *Queue) startAsyncLoop() {
break
}
}
q.mu.Unlock()
for _, v := range array {
q.C <- v
}
@ -93,9 +89,7 @@ func (q *Queue) Push(v interface{}) {
if q.limit > 0 {
q.C <- v
} else {
q.mu.Lock()
q.list.PushBack(v)
q.mu.Unlock()
q.events <- struct{}{}
}
}

View File

@ -149,8 +149,8 @@ func (r *Ring) Unlink(n int) *Ring {
// 读锁遍历往后只读遍历回调函数返回true表示继续遍历否则退出遍历
func (r *Ring) RLockIteratorNext(f func(value interface{}) bool) {
r.mu.RLock(true)
defer r.mu.RUnlock(true)
r.mu.RLock()
defer r.mu.RUnlock()
if !f(r.ring.Value) {
return
}
@ -163,8 +163,8 @@ func (r *Ring) RLockIteratorNext(f func(value interface{}) bool) {
// 读锁遍历往前只读遍历回调函数返回true表示继续遍历否则退出遍历
func (r *Ring) RLockIteratorPrev(f func(value interface{}) bool) {
r.mu.RLock(true)
defer r.mu.RUnlock(true)
r.mu.RLock()
defer r.mu.RUnlock()
if !f(r.ring.Value) {
return
}
@ -177,8 +177,8 @@ func (r *Ring) RLockIteratorPrev(f func(value interface{}) bool) {
// 写锁遍历往后写遍历回调函数返回true表示继续遍历否则退出遍历
func (r *Ring) LockIteratorNext(f func(item *ring.Ring) bool) {
r.mu.RLock(true)
defer r.mu.RUnlock(true)
r.mu.RLock()
defer r.mu.RUnlock()
if !f(r.ring) {
return
}
@ -191,8 +191,8 @@ func (r *Ring) LockIteratorNext(f func(item *ring.Ring) bool) {
// 写锁遍历往前写遍历回调函数返回true表示继续遍历否则退出遍历
func (r *Ring) LockIteratorPrev(f func(item *ring.Ring) bool) {
r.mu.RLock(true)
defer r.mu.RUnlock(true)
r.mu.RLock()
defer r.mu.RUnlock()
if !f(r.ring) {
return
}

View File

@ -139,8 +139,8 @@ func (set *Set) String() string {
//
// 使用自定义方法执行加锁修改操作。
func (set *Set) LockFunc(f func(m map[interface{}]struct{})) *Set {
set.mu.Lock(true)
defer set.mu.Unlock(true)
set.mu.Lock()
defer set.mu.Unlock()
f(set.m)
return set
}
@ -149,8 +149,8 @@ func (set *Set) LockFunc(f func(m map[interface{}]struct{})) *Set {
//
// 使用自定义方法执行加锁读取操作。
func (set *Set) RLockFunc(f func(m map[interface{}]struct{})) *Set {
set.mu.RLock(true)
defer set.mu.RUnlock(true)
set.mu.RLock()
defer set.mu.RUnlock()
f(set.m)
return set
}

View File

@ -130,8 +130,8 @@ func (set *IntSet) String() string {
//
// 使用自定义方法执行加锁修改操作。
func (set *IntSet) LockFunc(f func(m map[int]struct{})) *IntSet {
set.mu.Lock(true)
defer set.mu.Unlock(true)
set.mu.Lock()
defer set.mu.Unlock()
f(set.m)
return set
}
@ -140,8 +140,8 @@ func (set *IntSet) LockFunc(f func(m map[int]struct{})) *IntSet {
//
// 使用自定义方法执行加锁读取操作。
func (set *IntSet) RLockFunc(f func(m map[int]struct{})) *IntSet {
set.mu.RLock(true)
defer set.mu.RUnlock(true)
set.mu.RLock()
defer set.mu.RUnlock()
f(set.m)
return set
}

View File

@ -130,8 +130,8 @@ func (set *StringSet) String() string {
//
// 使用自定义方法执行加锁修改操作。
func (set *StringSet) LockFunc(f func(m map[string]struct{})) *StringSet {
set.mu.Lock(true)
defer set.mu.Unlock(true)
set.mu.Lock()
defer set.mu.Unlock()
f(set.m)
return set
}
@ -140,8 +140,8 @@ func (set *StringSet) LockFunc(f func(m map[string]struct{})) *StringSet {
//
// 使用自定义方法执行加锁读取操作。
func (set *StringSet) RLockFunc(f func(m map[string]struct{})) *StringSet {
set.mu.RLock(true)
defer set.mu.RUnlock(true)
set.mu.RLock()
defer set.mu.RUnlock()
f(set.m)
return set
}

View File

@ -18,7 +18,7 @@ import (
type Var struct {
value interface{} // 变量值
safe bool // 当为true时,value为 *gtype.Interface 类型
safe bool // 当为true时, value为 *gtype.Interface 类型
}
// 创建一个动态变量value参数可以为nil

View File

@ -37,17 +37,19 @@ type DB interface {
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)
doInsert(link dbLink, table string, data interface{}, option int, batch...int) (result sql.Result, err error)
doBatchInsert(link dbLink, table string, list interface{}, option int, batch...int) (result sql.Result, err error)
doUpdate(link dbLink, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error)
doDelete(link dbLink, table string, condition string, args ...interface{}) (result sql.Result, err 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
GetStruct(objPointer interface{}, query string, args ...interface{}) error
GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error
GetScan(objPointer interface{}, query string, args ...interface{}) error
// 创建底层数据库master/slave链接对象
Master() (*sql.DB, error)
@ -61,14 +63,14 @@ type DB interface {
Begin() (*TX, error)
// 数据表插入/更新/保存操作
Insert(table string, data Map) (sql.Result, error)
Replace(table string, data Map) (sql.Result, error)
Save(table string, data Map) (sql.Result, error)
Insert(table string, data interface{}, batch...int) (sql.Result, error)
Replace(table string, data interface{}, batch...int) (sql.Result, error)
Save(table string, data interface{}, batch...int) (sql.Result, error)
// 数据表插入/更新/保存操作(批量)
BatchInsert(table string, list List, batch int) (sql.Result, error)
BatchReplace(table string, list List, batch int) (sql.Result, error)
BatchSave(table string, list List, batch int) (sql.Result, error)
BatchInsert(table string, list interface{}, batch...int) (sql.Result, error)
BatchReplace(table string, list interface{}, batch...int) (sql.Result, error)
BatchSave(table string, list interface{}, batch...int) (sql.Result, error)
// 数据修改/删除
Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error)
@ -149,8 +151,11 @@ const (
OPTION_REPLACE = 1
OPTION_SAVE = 2
OPTION_IGNORE = 3
// 默认批量操作的数量值(Batch*操作)
gDEFAULT_BATCH_NUM = 10
// 默认的连接池连接存活时间(秒)
gDEFAULT_CONN_MAX_LIFE_TIME = 30
)
// 使用默认/指定分组配置进行连接数据库集群配置项default

View File

@ -14,8 +14,8 @@ import (
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/os/gcache"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/util/gconv"
"reflect"
"strings"
)
@ -165,13 +165,44 @@ func (bs *dbBase) GetOne(query string, args ...interface{}) (Record, error) {
return nil, nil
}
// 数据库查询,获取查询结果记录自动映射数据到给定的struct对象中
func (bs *dbBase) GetStruct(obj interface{}, query string, args ...interface{}) error {
// 数据库查询,查询单条记录自动映射数据到给定的struct对象中
func (bs *dbBase) GetStruct(objPointer interface{}, query string, args ...interface{}) error {
one, err := bs.GetOne(query, args...)
if err != nil {
return err
}
return one.ToStruct(obj)
return one.ToStruct(objPointer)
}
// 数据库查询查询多条记录并自动转换为指定的slice对象, 如: []struct/[]*struct。
func (bs *dbBase) GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error {
all, err := bs.GetAll(query, args...)
if err != nil {
return err
}
return all.ToStructs(objPointerSlice)
}
// 将结果转换为指定的struct/*struct/[]struct/[]*struct,
// 参数应该为指针类型,否则返回失败。
// 该方法自动识别参数类型调用Struct/Structs方法。
func (bs *dbBase) GetScan(objPointer interface{}, query string, args ...interface{}) error {
t := reflect.TypeOf(objPointer)
k := t.Kind()
if k != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", k)
}
k = t.Elem().Kind()
switch k {
case reflect.Array:
case reflect.Slice:
return bs.db.GetStructs(objPointer, query, args ...)
case reflect.Struct:
return bs.db.GetStruct(objPointer, query, args ...)
default:
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
}
return nil
}
// 数据库查询,获取查询字段值
@ -234,45 +265,72 @@ func (bs *dbBase) Begin() (*TX, error) {
}
}
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
func (bs *dbBase) Insert(table string, data Map) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_INSERT)
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
// 参数data支持map/struct/*struct/slice类型
// 当为slice(例如[]map/[]struct/[]*struct)类型时batch参数生效并自动切换为批量操作。
func (bs *dbBase) Insert(table string, data interface{}, batch...int) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_INSERT, batch...)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (bs *dbBase) Replace(table string, data Map) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_REPLACE)
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
// 参数data支持map/struct/*struct/slice类型
// 当为slice(例如[]map/[]struct/[]*struct)类型时batch参数生效并自动切换为批量操作。
func (bs *dbBase) Replace(table string, data interface{}, batch...int) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_REPLACE, batch...)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (bs *dbBase) Save(table string, data Map) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_SAVE)
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
// 参数data支持map/struct/*struct/slice类型
// 当为slice(例如[]map/[]struct/[]*struct)类型时batch参数生效并自动切换为批量操作。
func (bs *dbBase) Save(table string, data interface{}, batch...int) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_SAVE, batch...)
}
// insert、replace, save ignore操作
// 0: insert: 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
// 1: replace: 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
// 2: save: 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
// 3: ignore: 如果数据存在(主键或者唯一索引),那么什么也不做
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, charl + k + charr)
// 支持insert、replace, save ignore操作
// 0: insert: 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回;
// 1: replace: 如果数据存在(主键或者唯一索引),那么删除后重新写入一条;
// 2: save: 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据;
// 3: ignore: 如果数据存在(主键或者唯一索引),那么什么也不做;
//
// 参数data支持map/struct/*struct/slice类型
// 当为slice(例如[]map/[]struct/[]*struct)类型时batch参数生效并自动切换为批量操作。
func (bs *dbBase) doInsert(link dbLink, table string, data interface{}, option int, batch...int) (result sql.Result, err error) {
var fields []string
var values []string
var params []interface{}
var dataMap Map
// 使用反射判断data数据类型如果为slice类型那么自动转为批量操作
rv := reflect.ValueOf(data)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Slice: fallthrough
case reflect.Array:
return bs.db.doBatchInsert(link, table, data, option, batch...)
case reflect.Map: fallthrough
case reflect.Struct:
dataMap = gconv.Map(data)
default:
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
}
charL, charR := bs.db.getChars()
for k, v := range dataMap {
fields = append(fields, charL + k + charR)
values = append(values, "?")
params = append(params, v)
params = append(params, convertParam(v))
}
operation := getInsertOperationByOption(option)
updateStr := ""
if option == OPTION_SAVE {
var updates []string
for k, _ := range data {
for k, _ := range dataMap {
updates = append(updates,
fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
charl, k, charr,
charl, k, charr,
charL, k, charR,
charL, k, charR,
),
)
}
@ -290,28 +348,59 @@ func (bs *dbBase) doInsert(link dbLink, table string, data Map, option int) (res
}
// CURD操作:批量数据指定批次量写入
func (bs *dbBase) BatchInsert(table string, list List, batch int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, batch, OPTION_INSERT)
func (bs *dbBase) BatchInsert(table string, list interface{}, batch...int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, OPTION_INSERT, batch...)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (bs *dbBase) BatchReplace(table string, list List, batch int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, batch, OPTION_REPLACE)
func (bs *dbBase) BatchReplace(table string, list interface{}, batch...int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, OPTION_REPLACE, batch...)
}
// 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 (bs *dbBase) BatchSave(table string, list interface{}, batch...int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, OPTION_SAVE, batch...)
}
// 批量写入数据
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{}
// 批量写入数据, 参数list支持slice类型例如: []map/[]struct/[]*struct。
func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, option int, batch...int) (result sql.Result, err error) {
var keys []string
var values []string
var params []interface{}
listMap := (List)(nil)
switch v := list.(type) {
case Result:
listMap = v.ToList()
case Record:
listMap = List{v.ToMap()}
case List:
listMap = v
case Map:
listMap = List{v}
default:
rv := reflect.ValueOf(list)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
// 如果是slice那么转换为List类型
case reflect.Slice: fallthrough
case reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
listMap[i] = gconv.Map(rv.Index(i).Interface())
}
case reflect.Map: fallthrough
case reflect.Struct:
listMap = List{Map(gconv.Map(list))}
default:
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
}
}
// 判断长度
if len(list) < 1 {
if len(listMap) < 1 {
return result, errors.New("empty data list")
}
if link == nil {
@ -320,14 +409,15 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list List, batch int,
}
}
// 首先获取字段名称及记录长度
for k, _ := range list[0] {
keys = append(keys, k)
values = append(values, "?")
holders := []string(nil)
for k, _ := range listMap[0] {
keys = append(keys, k)
holders = append(holders, "?")
}
batchResult := new(batchSqlResult)
charl, charr := bs.db.getChars()
keyStr := charl + strings.Join(keys, charl + "," + charr) + charr
valueHolderStr := "(" + strings.Join(values, ",") + ")"
charL, charR := bs.db.getChars()
keyStr := charL + strings.Join(keys, charL + "," + charR) + charR
valueHolderStr := "(" + strings.Join(holders, ",") + ")"
// 操作判断
operation := getInsertOperationByOption(option)
updateStr := ""
@ -336,22 +426,26 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list List, batch int,
for _, k := range keys {
updates = append(updates,
fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
charl, k, charr,
charl, k, charr,
charL, k, charR,
charL, k, charR,
),
)
}
updateStr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
}
// 构造批量写入数据格式(注意map的遍历是无序的)
for i := 0; i < len(list); i++ {
batchNum := gDEFAULT_BATCH_NUM
if len(batch) > 0 {
batchNum = batch[0]
}
for i := 0; i < len(listMap); i++ {
for _, k := range keys {
params = append(params, list[i][k])
params = append(params, convertParam(listMap[i][k]))
}
bvalues = append(bvalues, valueHolderStr)
if len(bvalues) == batch {
values = append(values, valueHolderStr)
if len(values) == batchNum {
r, err := bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
operation, table, keyStr, strings.Join(values, ","),
updateStr),
params...)
if err != nil {
@ -363,14 +457,14 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list List, batch int,
batchResult.lastResult = r
batchResult.rowsAffected += n
}
params = params[:0]
bvalues = bvalues[:0]
params = params[:0]
values = values[:0]
}
}
// 处理最后不构成指定批量的数据
if len(bvalues) > 0 {
if len(values) > 0 {
r, err := bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
operation, table, keyStr, strings.Join(values, ","),
updateStr),
params...)
if err != nil {
@ -386,59 +480,70 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list List, batch int,
return batchResult, nil
}
// CURD操作:数据更新统一采用sql预处理
// data参数支持字符串或者关联数组类型,内部会自行做判断处理
// CURD操作:数据更新统一采用sql预处理
// data参数支持string/map/struct/*struct类型。
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 ...)
newWhere, newArgs := formatCondition(condition, args)
return bs.db.doUpdate(nil, table, data, newWhere, newArgs ...)
}
// CURD操作:数据更新统一采用sql预处理
// data参数支持字符串或者关联数组类型,内部会自行做判断处理
func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, condition interface{}, args ...interface{}) (result sql.Result, err error) {
params := ([]interface{})(nil)
// CURD操作:数据更新统一采用sql预处理
// data参数支持string/map/struct/*struct类型类型。
func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
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=?", charl, k, charr))
params = append(params, gconv.String(refValue.MapIndex(k).Interface()))
}
updates = strings.Join(fields, ",")
} else {
updates = gconv.String(data)
charL, charR := bs.db.getChars()
// 使用反射进行类型判断
rv := reflect.ValueOf(data)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
for _, v := range args {
params = append(params, gconv.String(v))
params := []interface{}(nil)
switch kind {
case reflect.Map: fallthrough
case reflect.Struct:
var fields []string
for k, v := range gconv.Map(data) {
fields = append(fields, fmt.Sprintf("%s%s%s=?", charL, k, charR))
params = append(params, convertParam(v))
}
updates = strings.Join(fields, ",")
default:
updates = gconv.String(data)
}
if len(params) > 0 {
args = append(params, args...)
}
// 如果没有传递link那么使用默认的写库对象
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...)
if len(condition) == 0 {
return bs.db.doExec(link, fmt.Sprintf("UPDATE %s SET %s", table, updates), args...)
}
return bs.db.doExec(link, fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, condition), args...)
}
// CURD操作:删除数据
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 ...)
newWhere, newArgs := formatCondition(condition, args)
return bs.db.doDelete(nil, table, newWhere, newArgs ...)
}
// 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) doDelete(link dbLink, table string, condition string, args ...interface{}) (result sql.Result, err error) {
if link == nil {
if link, err = bs.db.Master(); err != nil {
return nil, err
}
}
if len(condition) == 0 {
return bs.db.doExec(link, fmt.Sprintf("DELETE FROM %s", table), args...)
}
return bs.db.doExec(link, fmt.Sprintf("DELETE FROM %s WHERE %s", table, condition), args...)
}
// 获得缓存对象

View File

@ -12,44 +12,81 @@ import (
"fmt"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"reflect"
"strings"
)
// 格式化SQL查询条件
func formatCondition(where interface{}, args []interface{}) (string, []interface{}) {
func formatCondition(where interface{}, args []interface{}) (newWhere string, newArgs []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))
// 使用反射进行类型判断
rv := reflect.ValueOf(where)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
tmpArgs := []interface{}(nil)
switch kind {
// map/struct类型
case reflect.Map: fallthrough
case reflect.Struct:
for key, value := range gconv.Map(where) {
if buffer.Len() > 0 {
buffer.WriteString(" AND ")
}
// 支持slice键值/属性,如果只有一个?占位符号那么作为IN查询否则打散作为多个查询参数
rv := reflect.ValueOf(value)
switch rv.Kind() {
case reflect.Slice: fallthrough
case reflect.Array:
count := gstr.Count(key, "?")
if count == 0 {
buffer.WriteString(key + " IN(?)")
tmpArgs = append(tmpArgs, value)
} else if count != rv.Len() {
buffer.WriteString(key)
tmpArgs = append(tmpArgs, value)
} else {
buffer.WriteString(key)
// 如果键名/属性名称中带有多个?占位符号,那么将参数打散
tmpArgs = append(tmpArgs, gconv.Interfaces(value)...)
}
default:
if value == nil {
buffer.WriteString(key)
} else {
if gstr.Pos(key, "?") == -1 {
if gstr.Pos(key, "<") == -1 && gstr.Pos(key, ">") == -1 && gstr.Pos(key, "=") == -1 {
buffer.WriteString(key + "=?")
} else {
buffer.WriteString(key + "?")
}
} else {
buffer.WriteString(key)
}
tmpArgs = append(tmpArgs, value)
}
}
}
default:
buffer.WriteString(gconv.String(where))
}
// 没有任何条件查询参数,直接返回
if buffer.Len() == 0 {
buffer.WriteString("1=1")
return "", args
}
// 查询条件处理
newWhere := buffer.String()
newArgs := make([]interface{}, 0)
if len(args) > 0 {
for index, arg := range args {
newWhere = buffer.String()
tmpArgs = append(tmpArgs, args...)
// 查询条件参数处理主要处理slice参数类型
if len(tmpArgs) > 0 {
for index, arg := range tmpArgs {
rv := reflect.ValueOf(arg)
kind := rv.Kind()
if kind == reflect.Ptr {
@ -57,11 +94,14 @@ func formatCondition(where interface{}, args []interface{}) (string, []interface
kind = rv.Kind()
}
switch kind {
// '?'占位符支持slice类型,
// 这里会将slice参数拆散并更新原有占位符'?'为多个'?',使用','符号连接。
case reflect.Slice: fallthrough
case reflect.Array:
for i := 0; i < rv.Len(); i++ {
newArgs = append(newArgs, rv.Index(i).Interface())
}
// counter用于匹配该参数的位置(与index对应)
counter := 0
newWhere, _ = gregex.ReplaceStringFunc(`\?`, newWhere, func(s string) string {
counter++
@ -71,11 +111,35 @@ func formatCondition(where interface{}, args []interface{}) (string, []interface
return s
})
default:
// 支持例如 Where/And/Or("uid", 1) 这种格式
if gstr.Pos(newWhere, "?") == -1 {
if gstr.Pos(newWhere, "<") == -1 && gstr.Pos(newWhere, ">") == -1 && gstr.Pos(newWhere, "=") == -1 {
newWhere += "=?"
} else {
newWhere += "?"
}
}
newArgs = append(newArgs, arg)
}
}
}
return newWhere, newArgs
return
}
// 将预处理参数转换为底层数据库引擎支持的格式。
// 主要是判断参数是否为复杂数据类型,如果是,那么转换为基础类型。
func convertParam(value interface{}) interface{} {
rv := reflect.ValueOf(value)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Struct:
return gconv.String(value)
}
return value
}
// 打印SQL对象(仅在debug=true时有效)
@ -109,13 +173,13 @@ func formatError(err error, query string, args ...interface{}) error {
// 根据insert选项获得操作名称
func getInsertOperationByOption(option int) string {
oper := "INSERT"
operator := "INSERT"
switch option {
case OPTION_REPLACE:
oper = "REPLACE"
operator = "REPLACE"
case OPTION_SAVE:
case OPTION_IGNORE:
oper = "INSERT IGNORE"
operator = "INSERT IGNORE"
}
return oper
return operator
}

View File

@ -3,17 +3,18 @@
// 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://github.com/gogf/gf.
//
// @author john, ymrjqyy
package gdb
import (
"fmt"
"errors"
"database/sql"
"github.com/gogf/gf/g/util/gconv"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"database/sql"
"errors"
"fmt"
"github.com/gogf/gf/g/util/gconv"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"reflect"
"strings"
)
// 数据库链式操作模型对象
@ -35,6 +36,7 @@ type Model struct {
cacheEnabled bool // 当前SQL操作是否开启查询缓存功能
cacheTime int // 查询缓存时间
cacheName string // 查询缓存名称
safe bool // 当前模型是否运行安全模式(可修改当前模型,否则每一次链式操作都是返回新的模型对象)
}
// 链式操作,数据表字段,可支持多个表,以半角逗号连接
@ -44,6 +46,7 @@ func (bs *dbBase) Table(tables string) (*Model) {
tablesInit : tables,
tables : tables,
fields : "*",
safe : false,
}
}
@ -59,6 +62,7 @@ func (tx *TX) Table(tables string) (*Model) {
tx : tx,
tablesInit : tables,
tables : tables,
safe : false,
}
}
@ -79,98 +83,130 @@ func (md *Model) Clone() *Model {
return newModel
}
// 标识当前对象运行安全模式(可被修改)。
// 1. 默认情况下,模型对象的对象属性无法被修改,
// 每一次链式操作都是克隆一个新的模型对象,这样所有的操作都不会污染模型对象。
// 但是链式操作如果需要分开执行,那么需要将新的克隆对象赋值给旧的模型对象继续操作。
// 2. 当标识模型对象为可修改,那么在当前模型对象的所有链式操作均会影响下一次的链式操作,
// 即使是链式操作分开执行。
// 3. 大部分ORM框架默认模型对象是可修改的但是GF框架的ORM提供给开发者更灵活更安全的链式操作选项。
func (md *Model) Safe(safe...bool) *Model {
if len(safe) > 0 {
md.safe = safe[0]
} else {
md.safe = true
}
return md
}
// 返回操作的模型对象可能是当前对象也可能是新的克隆对象根据alterable决定。
func (md *Model) getModel() *Model {
if !md.safe {
return md
} else {
return md.Clone()
}
}
// 链式操作,左联表
func (md *Model) LeftJoin(joinTable string, on string) (*Model) {
model := md.Clone()
model := md.getModel()
model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", joinTable, on)
return model
}
// 链式操作,右联表
func (md *Model) RightJoin(joinTable string, on string) (*Model) {
model := md.Clone()
model := md.getModel()
model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", joinTable, on)
return model
}
// 链式操作,内联表
func (md *Model) InnerJoin(joinTable string, on string) (*Model) {
model := md.Clone()
model := md.getModel()
model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", joinTable, on)
return model
}
// 链式操作,查询字段
func (md *Model) Fields(fields string) (*Model) {
model := md.Clone()
model := md.getModel()
model.fields = fields
return model
}
// 链式操作,过滤字段
func (md *Model) Filter() (*Model) {
model := md.Clone()
model := md.getModel()
model.filter = true
return model
}
// 链式操作condition支持string & gdb.Map
// 链式操作condition支持string & gdb.Map.
// 注意多个Where调用时会自动转换为And条件调用。
func (md *Model) Where(where interface{}, args ...interface{}) (*Model) {
model := md.Clone()
model := md.getModel()
if model.where != "" {
return md.And(where, args...)
}
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 += "=?"
}
model.whereArgs = newArgs
return model
}
// 链式操作添加AND条件到Where中
func (md *Model) And(where interface{}, args ...interface{}) (*Model) {
model := md.Clone()
model := md.getModel()
newWhere, newArgs := formatCondition(where, args)
model.where += " AND " + newWhere
model.whereArgs = append(model.whereArgs, newArgs...)
if len(model.where) > 0 && model.where[0] == '(' {
model.where = fmt.Sprintf(`%s AND (%s)`, model.where, newWhere)
} else {
model.where = fmt.Sprintf(`(%s) AND (%s)`, model.where, newWhere)
}
model.whereArgs = append(model.whereArgs, newArgs...)
return model
}
// 链式操作添加OR条件到Where中
func (md *Model) Or(where interface{}, args ...interface{}) (*Model) {
model := md.Clone()
model := md.getModel()
newWhere, newArgs := formatCondition(where, args)
model.where += " OR " + newWhere
model.whereArgs = append(model.whereArgs, newArgs...)
if len(model.where) > 0 && model.where[0] == '(' {
model.where = fmt.Sprintf(`%s OR (%s)`, model.where, newWhere)
} else {
model.where = fmt.Sprintf(`(%s) OR (%s)`, model.where, newWhere)
}
model.whereArgs = append(model.whereArgs, newArgs...)
return model
}
// 链式操作group by
func (md *Model) GroupBy(groupBy string) (*Model) {
model := md.Clone()
model := md.getModel()
model.groupBy = groupBy
return model
}
// 链式操作order by
func (md *Model) OrderBy(orderBy string) (*Model) {
model := md.Clone()
model := md.getModel()
model.orderBy = orderBy
return model
}
// 链式操作limit
func (md *Model) Limit(start int, limit int) (*Model) {
model := md.Clone()
model := md.getModel()
model.start = start
model.limit = limit
return model
}
// 链式操作,翻页
// @author ymrjqyy
// 链式操作,翻页注意分页页码从1开始而Limit方法从0开始。
func (md *Model) ForPage(page, limit int) (*Model) {
model := md.Clone()
model := md.getModel()
model.start = (page - 1) * limit
model.limit = limit
return model
@ -178,7 +214,7 @@ func (md *Model) ForPage(page, limit int) (*Model) {
// 设置批处理的大小
func (md *Model) Batch(batch int) *Model {
model := md.Clone()
model := md.getModel()
model.batch = batch
return model
}
@ -188,7 +224,7 @@ func (md *Model) Batch(batch int) *Model {
// name表示自定义的缓存名称便于业务层精准定位缓存项(如果业务层需要手动清理时,必须指定缓存名称)
// 例如:查询缓存时设置名称,清理缓存时可以给定清理的缓存名称进行精准清理。
func (md *Model) Cache(time int, name ... string) *Model {
model := md.Clone()
model := md.getModel()
model.cacheTime = time
if len(name) > 0 {
model.cacheName = name[0]
@ -200,29 +236,35 @@ func (md *Model) Cache(time int, name ... string) *Model {
return model
}
// 链式操作,操作数据记录项,可以是string/Map, 也可以是key,value,key,value,...
func (md *Model) Data(data ...interface{}) (*Model) {
model := md.Clone()
// 链式操作,操作数据参数data类型支持 string/map/slice/struct/*struct ,
// 也可以是key,value,key,value,...。
func (md *Model) Data(data ...interface{}) *Model {
model := md.getModel()
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]
m[gconv.String(data[i])] = data[i + 1]
}
model.data = m
} else {
switch data[0].(type) {
switch params := data[0].(type) {
case Result:
model.data = params.ToList()
case Record:
model.data = params.ToMap()
case List:
model.data = data[0]
model.data = params
case Map:
model.data = data[0]
model.data = params
default:
rv := reflect.ValueOf(data[0])
rv := reflect.ValueOf(params)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
// 如果是slice那么转换为List类型
case reflect.Slice: fallthrough
case reflect.Array:
list := make(List, rv.Len())
@ -230,8 +272,9 @@ func (md *Model) Data(data ...interface{}) (*Model) {
list[i] = gconv.Map(rv.Index(i).Interface())
}
model.data = list
case reflect.Map:
model.data = gconv.Map(data[0])
case reflect.Map: fallthrough
case reflect.Struct:
model.data = Map(gconv.Map(data[0]))
default:
model.data = data[0]
}
@ -240,7 +283,9 @@ func (md *Model) Data(data ...interface{}) (*Model) {
return model
}
// 链式操作, CURD - Insert/BatchInsert
// 链式操作, CURD - Insert/BatchInsert
// 根据Data方法传递的参数类型决定该操作是单条操作还是批量操作
// 如果Data方法传递的是slice类型那么为批量操作。
func (md *Model) Insert() (result sql.Result, err error) {
defer func() {
if err == nil {
@ -279,7 +324,9 @@ func (md *Model) Insert() (result sql.Result, err error) {
return nil, errors.New("inserting into table with invalid data type")
}
// 链式操作, CURD - Replace/BatchReplace
// 链式操作, CURD - Replace/BatchReplace
// 根据Data方法传递的参数类型决定该操作是单条操作还是批量操作
// 如果Data方法传递的是slice类型那么为批量操作。
func (md *Model) Replace() (result sql.Result, err error) {
defer func() {
if err == nil {
@ -318,7 +365,9 @@ func (md *Model) Replace() (result sql.Result, err error) {
return nil, errors.New("replacing into table with invalid data type")
}
// 链式操作, CURD - Save/BatchSave
// 链式操作, CURD - Save/BatchSave
// 根据Data方法传递的参数类型决定该操作是单条操作还是批量操作
// 如果Data方法传递的是slice类型那么为批量操作。
func (md *Model) Save() (result sql.Result, err error) {
defer func() {
if err == nil {
@ -330,7 +379,7 @@ func (md *Model) Save() (result sql.Result, err error) {
}
// 批量操作
if list, ok := md.data.(List); ok {
batch := 10
batch := gDEFAULT_BATCH_NUM
if md.batch > 0 {
batch = md.batch
}
@ -375,9 +424,9 @@ func (md *Model) Update() (result sql.Result, err error) {
}
}
if md.tx == nil {
return md.db.Update(md.tables, md.data, md.where, md.whereArgs ...)
return md.db.doUpdate(nil, md.tables, md.data, md.where, md.whereArgs ...)
} else {
return md.tx.Update(md.tables, md.data, md.where, md.whereArgs ...)
return md.tx.doUpdate(md.tables, md.data, md.where, md.whereArgs ...)
}
}
@ -389,9 +438,9 @@ func (md *Model) Delete() (result sql.Result, err error) {
}
}()
if md.tx == nil {
return md.db.Delete(md.tables, md.where, md.whereArgs...)
return md.db.doDelete(nil, md.tables, md.where, md.whereArgs...)
} else {
return md.tx.Delete(md.tables, md.where, md.whereArgs...)
return md.tx.doDelete(md.tables, md.where, md.whereArgs...)
}
}
@ -429,13 +478,44 @@ func (md *Model) Value() (Value, error) {
return nil, nil
}
// 链式操作查询单条记录并自动转换为struct对象
func (md *Model) Struct(obj interface{}) error {
// 链式操作查询单条记录并自动转换为struct对象, 参数必须为对象的指针,不能为空指针。
func (md *Model) Struct(objPointer interface{}) error {
one, err := md.One()
if err != nil {
return err
}
return one.ToStruct(obj)
return one.ToStruct(objPointer)
}
// 链式操作查询多条记录并自动转换为指定的slice对象, 如: []struct/[]*struct。
func (md *Model) Structs(objPointerSlice interface{}) error {
r, err := md.All()
if err != nil {
return err
}
return r.ToStructs(objPointerSlice)
}
// 链式操作将结果转换为指定的struct/*struct/[]struct/[]*struct,
// 参数应该为指针类型,否则返回失败。
// 该方法自动识别参数类型调用Struct/Structs方法。
func (md *Model) Scan(objPointer interface{}) error {
t := reflect.TypeOf(objPointer)
k := t.Kind()
if k != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", k)
}
k = t.Elem().Kind()
switch k {
case reflect.Array:
case reflect.Slice:
return md.Structs(objPointer)
case reflect.Struct:
return md.Struct(objPointer)
default:
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
}
return nil
}
// 链式操作查询数量fields可以为空也可以自定义查询字段
@ -523,14 +603,13 @@ func (md *Model) getFormattedSql() string {
return s
}
// 组块结果集
// @author ymrjqyy
// @author 2018-08-15
// 组块结果集
func (md *Model) Chunk(limit int, callback func(result Result, err error) bool) {
page := 1
page := 1
model := md
for {
md.ForPage(page, limit)
data, err := md.getAll(md.getFormattedSql(), md.whereArgs...)
model = model.ForPage(page, limit)
data, err := model.All()
if err != nil {
callback(nil, err)
break

View File

@ -11,6 +11,7 @@
2.不支持save/replace方法
3.不支持LastInsertId方法
*/
package gdb
import (

View File

@ -11,6 +11,7 @@
2.不支持save/replace方法可以调用这2个方法估计会报错还没测试过,(应该是可以通过oracle的merge来实现这2个功能的还没仔细研究)
3.不支持LastInsertId方法
*/
package gdb
import (

View File

@ -4,7 +4,6 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb
import (
@ -85,8 +84,8 @@ func (bs *dbBase) getTableFields(table string) (fields map[string]string, err er
// 缓存不存在时会查询数据表结构,缓存后不过期,直至程序重启(重新部署)
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))
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
}

View File

@ -8,8 +8,10 @@ package gdb
import (
"database/sql"
"fmt"
"github.com/gogf/gf/g/text/gregex"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"reflect"
)
// 数据库事务对象
@ -75,6 +77,37 @@ func (tx *TX) GetStruct(obj interface{}, query string, args ...interface{}) erro
return one.ToStruct(obj)
}
// 数据库查询查询多条记录并自动转换为指定的slice对象, 如: []struct/[]*struct。
func (tx *TX) GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error {
all, err := tx.GetAll(query, args...)
if err != nil {
return err
}
return all.ToStructs(objPointerSlice)
}
// 将结果转换为指定的struct/*struct/[]struct/[]*struct,
// 参数应该为指针类型,否则返回失败。
// 该方法自动识别参数类型调用Struct/Structs方法。
func (tx *TX) GetScan(objPointer interface{}, query string, args ...interface{}) error {
t := reflect.TypeOf(objPointer)
k := t.Kind()
if k != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", k)
}
k = t.Elem().Kind()
switch k {
case reflect.Array:
case reflect.Slice:
return tx.db.GetStructs(objPointer, query, args ...)
case reflect.Struct:
return tx.db.GetStruct(objPointer, query, args ...)
default:
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
}
return nil
}
// 数据库查询,获取查询字段值
func (tx *TX) GetValue(query string, args ...interface{}) (Value, error) {
one, err := tx.GetOne(query, args ...)
@ -100,43 +133,57 @@ func (tx *TX) GetCount(query string, args ...interface{}) (int, error) {
}
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
func (tx *TX) Insert(table string, data Map) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_INSERT)
func (tx *TX) Insert(table string, data interface{}, batch...int) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_INSERT, batch...)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (tx *TX) Replace(table string, data Map) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_REPLACE)
func (tx *TX) Replace(table string, data interface{}, batch...int) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_REPLACE, batch...)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (tx *TX) Save(table string, data Map) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_SAVE)
func (tx *TX) Save(table string, data interface{}, batch...int) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_SAVE, batch...)
}
// CURD操作:批量数据指定批次量写入
func (tx *TX) BatchInsert(table string, list List, batch int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, batch, OPTION_INSERT)
func (tx *TX) BatchInsert(table string, list interface{}, batch...int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, OPTION_INSERT, batch...)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (tx *TX) BatchReplace(table string, list List, batch int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, batch, OPTION_REPLACE)
func (tx *TX) BatchReplace(table string, list interface{}, batch...int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, OPTION_REPLACE, batch...)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (tx *TX) BatchSave(table string, list List, batch int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, batch, OPTION_SAVE)
func (tx *TX) BatchSave(table string, list interface{}, batch...int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, OPTION_SAVE, batch...)
}
// CURD操作:数据更新统一采用sql预处理
// data参数支持字符串或者关联数组类型内部会自行做判断处理
// CURD操作:数据更新统一采用sql预处理,
// data参数支持字符串或者关联数组类型内部会自行做判断处理.
func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
newWhere, newArgs := formatCondition(condition, args)
return tx.doUpdate(table, data, newWhere, newArgs ...)
}
// 与Update方法的区别是不处理条件参数
func (tx *TX) doUpdate(table string, data interface{}, condition string, 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) {
newWhere, newArgs := formatCondition(condition, args)
return tx.doDelete(table, newWhere, newArgs ...)
}
// 与Delete方法的区别是不处理条件参数
func (tx *TX) doDelete(table string, condition string, args ...interface{}) (sql.Result, error) {
return tx.db.doDelete(tx.tx, table, condition, args ...)
}

View File

@ -33,10 +33,6 @@ func (r Record) ToMap() Map {
}
// 将Map变量映射到指定的struct对象中注意参数应当是一个对象的指针
func (r Record) ToStruct(obj interface{}) error {
m := make(map[string]interface{})
for k, v := range r {
m[k] = v.Val()
}
return gconv.Struct(m, obj)
func (r Record) ToStruct(objPointer interface{}) error {
return gconv.Struct(r.ToMap(), objPointer)
}

View File

@ -7,7 +7,9 @@
package gdb
import (
"fmt"
"github.com/gogf/gf/g/encoding/gparser"
"reflect"
)
// 将结果集转换为JSON字符串
@ -96,3 +98,30 @@ func (r Result) ToUintRecord(key string) map[uint]Record {
}
return m
}
// 将结果列表转换为指定对象的slice。
func (r Result) ToStructs(objPointerSlice interface{}) error {
l := len(r)
if l == 0 {
return nil
}
t := reflect.TypeOf(objPointerSlice)
if t.Kind() != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", t.Kind())
}
a := reflect.MakeSlice(t.Elem(), l, l)
itemType := a.Index(0).Type()
for i := 0; i < l; i++ {
if itemType.Kind() == reflect.Ptr {
e := reflect.New(itemType.Elem()).Elem()
r[i].ToStruct(e)
a.Index(i).Set(e.Addr())
} else {
e := reflect.New(itemType).Elem()
r[i].ToStruct(e)
a.Index(i).Set(e)
}
}
reflect.ValueOf(objPointerSlice).Elem().Set(a)
return nil
}

View File

@ -1,52 +0,0 @@
package gdb_test
import (
"github.com/gogf/gf/g/database/gdb"
"github.com/gogf/gf/g/test/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

@ -1,177 +0,0 @@
// 方法操作
package gdb_test
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/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 r, 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(),
},
}, 1); err != nil {
gtest.Fatal(err)
} else {
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
}
}
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

@ -1,222 +0,0 @@
// 链式操作
package gdb_test
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/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(1).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,119 @@
// Copyright 2019 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gdb_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/database/gdb"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"os"
)
const (
// 初始化表数据量
INIT_DATA_SIZE = 10
)
var (
// 数据库对象/接口
db gdb.DB
)
// 初始化连接参数。
// 测试前需要修改连接参数。
func init() {
node := gdb.ConfigNode{
Host: "127.0.0.1",
Port: "3306",
User: "root",
Pass: "",
Name: "",
Type: "mysql",
Role: "master",
Charset: "utf8",
Priority: 1,
}
hostname, _ := os.Hostname()
// 本地测试hack
if hostname == "ijohn" {
node.Pass = "12345678"
}
gdb.AddConfigNode("test", node)
gdb.AddConfigNode(gdb.DEFAULT_GROUP_NAME, node)
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")
// 创建默认用户表
createTable("user")
}
// 创建指定名称的user测试表当table为空时创建随机的表名。
// 创建的测试表默认没有任何数据。
// 执行完成后返回该表名。
// TODO 支持更多数据库
func createTable(table...string) (name string) {
if len(table) > 0 {
name = table[0]
} else {
name = fmt.Sprintf(`user_%d`, gtime.Nanosecond())
}
dropTable(name)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
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;
`, name)); err != nil {
gtest.Fatal(err)
}
return
}
// 删除指定表.
func dropTable(table string) {
if _, err := db.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil {
gtest.Fatal(err)
}
}
// See createTable.
// 创建测试表,并初始化默认数据。
func createInitTable(table...string) (name string) {
name = createTable(table...)
array := garray.New(true)
for i := 1; i <= INIT_DATA_SIZE; i++ {
array.Append(g.Map{
"id" : i,
"passport" : fmt.Sprintf(`t%d`, i),
"password" : fmt.Sprintf(`p%d`, i),
"nickname" : fmt.Sprintf(`T%d`, i),
"create_time" : gtime.Now().String(),
})
}
result, err := db.Table(name).Data(array.Slice()).Insert()
gtest.Assert(err, nil)
n, e := result.RowsAffected()
gtest.Assert(e, nil)
gtest.Assert(n, INIT_DATA_SIZE)
return
}

View File

@ -0,0 +1,490 @@
// Copyright 2019 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gdb_test
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func TestDbBase_Ping(t *testing.T) {
gtest.Case(t, func() {
err1 := db.PingMaster()
err2 := db.PingSlave()
gtest.Assert(err1, nil)
gtest.Assert(err2, nil)
})
}
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)
}
// normal map
result, err := db.Insert("user", map[interface{}]interface{} {
"id" : "2",
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
})
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
// struct
type User struct {
Id int `gconv:"id"`
Passport string `json:"passport"`
Password string `gconv:"password"`
Nickname string `gconv:"nickname"`
CreateTime string `json:"create_time"`
}
result, err = db.Insert("user", User{
Id : 3,
Passport : "t3",
Password : "25d55ad283aa400af464c76d713c07ad",
Nickname : "T3",
CreateTime : gtime.Now().String(),
})
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 1)
value, err := db.GetValue("select `passport` from `user` where id=?", 3)
gtest.Assert(err, nil)
gtest.Assert(value.String(), "t3")
// *struct
result, err = db.Insert("user", &User{
Id : 4,
Passport : "t4",
Password : "25d55ad283aa400af464c76d713c07ad",
Nickname : "T4",
CreateTime : gtime.Now().String(),
})
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 1)
value, err = db.GetValue("select `passport` from `user` where id=?", 4)
gtest.Assert(err, nil)
gtest.Assert(value.String(), "t4")
// batch with Insert
if r, err := db.Insert("user", []interface{} {
map[interface{}]interface{} {
"id" : 200,
"passport" : "t200",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T200",
"create_time" : gtime.Now().String(),
},
map[interface{}]interface{} {
"id" : 300,
"passport" : "t300",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T300",
"create_time" : gtime.Now().String(),
},
}); err != nil {
gtest.Fatal(err)
} else {
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
}
// clear unnecessary data
result, err = db.Delete("user", "id>?", 1)
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 5)
}
func TestDbBase_BatchInsert(t *testing.T) {
gtest.Case(t, func() {
if r, 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(),
},
}, 1); err != nil {
gtest.Fatal(err)
} else {
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
}
result, err := db.Delete("user", "id>?", 1)
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 2)
// []interface{}
if r, err := db.BatchInsert("user", []interface{} {
map[interface{}]interface{} {
"id" : 2,
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
},
map[interface{}]interface{} {
"id" : 3,
"passport" : "t3",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T3",
"create_time" : gtime.Now().String(),
},
}, 1); err != nil {
gtest.Fatal(err)
} else {
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
}
})
// batch insert map
gtest.Case(t, func() {
table := createTable()
defer dropTable(table)
result, err := db.BatchInsert(table, g.Map{
"id" : 1,
"passport" : "t1",
"password" : "p1",
"nickname" : "T1",
"create_time" : gtime.Now().String(),
})
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
})
// batch insert struct
gtest.Case(t, func() {
table := createTable()
defer dropTable(table)
type User struct {
Id int `gconv:"id"`
Passport string `gconv:"passport"`
Password string `gconv:"password"`
NickName string `gconv:"nickname"`
CreateTime *gtime.Time `gconv:"create_time"`
}
user := &User{
Id : 1,
Passport : "t1",
Password : "p1",
NickName : "T1",
CreateTime : gtime.Now(),
}
result, err := db.BatchInsert(table, user)
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
})
}
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) {
gtest.Case(t, func() {
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")
}
})
gtest.Case(t, func() {
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_GetStructs(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
var users []User
if err := db.GetStructs(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []User
if err := db.GetStructs(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
})
}
func TestDbBase_GetScan(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
if err := db.GetScan(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
}
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := new(User)
if err := db.GetScan(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
}
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
var users []User
if err := db.GetScan(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []User
if err := db.GetScan(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].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,655 @@
// Copyright 2019 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gdb_test
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/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)
result, err = db.Table("user").Filter().Data(map[interface{}]interface{} {
"id" : "2",
"uid" : "2",
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
}).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 1)
type User struct {
Id int `gconv:"id"`
Uid int `gconv:"uid"`
Passport string `json:"passport"`
Password string `gconv:"password"`
Nickname string `gconv:"nickname"`
CreateTime string `json:"create_time"`
}
result, err = db.Table("user").Filter().Data(User{
Id : 3,
Uid : 3,
Passport : "t3",
Password : "25d55ad283aa400af464c76d713c07ad",
Nickname : "T3",
CreateTime : gtime.Now().String(),
}).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 1)
value, err := db.Table("user").Fields("passport").Where("id=3").Value()
gtest.Assert(err, nil)
gtest.Assert(value.String(), "t3")
result, err = db.Table("user").Filter().Data(&User{
Id : 4,
Uid : 4,
Passport : "t4",
Password : "25d55ad283aa400af464c76d713c07ad",
Nickname : "T4",
CreateTime : gtime.Now().String(),
}).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 1)
value, err = db.Table("user").Fields("passport").Where("id=4").Value()
gtest.Assert(err, nil)
gtest.Assert(value.String(), "t4")
result, err = db.Table("user").Where("id>?", 1).Delete()
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 3)
}
func TestModel_Batch(t *testing.T) {
// batch insert
gtest.Case(t, func() {
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(1).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 2)
})
// batch save
gtest.Case(t, func() {
table := createInitTable()
defer dropTable(table)
result, err := db.Table(table).All()
gtest.Assert(err, nil)
gtest.Assert(len(result), INIT_DATA_SIZE)
for _, v := range result {
v["nickname"].Set(v["nickname"].String() + v["id"].String())
}
r, e := db.Table(table).Data(result).Save()
gtest.Assert(e, nil)
n, e := r.RowsAffected()
gtest.Assert(e, nil)
gtest.Assert(n, INIT_DATA_SIZE*2)
})
// batch replace
gtest.Case(t, func() {
table := createInitTable()
defer dropTable(table)
result, err := db.Table(table).All()
gtest.Assert(err, nil)
gtest.Assert(len(result), INIT_DATA_SIZE)
for _, v := range result {
v["nickname"].Set(v["nickname"].String() + v["id"].String())
}
r, e := db.Table(table).Data(result).Replace()
gtest.Assert(e, nil)
n, e := r.RowsAffected()
gtest.Assert(e, nil)
gtest.Assert(n, INIT_DATA_SIZE*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" : "2018-10-10 00:01:10",
}).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" : "2018-10-10 00:01:10",
}).Save()
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 2)
}
func TestModel_Update(t *testing.T) {
gtest.Case(t, func() {
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)
})
gtest.Case(t, func() {
result, err := db.Table("user").Data("passport", "t2").Where("passport='t22'").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_Safe(t *testing.T) {
gtest.Case(t, func() {
md := db.Table("user").Safe(false).Where("id IN(?)", g.Slice{1,3})
count, err := md.Count()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 2)
md.And("id = ?", 1)
count, err = md.Count()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 1)
})
gtest.Case(t, func() {
md := db.Table("user").Safe(true).Where("id IN(?)", g.Slice{1,3})
count, err := md.Count()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 2)
md.And("id = ?", 1)
count, err = md.Count()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 2)
})
}
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) {
gtest.Case(t, func() {
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")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
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")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
}
func TestModel_Structs(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
var users []User
err := db.Table("user").OrderBy("id asc").Structs(&users)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []*User
err := db.Table("user").OrderBy("id asc").Structs(&users)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
}
func TestModel_Scan(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
user := new(User)
err := db.Table("user").Where("id=1").Scan(user)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T111")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
user := new(User)
err := db.Table("user").Where("id=1").Scan(user)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T111")
gtest.Assert(user.CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
var users []User
err := db.Table("user").OrderBy("id asc").Scan(&users)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
gtest.Case(t, func() {
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []*User
err := db.Table("user").OrderBy("id asc").Scan(&users)
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 3)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T111")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[0].CreateTime.String(), "2018-10-10 00:01:10")
})
}
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_Where(t *testing.T) {
// string
gtest.Case(t, func() {
result, err := db.Table("user").Where("id=? and nickname=?", 3, "T3").One()
if err != nil {
gtest.Fatal(err)
}
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 3).One()
if err != nil {
gtest.Fatal(err)
}
gtest.AssertGT(len(result), 0)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 3).Where("nickname", "T3").One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 3).And("nickname", "T3").One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 30).Or("nickname", "T3").One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 30).Or("nickname", "T3").And("id>?", 1).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
gtest.Case(t, func() {
result, err := db.Table("user").Where("id", 30).Or("nickname", "T3").And("id>", 1).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 3)
})
// map
gtest.Case(t, func() {
result, err := db.Table("user").Where(g.Map{"id" : 3, "nickname" : "T3"}).One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
// map key operator
gtest.Case(t, func() {
result, err := db.Table("user").Where(g.Map{"id>" : 1, "id<" : 3}).One()
gtest.Assert(err, nil)
gtest.Assert(result["id"].Int(), 2)
})
// complicated where 1
gtest.Case(t, func() {
//db.SetDebug(true)
conditions := g.Map{
"nickname like ?" : "%T%",
"id between ? and ?" : g.Slice{1,3},
"id > 0" : nil,
"create_time > 0" : nil,
"id" : g.Slice{1,2,3},
}
result, err := db.Table("user").Where(conditions).OrderBy("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["id"].Int(), 1)
})
// complicated where 2
gtest.Case(t, func() {
//db.SetDebug(true)
conditions := g.Map{
"nickname like ?" : "%T%",
"id between ? and ?" : g.Slice{1,3},
"id >= ?" : 1,
"create_time > ?" : 0,
"id in(?)" : g.Slice{1,2,3},
}
result, err := db.Table("user").Where(conditions).OrderBy("id asc").All()
gtest.Assert(err, nil)
gtest.Assert(len(result), 3)
gtest.Assert(result[0]["id"].Int(), 1)
})
// struct
gtest.Case(t, func() {
type User struct {
Id int `json:"id"`
Nickname string `gconv:"nickname"`
}
result, err := db.Table("user").Where(User{3, "T3"}).One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
result, err = db.Table("user").Where(&User{3, "T3"}).One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
// slice single
gtest.Case(t, func() {
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)
})
// slice + string
gtest.Case(t, func() {
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)
})
// slice + map
gtest.Case(t, func() {
result, err := db.Table("user").Where(g.Map{
"id" : g.Slice{1,3},
"nickname" : "T3",
}).OrderBy("id ASC").All()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(result), 1)
gtest.Assert(result[0]["id"].Int(), 3)
})
// slice + struct
gtest.Case(t, func() {
type User struct {
Ids []int `json:"id"`
Nickname string `gconv:"nickname"`
}
result, err := db.Table("user").Where(User{
Ids : []int{1, 3},
Nickname : "T3",
}).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

@ -1,4 +1,8 @@
// 事务操作
// Copyright 2019 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gdb_test
@ -267,6 +271,29 @@ func TestTX_Save(t *testing.T) {
}
}
func TestTX_Update(t *testing.T) {
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
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)
}
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
if value, err := db.Table("user").Fields("create_time").Where("id", 3).Value(); err != nil {
gtest.Fatal(err)
} else {
gtest.Assert(value.String(), "2010-10-10 00:00:01")
}
})
}
func TestTX_GetAll(t *testing.T) {
tx, err := db.Begin()
if err != nil {
@ -331,26 +358,215 @@ func TestTX_GetCount(t *testing.T) {
}
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)
}
gtest.Case(t, func() {
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=?", 3); err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T3")
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
gtest.Case(t, func() {
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=?", 3); err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T3")
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
}
func TestTX_GetStructs(t *testing.T) {
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
var users []User
if err := tx.GetStructs(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 4)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T11")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []User
if err := tx.GetStructs(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 4)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T11")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
}
func TestTX_GetScan(t *testing.T) {
gtest.Case(t, func() {
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.GetScan(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T3")
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
gtest.Case(t, func() {
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.GetScan(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
gtest.Fatal(err)
}
gtest.Assert(user.NickName, "T3")
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime gtime.Time
}
var users []User
if err := tx.GetScan(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 4)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T11")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
gtest.Case(t, func() {
tx, err := db.Begin()
if err != nil {
gtest.Fatal(err)
}
type User struct {
Id int
Passport string
Password string
NickName string
CreateTime *gtime.Time
}
var users []User
if err := tx.GetScan(&users, "SELECT * FROM user WHERE id>=?", 1); err != nil {
gtest.Fatal(err)
}
gtest.Assert(len(users), 4)
gtest.Assert(users[0].Id, 1)
gtest.Assert(users[1].Id, 2)
gtest.Assert(users[2].Id, 3)
gtest.Assert(users[0].NickName, "T11")
gtest.Assert(users[1].NickName, "T2")
gtest.Assert(users[2].NickName, "T3")
gtest.Assert(users[2].CreateTime.String(), "2010-10-10 00:00:01")
if err := tx.Commit(); err != nil {
gtest.Fatal(err)
}
})
}
func TestTX_Delete(t *testing.T) {

View File

@ -11,30 +11,36 @@
package gredis
import (
"time"
"github.com/gogf/gf/third/github.com/gomodule/redigo/redis"
"github.com/gogf/gf/g/container/gmap"
"fmt"
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/third/github.com/gomodule/redigo/redis"
"time"
)
const (
gDEFAULT_POOL_MAX_IDLE = 1
gDEFAULT_POOL_MAX_ACTIVE = 10
gDEFAULT_POOL_IDLE_TIMEOUT = 180 * time.Second
gDEFAULT_POOL_MAX_LIFE_TIME = 60 * time.Second
gDEFAULT_POOL_IDLE_TIMEOUT = 60 * time.Second
gDEFAULT_POOL_MAX_LIFE_TIME = 60 * time.Second
)
// Redis客户端
// Redis客户端(管理连接池)
type Redis struct {
pool *redis.Pool
pool *redis.Pool
config Config
}
// Redis连接对象(连接池中的单个连接)
type Conn redis.Conn
// Redis服务端但节点连接配置信息
type Config struct {
Host string // IP/域名
Port int // 端口
Db int // db
Pass string // 密码
Host string // 地址
Port int // 端口
Db int // 数据库
Pass string // 授权密码
MaxIdle int // 最大允许空闲存在的连接数(默认为0表示不存在闲置连接)
MaxActive int // 最大连接数量限制(默认为0表示不限制)
IdleTimeout time.Duration // 连接最大空闲时间(默认为60秒,不允许设置为0)
MaxConnLifetime time.Duration // 连接最长存活时间(默认为60秒,不允许设置为0)
}
// Redis链接池统计信息
@ -45,88 +51,122 @@ type PoolStats struct {
// 连接池map
var pools = gmap.NewStringInterfaceMap()
// New creates a redis client object with given configuration.
// Redis client maintains a connection pool automatically.
//
// 创建redis操作对象.
func New(config Config) *Redis {
r := &Redis{}
poolKey := fmt.Sprintf("%s:%d,%d", config.Host, config.Port, config.Db)
if v := pools.Get(poolKey); v == nil {
pool := &redis.Pool {
MaxIdle : gDEFAULT_POOL_MAX_IDLE,
MaxActive : gDEFAULT_POOL_MAX_ACTIVE,
IdleTimeout : gDEFAULT_POOL_IDLE_TIMEOUT,
MaxConnLifetime : gDEFAULT_POOL_MAX_LIFE_TIME,
Dial : func() (redis.Conn, error) {
c, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", config.Host, config.Port))
if err != nil {
return nil, err
}
if len(config.Pass) > 0 {
if _, err := c.Do("AUTH", config.Pass); err != nil {
if config.IdleTimeout == 0 {
config.IdleTimeout = gDEFAULT_POOL_IDLE_TIMEOUT
}
if config.MaxConnLifetime == 0 {
config.MaxConnLifetime = gDEFAULT_POOL_MAX_LIFE_TIME
}
return &Redis{
config : config,
pool : pools.GetOrSetFuncLock(fmt.Sprintf("%v", config), func() interface{} {
return &redis.Pool {
IdleTimeout : config.IdleTimeout,
MaxConnLifetime : config.MaxConnLifetime,
Dial : func() (redis.Conn, error) {
c, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", config.Host, config.Port))
if err != nil {
return nil, err
}
}
if _, err := c.Do("SELECT", config.Db); err != nil {
return nil, err
}
return c, nil
},
// 用来测试连接是否可用
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
}
pools.Set(poolKey, pool)
r.pool = pool
} else {
r.pool = v.(*redis.Pool)
// 密码设置
if len(config.Pass) > 0 {
if _, err := c.Do("AUTH", config.Pass); err != nil {
return nil, err
}
}
// 数据库设置
if _, err := c.Do("SELECT", config.Db); err != nil {
return nil, err
}
return c, nil
},
// 用来测试连接是否可用
TestOnBorrow: func(c redis.Conn, t time.Time) error {
_, err := c.Do("PING")
return err
},
}
}).(*redis.Pool),
}
return r
}
// 关闭redis管理对象将会关闭底层的
// Close closes the redis connection pool,
// it will release all connections reserved by this pool.
//
// 关闭redis管理对象将会关闭底层的连接池。
func (r *Redis) Close() error {
pools.Remove(fmt.Sprintf("%v", r.config))
return r.pool.Close()
}
// 获得一个原生的redis连接对象用于自定义连接操作
// 但是需要注意的是如果不再使用该连接对象时需要手动Close连接否则会造成连接数超限。
func (r *Redis) GetConn() redis.Conn {
return r.pool.Get()
// See GetConn.
func (r *Redis) Conn() Conn {
return r.GetConn()
}
// GetConn returns a raw connection object,
// which expose more methods communication with server.
// **You should call Close function manually if you do not use this connection any further.**
//
// 获得一个原生的redis连接对象用于自定义连接操作
// 但是需要注意的是如果不再使用该连接对象时需要手动Close连接否则会造成连接数超限。
func (r *Redis) GetConn() Conn {
return r.pool.Get().(Conn)
}
// SetMaxIdle sets the MaxIdle attribute of the connection pool.
//
// 设置属性 - MaxIdle
func (r *Redis) SetMaxIdle(value int) {
r.pool.MaxIdle = value
}
// SetMaxIdle sets the MaxActive attribute of the connection pool.
//
// 设置属性 - MaxActive
func (r *Redis) SetMaxActive(value int) {
r.pool.MaxActive = value
}
// SetMaxIdle sets the IdleTimeout attribute of the connection pool.
//
// 设置属性 - IdleTimeout
func (r *Redis) SetIdleTimeout(value time.Duration) {
r.pool.IdleTimeout = value
}
// SetMaxIdle sets the MaxConnLifetime attribute of the connection pool.
//
// 设置属性 - MaxConnLifetime
func (r *Redis) SetMaxConnLifetime(value time.Duration) {
r.pool.MaxConnLifetime = value
}
// 获取当前连接池统计信息
// Stats returns pool's statistics.
//
// 获取当前连接池统计信息。
func (r *Redis) Stats() *PoolStats {
return &PoolStats{r.pool.Stats()}
}
// 执行同步命令 - Do
// Do sends a command to the server and returns the received reply.
// Do automatically get a connection from pool, and close it when reply received.
//
// 执行同步命令自动从连接池中获取连接使用完毕后关闭连接丢回连接池开发者不用自行Close.
func (r *Redis) Do(command string, args ...interface{}) (interface{}, error) {
conn := r.pool.Get()
defer conn.Close()
return conn.Do(command, args...)
}
// Deprecated.
// Send writes the command to the client's output buffer.
//
// 执行异步命令 - Send
func (r *Redis) Send(command string, args ...interface{}) error {
conn := r.pool.Get()

View File

@ -0,0 +1,112 @@
// Copyright 2019 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gredis_test
import (
"github.com/gogf/gf/g/database/gredis"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
var (
config = gredis.Config{
Host : "127.0.0.1",
Port : 6379,
Db : 1,
}
)
func Test_NewClose(t *testing.T) {
gtest.Case(t, func() {
redis := gredis.New(config)
gtest.AssertNE(redis, nil)
err := redis.Close()
gtest.Assert(err, nil)
})
}
func Test_Do(t *testing.T) {
gtest.Case(t, func() {
redis := gredis.New(config)
defer redis.Close()
_, err := redis.Do("SET", "k", "v")
gtest.Assert(err, nil)
r, err := redis.Do("GET", "k")
gtest.Assert(err, nil)
gtest.Assert(r, []byte("v"))
_, err = redis.Do("DEL", "k")
gtest.Assert(err, nil)
r, err = redis.Do("GET", "k")
gtest.Assert(err, nil)
gtest.Assert(r, nil)
})
}
func Test_Send(t *testing.T) {
gtest.Case(t, func() {
redis := gredis.New(config)
defer redis.Close()
err := redis.Send("SET", "k", "v")
gtest.Assert(err, nil)
r, err := redis.Do("GET", "k")
gtest.Assert(err, nil)
gtest.Assert(r, []byte("v"))
})
}
func Test_Stats(t *testing.T) {
gtest.Case(t, func() {
redis := gredis.New(config)
defer redis.Close()
redis.SetMaxIdle(2)
redis.SetMaxActive(100)
redis.SetIdleTimeout(500*time.Millisecond)
redis.SetMaxConnLifetime(500*time.Millisecond)
array := make([]gredis.Conn, 0)
for i := 0; i < 10; i++ {
array = append(array, redis.Conn())
}
stats := redis.Stats()
gtest.Assert(stats.ActiveCount, 10)
gtest.Assert(stats.IdleCount, 0)
for i := 0; i < 10; i++ {
array[i].Close()
}
stats = redis.Stats()
gtest.Assert(stats.ActiveCount, 2)
gtest.Assert(stats.IdleCount, 2)
//time.Sleep(3000*time.Millisecond)
//stats = redis.Stats()
//fmt.Println(stats)
//gtest.Assert(stats.ActiveCount, 0)
//gtest.Assert(stats.IdleCount, 0)
})
}
func Test_Conn(t *testing.T) {
gtest.Case(t, func() {
redis := gredis.New(config)
defer redis.Close()
conn := redis.Conn()
defer conn.Close()
r, err := conn.Do("GET", "k")
gtest.Assert(err, nil)
gtest.Assert(r, []byte("v"))
_, err = conn.Do("DEL", "k")
gtest.Assert(err, nil)
r, err = conn.Do("GET", "k")
gtest.Assert(err, nil)
gtest.Assert(r, nil)
})
}

View File

@ -8,21 +8,21 @@
package gjson
import (
"errors"
"github.com/gogf/gf/g/text/gregex"
"strings"
"strconv"
"io/ioutil"
"encoding/json"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/util/gconv"
"fmt"
"github.com/gogf/gf/g/encoding/gtoml"
"github.com/gogf/gf/g/encoding/gxml"
"github.com/gogf/gf/g/encoding/gyaml"
"github.com/gogf/gf/g/encoding/gtoml"
"github.com/gogf/gf/g/text/gstr"
"time"
"github.com/gogf/gf/g/internal/rwmutex"
"fmt"
"github.com/gogf/gf/g/os/gfcache"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
"reflect"
"strconv"
"strings"
"time"
)
const (
@ -79,6 +79,11 @@ func NewUnsafe(value...interface{}) *Json {
return New(nil, true)
}
// 识别当前给定内容是否为JSON格式
func Valid (v interface{}) bool {
return json.Valid(gconv.Bytes(v))
}
// 编码go变量为json字符串并返回json字符串指针
func Encode (v interface{}) ([]byte, error) {
return json.Marshal(v)
@ -110,11 +115,7 @@ func DecodeToJson (b []byte) (*Json, error) {
// 支持多种配置文件类型转换为json格式内容并解析为gjson.Json对象
func Load (path string) (*Json, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return LoadContent(data, gfile.Ext(path))
return LoadContent(gfcache.GetBinContents(path), gfile.Ext(path))
}
// 支持的配置文件格式xml, json, yaml/yml, toml,
@ -485,16 +486,28 @@ 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:
rv := reflect.ValueOf(value)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Array: return gconv.Interfaces(value)
case reflect.Slice: return gconv.Interfaces(value)
case reflect.Map: return gconv.Map(value)
case reflect.Struct: return gconv.Map(value)
default:
// 最后使用JSON编解码
b, _ := Encode(value)
v, _ := Decode(b)
return v
}
}
}
@ -502,23 +515,23 @@ func (j *Json) convertValue(value interface{}) interface{} {
// 返回修改后的父级指针
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
}
@ -538,7 +551,7 @@ func (j *Json) Get(pattern...string) interface{} {
if j.vc {
result = j.getPointerByPattern(queryPattern)
} else {
result = j.getPointerByPatternWithoutSplitCharViolenceCheck(queryPattern)
result = j.getPointerByPatternWithoutViolenceCheck(queryPattern)
}
if result != nil {
return *result
@ -546,17 +559,23 @@ func (j *Json) Get(pattern...string) interface{} {
return nil
}
// 计算指定pattern的元素长度(pattern对应数据类型为map[string]interface{}/[]interface{}时有效)
// 判断锁给定pattern是否数据存在
func (j *Json) Contains(pattern...string) bool {
return j.Get(pattern...) != nil
}
// 计算指定pattern的元素长度(pattern对应数据类型为map/slice时有效)。
// 当pattern对应的数据类型非map/slice时返回-1。
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
@ -564,14 +583,27 @@ func (j *Json) Len(pattern string) int {
// 指定pattern追加元素
func (j *Json) Append(pattern string, value interface{}) error {
length := j.Len(pattern)
if length != -1 {
return j.Set(fmt.Sprintf("%s.%d", pattern, length), value)
p := j.getPointerByPattern(pattern)
if p == nil {
return j.Set(fmt.Sprintf("%s.0", pattern), value)
}
return errors.New(fmt.Sprintf("cannot find item for pattern: %s", pattern))
switch (*p).(type) {
case []interface{}:
return j.Set(fmt.Sprintf("%s.%d", pattern, len((*p).([]interface{}))), value)
}
return fmt.Errorf("invalid variable type of %s", pattern)
}
// 根据pattern层级查找**变量指针**
// 根据pattern获取对应元素项的指针
func (j *Json) getPointerByPattern(pattern string) *interface{} {
if j.vc {
return j.getPointerByPatternWithViolenceCheck(pattern)
} else {
return j.getPointerByPatternWithoutViolenceCheck(pattern)
}
}
// 根据pattern层级查找**变量指针**, 执行冲突检测。
// 检索方式:例如检索 a.a.a 值为1
// 1. 检索 a.a.a.a 是否存在对应map的键名
// 2. 检索 a.a.a 是否存在对应map的键名
@ -583,7 +615,10 @@ func (j *Json) Append(pattern string, value interface{}) error {
// 8. 在m2中检索 a.a 否存在对应map的键名
// 9. 在m2中检索 a 否存在对应map的键名检索到有值值为1
// 这样检索的复杂度很高,主要是为了避免键名中存在分隔符号(默认为".")的情况,避免歧义。
func (j *Json) getPointerByPattern(pattern string) *interface{} {
func (j *Json) getPointerByPatternWithViolenceCheck(pattern string) *interface{} {
if !j.vc {
return j.getPointerByPatternWithoutViolenceCheck(pattern)
}
index := len(pattern)
start := 0
length := 0
@ -619,7 +654,10 @@ func (j *Json) getPointerByPattern(pattern string) *interface{} {
}
// 层级检索,内部不执行分隔符冲突检查,检索效率会有所提高,但是冲突需要开发者自己根据自定义的分隔符来进行解决
func (j *Json) getPointerByPatternWithoutSplitCharViolenceCheck(pattern string) *interface{} {
func (j *Json) getPointerByPatternWithoutViolenceCheck(pattern string) *interface{} {
if j.vc {
return j.getPointerByPatternWithViolenceCheck(pattern)
}
pointer := j.p
if len(pattern) == 0 {
return pointer
@ -639,21 +677,21 @@ func (j *Json) getPointerByPatternWithoutSplitCharViolenceCheck(pattern string)
return nil
}
// 判断给定的key在当前的pointer下是否有值并返回对应的pointer
// 判断给定的key在当前的pointer下是否有值并返回对应的pointer,
// 注意这里返回的指针都是临时变量的内存地址
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
}

View File

@ -0,0 +1,30 @@
// Copyright 2017 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gjson_test
import (
"github.com/gogf/gf/g/encoding/gjson"
"testing"
)
func Benchmark_Set1(b *testing.B) {
for i := 0; i < b.N; i++ {
p := gjson.New(map[string]string{
"k1" : "v1",
"k2" : "v2",
})
p.Set("k1.k11", []int{1,2,3})
}
}
func Benchmark_Set2(b *testing.B) {
for i := 0; i < b.N; i++ {
p := gjson.New([]string{"a"})
p.Set("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0", []int{1,2,3})
}
}

View File

@ -0,0 +1,280 @@
// Copyright 2017 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gjson_test
import (
"bytes"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/encoding/gjson"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func Test_Set1(t *testing.T) {
e := []byte(`{"k1":{"k11":[1,2,3]},"k2":"v2"}`)
p := gjson.New(map[string]string{
"k1" : "v1",
"k2" : "v2",
})
p.Set("k1.k11", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, []byte(`{"k1":{"k11":[1,2,3]},"k2":"v2"}`)) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Set2(t *testing.T) {
e := []byte(`[[null,1]]`)
p := gjson.New([]string{"a"})
p.Set("0.1", 1)
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Set3(t *testing.T) {
e := []byte(`{"kv":{"k1":"v1"}}`)
p := gjson.New([]string{"a"})
p.Set("kv", map[string]string {
"k1" : "v1",
})
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Set4(t *testing.T) {
e := []byte(`["a",[{"k1":"v1"}]]`)
p := gjson.New([]string{"a"})
p.Set("1.0", map[string]string{
"k1" : "v1",
})
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Set5(t *testing.T) {
e := []byte(`[[[[[[[[[[[[[[[[[[[[[1,2,3]]]]]]]]]]]]]]]]]]]]]`)
p := gjson.New([]string{"a"})
p.Set("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Set6(t *testing.T) {
e := []byte(`["a",[1,2,3]]`)
p := gjson.New([]string{"a"})
p.Set("1", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Set7(t *testing.T) {
e := []byte(`{"0":[null,[1,2,3]],"k1":"v1","k2":"v2"}`)
p := gjson.New(map[string]string{
"k1" : "v1",
"k2" : "v2",
})
p.Set("0.1", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Set8(t *testing.T) {
e := []byte(`{"0":[[[[[[null,[1,2,3]]]]]]],"k1":"v1","k2":"v2"}`)
p := gjson.New(map[string]string{
"k1" : "v1",
"k2" : "v2",
})
p.Set("0.0.0.0.0.0.1", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Set9(t *testing.T) {
e := []byte(`{"k1":[null,[1,2,3]],"k2":"v2"}`)
p := gjson.New(map[string]string{
"k1" : "v1",
"k2" : "v2",
})
p.Set("k1.1", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Set10(t *testing.T) {
e := []byte(`{"a":{"b":{"c":1}}}`)
p := gjson.New(nil)
p.Set("a.b.c", 1)
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Set11(t *testing.T) {
e := []byte(`{"a":{"b":{}}}`)
p, _ := gjson.LoadContent([]byte(`{"a":{"b":{"c":1}}}`), "json")
p.Remove("a.b.c")
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Set12(t *testing.T) {
e := []byte(`[0,1]`)
p := gjson.New(nil)
p.Set("0", 0)
p.Set("1", 1)
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Set13(t *testing.T) {
e := []byte(`{"array":[0,1]}`)
p := gjson.New(nil)
p.Set("array.0", 0)
p.Set("array.1", 1)
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Set14(t *testing.T) {
e := []byte(`{"f":{"a":1}}`)
p := gjson.New(nil)
p.Set("f", "m")
p.Set("f.a", 1)
if c, err := p.ToJson(); err == nil {
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
} else {
t.Error(err)
}
}
func Test_Len(t *testing.T) {
gtest.Case(t, func() {
p := gjson.New(nil)
p.Append("a", 1)
p.Append("a", 2)
gtest.Assert(p.Len("a"), 2)
})
gtest.Case(t, func() {
p := gjson.New(nil)
p.Append("a.b", 1)
p.Append("a.c", 2)
gtest.Assert(p.Len("a"), 2)
})
gtest.Case(t, func() {
p := gjson.New(nil)
p.Set("a", 1)
gtest.Assert(p.Len("a"), -1)
})
}
func Test_Append(t *testing.T) {
gtest.Case(t, func() {
p := gjson.New(nil)
p.Append("a", 1)
p.Append("a", 2)
gtest.Assert(p.Get("a"), g.Slice{1, 2})
})
gtest.Case(t, func() {
p := gjson.New(nil)
p.Append("a.b", 1)
p.Append("a.c", 2)
gtest.Assert(p.Get("a"), g.Map{
"b" : g.Slice{1},
"c" : g.Slice{2},
})
})
gtest.Case(t, func() {
p := gjson.New(nil)
p.Set("a", 1)
err := p.Append("a", 2)
gtest.AssertNE(err, nil)
gtest.Assert(p.Get("a"), 1)
})
}

View File

@ -10,9 +10,8 @@ package gparser_test
import (
"bytes"
"testing"
"github.com/gogf/gf/g/encoding/gparser"
"fmt"
"testing"
)
func Test_Set1(t *testing.T) {
@ -23,7 +22,7 @@ func Test_Set1(t *testing.T) {
})
p.Set("k1.k11", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, []byte(`{"k1":{"k11":[1,2,3]},"k2":"v2"}`)) != 0 {
t.Error("expect:", string(e))
}
@ -37,7 +36,7 @@ func Test_Set2(t *testing.T) {
p := gparser.New([]string{"a"})
p.Set("0.1", 1)
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -53,7 +52,7 @@ func Test_Set3(t *testing.T) {
"k1" : "v1",
})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -69,7 +68,7 @@ func Test_Set4(t *testing.T) {
"k1" : "v1",
})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -83,7 +82,7 @@ func Test_Set5(t *testing.T) {
p := gparser.New([]string{"a"})
p.Set("0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -97,7 +96,7 @@ func Test_Set6(t *testing.T) {
p := gparser.New([]string{"a"})
p.Set("1", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -114,7 +113,7 @@ func Test_Set7(t *testing.T) {
})
p.Set("0.1", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -131,7 +130,7 @@ func Test_Set8(t *testing.T) {
})
p.Set("0.0.0.0.0.0.1", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -148,7 +147,7 @@ func Test_Set9(t *testing.T) {
})
p.Set("k1.1", []int{1,2,3})
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -163,7 +162,7 @@ func Test_Set10(t *testing.T) {
p := gparser.New(nil)
p.Set("a.b.c", 1)
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -178,7 +177,7 @@ func Test_Set11(t *testing.T) {
p, _ := gparser.LoadContent([]byte(`{"a":{"b":{"c":1}}}`), "json")
p.Remove("a.b.c")
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -193,7 +192,7 @@ func Test_Set12(t *testing.T) {
p.Set("0", 0)
p.Set("1", 1)
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}
@ -208,7 +207,7 @@ func Test_Set13(t *testing.T) {
p.Set("array.0", 0)
p.Set("array.1", 1)
if c, err := p.ToJson(); err == nil {
fmt.Println(string(c))
if bytes.Compare(c, e) != 0 {
t.Error("expect:", string(e))
}

View File

@ -21,8 +21,10 @@ import (
"github.com/gogf/gf/g/os/gfsnotify"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gview"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/text/gregex"
"time"
)
const (
@ -73,7 +75,7 @@ func View(name...string) *gview.View {
}
key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_VIEW, group)
return instances.GetOrSetFuncLock(key, func() interface{} {
path := cmdenv.Get("gf.gview.path", gfile.SelfDir()).String()
path := cmdenv.Get("gf.gview.path", gfile.Pwd()).String()
view := gview.New(path)
// 添加基于源码的搜索目录检索地址,常用于开发环境调试,只添加入口文件目录
if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) {
@ -94,11 +96,22 @@ func Config(file...string) *gcfg.Config {
}
return instances.GetOrSetFuncLock(fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_CONFIG, configFile),
func() interface{} {
path := cmdenv.Get("gf.gcfg.path", gfile.SelfDir()).String()
config := gcfg.New(path, configFile)
// 添加基于源码的搜索目录检索地址,常用于开发环境调试,只添加入口文件目录
if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) {
config.AddPath(p)
pwdPath := gfile.Pwd()
envPath := cmdenv.Get("gf.gcfg.path").String()
selfPath := gfile.SelfDir()
mainPath := gfile.MainPkgPath()
config := gcfg.New(pwdPath, configFile)
// 自定义的环境变量/启动参数路径,优先级最高,覆盖默认的工作目录
if envPath != "" && gfile.Exists(envPath) {
config.SetPath(envPath)
}
// 二进制文件执行目录
if selfPath != "" && gfile.Exists(selfPath) {
config.AddPath(selfPath)
}
// 开发环境源码main包目录
if mainPath != "" && gfile.Exists(mainPath) {
config.AddPath(mainPath)
}
return config
}).(*gcfg.Config)
@ -152,18 +165,34 @@ func Database(name...string) gdb.DB {
if value, ok := nodem["priority"]; ok {
node.Priority = gconv.Int(value)
}
// Deprecated
if value, ok := nodem["linkinfo"]; ok {
node.Linkinfo = gconv.String(value)
}
if value, ok := nodem["linkInfo"]; ok {
node.Linkinfo = gconv.String(value)
}
// Deprecated
if value, ok := nodem["max-idle"]; ok {
node.MaxIdleConnCount = gconv.Int(value)
}
if value, ok := nodem["maxIdle"]; ok {
node.MaxIdleConnCount = gconv.Int(value)
}
// Deprecated
if value, ok := nodem["max-open"]; ok {
node.MaxOpenConnCount = gconv.Int(value)
}
if value, ok := nodem["maxOpen"]; ok {
node.MaxOpenConnCount = gconv.Int(value)
}
// Deprecated
if value, ok := nodem["max-lifetime"]; ok {
node.MaxConnLifetime = gconv.Int(value)
}
if value, ok := nodem["maxLifetime"]; ok {
node.MaxConnLifetime = gconv.Int(value)
}
cg = append(cg, node)
}
}
@ -197,11 +226,34 @@ func Redis(name...string) *gredis.Redis {
key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_REDIS, group)
result := instances.GetOrSetFuncLock(key, func() interface{} {
if m := config.GetMap("redis"); m != nil {
// host:port[,db[,pass]]
// host:port[,db,pass?maxIdle=x&maxActive=x&idleTimeout=x&maxConnLifetime=x]
if v, ok := m[group]; ok {
line := gconv.String(v)
array, _ := gregex.MatchString(`(.+):(\d+),{0,1}(\d*),{0,1}(.*)`, line)
if len(array) > 4 {
line := gconv.String(v)
array, _ := gregex.MatchString(`(.+):(\d+),{0,1}(\d*),{0,1}(.*)\?(.+)`, line)
if len(array) == 6 {
parse, _ := gstr.Parse(array[5])
config := gredis.Config{
Host : array[1],
Port : gconv.Int(array[2]),
Db : gconv.Int(array[3]),
Pass : array[4],
}
if v, ok := parse["maxIdle"]; ok {
config.MaxIdle = gconv.Int(v)
}
if v, ok := parse["maxActive"]; ok {
config.MaxActive = gconv.Int(v)
}
if v, ok := parse["idleTimeout"]; ok {
config.IdleTimeout = gconv.TimeDuration(v)*time.Second
}
if v, ok := parse["maxConnLifetime"]; ok {
config.MaxConnLifetime = gconv.TimeDuration(v)*time.Second
}
return gredis.New(config)
}
array, _ = gregex.MatchString(`(.+):(\d+),{0,1}(\d*),{0,1}(.*)`, line)
if len(array) == 5 {
return gredis.New(gredis.Config{
Host : array[1],
Port : gconv.Int(array[2]),

View File

@ -0,0 +1,43 @@
// Copyright 2017 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gins_test
import (
"github.com/gogf/gf/g/frame/gins"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func Test_SetGet(t *testing.T) {
gtest.Case(t, func() {
gins.Set("test-user", 1)
gtest.Assert(gins.Get("test-user"), 1)
gtest.Assert(gins.Get("none-exists"), nil)
})
gtest.Case(t, func() {
gtest.Assert(gins.GetOrSet("test-1", 1), 1)
gtest.Assert(gins.Get("test-1"), 1)
})
gtest.Case(t, func() {
gtest.Assert(gins.GetOrSetFunc("test-2", func() interface{} {
return 2
}), 2)
gtest.Assert(gins.Get("test-2"), 2)
})
gtest.Case(t, func() {
gtest.Assert(gins.GetOrSetFuncLock("test-3", func() interface{} {
return 3
}), 3)
gtest.Assert(gins.Get("test-3"), 3)
})
gtest.Case(t, func() {
gtest.Assert(gins.SetIfNotExist("test-4", 4), true)
gtest.Assert(gins.Get("test-4"), 4)
gtest.Assert(gins.SetIfNotExist("test-4", 5), false)
gtest.Assert(gins.Get("test-4"), 4)
})
}

View File

@ -0,0 +1,166 @@
// Copyright 2017 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gins_test
import (
"fmt"
"github.com/gogf/gf/g/frame/gins"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Config(t *testing.T) {
config := `
# 模板引擎目录
viewpath = "/home/www/templates/"
test = "v=1"
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = "8692651"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
# Redis数据库配置
[redis]
disk = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"
`
gtest.Case(t, func() {
gtest.AssertNE(gins.Config(), nil)
})
// relative path
gtest.Case(t, func() {
path := "config.toml"
err := gfile.PutContents(path, config)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer gins.Config().Reload()
gtest.Assert(gins.Config().Get("test"), "v=1")
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500*time.Millisecond)
// relative path, config folder
gtest.Case(t, func() {
path := "config/config.toml"
err := gfile.PutContents(path, config)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer gins.Config().Reload()
gtest.Assert(gins.Config().Get("test"), "v=1")
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
path := "test.toml"
err := gfile.PutContents(path, config)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer gins.Config().Reload()
gtest.Assert(gins.Config("test.toml").Get("test"), "v=1")
gtest.Assert(gins.Config("test.toml").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config("test.toml").Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
path := "config/test.toml"
err := gfile.PutContents(path, config)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer gins.Config().Reload()
gtest.Assert(gins.Config("test.toml").Get("test"), "v=1")
gtest.Assert(gins.Config("test.toml").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config("test.toml").Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500*time.Millisecond)
// absolute path
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.Nanosecond())
file := fmt.Sprintf(`%s/%s`, path, "config.toml")
err := gfile.PutContents(file, config)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer gins.Config().Reload()
gtest.Assert(gins.Config().AddPath(path), nil)
gtest.Assert(gins.Config().Get("test"), "v=1")
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d/config`, gfile.TempDir(), gtime.Nanosecond())
file := fmt.Sprintf(`%s/%s`, path, "config.toml")
err := gfile.PutContents(file, config)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer gins.Config().Reload()
gtest.Assert(gins.Config().AddPath(path), nil)
gtest.Assert(gins.Config().Get("test"), "v=1")
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.Nanosecond())
file := fmt.Sprintf(`%s/%s`, path, "test.toml")
err := gfile.PutContents(file, config)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer gins.Config("test.toml").Reload()
gtest.Assert(gins.Config("test.toml").AddPath(path), nil)
gtest.Assert(gins.Config("test.toml").Get("test"), "v=1")
gtest.Assert(gins.Config("test.toml").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config("test.toml").Get("redis.disk"), "127.0.0.1:6379,0")
})
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d/config`, gfile.TempDir(), gtime.Nanosecond())
file := fmt.Sprintf(`%s/%s`, path, "test.toml")
err := gfile.PutContents(file, config)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer gins.Config("test.toml").Reload()
gtest.Assert(gins.Config("test.toml").AddPath(path), nil)
gtest.Assert(gins.Config("test.toml").Get("test"), "v=1")
gtest.Assert(gins.Config("test.toml").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config("test.toml").Get("redis.disk"), "127.0.0.1:6379,0")
})
}

View File

@ -0,0 +1,75 @@
// Copyright 2017 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gins_test
import (
"fmt"
"github.com/gogf/gf/g/frame/gins"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Database(t *testing.T) {
config := `
# 模板引擎目录
viewpath = "/home/www/templates/"
test = "v=2"
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
# pass = "12345678"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
[[database.test]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
# pass = "12345678"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
# Redis数据库配置
[redis]
default = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"
`
path := "config.toml"
err := gfile.PutContents(path, config)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer gins.Config().Reload()
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
fmt.Println("gins Test_Database", gins.Config().Get("test"))
dbDefault := gins.Database()
dbTest := gins.Database("test")
gtest.AssertNE(dbDefault, nil)
gtest.AssertNE(dbTest, nil)
gtest.Assert(dbDefault.PingMaster(), nil)
gtest.Assert(dbDefault.PingSlave(), nil)
gtest.Assert(dbTest.PingMaster(), nil)
gtest.Assert(dbTest.PingSlave(), nil)
})
}

View File

@ -0,0 +1,86 @@
// Copyright 2017 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gins_test
import (
"github.com/gogf/gf/g/frame/gins"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Redis(t *testing.T) {
config := `
# 模板引擎目录
viewpath = "/home/www/templates/"
test = "v=3"
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
# pass = "12345678"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
[[database.test]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
# pass = "12345678"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
# Redis数据库配置
[redis]
default = "127.0.0.1:6379,7"
cache = "127.0.0.1:6379,8"
disk = "127.0.0.1:6379,9,?maxIdle=1&maxActive=10&idleTimeout=10&maxConnLifetime=10"
`
path := "config.toml"
err := gfile.PutContents(path, config)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer gins.Config().Reload()
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
//fmt.Println("gins Test_Redis", gins.Config().Get("test"))
redisDefault := gins.Redis()
redisCache := gins.Redis("cache")
redisDisk := gins.Redis("disk")
gtest.AssertNE(redisDefault, nil)
gtest.AssertNE(redisCache, nil)
gtest.AssertNE(redisDisk, nil)
r, err := redisDefault.Do("PING")
gtest.Assert(err, nil)
gtest.Assert(r, "PONG")
r, err = redisCache.Do("PING")
gtest.Assert(err, nil)
gtest.Assert(r, "PONG")
_, err = redisDisk.Do("SET", "k", "v")
gtest.Assert(err, nil)
r, err = redisDisk.Do("GET", "k")
gtest.Assert(err, nil)
gtest.Assert(r, []byte("v"))
})
}

View File

@ -0,0 +1,49 @@
// Copyright 2017 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package gins_test
import (
"fmt"
"github.com/gogf/gf/g/frame/gins"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func Test_View(t *testing.T) {
gtest.Case(t, func() {
gtest.AssertNE(gins.View(), nil)
b, e := gins.View().ParseContent(`{{"我是中国人" | substr 2 -1}}`, nil)
gtest.Assert(e, nil)
gtest.Assert(string(b), "中国人")
})
gtest.Case(t, func() {
tpl := "t.tpl"
err := gfile.PutContents(tpl, `{{"我是中国人" | substr 2 -1}}`)
gtest.Assert(err, nil)
defer gfile.Remove(tpl)
b, e := gins.View().Parse("t.tpl", nil)
gtest.Assert(e, nil)
gtest.Assert(string(b), "中国人")
})
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.Nanosecond())
tpl := fmt.Sprintf(`%s/%s`, path, "t.tpl")
err := gfile.PutContents(tpl, `{{"我是中国人" | substr 2 -1}}`)
gtest.Assert(err, nil)
defer gfile.Remove(tpl)
err = gins.View().AddPath(path)
gtest.Assert(err, nil)
b, e := gins.View().Parse("t.tpl", nil)
gtest.Assert(e, nil)
gtest.Assert(string(b), "中国人")
})
}

31
g/g.go
View File

@ -3,34 +3,51 @@
// 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://github.com/gogf/gf.
// 常用数据类型以及对象封装
package g
import "github.com/gogf/gf/g/container/gvar"
// 框架动态变量可以用该类型替代interface{}类型
type Var = gvar.Var
// Universal variable type, like generics.
//
// 动态变量类型可以用该类型替代interface{}类型
type Var = gvar.Var
// Frequently-used map type alias.
//
// 常用map数据结构(使用别名)
type Map = map[string]interface{}
type MapAnyAny = map[interface{}]interface{}
type MapAnyStr = map[interface{}]string
type MapAnyInt = map[interface{}]int
type MapStrAny = map[string]interface{}
type MapStrStr = map[string]string
type MapStrInt = map[string]int
type MapIntAny = map[int]interface{}
type MapIntStr = map[int]string
type MapIntInt = map[int]int
// Frequently-used slice type alias.
//
// 常用list数据结构(使用别名)
type List = []Map
type ListAnyStr = []map[interface{}]string
type ListAnyInt = []map[interface{}]int
type ListStrAny = []map[string]interface{}
type ListStrStr = []map[string]string
type ListStrInt = []map[string]int
type ListIntAny = []map[int]interface{}
type ListIntStr = []map[int]string
type ListIntInt = []map[int]int
// Frequently-used slice type alias.
//
// 常用slice数据结构(使用别名)
type Slice = []interface{}
type SliceAny = []interface{}
type SliceStr = []string
type SliceInt = []int
type Array = Slice
type ArrayStr = SliceStr
type ArrayInt = SliceInt
type Array = []interface{}
type ArrayAny = []interface{}
type ArrayStr = []string
type ArrayInt = []int

View File

@ -7,43 +7,60 @@
package g
import (
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/internal/empty"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/util/gutil"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/container/gvar"
)
const (
LOG_LEVEL_ALL = glog.LEVEL_ALL
LOG_LEVEL_DEBU = glog.LEVEL_DEBU
LOG_LEVEL_INFO = glog.LEVEL_INFO
LOG_LEVEL_NOTI = glog.LEVEL_NOTI
LOG_LEVEL_WARN = glog.LEVEL_WARN
LOG_LEVEL_ERRO = glog.LEVEL_ERRO
LOG_LEVEL_CRIT = glog.LEVEL_CRIT
)
// NewVar creates a *Var.
//
// 动态变量
func NewVar(i interface{}, unsafe...bool) *Var {
return gvar.New(i, unsafe...)
}
// Wait blocks until all the web servers shutdown.
//
// 阻塞等待HTTPServer执行完成(同一进程多HTTPServer情况下)
func Wait() {
ghttp.Wait()
}
// 打印变量
// Dump dumps a variable to stdout with more manually readable.
//
// 格式化打印变量.
func Dump(i...interface{}) {
gutil.Dump(i...)
}
// Export exports a variable to string with more manually readable.
//
// 格式化导出变量.
func Export(i...interface{}) string {
return gutil.Export(i...)
}
// Throw throws a exception, which can be caught by Catch function.
// It always be used in TryCatch function.
//
// 抛出一个异常
func Throw(exception interface{}) {
gutil.Throw(exception)
}
// try...catch...
// TryCatch does the try...catch... logic.
func TryCatch(try func(), catch ... func(exception interface{})) {
gutil.TryCatch(try, catch...)
}
// IsEmpty checks given value empty or not.
// false: integer(0), bool(false), slice/map(len=0), nil;
// true : other.
//
// 判断给定的变量是否为空。
// 整型为0, 布尔为false, slice/map长度为0, 其他为nil的情况都为空。
// 为空时返回true否则返回false。
func IsEmpty(value interface{}) bool {
return empty.IsEmpty(value)
}

View File

@ -10,16 +10,22 @@ import (
"github.com/gogf/gf/g/os/glog"
)
// Disable/Enabled debug of logging globally.
//
// 是否显示调试信息
func SetDebug(debug bool) {
glog.SetDebug(debug)
}
// Set the logging level globally.
//
// 设置日志的显示等级
func SetLogLevel(level int) {
glog.SetLevel(level)
}
// Get the global logging level.
//
// 获取设置的日志显示等级
func GetLogLevel() int {
return glog.GetLevel()

View File

@ -17,42 +17,58 @@ import (
"github.com/gogf/gf/g/os/gcfg"
)
// Get an instance of http server with specified name.
//
// HTTPServer单例对象
func Server(name...interface{}) *ghttp.Server {
return ghttp.GetServer(name...)
}
// Get an instance of tcp server with specified name.
//
// TCPServer单例对象
func TCPServer(name...interface{}) *gtcp.Server {
return gtcp.GetServer(name...)
}
// Get an instance of udp server with specified name.
//
// UDPServer单例对象
func UDPServer(name...interface{}) *gudp.Server {
return gudp.GetServer(name...)
}
// Get an instance of template engine object with specified name.
//
// 核心对象View
func View(name...string) *gview.View {
return gins.View(name...)
}
// Config配置管理对象
// Get an instance of config object with specified default config file name.
//
// Config配置管理对象,
// 配置文件目录查找依次为启动参数cfgpath、当前程序运行目录
func Config(file...string) *gcfg.Config {
return gins.Config(file...)
}
// Get an instance of database ORM object with specified configuration group name.
//
// 数据库操作对象,使用了连接池
func Database(name...string) gdb.DB {
return gins.Database(name...)
}
// Alias of Database.
//
// (别名)Database
func DB(name...string) gdb.DB {
return gins.Database(name...)
}
// Get an instance of redis client with specified configuration group name.
//
// Redis操作对象使用了连接池
func Redis(name...string) *gredis.Redis {
return gins.Redis(name...)

View File

@ -17,7 +17,7 @@ import (
// 规则:
// 1、命令行参数以小写字母格式使用: gf.包名.变量名 传递;
// 2、环境变量参数以大写字母格式使用: GF_包名_变量名 传递;
func Get(key string, def...interface{}) *gvar.Var {
func Get(key string, def...interface{}) gvar.VarRead {
value := interface{}(nil)
if len(def) > 0 {
value = def[0]

52
g/internal/empty/empty.go Normal file
View File

@ -0,0 +1,52 @@
// Copyright 2019 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package empty
import (
"reflect"
)
// 判断给定的变量是否为空。
// 整型为0, 布尔为false, slice/map长度为0, 其他为nil的情况都为空。
// 为空时返回true否则返回false。
func IsEmpty(value interface{}) bool {
if value == nil {
return true
}
// 优先通过断言来进行常用类型判断
switch value := value.(type) {
case int: return value == 0
case int8: return value == 0
case int16: return value == 0
case int32: return value == 0
case int64: return value == 0
case uint: return value == 0
case uint8: return value == 0
case uint16: return value == 0
case uint32: return value == 0
case uint64: return value == 0
case float32: return value == 0
case float64: return value == 0
case bool: return value == false
case string: return value == ""
case []byte: return len(value) == 0
default:
// 最后通过反射来判断
rv := reflect.ValueOf(value)
if rv.IsNil() {
return true
}
kind := rv.Kind()
switch kind {
case reflect.Map: fallthrough
case reflect.Slice: fallthrough
case reflect.Array:
return rv.Len() == 0
}
}
return false
}

View File

@ -4,5 +4,7 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// Package ghttp provides quite powerful HTTP server and simple client implementations.
// Package ghttp provides a powerful http server and a simple client.
//
// ghttp是GF框架的核心模块实现了一个强大的Web Server并提供了一个简便的HTTP客户端。
package ghttp

View File

@ -0,0 +1,96 @@
// Copyright 2017 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
// HTTP客户端请求.
package ghttp
import (
"github.com/gogf/gf/g/text/gregex"
"strings"
"time"
)
// 是否模拟浏览器模式(自动保存提交COOKIE)
func (c *Client) SetBrowserMode(enabled bool) {
c.browserMode = enabled
}
// 设置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]
}
}
}
// 设置COOKIE
func (c *Client) SetCookie(key, value string) {
c.cookies[key] = value
}
// 使用Map设置COOKIE
func (c *Client) SetCookieMap(cookieMap map[string]string) {
for k, v := range cookieMap {
c.cookies[k] = v
}
}
// 设置请求的URL前缀
func (c *Client) SetPrefix(prefix string) {
c.prefix = prefix
}
// 设置请求过期时间
func (c *Client) SetTimeOut(t time.Duration) {
c.Timeout = t
}
// 设置HTTP访问账号密码
func (c *Client) SetBasicAuth(user, pass string) {
c.authUser = user
c.authPass = pass
}
// 设置失败重试次数及间隔,失败仅针对网络请求失败情况。
// 重试间隔时间单位为秒。
func (c *Client) SetRetry(retryCount int, retryInterval int) {
c.retryCount = retryCount
c.retryInterval = retryInterval
}
// 链式操作, See SetBrowserMode
func (c *Client) BrowserMode(enabled bool) *Client {
c.browserMode = enabled
return c
}
// 链式操作, See SetTimeOut
func (c *Client) TimeOut(t time.Duration) *Client {
c.Timeout = t
return c
}
// 链式操作, See SetBasicAuth
func (c *Client) BasicAuth(user, pass string) *Client {
c.authUser = user
c.authPass = pass
return c
}
// 链式操作, See SetRetry
func (c *Client) Retry(retryCount int, retryInterval int) *Client {
c.retryCount = retryCount
c.retryInterval = retryInterval
return c
}

View File

@ -9,69 +9,58 @@
package ghttp
import (
"github.com/gogf/gf/g/text/gregex"
"time"
"bytes"
"strings"
"net/http"
"mime/multipart"
"os"
"io"
"github.com/gogf/gf/g/os/gfile"
"encoding/json"
"errors"
"fmt"
"github.com/gogf/gf/g/os/gfile"
"io"
"mime/multipart"
"net/http"
"os"
"strings"
"time"
)
// http客户端
type Client struct {
http.Client // 底层http client对象
header map[string]string // HEADER信息Map
prefix string // 设置请求的URL前缀
authUser string // HTTP基本权限设置名称
authPass string // HTTP基本权限设置密码
http.Client // 底层http client对象
header map[string]string // HEADER信息Map
cookies map[string]string // 自定义COOKIE
prefix string // 设置请求的URL前缀
authUser string // HTTP基本权限设置名称
authPass string // HTTP基本权限设置密码
browserMode bool // 是否模拟浏览器模式(自动保存提交COOKIE)
retryCount int // 失败重试次数(网络失败情况下)
retryInterval int // 失败重试间隔
}
// http客户端对象指针
func NewClient() (*Client) {
func NewClient() *Client {
return &Client{
Client : http.Client {
Transport: &http.Transport {
DisableKeepAlives: true,
},
},
header : make(map[string]string),
header : make(map[string]string),
cookies : make(map[string]string),
}
}
// 设置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]
}
// 克隆当前客户端对象,复制属性。
func (c *Client) Clone() *Client {
newClient := NewClient()
*newClient = *c
newClient.header = make(map[string]string)
newClient.cookies = make(map[string]string)
for k, v := range c.header {
newClient.header[k] = v
}
}
// 设置请求的URL前缀
func (c *Client) SetPrefix(prefix string) {
c.prefix = prefix
}
// 设置请求过期时间
func (c *Client) SetTimeOut(t time.Duration) {
c.Timeout = t
}
// 设置HTTP访问账号密码
func (c *Client) SetBasicAuth(user, pass string) {
c.authUser = user
c.authPass = pass
for k, v := range c.cookies {
newClient.cookies[k] = v
}
return newClient
}
// GET请求
@ -97,6 +86,7 @@ func (c *Client) Post(url string, data...string) (*ClientResponse, error) {
}
req := (*http.Request)(nil)
if strings.Contains(param, "@file:") {
// 文件上传
buffer := new(bytes.Buffer)
writer := multipart.NewWriter(buffer)
for _, item := range strings.Split(param, "&") {
@ -130,11 +120,17 @@ func (c *Client) Post(url string, data...string) (*ClientResponse, error) {
req.Header.Set("Content-Type", writer.FormDataContentType())
}
} else {
if r, err := http.NewRequest("POST", url, bytes.NewReader([]byte(param))); err != nil {
// 识别提交数据格式
paramBytes := []byte(param)
if r, err := http.NewRequest("POST", url, bytes.NewReader(paramBytes)); err != nil {
return nil, err
} else {
req = r
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if json.Valid(paramBytes) {
req.Header.Set("Content-Type", "application/json")
} else {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}
}
}
// 自定义header
@ -143,17 +139,41 @@ func (c *Client) Post(url string, data...string) (*ClientResponse, error) {
req.Header.Set(k, v)
}
}
// COOKIE
if len(c.cookies) > 0 {
headerCookie := ""
for k, v := range c.cookies {
if len(headerCookie) > 0 {
headerCookie += ";"
}
headerCookie += k + "=" + v
}
if len(headerCookie) > 0 {
req.Header.Set("Cookie", headerCookie)
}
}
// HTTP账号密码
if len(c.authUser) > 0 {
req.SetBasicAuth(c.authUser, c.authPass)
}
// 执行请求
resp, err := c.Do(req)
if err != nil {
return nil, err
resp := (*http.Response)(nil)
for {
if r, err := c.Do(req); err != nil {
if c.retryCount > 0 {
c.retryCount--
} else {
return nil, err
}
} else {
resp = r
break
}
}
r := &ClientResponse{}
r.Response = *resp
r := &ClientResponse{
cookies : make(map[string]string),
}
r.Response = resp
return r, nil
}
@ -254,13 +274,49 @@ func (c *Client) DoRequest(method, url string, data...string) (*ClientResponse,
req.Header.Set(k, v)
}
}
// 执行请求
resp, err := c.Do(req)
if err != nil {
return nil, err
// COOKIE
if len(c.cookies) > 0 {
headerCookie := ""
for k, v := range c.cookies {
if len(headerCookie) > 0 {
headerCookie += ";"
}
headerCookie += k + "=" + v
}
if len(headerCookie) > 0 {
req.Header.Set("Cookie", headerCookie)
}
}
r := &ClientResponse{}
r.Response = *resp
// 执行请求
resp := (*http.Response)(nil)
for {
if r, err := c.Do(req); err != nil {
if c.retryCount > 0 {
c.retryCount--
} else {
return nil, err
}
} else {
resp = r
break
}
}
r := &ClientResponse{
cookies : make(map[string]string),
}
r.Response = resp
// 浏览器模式
if c.browserMode {
now := time.Now()
for _, v := range r.Cookies() {
if v.Expires.UnixNano() < now.UnixNano() {
delete(c.cookies, v.Name)
} else {
c.cookies[v.Name] = v.Value
}
}
}
//fmt.Println(url, c.cookies)
return r, nil
}

View File

@ -10,11 +10,27 @@ package ghttp
import (
"io/ioutil"
"net/http"
"time"
)
// 客户端请求结果对象
type ClientResponse struct {
http.Response
*http.Response
cookies map[string]string
}
// 获得返回的指定COOKIE值
func (r *ClientResponse) GetCookie(key string) string {
if r.cookies == nil {
now := time.Now()
for _, v := range r.Cookies() {
if v.Expires.UnixNano() < now.UnixNano() {
continue
}
r.cookies[v.Name] = v.Value
}
}
return r.cookies[key]
}
// 获取返回的数据(二进制).

View File

@ -57,7 +57,7 @@ func newRequest(s *Server, r *http.Request, w http.ResponseWriter) *Request {
return request
}
// 获取Web Socket连接对象(如果是非WS请求会失败注意检查然会的error结果)
// 获取Web Socket连接对象(如果是非WS请求会失败注意检查返回的error结果)
func (r *Request) WebSocket() (*WebSocket, error) {
if conn, err := wsUpgrader.Upgrade(r.Response.ResponseWriter.ResponseWriter, r.Request, nil); err == nil {
return &WebSocket {
@ -79,7 +79,7 @@ func (r *Request) GetVar(key string, def ... interface{}) gvar.VarRead {
return r.GetRequestVar(key, def...)
}
// 获取原始请求输入字符串
// 获取原始请求输入二进制。
func (r *Request) GetRaw() []byte {
if r.rawContent == nil {
r.rawContent, _ = ioutil.ReadAll(r.Body)
@ -87,12 +87,22 @@ func (r *Request) GetRaw() []byte {
return r.rawContent
}
// 获取原始请求输入字符串。
func (r *Request) GetRawString() string {
if r.rawContent == nil {
r.rawContent, _ = ioutil.ReadAll(r.Body)
}
return string(r.rawContent)
}
// 获取原始json请求输入字符串并解析为json对象
func (r *Request) GetJson() *gjson.Json {
data := r.GetRaw()
if data != nil {
if j, err := gjson.DecodeToJson(data); err == nil {
return j
} else {
panic(err)
}
}
return nil
@ -213,14 +223,14 @@ func (r *Request) GetReferer() string {
// 获得结构体对象的参数名称标签构成map返回
func (r *Request) getStructParamsTagMap(object interface{}) map[string]string {
tagmap := make(map[string]string)
tagMap := make(map[string]string)
fields := structs.Fields(object)
for _, field := range fields {
if tag := field.Tag("params"); tag != "" {
for _, v := range strings.Split(tag, ",") {
tagmap[strings.TrimSpace(v)] = field.Name()
tagMap[strings.TrimSpace(v)] = field.Name()
}
}
}
return tagmap
return tagMap
}

View File

@ -16,9 +16,10 @@ func (r *Request) initGet() {
if !r.parsedGet {
r.queryVars = r.URL.Query()
if strings.EqualFold(r.Method, "GET") {
if raw := r.GetRaw(); len(raw) > 0 {
for _, item := range strings.Split(string(raw), "&") {
array := strings.Split(item, "=")
if raw := r.GetRawString(); len(raw) > 0 {
var array []string
for _, item := range strings.Split(raw, "&") {
array = strings.Split(item, "=")
r.queryVars[array[0]] = append(r.queryVars[array[0]], array[1])
}
}

View File

@ -45,13 +45,10 @@ func (r *Response) Write(content ... interface{}) {
return
}
for _, v := range content {
switch v.(type) {
case []byte:
// 如果是二进制数据,那么返回二进制数据
r.buffer.Write(gconv.Bytes(v))
switch value := v.(type) {
case []byte: r.buffer.Write(value)
case string: r.buffer.WriteString(value)
default:
// 否则一律按照可显示的字符串进行转换
r.buffer.WriteString(gconv.String(v))
}
}
@ -118,15 +115,17 @@ func (r *Response) WriteXml(content interface{}, rootTag...string) error {
return nil
}
// 允许AJAX跨域访问
// Deprecated, please use CORSDefault instead.
//
// (已废弃请使用CORSDefault)允许AJAX跨域访问.
func (r *Response) SetAllowCrossDomainRequest(allowOrigin string, allowMethods string, maxAge...int) {
age := 3628800
if len(maxAge) > 0 {
age = maxAge[0]
}
r.Header().Set("Access-Control-Allow-Origin", allowOrigin);
r.Header().Set("Access-Control-Allow-Methods", allowMethods);
r.Header().Set("Access-Control-Max-Age", strconv.Itoa(age));
r.Header().Set("Access-Control-Allow-Origin", allowOrigin)
r.Header().Set("Access-Control-Allow-Methods", allowMethods)
r.Header().Set("Access-Control-Max-Age", strconv.Itoa(age))
}
// 返回HTTP Code状态码
@ -185,7 +184,8 @@ func (r *Response) ServeFileDownload(path string, name...string) {
r.Server.serveFile(r.request, path)
}
// 返回location标识引导客户端跳转
// 返回location标识引导客户端跳转
// 注意这里要先把设置的cookie输出否则会被忽略。
func (r *Response) RedirectTo(location string) {
r.Header().Set("Location", location)
r.WriteHeader(http.StatusFound)
@ -218,10 +218,19 @@ func (r *Response) ClearBuffer() {
r.buffer.Reset()
}
// 输出缓冲区数据到客户端
// Deprecated.
//
// 输出缓冲区数据到客户端.
func (r *Response) OutputBuffer() {
r.Header().Set("Server", r.Server.config.ServerAgent)
//r.handleGzip()
r.Writer.OutputBuffer()
}
// 输出缓冲区数据到客户端.
func (r *Response) Output() {
r.Header().Set("Server", r.Server.config.ServerAgent)
//r.handleGzip()
r.Writer.OutputBuffer()
}

View File

@ -0,0 +1,62 @@
// Copyright 2019 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
//
package ghttp
import (
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
)
// See https://www.w3.org/TR/cors/ .
// 服务端允许跨域请求选项
type CORSOptions struct {
AllowOrigin string // Access-Control-Allow-Origin
AllowCredentials string // Access-Control-Allow-Credentials
ExposeHeaders string // Access-Control-Expose-Headers
MaxAge int // Access-Control-Max-Age
AllowMethods string // Access-Control-Allow-Methods
AllowHeaders string // Access-Control-Allow-Headers
}
// 默认的CORS配置
func (r *Response) DefaultCORSOptions() CORSOptions {
return CORSOptions {
AllowOrigin : gstr.TrimRight(r.request.Referer(), "/"),
AllowMethods : HTTP_METHODS,
AllowCredentials : "true",
MaxAge : 3628800,
}
}
// See https://www.w3.org/TR/cors/ .
// 允许请求跨域访问.
func (r *Response) CORS(options CORSOptions) {
if options.AllowOrigin != "" {
r.Header().Set("Access-Control-Allow-Origin", options.AllowOrigin)
}
if options.AllowCredentials != "" {
r.Header().Set("Access-Control-Allow-Credentials", options.AllowCredentials)
}
if options.ExposeHeaders != "" {
r.Header().Set("Access-Control-Expose-Headers", options.ExposeHeaders)
}
if options.MaxAge != 0 {
r.Header().Set("Access-Control-Max-Age", gconv.String(options.MaxAge))
}
if options.AllowMethods != "" {
r.Header().Set("Access-Control-Allow-Methods", options.AllowMethods)
}
if options.AllowHeaders != "" {
r.Header().Set("Access-Control-Allow-Headers", options.AllowHeaders)
}
}
// 允许请求跨域访问(使用more配置).
func (r *Response) CORSDefault() {
r.CORS(r.DefaultCORSOptions())
}

View File

@ -13,10 +13,10 @@ import (
)
// 展示模板,可以给定模板参数,及临时的自定义模板函数
func (r *Response) WriteTpl(tpl string, params map[string]interface{}, funcmap...map[string]interface{}) error {
func (r *Response) WriteTpl(tpl string, params map[string]interface{}, funcMap...map[string]interface{}) error {
fmap := make(gview.FuncMap)
if len(funcmap) > 0 {
fmap = funcmap[0]
if len(funcMap) > 0 {
fmap = funcMap[0]
}
if b, err := r.ParseTpl(tpl, params, fmap); err != nil {
r.Write("Tpl Parsing Error: " + err.Error())
@ -28,10 +28,10 @@ func (r *Response) WriteTpl(tpl string, params map[string]interface{}, funcmap..
}
// 展示模板内容,可以给定模板参数,及临时的自定义模板函数
func (r *Response) WriteTplContent(content string, params map[string]interface{}, funcmap...map[string]interface{}) error {
func (r *Response) WriteTplContent(content string, params map[string]interface{}, funcMap...map[string]interface{}) error {
fmap := make(gview.FuncMap)
if len(funcmap) > 0 {
fmap = funcmap[0]
if len(funcMap) > 0 {
fmap = funcMap[0]
}
if b, err := r.ParseTplContent(content, params, fmap); err != nil {
r.Write("Tpl Parsing Error: " + err.Error())
@ -43,19 +43,19 @@ func (r *Response) WriteTplContent(content string, params map[string]interface{}
}
// 解析模板文件,并返回模板内容
func (r *Response) ParseTpl(tpl string, params gview.Params, funcmap...map[string]interface{}) ([]byte, error) {
func (r *Response) ParseTpl(tpl string, params gview.Params, funcMap...map[string]interface{}) ([]byte, error) {
fmap := make(gview.FuncMap)
if len(funcmap) > 0 {
fmap = funcmap[0]
if len(funcMap) > 0 {
fmap = funcMap[0]
}
return gins.View().Parse(tpl, r.buildInVars(params), r.buildInFuncs(fmap))
}
// 解析并返回模板内容
func (r *Response) ParseTplContent(content string, params gview.Params, funcmap...map[string]interface{}) ([]byte, error) {
func (r *Response) ParseTplContent(content string, params gview.Params, funcMap...map[string]interface{}) ([]byte, error) {
fmap := make(gview.FuncMap)
if len(funcmap) > 0 {
fmap = funcmap[0]
if len(funcMap) > 0 {
fmap = funcMap[0]
}
return gins.View().ParseContent(content, r.buildInVars(params), r.buildInFuncs(fmap))
}
@ -65,7 +65,6 @@ func (r *Response) buildInVars(params map[string]interface{}) map[string]interfa
if params == nil {
params = make(map[string]interface{})
}
c := gins.Config()
if c.GetFilePath() != "" {
params["Config"] = c.GetMap("")
@ -78,14 +77,14 @@ func (r *Response) buildInVars(params map[string]interface{}) map[string]interfa
}
// 内置函数
func (r *Response) buildInFuncs(funcmap map[string]interface{}) map[string]interface{} {
if funcmap == nil {
funcmap = make(map[string]interface{})
func (r *Response) buildInFuncs(funcMap map[string]interface{}) map[string]interface{} {
if funcMap == nil {
funcMap = make(map[string]interface{})
}
funcmap["get"] = r.funcGet
funcmap["post"] = r.funcPost
funcmap["request"] = r.funcRequest
return funcmap
funcMap["get"] = r.funcGet
funcMap["post"] = r.funcPost
funcMap["request"] = r.funcRequest
return funcMap
}
// 模板内置函数: get

View File

@ -25,16 +25,19 @@ func (w *ResponseWriter) Write(data []byte) (int, error) {
return len(data), nil
}
// 覆盖父级的WriteHeader方法
func (w *ResponseWriter) WriteHeader(code int) {
w.Status = code
w.ResponseWriter.WriteHeader(code)
// 覆盖父级的WriteHeader方法, 这里只会记录Status做缓冲处理, 并不会立即输出到HEADER。
func (w *ResponseWriter) WriteHeader(status int) {
w.Status = status
}
// 输出buffer数据到客户端
// 输出buffer数据到客户端.
func (w *ResponseWriter) OutputBuffer() {
if w.Status != 0 {
w.ResponseWriter.WriteHeader(w.Status)
}
if w.buffer.Len() > 0 {
w.ResponseWriter.Write(w.buffer.Bytes())
w.buffer.Reset()
}
}

View File

@ -19,8 +19,8 @@ import (
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gproc"
"github.com/gogf/gf/g/os/gtimer"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/third/github.com/gorilla/websocket"
"github.com/gogf/gf/third/github.com/olekukonko/tablewriter"
"net/http"
@ -39,7 +39,8 @@ type (
name string // 服务名称,方便识别
config ServerConfig // 配置对象
servers []*gracefulServer // 底层http.Server列表
methodsMap map[string]struct{} // 所有支持的HTTP Method(初始化时自动填充)
serverCount *gtype.Int // 底层http.Server数量
closeChan chan struct{} // 用以关闭事件通知的通道
servedCount *gtype.Int // 已经服务的请求数(4-8字节不考虑溢出情况)同时作为请求ID
// 服务注册相关
serveTree map[string]interface{} // 所有注册的服务回调函数(路由表,树型结构,哈希表+链表优先级匹配)
@ -101,8 +102,8 @@ type (
)
const (
SERVER_STATUS_STOPPED = 0 // Server状态停止
SERVER_STATUS_RUNNING = 1 // Server状态运行
SERVER_STATUS_STOPPED = 0 // Server状态停止
SERVER_STATUS_RUNNING = 1 // Server状态运行
HOOK_BEFORE_SERVE = "BeforeServe"
HOOK_AFTER_SERVE = "AfterServe"
HOOK_BEFORE_OUTPUT = "BeforeOutput"
@ -110,7 +111,7 @@ const (
HOOK_BEFORE_CLOSE = "BeforeClose"
HOOK_AFTER_CLOSE = "AfterClose"
gHTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
HTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
gDEFAULT_SERVER = "default"
gDEFAULT_DOMAIN = "default"
gDEFAULT_METHOD = "ALL"
@ -123,29 +124,39 @@ const (
)
var (
// Server表用以存储和检索名称与Server对象之间的关联关系
// 所有支持的HTTP Method Map(初始化时自动填充),
// 用于快速检索需要
methodsMap = make(map[string]struct{})
// WebServer表用以存储和检索名称与Server对象之间的关联关系
serverMapping = gmap.NewStringInterfaceMap()
// 正常运行的Server数量如果没有运行、失败或者全部退出那么该值为0
// 正常运行的WebServer数量如果没有运行、失败或者全部退出那么该值为0
serverRunning = gtype.NewInt()
// Web Socket默认配置
// WebSocket默认配置
wsUpgrader = websocket.Upgrader {
// 默认允许WebSocket请求跨域权限控制可以由业务层自己负责灵活度更高
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// Web Server已完成服务事件通道当有事件时表示服务完成当前进程退出
doneChan = make(chan struct{}, 1000)
// WebServer已完成服务事件通道当有事件时表示服务完成当前进程退出
allDoneChan = make(chan struct{}, 1000)
// 用于服务进程初始化,只能初始化一次,采用“懒初始化”(在server运行时才初始化)
serverProcessInited = gtype.NewBool()
// 是否开启WebServer平滑重启特性, 会开启额外的本地端口监听,用于进程管理通信
// 是否开启WebServer平滑重启特性, 会开启额外的本地端口监听,用于进程管理通信(默认开启)
gracefulEnabled = true
)
func init() {
for _, v := range strings.Split(HTTP_METHODS, ",") {
methodsMap[v] = struct{}{}
}
}
// 是否开启平滑重启特性
func SetGraceful(enabled bool) {
gracefulEnabled = enabled
@ -174,6 +185,11 @@ func serverProcessInit() {
if gracefulEnabled {
go handleProcessMessage()
}
// 是否处于开发环境
if gfile.MainPkgPath() != "" {
glog.Debug("GF notices that you're in develop environment, so error logs are auto enabled to stdout.")
}
}
// 获取/创建一个默认配置的HTTP Server(默认监听端口是80)
@ -189,7 +205,8 @@ func GetServer(name...interface{}) (*Server) {
s := &Server {
name : sname,
servers : make([]*gracefulServer, 0),
methodsMap : make(map[string]struct{}),
closeChan : make(chan struct{}, 100),
serverCount : gtype.NewInt(),
statusHandlerMap : make(map[string]HandlerFunc),
serveTree : make(map[string]interface{}),
hooksTree : make(map[string]interface{}),
@ -202,9 +219,6 @@ func GetServer(name...interface{}) (*Server) {
}
// 日志的标准输出默认关闭,但是错误信息会特殊处理
s.logger.SetStdPrint(false)
for _, v := range strings.Split(gHTTP_METHODS, ",") {
s.methodsMap[v] = struct{}{}
}
// 初始化时使用默认配置
s.SetConfig(defaultServerConfig)
// 记录到全局ServerMap中
@ -212,8 +226,8 @@ func GetServer(name...interface{}) (*Server) {
return s
}
// 作为守护协程异步执行(当同一进程中存在多个Web Server时需要采用这种方式执行)
// 需要结合Wait方式一起使用
// 作为守护协程异步执行(当同一进程中存在多个Web Server时需要采用这种方式执行),
// 需要结合Wait方式一起使用.
func (s *Server) Start() error {
// 服务进程初始化,只会初始化一次
serverProcessInit()
@ -228,11 +242,12 @@ func (s *Server) Start() error {
s.config.Handler = http.HandlerFunc(s.defaultHttpHandle)
}
// 不允许访问的路由注册(使用HOOK实现)
// TODO 去掉HOOK的实现方式
if s.config.DenyRoutes != nil {
for _, v := range s.config.DenyRoutes {
s.BindHookHandler(v, HOOK_BEFORE_SERVE, func(r *Request) {
r.Response.WriteStatus(403)
r.Exit()
r.ExitAll()
})
}
}
@ -266,10 +281,6 @@ func (s *Server) Start() error {
}
})
}
// 是否处于开发环境
if gfile.MainPkgPath() != "" {
glog.Debug("GF notices that you're in develop environment, so error logs are auto enabled to stdout.")
}
// 打印展示路由表
s.DumpRoutesMap()
@ -367,7 +378,7 @@ func (s *Server) Run() error {
return err
}
// 阻塞等待服务执行完成
<- doneChan
<- s.closeChan
glog.Printfln("%d: all servers shutdown", gproc.Pid())
return nil
@ -378,7 +389,7 @@ func (s *Server) Run() error {
// 这是一个与进程相关的方法
func Wait() {
// 阻塞等待服务执行完成
<- doneChan
<- allDoneChan
glog.Printfln("%d: all servers shutdown", gproc.Pid())
}
@ -387,7 +398,8 @@ func Wait() {
// 开启底层Web Server执行
func (s *Server) startServer(fdMap listenerFdMap) {
var httpsEnabled bool
if len(s.config.HTTPSCertPath) > 0 && len(s.config.HTTPSKeyPath) > 0 {
// 判断是否启用HTTPS
if len(s.config.TLSConfig.Certificates) > 0 || (len(s.config.HTTPSCertPath) > 0 && len(s.config.HTTPSKeyPath) > 0) {
// ================
// HTTPS
// ================
@ -462,23 +474,28 @@ func (s *Server) startServer(fdMap listenerFdMap) {
}
}
// 开始执行异步监听
serverRunning.Add(1)
for _, v := range s.servers {
go func(server *gracefulServer) {
serverRunning.Add(1)
s.serverCount.Add(1)
err := (error)(nil)
if server.isHttps {
err = server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath)
err = server.ListenAndServeTLS(s.config.HTTPSCertPath, s.config.HTTPSKeyPath, &s.config.TLSConfig)
} else {
err = server.ListenAndServe()
}
serverRunning.Add(-1)
// 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作
if err != nil && !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) {
glog.Fatal(err)
}
// 如果所有异步的Server都已经停止并且没有在管理操作(重启/关闭)进行中,那么主Server就可以退出了
if serverRunning.Val() < 1 && serverProcessStatus.Val() == 0 {
doneChan <- struct{}{}
// 如果所有异步的http.Server都已经停止那么WebServer就可以退出了
if s.serverCount.Add(-1) < 1 {
s.closeChan <- struct{}{}
// 如果所有WebServer都退出那么退出Wait等待
if serverRunning.Add(-1) < 1 {
serverMapping.Remove(s.name)
allDoneChan <- struct{}{}
}
}
}(v)
}

View File

@ -8,46 +8,14 @@
package ghttp
import (
"github.com/gogf/gf/g/os/gtimer"
"strings"
"github.com/gogf/gf/g/os/gview"
"github.com/gogf/gf/g/os/gproc"
"sync"
"github.com/gogf/gf/g/os/gtime"
"errors"
"fmt"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gtimer"
"github.com/gogf/gf/g/os/gview"
"os"
"github.com/gogf/gf/g/encoding/gjson"
"github.com/gogf/gf/g/util/gconv"
"strings"
"time"
"runtime"
"bytes"
)
const (
gADMIN_ACTION_INTERVAL_LIMIT = 2000 // (毫秒)服务开启后允许执行管理操作的间隔限制
gADMIN_ACTION_NONE = 0
gADMIN_ACTION_RESTARTING = 1
gADMIN_ACTION_SHUTINGDOWN = 2
gADMIN_ACTION_RELOAD_ENVKEY = "GF_SERVER_RELOAD"
gADMIN_ACTION_RESTART_ENVKEY = "GF_SERVER_RESTART"
gADMIN_GPROC_COMM_GROUP = "GF_GPROC_HTTP_SERVER"
)
// 用于服务管理的对象
type utilAdmin struct {}
// (进程级别)用于Web Server管理操作的互斥锁保证管理操作的原子性
var serverActionLocker sync.Mutex
// (进程级别)用于记录上一次操作的时间(毫秒)
var serverActionLastTime = gtype.NewInt64(gtime.Millisecond())
// 当前服务进程所处的互斥管理操作状态
var serverProcessStatus = gtype.NewInt()
// 服务管理首页
func (p *utilAdmin) Index(r *Request) {
data := map[string]interface{}{
@ -79,9 +47,9 @@ func (p *utilAdmin) Restart(r *Request) {
}
// 执行重启操作
if len(path) > 0 {
err = r.Server.Restart(path)
err = RestartAllServer(path)
} else {
err = r.Server.Restart()
err = RestartAllServer()
}
if err == nil {
r.Response.Write("server restarted")
@ -93,7 +61,7 @@ func (p *utilAdmin) Restart(r *Request) {
// 服务关闭
func (p *utilAdmin) Shutdown(r *Request) {
r.Server.Shutdown()
if err := r.Server.Shutdown(); err == nil {
if err := ShutdownAllServer(); err == nil {
r.Response.Write("server shutdown")
} else {
r.Response.Write(err.Error())
@ -109,222 +77,15 @@ func (s *Server) EnableAdmin(pattern...string) {
s.BindObject(p, &utilAdmin{})
}
// 重启Web Server,参数支持自定义重启的可执行文件路径,不传递时默认和原有可执行文件路径一致。
// 针对*niux系统: 平滑重启
// 针对windows : 完整重启
func (s *Server) Restart(newExeFilePath...string) error {
serverActionLocker.Lock()
defer serverActionLocker.Unlock()
if err := s.checkActionStatus(); err != nil {
return err
}
if err := s.checkActionFrequence(); err != nil {
return err
}
return restartWebServers("", newExeFilePath...)
}
// 关闭Web Server
// 关闭当前Web Server
func (s *Server) Shutdown() error {
serverActionLocker.Lock()
defer serverActionLocker.Unlock()
if err := s.checkActionStatus(); err != nil {
return err
}
if err := s.checkActionFrequence(); err != nil {
return err
}
shutdownWebServers("")
return nil
}
// 检测当前操作的频繁度
func (s *Server) checkActionFrequence() error {
interval := gtime.Millisecond() - serverActionLastTime.Val()
if interval < gADMIN_ACTION_INTERVAL_LIMIT {
return errors.New(fmt.Sprintf("too frequent action, please retry in %d ms", gADMIN_ACTION_INTERVAL_LIMIT - interval))
}
serverActionLastTime.Set(gtime.Millisecond())
return nil
}
// 检查当前服务进程的状态
func (s *Server) checkActionStatus() error {
status := serverProcessStatus.Val()
if status > 0 {
switch status {
case gADMIN_ACTION_RESTARTING: return errors.New("server is restarting")
case gADMIN_ACTION_SHUTINGDOWN: return errors.New("server is shutting down")
}
}
return nil
}
// 平滑重启:创建一个子进程,通过环境变量传参
func forkReloadProcess(newExeFilePath...string) error {
path := os.Args[0]
if len(newExeFilePath) > 0 {
path = newExeFilePath[0]
}
p := gproc.NewProcess(path, os.Args, os.Environ())
// 创建新的服务进程,子进程自动从父进程复制文件描述来监听同样的端口
sfm := getServerFdMap()
// 将sfm中的fd按照子进程创建时的文件描述符顺序进行整理以便子进程获取到正确的fd
for name, m := range sfm {
for fdk, fdv := range m {
if len(fdv) > 0 {
s := ""
for _, item := range strings.Split(fdv, ",") {
array := strings.Split(item, "#")
fd := uintptr(gconv.Uint(array[1]))
if fd > 0 {
s += fmt.Sprintf("%s#%d,", array[0], 3 + len(p.ExtraFiles))
p.ExtraFiles = append(p.ExtraFiles, os.NewFile(fd, ""))
} else {
s += fmt.Sprintf("%s#%d,", array[0], 0)
}
}
sfm[name][fdk] = strings.TrimRight(s, ",")
}
}
}
buffer, _ := gjson.Encode(sfm)
p.Env = append(p.Env, gADMIN_ACTION_RELOAD_ENVKEY + "=" + string(buffer))
if _, err := p.Start(); err != nil {
glog.Errorfln("%d: fork process failed, error:%s, %s", gproc.Pid(), err.Error(), string(buffer))
return err
}
return nil
}
// 完整重启:创建一个新的子进程
func forkRestartProcess(newExeFilePath...string) error {
path := os.Args[0]
if len(newExeFilePath) > 0 {
path = newExeFilePath[0]
}
// 去掉平滑重启的环境变量参数
os.Unsetenv(gADMIN_ACTION_RELOAD_ENVKEY)
env := os.Environ()
env = append(env, gADMIN_ACTION_RESTART_ENVKEY + "=1")
p := gproc.NewProcess(path, os.Args, env)
if _, err := p.Start(); err != nil {
glog.Errorfln("%d: fork process failed, error:%s", gproc.Pid(), err.Error())
return err
}
return nil
}
// 获取所有Web Server的文件描述符map
func getServerFdMap() map[string]listenerFdMap {
sfm := make(map[string]listenerFdMap)
serverMapping.RLockFunc(func(m map[string]interface{}) {
for k, v := range m {
sfm[k] = v.(*Server).getListenerFdMap()
// 非终端信号下异步1秒后再执行关闭
// 目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了)
gtimer.SetTimeout(time.Second, func() {
// 只关闭当前的Web Server
for _, v := range s.servers {
v.close()
}
})
return sfm
}
// 二进制转换为FdMap
func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap {
sfm := make(map[string]listenerFdMap)
if len(buffer) > 0 {
j, _ := gjson.LoadContent(buffer, "json")
for k, _ := range j.ToMap() {
m := make(map[string]string)
for k, v := range j.GetMap(k) {
m[k] = gconv.String(v)
}
sfm[k] = m
}
}
return sfm
}
// Web Server重启
func restartWebServers(signal string, newExeFilePath...string) error {
serverProcessStatus.Set(gADMIN_ACTION_RESTARTING)
if runtime.GOOS == "windows" {
if len(signal) > 0 {
// 在终端信号下,立即执行重启操作
forcedlyCloseWebServers()
forkRestartProcess(newExeFilePath...)
} else {
// 非终端信号下异步1秒后再执行重启目的是让接口能够正确返回结果否则接口会报错(因为web server关闭了)
gtimer.SetTimeout(time.Second, func() {
forcedlyCloseWebServers()
forkRestartProcess(newExeFilePath...)
})
}
} else {
if err := forkReloadProcess(newExeFilePath...); err != nil {
glog.Printfln("%d: server restarts failed", gproc.Pid())
serverProcessStatus.Set(gADMIN_ACTION_NONE)
return err
} else {
if len(signal) > 0 {
glog.Printfln("%d: server restarting by signal: %s", gproc.Pid(), signal)
} else {
glog.Printfln("%d: server restarting by web admin", gproc.Pid())
}
}
}
return nil
}
// Web Server关闭服务
func shutdownWebServers(signal string) {
serverProcessStatus.Set(gADMIN_ACTION_SHUTINGDOWN)
if len(signal) > 0 {
glog.Printfln("%d: server shutting down by signal: %s", gproc.Pid(), signal)
// 在终端信号下,立即执行关闭操作
forcedlyCloseWebServers()
doneChan <- struct{}{}
} else {
glog.Printfln("%d: server shutting down by web admin", gproc.Pid())
// 非终端信号下异步1秒后再执行关闭目的是让接口能够正确返回结果否则接口会报错(因为web server关闭了)
gtimer.SetTimeout(time.Second, func() {
forcedlyCloseWebServers()
doneChan <- struct{}{}
})
}
}
// 关优雅闭进程所有端口的Web Server服务
// 注意只是关闭Web Server服务并不是退出进程
func gracefulShutdownWebServers() {
serverMapping.RLockFunc(func(m map[string]interface{}) {
for _, v := range m {
for _, s := range v.(*Server).servers {
s.shutdown()
}
}
})
}
// 强制关闭进程所有端口的Web Server服务
// 注意只是关闭Web Server服务并不是退出进程
func forcedlyCloseWebServers() {
serverMapping.RLockFunc(func(m map[string]interface{}) {
for _, v := range m {
for _, s := range v.(*Server).servers {
s.close()
}
}
})
}
// 异步监听进程间消息
func handleProcessMessage() {
for {
if msg := gproc.Receive(gADMIN_GPROC_COMM_GROUP); msg != nil {
if bytes.EqualFold(msg.Data, []byte("exit")) {
gracefulShutdownWebServers()
doneChan <- struct{}{}
return
}
}
}
}

View File

@ -0,0 +1,269 @@
// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
// pprof封装.
package ghttp
import (
"bytes"
"errors"
"fmt"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/encoding/gjson"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gproc"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/os/gtimer"
"github.com/gogf/gf/g/util/gconv"
"os"
"runtime"
"strings"
"sync"
"time"
)
const (
gADMIN_ACTION_INTERVAL_LIMIT = 2000 // (毫秒)服务开启后允许执行管理操作的间隔限制
gADMIN_ACTION_NONE = 0
gADMIN_ACTION_RESTARTING = 1
gADMIN_ACTION_SHUTINGDOWN = 2
gADMIN_ACTION_RELOAD_ENVKEY = "GF_SERVER_RELOAD"
gADMIN_ACTION_RESTART_ENVKEY = "GF_SERVER_RESTART"
gADMIN_GPROC_COMM_GROUP = "GF_GPROC_HTTP_SERVER"
)
// 用于服务管理的对象
type utilAdmin struct {}
// (进程级别)用于Web Server管理操作的互斥锁保证管理操作的原子性
var serverActionLocker sync.Mutex
// (进程级别)用于记录上一次操作的时间(毫秒)
var serverActionLastTime = gtype.NewInt64(gtime.Millisecond())
// 当前服务进程所处的互斥管理操作状态
var serverProcessStatus = gtype.NewInt()
// 重启Web Server参数支持自定义重启的可执行文件路径不传递时默认和原有可执行文件路径一致。
// 针对*niux系统: 平滑重启
// 针对windows : 完整重启
func RestartAllServer(newExeFilePath...string) error {
serverActionLocker.Lock()
defer serverActionLocker.Unlock()
if err := checkProcessStatus(); err != nil {
return err
}
if err := checkActionFrequence(); err != nil {
return err
}
return restartWebServers("", newExeFilePath...)
}
// 关闭所有的WebServer
func ShutdownAllServer() error {
serverActionLocker.Lock()
defer serverActionLocker.Unlock()
if err := checkProcessStatus(); err != nil {
return err
}
if err := checkActionFrequence(); err != nil {
return err
}
shutdownWebServers()
return nil
}
// 检查当前服务进程的状态
func checkProcessStatus() error {
status := serverProcessStatus.Val()
if status > 0 {
switch status {
case gADMIN_ACTION_RESTARTING: return errors.New("server is restarting")
case gADMIN_ACTION_SHUTINGDOWN: return errors.New("server is shutting down")
}
}
return nil
}
// 检测当前操作的频繁度
func checkActionFrequence() error {
interval := gtime.Millisecond() - serverActionLastTime.Val()
if interval < gADMIN_ACTION_INTERVAL_LIMIT {
return errors.New(fmt.Sprintf("too frequent action, please retry in %d ms", gADMIN_ACTION_INTERVAL_LIMIT - interval))
}
serverActionLastTime.Set(gtime.Millisecond())
return nil
}
// 平滑重启:创建一个子进程,通过环境变量传参
func forkReloadProcess(newExeFilePath...string) error {
path := os.Args[0]
if len(newExeFilePath) > 0 {
path = newExeFilePath[0]
}
p := gproc.NewProcess(path, os.Args, os.Environ())
// 创建新的服务进程,子进程自动从父进程复制文件描述来监听同样的端口
sfm := getServerFdMap()
// 将sfm中的fd按照子进程创建时的文件描述符顺序进行整理以便子进程获取到正确的fd
for name, m := range sfm {
for fdk, fdv := range m {
if len(fdv) > 0 {
s := ""
for _, item := range strings.Split(fdv, ",") {
array := strings.Split(item, "#")
fd := uintptr(gconv.Uint(array[1]))
if fd > 0 {
s += fmt.Sprintf("%s#%d,", array[0], 3 + len(p.ExtraFiles))
p.ExtraFiles = append(p.ExtraFiles, os.NewFile(fd, ""))
} else {
s += fmt.Sprintf("%s#%d,", array[0], 0)
}
}
sfm[name][fdk] = strings.TrimRight(s, ",")
}
}
}
buffer, _ := gjson.Encode(sfm)
p.Env = append(p.Env, gADMIN_ACTION_RELOAD_ENVKEY + "=" + string(buffer))
if _, err := p.Start(); err != nil {
glog.Errorfln("%d: fork process failed, error:%s, %s", gproc.Pid(), err.Error(), string(buffer))
return err
}
return nil
}
// 完整重启:创建一个新的子进程
func forkRestartProcess(newExeFilePath...string) error {
path := os.Args[0]
if len(newExeFilePath) > 0 {
path = newExeFilePath[0]
}
// 去掉平滑重启的环境变量参数
os.Unsetenv(gADMIN_ACTION_RELOAD_ENVKEY)
env := os.Environ()
env = append(env, gADMIN_ACTION_RESTART_ENVKEY + "=1")
p := gproc.NewProcess(path, os.Args, env)
if _, err := p.Start(); err != nil {
glog.Errorfln("%d: fork process failed, error:%s", gproc.Pid(), err.Error())
return err
}
return nil
}
// 获取所有Web Server的文件描述符map
func getServerFdMap() map[string]listenerFdMap {
sfm := make(map[string]listenerFdMap)
serverMapping.RLockFunc(func(m map[string]interface{}) {
for k, v := range m {
sfm[k] = v.(*Server).getListenerFdMap()
}
})
return sfm
}
// 二进制转换为FdMap
func bufferToServerFdMap(buffer []byte) map[string]listenerFdMap {
sfm := make(map[string]listenerFdMap)
if len(buffer) > 0 {
j, _ := gjson.LoadContent(buffer, "json")
for k, _ := range j.ToMap() {
m := make(map[string]string)
for k, v := range j.GetMap(k) {
m[k] = gconv.String(v)
}
sfm[k] = m
}
}
return sfm
}
// Web Server重启
func restartWebServers(signal string, newExeFilePath...string) error {
serverProcessStatus.Set(gADMIN_ACTION_RESTARTING)
if runtime.GOOS == "windows" {
if len(signal) > 0 {
// 在终端信号下,立即执行重启操作
forceCloseWebServers()
forkRestartProcess(newExeFilePath...)
} else {
// 非终端信号下异步1秒后再执行重启目的是让接口能够正确返回结果否则接口会报错(因为web server关闭了)
gtimer.SetTimeout(time.Second, func() {
forceCloseWebServers()
forkRestartProcess(newExeFilePath...)
})
}
} else {
if err := forkReloadProcess(newExeFilePath...); err != nil {
glog.Printfln("%d: server restarts failed", gproc.Pid())
serverProcessStatus.Set(gADMIN_ACTION_NONE)
return err
} else {
if len(signal) > 0 {
glog.Printfln("%d: server restarting by signal: %s", gproc.Pid(), signal)
} else {
glog.Printfln("%d: server restarting by web admin", gproc.Pid())
}
}
}
return nil
}
// 关闭所有Web Server
func shutdownWebServers(signal...string) {
serverProcessStatus.Set(gADMIN_ACTION_SHUTINGDOWN)
if len(signal) > 0 {
glog.Printfln("%d: server shutting down by signal: %s", gproc.Pid(), signal[0])
// 在终端信号下,立即执行关闭操作
forceCloseWebServers()
allDoneChan <- struct{}{}
} else {
glog.Printfln("%d: server shutting down by api", gproc.Pid())
// 非终端信号下异步1秒后再执行关闭
// 目的是让接口能够正确返回结果,否则接口会报错(因为web server关闭了)
gtimer.SetTimeout(time.Second, func() {
forceCloseWebServers()
allDoneChan <- struct{}{}
})
}
}
// 关优雅闭进程所有端口的Web Server服务
// 注意只是关闭Web Server服务并不是退出进程
func gracefulShutdownWebServers() {
serverMapping.RLockFunc(func(m map[string]interface{}) {
for _, v := range m {
for _, s := range v.(*Server).servers {
s.shutdown()
}
}
})
}
// 强制关闭进程所有端口的Web Server服务
// 注意只是关闭Web Server服务并不是退出进程
func forceCloseWebServers() {
serverMapping.RLockFunc(func(m map[string]interface{}) {
for _, v := range m {
for _, s := range v.(*Server).servers {
s.close()
}
}
})
}
// 异步监听进程间消息
func handleProcessMessage() {
for {
if msg := gproc.Receive(gADMIN_GPROC_COMM_GROUP); msg != nil {
if bytes.EqualFold(msg.Data, []byte("exit")) {
gracefulShutdownWebServers()
allDoneChan <- struct{}{}
return
}
}
}
}

View File

@ -7,6 +7,7 @@
package ghttp
import (
"crypto/tls"
"fmt"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/glog"
@ -24,7 +25,7 @@ const (
NAME_TO_URI_TYPE_CAMEL = 3 // 采用驼峰命名方式
gDEFAULT_COOKIE_PATH = "/" // 默认path
gDEFAULT_COOKIE_MAX_AGE = 86400*365 // 默认cookie有效期(一年)
gDEFAULT_SESSION_MAX_AGE = 600 // 默认session有效期(600秒)
gDEFAULT_SESSION_MAX_AGE = 600000 // 默认session有效期(600秒)
gDEFAULT_SESSION_ID_NAME = "gfsessionid" // 默认存放Cookie中的SessionId名称
gCHANGE_CONFIG_WHILE_RUNNING_ERROR = "cannot be changed while running"
)
@ -44,6 +45,7 @@ type ServerConfig struct {
WriteTimeout time.Duration // 写入超时
IdleTimeout time.Duration // 等待超时
MaxHeaderBytes int // 最大的header长度
TLSConfig tls.Config
// 静态文件配置
IndexFiles []string // 默认访问的文件列表
@ -191,28 +193,46 @@ func (s *Server)SetHTTPSPort(port...int) {
}
}
// 开启HTTPS支持但是必须提供Cert和Key文件
func (s *Server)EnableHTTPS(certFile, keyFile string) {
// 开启HTTPS支持但是必须提供Cert和Key文件tlsConfig为可选项
func (s *Server)EnableHTTPS(certFile, keyFile string, tlsConfig...tls.Config) {
if s.Status() == SERVER_STATUS_RUNNING {
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
return
}
certFileRealPath := gfile.RealPath(certFile)
if certFileRealPath == "" {
certFileRealPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + certFileRealPath)
certFileRealPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + certFile)
if certFileRealPath == "" {
certFileRealPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + certFile)
}
}
if certFileRealPath == "" {
glog.Fatal(fmt.Sprintf(`[ghttp] EnableHTTPS failed: certFile "%s" does not exist`, certFile))
}
keyFileRealPath := gfile.RealPath(keyFile)
if keyFileRealPath == "" {
keyFileRealPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + keyFileRealPath)
keyFileRealPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + keyFile)
if keyFileRealPath == "" {
keyFileRealPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + keyFile)
}
}
if keyFileRealPath == "" {
glog.Fatal(fmt.Sprintf(`[ghttp] EnableHTTPS failed: keyFile "%s" does not exist`, keyFile))
}
s.config.HTTPSCertPath = certFileRealPath
s.config.HTTPSKeyPath = keyFileRealPath
if len(tlsConfig) > 0 {
s.config.TLSConfig = tlsConfig[0]
}
}
// 设置TLS配置对象
func (s *Server)SetTLSConfig(tlsConfig tls.Config) {
if s.Status() == SERVER_STATUS_RUNNING {
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
return
}
s.config.TLSConfig = tlsConfig
}
// 设置http server参数 - ReadTimeout

View File

@ -64,6 +64,7 @@ func (s *Server)SetServerRoot(root string) {
if path == "" {
glog.Fatal(fmt.Sprintf(`[ghttp] SetServerRoot failed: path "%s" does not exist`, root))
}
glog.Debug("[ghttp] SetServerRoot path:", path)
s.config.SearchPaths = []string{strings.TrimRight(path, gfile.Separator)}
s.config.FileServerEnabled = true
}

View File

@ -5,7 +5,7 @@
// You can obtain one at https://github.com/gogf/gf.
//
// HTTP Cookie管理对象
// 由于Cookie是和HTTP请求挂钩的因此被包含到 ghttp 包中进行管理
// 由于Cookie是和HTTP请求挂钩的因此被包含到 ghttp 包中进行管理
package ghttp
@ -42,6 +42,7 @@ func GetCookie(r *Request) *Cookie {
}
return &Cookie {
request : r,
server : r.Server,
}
}
@ -52,7 +53,6 @@ func (c *Cookie) init() {
c.path = c.request.Server.GetCookiePath()
c.domain = c.request.Server.GetCookieDomain()
c.maxage = c.request.Server.GetCookieMaxAge()
c.server = c.request.Server
c.response = c.request.Response
// 如果没有设置COOKIE有效域名那么设置HOST为默认有效域名
if c.domain == "" {
@ -87,6 +87,14 @@ func (c *Cookie) SessionId() string {
return id
}
// 获取SessionId不存在时则创建
func (c *Cookie) MakeSessionId() string {
c.init()
id := makeSessionId()
c.SetSessionId(id)
return id
}
// 判断Cookie中是否存在制定键名(并且没有过期)
func (c *Cookie) Contains(key string) bool {
c.init()
@ -115,6 +123,11 @@ func (c *Cookie) SetCookie(key, value, domain, path string, maxAge int, httpOnly
}
}
// 获得客户端提交的SessionId
func (c *Cookie) GetSessionId() string {
return c.Get(c.server.GetSessionIdName())
}
// 设置SessionId
func (c *Cookie) SetSessionId(id string) {
c.Set(c.server.GetSessionIdName(), id)
@ -133,9 +146,14 @@ func (c *Cookie) Get(key string) string {
return ""
}
// 删除COOKIE使用默认的domain&path
func (c *Cookie) Remove(key string) {
c.SetCookie(key, "", c.domain, c.path, -86400)
}
// 标记该cookie在对应的域名和路径失效
// 删除cookie的重点是需要通知浏览器客户端cookie已过期
func (c *Cookie) Remove(key, domain, path string) {
func (c *Cookie) RemoveCookie(key, domain, path string) {
c.SetCookie(key, "", domain, path, -86400)
}

View File

@ -9,7 +9,6 @@ package ghttp
import (
"strings"
"github.com/gogf/gf/g/container/gmap"
)
// 域名管理器对象
@ -18,121 +17,80 @@ type Domain struct {
m map[string]bool // 多域名
}
// 域名对象表,用以存储和检索域名(支持多域名)与域名对象之间的关联关系
var domainMap = gmap.NewStringInterfaceMap()
// 生成一个域名对象
// 生成一个域名对象, 参数 domains 支持给定多个域名。
func (s *Server) Domain(domains string) *Domain {
if r := domainMap.Get(domains); r != nil {
return r.(*Domain)
}
d := &Domain{
s : s,
m : make(map[string]bool),
}
result := strings.Split(domains, ",")
for _, v := range result {
for _, v := range strings.Split(domains, ",") {
d.m[strings.TrimSpace(v)] = true
}
domainMap.Set(domains, d)
return d
}
// 注意该方法是直接绑定方法的内存地址,执行的时候直接执行该方法,不会存在初始化新的控制器逻辑
func (d *Domain) BindHandler(pattern string, handler HandlerFunc) error {
func (d *Domain) BindHandler(pattern string, handler HandlerFunc) {
for domain, _ := range d.m {
if err := d.s.BindHandler(pattern + "@" + domain, handler); err != nil {
return err
}
d.s.BindHandler(pattern + "@" + domain, handler)
}
return nil
}
// 执行对象方法
func (d *Domain) BindObject(pattern string, obj interface{}, methods...string) error {
if len(methods) > 0 {
return d.BindObjectMethod(pattern, obj, strings.Join(methods, ","))
}
func (d *Domain) BindObject(pattern string, obj interface{}, methods...string) {
for domain, _ := range d.m {
if err := d.s.BindObject(pattern + "@" + domain, obj); err != nil {
return err
}
d.s.BindObject(pattern + "@" + domain, obj, methods...)
}
return nil
}
// 执行对象方法注册methods参数不区分大小写
func (d *Domain) BindObjectMethod(pattern string, obj interface{}, method string) error {
func (d *Domain) BindObjectMethod(pattern string, obj interface{}, method string) {
for domain, _ := range d.m {
if err := d.s.BindObjectMethod(pattern + "@" + domain, obj, method); err != nil {
return err
}
d.s.BindObjectMethod(pattern + "@" + domain, obj, method)
}
return nil
}
// RESTful执行对象注册
func (d *Domain) BindObjectRest(pattern string, obj interface{}) error {
func (d *Domain) BindObjectRest(pattern string, obj interface{}) {
for domain, _ := range d.m {
if err := d.s.BindObjectRest(pattern + "@" + domain, obj); err != nil {
return err
}
d.s.BindObjectRest(pattern + "@" + domain, obj)
}
return nil
}
// 控制器注册
func (d *Domain) BindController(pattern string, c Controller, methods...string) error {
if len(methods) > 0 {
return d.BindControllerMethod(pattern, c, strings.Join(methods, ","))
}
func (d *Domain) BindController(pattern string, c Controller, methods...string) {
for domain, _ := range d.m {
if err := d.s.BindController(pattern + "@" + domain, c); err != nil {
return err
}
d.s.BindController(pattern + "@" + domain, c, methods...)
}
return nil
}
// 控制器方法注册methods参数区分大小写
func (d *Domain) BindControllerMethod(pattern string, c Controller, method string) error {
func (d *Domain) BindControllerMethod(pattern string, c Controller, method string) {
for domain, _ := range d.m {
if err := d.s.BindControllerMethod(pattern + "@" + domain, c, method); err != nil {
return err
}
d.s.BindControllerMethod(pattern + "@" + domain, c, method)
}
return nil
}
// RESTful控制器注册
func (d *Domain) BindControllerRest(pattern string, c Controller) error {
func (d *Domain) BindControllerRest(pattern string, c Controller) {
for domain, _ := range d.m {
if err := d.s.BindControllerRest(pattern + "@" + domain, c); err != nil {
return err
}
d.s.BindControllerRest(pattern + "@" + domain, c)
}
return nil
}
// 绑定指定的hook回调函数, hook参数的值由ghttp server设定参数不区分大小写
// 目前hook支持Init/Shut
func (d *Domain)BindHookHandler(pattern string, hook string, handler HandlerFunc) error {
func (d *Domain)BindHookHandler(pattern string, hook string, handler HandlerFunc) {
for domain, _ := range d.m {
if err := d.s.BindHookHandler(pattern + "@" + domain, hook, handler); err != nil {
return err
}
d.s.BindHookHandler(pattern + "@" + domain, hook, handler)
}
return nil
}
// 通过map批量绑定回调函数
func (d *Domain)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error {
func (d *Domain)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) {
for domain, _ := range d.m {
if err := d.s.BindHookHandlerByMap(pattern + "@" + domain, hookmap); err != nil {
return err
}
d.s.BindHookHandlerByMap(pattern + "@" + domain, hookmap)
}
return nil
}
// 绑定指定的状态码回调函数

View File

@ -84,18 +84,22 @@ func (s *gracefulServer) setFd(fd int) {
}
// 执行HTTPS监听
func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string) error {
func (s *gracefulServer) ListenAndServeTLS(certFile, keyFile string, tlsConfig...*tls.Config) error {
addr := s.httpServer.Addr
config := &tls.Config{}
if s.httpServer.TLSConfig != nil {
config := (*tls.Config)(nil)
if len(tlsConfig) > 0 {
config = tlsConfig[0]
} else if s.httpServer.TLSConfig != nil {
*config = *s.httpServer.TLSConfig
}
if config.NextProtos == nil {
config.NextProtos = []string{"http/1.1"}
}
err := error(nil)
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if len(config.Certificates) == 0 {
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
}
if err != nil {
return errors.New(fmt.Sprintf(`open cert file "%s","%s" failed: %s`, certFile, keyFile, err.Error()))
}

View File

@ -24,7 +24,7 @@ func (s *Server)defaultHttpHandle(w http.ResponseWriter, r *http.Request) {
s.handleRequest(w, r)
}
// 执行处理HTTP请求
// 执行处理HTTP请求
// 首先,查找是否有对应域名的处理接口配置;
// 其次,如果没有对应的自定义处理接口配置,那么走默认的域名处理接口配置;
// 最后,如果以上都没有找到处理接口,那么进行文件处理;
@ -55,12 +55,11 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
// 输出Cookie
request.Cookie.Output()
// 输出缓冲区
request.Response.OutputBuffer()
request.Response.Output()
// 事件 - AfterOutput
if !request.IsExited() {
s.callHookHandler(HOOK_AFTER_OUTPUT, request)
}
// 事件 - BeforeClose
s.callHookHandler(HOOK_BEFORE_CLOSE, request)
// access log
@ -92,7 +91,7 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
// 动态服务检索
handler := (*handlerItem)(nil)
if !request.IsFileRequest() || isStaticDir {
if !request.isFileRequest || isStaticDir {
if parsedItem := s.getServeHandlerWithCache(request); parsedItem != nil {
handler = parsedItem.handler
for k, v := range parsedItem.values {
@ -112,9 +111,9 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
// 执行静态文件服务/回调控制器/执行对象/方法
if !request.IsExited() {
// 需要再次判断文件是否真实存在,因为文件检索可能使用了缓存,从健壮性考虑这里需要二次判断
// 需要再次判断文件是否真实存在,
// 因为文件检索可能使用了缓存,从健壮性考虑这里需要二次判断
if request.isFileRequest /* && gfile.Exists(staticFile) */{
// 静态文件
s.serveFile(request, staticFile)
} else {
if handler != nil {

View File

@ -37,7 +37,7 @@ func (s *Server)parsePattern(pattern string) (domain, method, path string, err e
}
}
if path == "" {
err = errors.New("invalid pattern")
err = errors.New("invalid pattern: URI should not be empty")
}
// 去掉末尾的"/"符号,与路由匹配时处理一致
if path != "/" {
@ -60,10 +60,11 @@ func (s *Server) getHandlerRegisterCallerLine(handler *handlerItem) string {
// 路由注册处理方法。
// 如果带有hook参数表示是回调注册方法; 否则为普通路由执行方法。
func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... string) (resultErr error) {
func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... string) {
// Web Server正常运行时无法动态注册路由方法
if s.Status() == SERVER_STATUS_RUNNING {
return errors.New("cannot bind handler while server running")
glog.Error("cannot bind handler while server running")
return
}
var hookName string
if len(hook) > 0 {
@ -71,29 +72,22 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin
}
domain, method, uri, err := s.parsePattern(pattern)
if err != nil {
return errors.New("invalid pattern")
glog.Error("invalid pattern:", pattern, err)
return
}
if len(uri) == 0 || uri[0] != '/' {
glog.Error("invalid pattern:", pattern, "URI should lead with '/'")
return
}
// 注册地址记录及重复注册判断
regkey := s.handlerKey(hookName, method, uri, domain)
caller := s.getHandlerRegisterCallerLine(handler)
if len(hook) == 0 {
if item, ok := s.routesMap[regkey]; ok {
s := fmt.Sprintf(`duplicated route registry "%s", already registered in %s`, pattern, item[0].file)
glog.Errorfln(s)
return errors.New(s)
glog.Errorfln(`duplicated route registry "%s", already registered at %s`, pattern, item[0].file)
return
}
}
defer func() {
if resultErr == nil {
if _, ok := s.routesMap[regkey]; !ok {
s.routesMap[regkey] = make([]registeredRouteItem, 0)
}
s.routesMap[regkey] = append(s.routesMap[regkey], registeredRouteItem {
file : caller,
handler : handler,
})
}
}()
// 路由对象
handler.router = &Router {
@ -193,9 +187,15 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin
l.PushBack(handler)
}
}
//gutil.Dump(s.serveTree)
//gutil.Dump(s.hooksTree)
return nil
// gutil.Dump(s.serveTree)
// gutil.Dump(s.hooksTree)
if _, ok := s.routesMap[regkey]; !ok {
s.routesMap[regkey] = make([]registeredRouteItem, 0)
}
s.routesMap[regkey] = append(s.routesMap[regkey], registeredRouteItem {
file : caller,
handler : handler,
})
}
// 对比两个handlerItem的优先级需要非常注意的是注意新老对比项的参数先后顺序。

View File

@ -18,8 +18,8 @@ import (
)
// 绑定指定的hook回调函数, pattern参数同BindHandler支持命名路由hook参数的值由ghttp server设定参数不区分大小写
func (s *Server)BindHookHandler(pattern string, hook string, handler HandlerFunc) error {
return s.setHandler(pattern, &handlerItem {
func (s *Server)BindHookHandler(pattern string, hook string, handler HandlerFunc) {
s.setHandler(pattern, &handlerItem {
name : runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name(),
ctype : nil,
fname : "",
@ -28,13 +28,10 @@ func (s *Server)BindHookHandler(pattern string, hook string, handler HandlerFunc
}
// 通过map批量绑定回调函数
func (s *Server)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error {
func (s *Server)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) {
for k, v := range hookmap {
if err := s.BindHookHandler(pattern, k, v); err != nil {
return err
}
s.BindHookHandler(pattern, k, v)
}
return nil
}
// 事件回调处理,内部使用了缓存处理.

View File

@ -8,19 +8,19 @@
package ghttp
import (
"errors"
"github.com/gogf/gf/g/os/glog"
"strings"
"reflect"
"fmt"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/text/gstr"
"reflect"
"strings"
)
// 绑定控制器控制器需要实现gmvc.Controller接口
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话
// 第三个参数methods用以指定需要注册的方法支持多个方法名称多个方法以英文“,”号分隔,区分大小写
func (s *Server)BindController(pattern string, c Controller, methods...string) error {
// 绑定控制器,控制器需要实现 gmvc.Controller 接口,
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话,
// 第三个参数methods用以指定需要注册的方法支持多个方法名称多个方法以英文“,”号分隔,区分大小写.
func (s *Server)BindController(pattern string, c Controller, methods...string) {
methodMap := (map[string]bool)(nil)
if len(methods) > 0 {
methodMap = make(map[string]bool)
@ -44,11 +44,7 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) e
continue
}
if _, ok := v.Method(i).Interface().(func()); !ok {
if methodMap != nil {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
}
glog.Errorfln(`invalid method definition "%s", while "func()" is required`, v.Method(i).Type().String())
continue
}
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
@ -63,16 +59,17 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) e
fname : mname,
faddr : nil,
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
if strings.EqualFold(mname, "Index") {
p := key
if strings.EqualFold(p[len(p) - 6:], "/index") {
p = p[0 : len(p) - 6]
if len(p) == 0 {
p = "/"
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
// 例如: pattern为/user, 那么会同时注册/user及/user/index
// 这里处理新增/user路由绑定。
// 注意当pattern带有内置变量时不会自动加该路由。
if strings.EqualFold(mname, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
p := gstr.PosR(key, "/index")
k := key[0 : p] + key[p + 6 : ]
if len(k) == 0 || k[0] == '@' {
k = "/" + k
}
m[p] = &handlerItem {
m[k] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, mname),
rtype : gROUTE_REGISTER_CONTROLLER,
ctype : v.Elem().Type(),
@ -81,11 +78,11 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) e
}
}
}
return s.bindHandlerByMap(m)
s.bindHandlerByMap(m)
}
// 绑定路由到指定的方法执行
func (s *Server)BindControllerMethod(pattern string, c Controller, method string) error {
// 绑定路由到指定的方法执行, 第三个参数method仅支持一个方法注册不支持多个并且区分大小写。
func (s *Server)BindControllerMethod(pattern string, c Controller, method string) {
m := make(handlerMap)
v := reflect.ValueOf(c)
t := v.Type()
@ -93,12 +90,12 @@ func (s *Server)BindControllerMethod(pattern string, c Controller, method string
mname := strings.TrimSpace(method)
fval := v.MethodByName(mname)
if !fval.IsValid() {
return errors.New("invalid method name:" + mname)
glog.Error("invalid method name:" + mname)
return
}
if _, ok := fval.Interface().(func()); !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, fval.Type().String())
glog.Error(s)
return errors.New(s)
glog.Errorfln(`invalid method definition "%s", while "func()" is required`, fval.Type().String())
return
}
pkgPath := t.Elem().PkgPath()
pkgName := gfile.Basename(pkgPath)
@ -114,37 +111,37 @@ func (s *Server)BindControllerMethod(pattern string, c Controller, method string
fname : mname,
faddr : nil,
}
return s.bindHandlerByMap(m)
s.bindHandlerByMap(m)
}
// 绑定控制器(RESTFul)控制器需要实现gmvc.Controller接口
// 方法会识别HTTP方法并做REST绑定处理例如Post方法会绑定到HTTP POST的方法请求处理Delete方法会绑定到HTTP DELETE的方法请求处理
// 因此只会绑定HTTP Method对应的方法其他方法不会自动注册绑定
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话
func (s *Server)BindControllerRest(pattern string, c Controller) error {
func (s *Server)BindControllerRest(pattern string, c Controller) {
// 遍历控制器获取方法列表并构造成uri
m := make(handlerMap)
v := reflect.ValueOf(c)
t := v.Type()
sname := t.Elem().Name()
pkgPath := t.Elem().PkgPath()
// 如果存在与HttpMethod对应名字的方法那么绑定这些方法
for i := 0; i < v.NumMethod(); i++ {
mname := t.Method(i).Name
method := strings.ToUpper(mname)
if _, ok := s.methodsMap[method]; !ok {
if _, ok := methodsMap[method]; !ok {
continue
}
if _, ok := v.Method(i).Interface().(func()); !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
glog.Errorfln(`invalid method definition "%s", while "func()" is required`, v.Method(i).Type().String())
return
}
pkgName := gfile.Basename(pkgPath)
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if ctlName[0] == '*' {
ctlName = fmt.Sprintf(`(%s)`, ctlName)
}
key := mname + ":" + pattern
key := s.mergeBuildInNameToPattern(mname + ":" + pattern, sname, mname, false)
m[key] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, mname),
rtype : gROUTE_REGISTER_CONTROLLER,
@ -153,5 +150,5 @@ func (s *Server)BindControllerRest(pattern string, c Controller) error {
faddr : nil,
}
}
return s.bindHandlerByMap(m)
s.bindHandlerByMap(m)
}

View File

@ -8,17 +8,17 @@
package ghttp
import (
"errors"
"strings"
"github.com/gogf/gf/g/text/gstr"
"bytes"
"runtime"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/text/gstr"
"reflect"
"runtime"
"strings"
)
// 注意该方法是直接绑定函数的内存地址,执行的时候直接执行该方法,不会存在初始化新的控制器逻辑
func (s *Server) BindHandler(pattern string, handler HandlerFunc) error {
return s.bindHandlerItem(pattern, &handlerItem {
func (s *Server) BindHandler(pattern string, handler HandlerFunc) {
s.bindHandlerItem(pattern, &handlerItem {
name : runtime.FuncForPC(reflect.ValueOf(handler).Pointer()).Name(),
rtype : gROUTE_REGISTER_HANDLER,
ctype : nil,
@ -30,26 +30,24 @@ func (s *Server) BindHandler(pattern string, handler HandlerFunc) error {
// 绑定URI到操作函数/方法
// pattern的格式形如/user/list, put:/user, delete:/user, post:/user@johng.cn
// 支持RESTful的请求格式具体业务逻辑由绑定的处理方法来执行
func (s *Server) bindHandlerItem(pattern string, item *handlerItem) error {
func (s *Server) bindHandlerItem(pattern string, item *handlerItem) {
if s.Status() == SERVER_STATUS_RUNNING {
return errors.New("server handlers cannot be changed while running")
glog.Error("server handlers cannot be changed while running")
return
}
return s.setHandler(pattern, item)
s.setHandler(pattern, item)
}
// 通过映射数组绑定URI到操作函数/方法
func (s *Server) bindHandlerByMap(m handlerMap) error {
func (s *Server) bindHandlerByMap(m handlerMap) {
for p, h := range m {
if err := s.bindHandlerItem(p, h); err != nil {
return err
}
s.bindHandlerItem(p, h)
}
return nil
}
// 将内置的名称按照设定的规则合并到pattern中内置名称按照{.xxx}规则命名。
// 规则1pattern中的URI包含{.struct}关键字,则替换该关键字为结构体名称;
// 规则1pattern中的URI包含{.method}关键字,则替换该关键字为方法名称;
// 规则2pattern中的URI包含{.method}关键字,则替换该关键字为方法名称;
// 规则2如果不满足规则1那么直接将防发明附加到pattern中的URI后面
func (s *Server) mergeBuildInNameToPattern(pattern string, structName, methodName string, allowAppend bool) string {
structName = s.nameToUrlPart(structName)

View File

@ -8,18 +8,18 @@
package ghttp
import (
"errors"
"github.com/gogf/gf/g/os/glog"
"strings"
"reflect"
"fmt"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/text/gstr"
"reflect"
"strings"
)
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面
// 第三个参数methods用以指定需要注册的方法支持多个方法名称多个方法以英文“,”号分隔,区分大小写
func (s *Server)BindObject(pattern string, obj interface{}, methods...string) error {
func (s *Server)BindObject(pattern string, obj interface{}, methods...string) {
methodMap := (map[string]bool)(nil)
if len(methods) > 0 {
methodMap = make(map[string]bool)
@ -51,11 +51,7 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
}
faddr, ok := v.Method(i).Interface().(func(*Request))
if !ok {
if methodMap != nil {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func(*Request))" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
}
glog.Errorfln(`invalid method definition "%s", while "func(*Request))" is required`, v.Method(i).Type().String())
continue
}
objName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
@ -72,16 +68,15 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
finit : finit,
fshut : fshut,
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
if strings.EqualFold(mname, "Index") {
p := key
if strings.EqualFold(p[len(p) - 6:], "/index") {
p = p[0 : len(p) - 6]
if len(p) == 0 {
p = "/"
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
// 注意当pattern带有内置变量时不会自动加该路由。
if strings.EqualFold(mname, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
p := gstr.PosR(key, "/index")
k := key[0 : p] + key[p + 6 : ]
if len(k) == 0 || k[0] == '@' {
k = "/" + k
}
m[p] = &handlerItem {
m[k] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, mname),
rtype : gROUTE_REGISTER_OBJECT,
ctype : nil,
@ -92,12 +87,12 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
}
}
}
return s.bindHandlerByMap(m)
s.bindHandlerByMap(m)
}
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面
// 第三个参数methods支持个方法注册,多个方法以英文“,”号分隔,区分大小写
func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string) error {
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面
// 第三个参数method支持个方法注册,不支持多个,并且区分大小写
func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string) {
m := make(handlerMap)
v := reflect.ValueOf(obj)
t := v.Type()
@ -105,13 +100,13 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string)
mname := strings.TrimSpace(method)
fval := v.MethodByName(mname)
if !fval.IsValid() {
return errors.New("invalid method name:" + mname)
glog.Error("invalid method name:" + mname)
return
}
faddr, ok := fval.Interface().(func(*Request))
if !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func(*Request)" is required`, fval.Type().String())
glog.Error(s)
return errors.New(s)
glog.Errorfln(`invalid method definition "%s", while "func(*Request)" is required`, fval.Type().String())
return
}
finit := (func(*Request))(nil)
fshut := (func(*Request))(nil)
@ -138,15 +133,16 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string)
fshut : fshut,
}
return s.bindHandlerByMap(m)
s.bindHandlerByMap(m)
}
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面
// 需要注意对象方法的定义必须按照ghttp.HandlerFunc来定义
func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
// 绑定对象到URI请求处理中会自动识别方法名称并附加到对应的URI地址后面,
// 需要注意对象方法的定义必须按照 ghttp.HandlerFunc 来定义
func (s *Server)BindObjectRest(pattern string, obj interface{}) {
m := make(handlerMap)
v := reflect.ValueOf(obj)
t := v.Type()
sname := t.Elem().Name()
finit := (func(*Request))(nil)
fshut := (func(*Request))(nil)
if v.MethodByName("Init").IsValid() {
@ -159,21 +155,20 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
for i := 0; i < v.NumMethod(); i++ {
mname := t.Method(i).Name
method := strings.ToUpper(mname)
if _, ok := s.methodsMap[method]; !ok {
if _, ok := methodsMap[method]; !ok {
continue
}
faddr, ok := v.Method(i).Interface().(func(*Request))
if !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
glog.Errorfln(`invalid method definition "%s", while "func(*Request)" is required`, v.Method(i).Type().String())
continue
}
pkgName := gfile.Basename(pkgPath)
objName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if objName[0] == '*' {
objName = fmt.Sprintf(`(%s)`, objName)
}
key := mname + ":" + pattern
key := s.mergeBuildInNameToPattern(mname + ":" + pattern, sname, mname, false)
m[key] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, mname),
rtype : gROUTE_REGISTER_OBJECT,
@ -184,5 +179,5 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
fshut : fshut,
}
}
return s.bindHandlerByMap(m)
s.bindHandlerByMap(m)
}

View File

@ -26,9 +26,9 @@ type Session struct {
request *Request // 关联的请求
}
// 生成一个唯一的SessionId字符串长度16位
// 生成一个唯一的SessionId字符串长度18位。
func makeSessionId() string {
return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 32) + grand.RandStr(3))
return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 36) + grand.RandStr(6))
}
// 获取或者生成一个session对象(延迟初始化)
@ -41,18 +41,28 @@ func GetSession(r *Request) *Session {
}
}
// 执行初始化(用于延迟初始化)
// 执行初始化(用于延迟初始化).
func (s *Session) init() {
if len(s.id) == 0 {
s.id = s.request.Cookie.SessionId()
s.server = s.request.Server
s.data = s.server.sessions.GetOrSetFuncLock(s.id, func() interface{} {
return gmap.NewStringInterfaceMap()
}, s.server.GetSessionMaxAge()).(*gmap.StringInterfaceMap)
// 根据提交的SESSION ID获取已存在SESSION
id := s.request.Cookie.GetSessionId()
if id != "" {
data := s.server.sessions.Get(id)
if data != nil {
s.id = id
s.data = data.(*gmap.StringInterfaceMap)
return
}
}
// 否则执行初始化创建
s.id = s.request.Cookie.MakeSessionId()
s.data = gmap.NewStringInterfaceMap()
s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge())
}
}
// 获取SessionId
// 获取/创建SessionId
func (s *Session) Id() string {
s.init()
return s.id
@ -60,8 +70,11 @@ func (s *Session) Id() string {
// 获取当前session所有数据
func (s *Session) Data() map[string]interface{} {
s.init()
return s.data.Map()
if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" {
s.init()
return s.data.Map()
}
return nil
}
// 设置session
@ -84,70 +97,141 @@ func (s *Session) BatchSet(m map[string]interface{}) {
// 判断键名是否存在
func (s *Session) Contains (key string) bool {
s.init()
return s.data.Contains(key)
if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" {
s.init()
return s.data.Contains(key)
}
return false
}
// 获取SESSION
func (s *Session) Get (key string) interface{} {
s.init()
return s.data.Get(key)
// 获取SESSION变量
func (s *Session) Get(key string) interface{} {
if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" {
s.init()
return s.data.Get(key)
}
return nil
}
// 获取SESSION建议都用该方法获取参数
func (s *Session) GetVar(key string) gvar.VarRead {
s.init()
return gvar.NewRead(s.data.Get(key), true)
return gvar.NewRead(s.Get(key), true)
}
// 删除session
func (s *Session) Remove(key string) {
if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" {
s.init()
s.data.Remove(key)
}
}
func (s *Session) GetString (key string) string { return gconv.String(s.Get(key)) }
func (s *Session) GetBool(key string) bool { return gconv.Bool(s.Get(key)) }
// 清空session
func (s *Session) Clear() {
if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" {
s.init()
s.data.Clear()
}
}
func (s *Session) GetInt(key string) int { return gconv.Int(s.Get(key)) }
func (s *Session) GetInt8(key string) int8 { return gconv.Int8(s.Get(key)) }
func (s *Session) GetInt16(key string) int16 { return gconv.Int16(s.Get(key)) }
func (s *Session) GetInt32(key string) int32 { return gconv.Int32(s.Get(key)) }
func (s *Session) GetInt64(key string) int64 { return gconv.Int64(s.Get(key)) }
// 更新过期时间(如果用在守护进程中长期使用,需要手动调用进行更新,防止超时被清除)
func (s *Session) UpdateExpire() {
if len(s.id) > 0 && s.data.Size() > 0 {
s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000)
}
}
func (s *Session) GetUint(key string) uint { return gconv.Uint(s.Get(key)) }
func (s *Session) GetUint8(key string) uint8 { return gconv.Uint8(s.Get(key)) }
func (s *Session) GetUint16(key string) uint16 { return gconv.Uint16(s.Get(key)) }
func (s *Session) GetUint32(key string) uint32 { return gconv.Uint32(s.Get(key)) }
func (s *Session) GetUint64(key string) uint64 { return gconv.Uint64(s.Get(key)) }
func (s *Session) GetString(key string) string {
return gconv.String(s.Get(key))
}
func (s *Session) GetFloat32 (key string) float32 { return gconv.Float32(s.Get(key)) }
func (s *Session) GetFloat64 (key string) float64 { return gconv.Float64(s.Get(key)) }
func (s *Session) GetBool(key string) bool {
return gconv.Bool(s.Get(key))
}
func (s *Session) GetBytes(key string) []byte { return gconv.Bytes(s.Get(key)) }
func (s *Session) GetInts(key string) []int { return gconv.Ints(s.Get(key)) }
func (s *Session) GetFloats(key string) []float64 { return gconv.Floats(s.Get(key)) }
func (s *Session) GetStrings(key string) []string { return gconv.Strings(s.Get(key)) }
func (s *Session) GetInterfaces(key string) []interface{} { return gconv.Interfaces(s.Get(key)) }
func (s *Session) GetInt(key string) int {
return gconv.Int(s.Get(key)) }
func (s *Session) GetTime(key string, format...string) time.Time { return gconv.Time(s.Get(key), format...) }
func (s *Session) GetTimeDuration(key string) time.Duration { return gconv.TimeDuration(s.Get(key)) }
func (s *Session) GetInt8(key string) int8 {
return gconv.Int8(s.Get(key))
}
func (s *Session) GetInt16(key string) int16 {
return gconv.Int16(s.Get(key))
}
func (s *Session) GetInt32(key string) int32 {
return gconv.Int32(s.Get(key))
}
func (s *Session) GetInt64(key string) int64 {
return gconv.Int64(s.Get(key))
}
func (s *Session) GetUint(key string) uint {
return gconv.Uint(s.Get(key))
}
func (s *Session) GetUint8(key string) uint8 {
return gconv.Uint8(s.Get(key))
}
func (s *Session) GetUint16(key string) uint16 {
return gconv.Uint16(s.Get(key))
}
func (s *Session) GetUint32(key string) uint32 {
return gconv.Uint32(s.Get(key))
}
func (s *Session) GetUint64(key string) uint64 {
return gconv.Uint64(s.Get(key))
}
func (s *Session) GetFloat32(key string) float32 {
return gconv.Float32(s.Get(key))
}
func (s *Session) GetFloat64(key string) float64 {
return gconv.Float64(s.Get(key))
}
func (s *Session) GetBytes(key string) []byte {
return gconv.Bytes(s.Get(key))
}
func (s *Session) GetInts(key string) []int {
return gconv.Ints(s.Get(key))
}
func (s *Session) GetFloats(key string) []float64 {
return gconv.Floats(s.Get(key))
}
func (s *Session) GetStrings(key string) []string {
return gconv.Strings(s.Get(key))
}
func (s *Session) GetInterfaces(key string) []interface{} {
return gconv.Interfaces(s.Get(key))
}
func (s *Session) GetTime(key string, format...string) time.Time {
return gconv.Time(s.Get(key), format...)
}
func (s *Session) GetGTime(key string, format...string) *gtime.Time {
return gconv.GTime(s.Get(key), format...)
}
func (s *Session) GetTimeDuration(key string) time.Duration {
return gconv.TimeDuration(s.Get(key))
}
// 将变量转换为对象,注意 objPointer 参数必须为struct指针
func (s *Session) GetStruct(key string, objPointer interface{}, attrMapping...map[string]string) error {
return gconv.Struct(s.Get(key), objPointer, attrMapping...)
}
// 删除session
func (s *Session) Remove(key string) {
s.init()
s.data.Remove(key)
}
// 清空session
func (s *Session) Clear() {
s.init()
s.data.Clear()
}
// 更新过期时间(如果用在守护进程中长期使用,需要手动调用进行更新,防止超时被清除)
func (s *Session) UpdateExpire() {
if len(s.id) > 0 {
s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000)
}
}

View File

@ -1,137 +0,0 @@
// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
// 分组路由测试
package ghttp_test
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/frame/gmvc"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
// 执行对象
type Object struct {}
func (o *Object) Show(r *ghttp.Request) {
r.Response.Write("Object Show")
}
func (o *Object) Delete(r *ghttp.Request) {
r.Response.Write("Object REST Delete")
}
// 控制器
type Controller struct {
gmvc.Controller
}
func (c *Controller) Show() {
c.Response.Write("Controller Show")
}
func (c *Controller) Post() {
c.Response.Write("Controller REST Post")
}
func Handler(r *ghttp.Request) {
r.Response.Write("Handler")
}
func Test_Router_Group1(t *testing.T) {
s := g.Server(gtime.Nanosecond())
obj := new(Object)
ctl := new(Controller)
// 分组路由方法注册
g := s.Group("/api")
g.ALL ("/handler", Handler)
g.ALL ("/ctl", ctl)
g.GET ("/ctl/my-show", ctl, "Show")
g.REST("/ctl/rest", ctl)
g.ALL ("/obj", obj)
g.GET ("/obj/my-show", obj, "Show")
g.REST("/obj/rest", obj)
s.SetPort(8200)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8200")
gtest.Assert(client.GetContent ("/api/handler"), "Handler")
gtest.Assert(client.GetContent ("/api/ctl/my-show"), "Controller Show")
gtest.Assert(client.GetContent ("/api/ctl/post"), "Controller REST Post")
gtest.Assert(client.GetContent ("/api/ctl/show"), "Controller Show")
gtest.Assert(client.PostContent("/api/ctl/rest"), "Controller REST Post")
gtest.Assert(client.GetContent ("/api/obj/delete"), "Object REST Delete")
gtest.Assert(client.GetContent ("/api/obj/my-show"), "Object Show")
gtest.Assert(client.GetContent ("/api/obj/show"), "Object Show")
gtest.Assert(client.DeleteContent("/api/obj/rest"), "Object REST Delete")
// 测试404
resp, err := client.Get("/ThisDoesNotExist")
defer resp.Close()
gtest.Assert(err, nil)
gtest.Assert(resp.StatusCode, 404)
})
}
func Test_Router_Group2(t *testing.T) {
s := g.Server(gtime.Nanosecond())
obj := new(Object)
ctl := new(Controller)
// 分组路由批量注册
s.Group("/api").Bind("/api", []ghttp.GroupItem{
{"ALL", "/handler", Handler},
{"ALL", "/ctl", ctl},
{"GET", "/ctl/my-show", ctl, "Show"},
{"REST", "/ctl/rest", ctl},
{"ALL", "/obj", obj},
{"GET", "/obj/my-show", obj, "Show"},
{"REST", "/obj/rest", obj},
})
s.SetPort(8300)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8300")
gtest.Assert(client.GetContent ("/api/handler"), "Handler")
gtest.Assert(client.GetContent ("/api/ctl/my-show"), "Controller Show")
gtest.Assert(client.GetContent ("/api/ctl/post"), "Controller REST Post")
gtest.Assert(client.GetContent ("/api/ctl/show"), "Controller Show")
gtest.Assert(client.PostContent("/api/ctl/rest"), "Controller REST Post")
gtest.Assert(client.GetContent ("/api/obj/delete"), "Object REST Delete")
gtest.Assert(client.GetContent ("/api/obj/my-show"), "Object Show")
gtest.Assert(client.GetContent ("/api/obj/show"), "Object Show")
gtest.Assert(client.DeleteContent("/api/obj/rest"), "Object REST Delete")
// 测试404
resp, err := client.Get("/ThisDoesNotExist")
defer resp.Close()
gtest.Assert(err, nil)
gtest.Assert(resp.StatusCode, 404)
})
}

View File

@ -0,0 +1,61 @@
// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
// COOKIE测试
package ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Cookie(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/set", func(r *ghttp.Request){
r.Cookie.Set(r.Get("k"), r.Get("v"))
})
s.BindHandler("/get", func(r *ghttp.Request){
//fmt.Println(r.Cookie.Map())
r.Response.Write(r.Cookie.Get(r.Get("k")))
})
s.BindHandler("/remove", func(r *ghttp.Request){
r.Cookie.Remove(r.Get("k"))
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetBrowserMode(true)
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
r1, e1 := client.Get("/set?k=key1&v=100")
if r1 != nil {
defer r1.Close()
}
gtest.Assert(e1, nil)
gtest.Assert(r1.ReadAllString(), "")
gtest.Assert(client.GetContent("/set?k=key2&v=200"), "")
gtest.Assert(client.GetContent("/get?k=key1"), "100")
gtest.Assert(client.GetContent("/get?k=key2"), "200")
gtest.Assert(client.GetContent("/get?k=key3"), "")
gtest.Assert(client.GetContent("/remove?k=key1"), "")
gtest.Assert(client.GetContent("/remove?k=key3"), "")
gtest.Assert(client.GetContent("/remove?k=key4"), "")
gtest.Assert(client.GetContent("/get?k=key1"), "")
gtest.Assert(client.GetContent("/get?k=key2"), "200")
})
}

View File

@ -0,0 +1,25 @@
// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
// 测试初始化
package ghttp_test
import (
"github.com/gogf/gf/g/container/garray"
)
var (
// 用于测试的端口数组,随机获取
ports = garray.NewIntArray()
)
func init() {
for i := 8000; i <= 9000; i++ {
ports.Append(i)
}
}

View File

@ -0,0 +1,79 @@
// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package ghttp_test
import (
"encoding/json"
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Params_Json(t *testing.T) {
type User struct {
Uid int
Name string
SiteUrl string `gconv:"-"`
NickName string `gconv:"nickname, omitempty"`
Pass1 string `gconv:"password1"`
Pass2 string `gconv:"password2"`
}
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/json1", func(r *ghttp.Request){
r.Response.WriteJson(User{
Uid : 100,
Name : "john",
SiteUrl : "https://goframe.org",
Pass1 : "123",
Pass2 : "456",
})
})
s.BindHandler("/json2", func(r *ghttp.Request){
r.Response.WriteJson(&User{
Uid : 100,
Name : "john",
SiteUrl : "https://goframe.org",
Pass1 : "123",
Pass2 : "456",
})
})
s.SetPort(p)
s.SetDumpRouteMap(false)
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
map1 := make(map[string]interface{})
err1 := json.Unmarshal([]byte(client.GetContent("/json1")), &map1)
gtest.Assert(err1, nil)
gtest.Assert(len(map1), 4)
gtest.Assert(map1["Name"], "john")
gtest.Assert(map1["Uid"], 100)
gtest.Assert(map1["password1"], "123")
gtest.Assert(map1["password2"], "456")
map2 := make(map[string]interface{})
err2 := json.Unmarshal([]byte(client.GetContent("/json1")), &map2)
gtest.Assert(err2, nil)
gtest.Assert(len(map2), 4)
gtest.Assert(map2["Name"], "john")
gtest.Assert(map2["Uid"], 100)
gtest.Assert(map2["password1"], "123")
gtest.Assert(map2["password2"], "456")
})
}

View File

@ -8,22 +8,23 @@
package ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Params(t *testing.T) {
func Test_Params_Basic(t *testing.T) {
type User struct {
Id int
Name string
Pass1 string `params:"password1"`
Pass2 string `params:"password2"`
}
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/get", func(r *ghttp.Request){
if r.GetQuery("slice") != nil {
r.Response.Write(r.GetQuery("slice"))
@ -96,18 +97,16 @@ func Test_Params(t *testing.T) {
r.Response.Write(user.Id, user.Name, user.Pass1, user.Pass2)
}
})
s.SetPort(8400)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8400")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
// GET
gtest.Assert(client.GetContent("/get", "slice=1&slice=2"), `["1","2"]`)
gtest.Assert(client.GetContent("/get", "bool=1"), `true`)

View File

@ -8,18 +8,18 @@
package ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
// 基本路由功能测试
func Test_Router_Basic(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/:name", func(r *ghttp.Request){
r.Response.Write("/:name")
})
@ -35,19 +35,16 @@ func Test_Router_Basic(t *testing.T) {
s.BindHandler("/user/list/{field}.html", func(r *ghttp.Request){
r.Response.Write(r.Get("field"))
})
s.SetPort(8100)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8100")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/john"), "")
gtest.Assert(client.GetContent("/john/update"), "john")
gtest.Assert(client.GetContent("/john/edit"), "edit")
@ -57,25 +54,24 @@ func Test_Router_Basic(t *testing.T) {
// 测试HTTP Method注册.
func Test_Router_Method(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("GET:/get", func(r *ghttp.Request){
})
s.BindHandler("POST:/post", func(r *ghttp.Request){
})
s.SetPort(8105)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8105")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
resp1, err := client.Get("/get")
defer resp1.Close()
@ -101,7 +97,8 @@ func Test_Router_Method(t *testing.T) {
// 测试状态返回.
func Test_Router_Status(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/200", func(r *ghttp.Request){
r.Response.WriteStatus(200)
})
@ -114,18 +111,16 @@ func Test_Router_Status(t *testing.T) {
s.BindHandler("/500", func(r *ghttp.Request){
r.Response.WriteStatus(500)
})
s.SetPort(8110)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8110")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
resp1, err := client.Get("/200")
defer resp1.Close()
@ -151,25 +146,24 @@ func Test_Router_Status(t *testing.T) {
// 自定义状态码处理.
func Test_Router_CustomStatusHandler(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Write("hello")
})
s.BindStatusHandler(404, func(r *ghttp.Request){
r.Response.Write("404 page")
})
s.SetPort(8120)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8120")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "hello")
resp, err := client.Get("/ThisDoesNotExist")
@ -182,22 +176,21 @@ func Test_Router_CustomStatusHandler(t *testing.T) {
// 测试不存在的路由.
func Test_Router_404(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Write("hello")
})
s.SetPort(8130)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8130")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "hello")
resp, err := client.Get("/ThisDoesNotExist")

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