Compare commits

..

41 Commits

Author SHA1 Message Date
6ab0a77364 add more defaulr searching paths for g.Config() 2019-03-13 22:12:59 +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
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
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
83 changed files with 3885 additions and 655 deletions

View File

@ -15,18 +15,21 @@ env:
services:
- mysql
addons:
hosts:
- local
before_install:
- 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
after_success:
- 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
```

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等特性。

View File

@ -55,6 +55,10 @@
1. gfcache依旧使用gcache作为缓存控制对象不要使用gmap
1. 增加对ghttp路由注册的{.struct}/{.method}单元测试;
1. 更新跨域请求CORS相关功能文档
1. ghttp的热重启的本地进程端口监听在不使用该特性时默认关闭掉
1. gcfg包目前允许添加重复的目录路径需要在SetPath/AddPath时判断重复性不能添加重复的路径

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个数据项移出数组并返回该数据项。
@ -425,17 +457,22 @@ func (a *IntArray) RLockFunc(f func(array []int)) *IntArray {
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)的数据项移出数组,并返回该数据项。
@ -408,17 +449,22 @@ func (a *Array) RLockFunc(f func(array []interface{})) *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个数据项移出数组并返回该数据项
@ -423,17 +456,22 @@ func (a *StringArray) RLockFunc(f func(array []string)) *StringArray {
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个数据项移出数组并返回该数据项
@ -399,18 +431,22 @@ func (a *SortedIntArray) RLockFunc(f func(array []int)) *SortedIntArray {
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个数据项移出数组并返回该数据项
@ -406,20 +438,22 @@ func (a *SortedArray) RLockFunc(f func(array []interface{})) *SortedArray {
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个数据项移出数组并返回该数据项
@ -393,18 +426,22 @@ func (a *SortedStringArray) RLockFunc(f func(array []string)) *SortedStringArray
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

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

@ -47,7 +47,9 @@ type DB interface {
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)

View File

@ -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
}
// 数据库查询,获取查询字段值

View File

@ -13,6 +13,7 @@ import (
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gtime"
"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"
@ -30,28 +31,61 @@ func formatCondition(where interface{}, args []interface{}) (newWhere string, ne
rv = rv.Elem()
kind = rv.Kind()
}
tmpArgs := []interface{}(nil)
switch kind {
// 注意当where为map/struct类型args参数必须为空。
// map/struct类型
case reflect.Map: fallthrough
case reflect.Struct:
for k, v := range gconv.Map(where) {
for key, value := range gconv.Map(where) {
if buffer.Len() > 0 {
buffer.WriteString(" AND ")
}
buffer.WriteString(k + "=?")
newArgs = append(newArgs, v)
// 支持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)
}
}
}
newWhere = buffer.String()
default:
buffer.WriteString(gconv.String(where))
}
if buffer.Len() == 0 {
buffer.WriteString("1=1")
}
// 查询条件参数处理主要处理slice参数类型
newWhere = buffer.String()
if len(args) > 0 {
for index, arg := range args {
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 {
@ -66,6 +100,7 @@ func formatCondition(where interface{}, args []interface{}) (newWhere string, ne
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++
@ -75,6 +110,14 @@ func formatCondition(where interface{}, args []interface{}) (newWhere string, ne
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)
}
}

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]
@ -203,7 +239,7 @@ func (md *Model) Cache(time int, name ... string) *Model {
// 链式操作操作数据项参数data类型支持 string/map/slice/struct/*struct ,
// 也可以是key,value,key,value,...。
func (md *Model) Data(data ...interface{}) *Model {
model := md.Clone()
model := md.getModel()
if len(data) > 1 {
m := make(map[string]interface{})
for i := 0; i < len(data); i += 2 {
@ -438,13 +474,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可以为空也可以自定义查询字段
@ -532,14 +599,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 (

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

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

@ -266,19 +266,159 @@ func TestDbBase_GetCount(t *testing.T) {
}
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")
}
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) {

View File

@ -121,7 +121,7 @@ func TestModel_Replace(t *testing.T) {
"passport" : "t11",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T11",
"create_time" : gtime.Now().String(),
"create_time" : "2018-10-10 00:01:10",
}).Replace()
if err != nil {
gtest.Fatal(err)
@ -136,7 +136,7 @@ func TestModel_Save(t *testing.T) {
"passport" : "t111",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T111",
"create_time" : gtime.Now().String(),
"create_time" : "2018-10-10 00:01:10",
}).Save()
if err != nil {
gtest.Fatal(err)
@ -168,13 +168,44 @@ func TestModel_Clone(t *testing.T) {
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(count, 2)
gtest.Assert(record["id"].Int(), 3)
gtest.Assert(len(result), 2)
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 {
@ -222,19 +253,164 @@ func TestModel_Select(t *testing.T) {
}
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")
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) {
@ -255,19 +431,56 @@ func TestModel_GroupBy(t *testing.T) {
gtest.Assert(result[0]["nickname"].String(), "T111")
}
// where string
func TestModel_WhereString(t *testing.T) {
func TestModel_Where(t *testing.T) {
// string
gtest.Case(t, func() {
result, err := db.Table("user").Where("id=? and nickname=?", 3, "T3").One()
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)
})
}
// where map
func TestModel_WhereMap(t *testing.T) {
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 {
@ -275,10 +488,43 @@ func TestModel_WhereMap(t *testing.T) {
}
gtest.Assert(result["id"].Int(), 3)
})
}
// where struct
func TestModel_WhereStruct(t *testing.T) {
// 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"`
@ -296,27 +542,53 @@ func TestModel_WhereStruct(t *testing.T) {
}
gtest.Assert(result["id"].Int(), 3)
})
}
// where slice
func TestModel_WhereSlice1(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)
}
// where slice
func TestModel_WhereSlice2(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)
// 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) {

View File

@ -267,6 +267,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 +354,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

@ -94,11 +94,36 @@ 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)
// 添加工作目录下的config目录
if path := envPath + gfile.Separator + "config"; gfile.Exists(path) {
config.AddPath(path)
}
// 自定义的环境变量/启动参数路径,优先级最高,覆盖默认的工作目录
if envPath != "" && gfile.Exists(envPath) {
config.SetPath(envPath)
if path := envPath + gfile.Separator + "config"; gfile.Exists(path) {
config.AddPath(path)
}
}
// 二进制文件执行目录
if selfPath != "" && gfile.Exists(selfPath) {
config.AddPath(selfPath)
if path := selfPath + gfile.Separator + "config"; gfile.Exists(path) {
config.AddPath(path)
}
}
// 开发环境源码main包目录
if mainPath != "" && gfile.Exists(mainPath) {
config.AddPath(mainPath)
if path := mainPath + gfile.Separator + "config"; gfile.Exists(path) {
config.AddPath(path)
}
}
return config
}).(*gcfg.Config)

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

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

View File

@ -36,7 +36,10 @@ func IsEmpty(value interface{}) bool {
case []byte: return len(value) == 0
default:
// 最后通过反射来判断
rv := reflect.ValueOf(value)
rv := reflect.ValueOf(value)
if rv.IsNil() {
return true
}
kind := rv.Kind()
switch kind {
case reflect.Map: fallthrough

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

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

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

@ -147,7 +147,7 @@ var (
// 用于服务进程初始化,只能初始化一次,采用“懒初始化”(在server运行时才初始化)
serverProcessInited = gtype.NewBool()
// 是否开启WebServer平滑重启特性, 会开启额外的本地端口监听,用于进程管理通信
// 是否开启WebServer平滑重启特性, 会开启额外的本地端口监听,用于进程管理通信(默认开启)
gracefulEnabled = true
)

View File

@ -24,7 +24,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"
)

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

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

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

@ -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)
return
}
if len(uri) == 0 || uri[0] != '/' {
glog.Error("invalid pattern:", pattern)
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 in %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,20 +8,19 @@
package ghttp
import (
"errors"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/text/gregex"
"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)
@ -45,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), "")
@ -71,8 +66,8 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) e
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 = "/"
if len(k) == 0 || k[0] == '@' {
k = "/" + k
}
m[k] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, mname),
@ -83,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()
@ -95,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)
@ -116,14 +111,14 @@ 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)
@ -138,9 +133,8 @@ func (s *Server)BindControllerRest(pattern string, c Controller) error {
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), "")
@ -156,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,21 +30,19 @@ 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}规则命名。

View File

@ -8,19 +8,18 @@
package ghttp
import (
"errors"
"fmt"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/text/gregex"
"strings"
"reflect"
"fmt"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/os/gfile"
"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)
@ -52,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), "")
@ -78,8 +73,8 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
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 = "/"
if len(k) == 0 || k[0] == '@' {
k = "/" + k
}
m[k] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, mname),
@ -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,12 +133,12 @@ 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()
@ -165,9 +160,8 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
}
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), "")
@ -185,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,14 +41,24 @@ 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())
}
}

View File

@ -17,7 +17,7 @@ var (
)
func init() {
for i := 8000; i <= 8100; i++ {
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

@ -38,9 +38,11 @@ func (c *Controller) Show() {
c.Response.Write("Controller Show")
}
func (c *Controller) Info() {
c.Response.Write("Controller Info")
}
// 控制器注册测试
func Test_Router_Controller(t *testing.T) {
func Test_Router_Controller1(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindController("/", new(Controller))
@ -71,3 +73,58 @@ func Test_Router_Controller(t *testing.T) {
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_Controller2(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindController("/controller", new(Controller), "Show, Info")
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/controller/info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_ControllerMethod(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindControllerMethod("/controller-info", new(Controller), "Info")
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "Not Found")
gtest.Assert(client.GetContent("/controller/info"), "Not Found")
gtest.Assert(client.GetContent("/controller-info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,338 @@
// 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 (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
// 基本路由功能测试
func Test_Router_DomainBasic(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindHandler("/:name", func(r *ghttp.Request){
r.Response.Write("/:name")
})
d.BindHandler("/:name/update", func(r *ghttp.Request){
r.Response.Write(r.Get("name"))
})
d.BindHandler("/:name/:action", func(r *ghttp.Request){
r.Response.Write(r.Get("action"))
})
d.BindHandler("/:name/*any", func(r *ghttp.Request){
r.Response.Write(r.Get("any"))
})
d.BindHandler("/user/list/{field}.html", func(r *ghttp.Request){
r.Response.Write(r.Get("field"))
})
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))
gtest.Assert(client.GetContent("/john"), "Not Found")
gtest.Assert(client.GetContent("/john/update"), "Not Found")
gtest.Assert(client.GetContent("/john/edit"), "Not Found")
gtest.Assert(client.GetContent("/user/list/100.html"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/john"), "")
gtest.Assert(client.GetContent("/john/update"), "john")
gtest.Assert(client.GetContent("/john/edit"), "edit")
gtest.Assert(client.GetContent("/user/list/100.html"), "100")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/john"), "")
gtest.Assert(client.GetContent("/john/update"), "john")
gtest.Assert(client.GetContent("/john/edit"), "edit")
gtest.Assert(client.GetContent("/user/list/100.html"), "100")
})
}
// 测试HTTP Method注册.
func Test_Router_DomainMethod(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindHandler("GET:/get", func(r *ghttp.Request){
})
d.BindHandler("POST:/post", func(r *ghttp.Request){
})
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))
resp1, err := client.Get("/get")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 404)
resp2, err := client.Post("/get")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 404)
resp3, err := client.Get("/post")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 404)
resp4, err := client.Post("/post")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 404)
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
resp1, err := client.Get("/get")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 200)
resp2, err := client.Post("/get")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 404)
resp3, err := client.Get("/post")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 404)
resp4, err := client.Post("/post")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 200)
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
resp1, err := client.Get("/get")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 200)
resp2, err := client.Post("/get")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 404)
resp3, err := client.Get("/post")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 404)
resp4, err := client.Post("/post")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 200)
})
}
// 测试状态返回.
func Test_Router_DomainStatus(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindHandler("/200", func(r *ghttp.Request){
r.Response.WriteStatus(200)
})
d.BindHandler("/300", func(r *ghttp.Request){
r.Response.WriteStatus(300)
})
d.BindHandler("/400", func(r *ghttp.Request){
r.Response.WriteStatus(400)
})
d.BindHandler("/500", func(r *ghttp.Request){
r.Response.WriteStatus(500)
})
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))
resp1, err := client.Get("/200")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 404)
resp2, err := client.Get("/300")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 404)
resp3, err := client.Get("/400")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 404)
resp4, err := client.Get("/500")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 404)
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
resp1, err := client.Get("/200")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 200)
resp2, err := client.Get("/300")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 300)
resp3, err := client.Get("/400")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 400)
resp4, err := client.Get("/500")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 500)
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
resp1, err := client.Get("/200")
defer resp1.Close()
gtest.Assert(err, nil)
gtest.Assert(resp1.StatusCode, 200)
resp2, err := client.Get("/300")
defer resp2.Close()
gtest.Assert(err, nil)
gtest.Assert(resp2.StatusCode, 300)
resp3, err := client.Get("/400")
defer resp3.Close()
gtest.Assert(err, nil)
gtest.Assert(resp3.StatusCode, 400)
resp4, err := client.Get("/500")
defer resp4.Close()
gtest.Assert(err, nil)
gtest.Assert(resp4.StatusCode, 500)
})
}
// 自定义状态码处理.
func Test_Router_DomainCustomStatusHandler(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindHandler("/", func(r *ghttp.Request){
r.Response.Write("hello")
})
d.BindStatusHandler(404, func(r *ghttp.Request){
r.Response.Write("404 page")
})
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/ThisDoesNotExist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "hello")
gtest.Assert(client.GetContent("/ThisDoesNotExist"), "404 page")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "hello")
gtest.Assert(client.GetContent("/ThisDoesNotExist"), "404 page")
})
}
// 测试不存在的路由.
func Test_Router_Domain404(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindHandler("/", func(r *ghttp.Request){
r.Response.Write("hello")
})
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))
gtest.Assert(client.GetContent("/"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "hello")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "hello")
})
}

View File

@ -0,0 +1,127 @@
// 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 (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/frame/gmvc"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type DomainControllerRest struct {
gmvc.Controller
}
func (c *DomainControllerRest) Init(r *ghttp.Request) {
c.Controller.Init(r)
c.Response.Write("1")
}
func (c *DomainControllerRest) Shut() {
c.Response.Write("2")
}
func (c *DomainControllerRest) Get() {
c.Response.Write("Controller Get")
}
func (c *DomainControllerRest) Put() {
c.Response.Write("Controller Put")
}
func (c *DomainControllerRest) Post() {
c.Response.Write("Controller Post")
}
func (c *DomainControllerRest) Delete() {
c.Response.Write("Controller Delete")
}
func (c *DomainControllerRest) Patch() {
c.Response.Write("Controller Patch")
}
func (c *DomainControllerRest) Options() {
c.Response.Write("Controller Options")
}
func (c *DomainControllerRest) Head() {
c.Response.Header().Set("head-ok", "1")
}
// 控制器注册测试
func Test_Router_DomainControllerRest(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindControllerRest("/", new(DomainControllerRest))
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.PutContent("/"), "Not Found")
gtest.Assert(client.PostContent("/"), "Not Found")
gtest.Assert(client.DeleteContent("/"), "Not Found")
gtest.Assert(client.PatchContent("/"), "Not Found")
gtest.Assert(client.OptionsContent("/"), "Not Found")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "1Controller Get2")
gtest.Assert(client.PutContent("/"), "1Controller Put2")
gtest.Assert(client.PostContent("/"), "1Controller Post2")
gtest.Assert(client.DeleteContent("/"), "1Controller Delete2")
gtest.Assert(client.PatchContent("/"), "1Controller Patch2")
gtest.Assert(client.OptionsContent("/"), "1Controller Options2")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "1Controller Get2")
gtest.Assert(client.PutContent("/"), "1Controller Put2")
gtest.Assert(client.PostContent("/"), "1Controller Post2")
gtest.Assert(client.DeleteContent("/"), "1Controller Delete2")
gtest.Assert(client.PatchContent("/"), "1Controller Patch2")
gtest.Assert(client.OptionsContent("/"), "1Controller Options2")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,202 @@
// 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 (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/frame/gmvc"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type DomainController struct {
gmvc.Controller
}
func (c *DomainController) Init(r *ghttp.Request) {
c.Controller.Init(r)
c.Response.Write("1")
}
func (c *DomainController) Shut() {
c.Response.Write("2")
}
func (c *DomainController) Index() {
c.Response.Write("Controller Index")
}
func (c *DomainController) Show() {
c.Response.Write("Controller Show")
}
func (c *DomainController) Info() {
c.Response.Write("Controller Info")
}
func Test_Router_DomainController1(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Domain("localhost, local").BindController("/", new(DomainController))
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "Not Found")
gtest.Assert(client.GetContent("/show"), "Not Found")
gtest.Assert(client.GetContent("/info"), "Not Found")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "1Controller Index2")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "1Controller Index2")
gtest.Assert(client.GetContent("/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "1Controller Index2")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "1Controller Index2")
gtest.Assert(client.GetContent("/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_DomainController2(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Domain("localhost, local").BindController("/controller", new(DomainController), "Show, Info")
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "Not Found")
gtest.Assert(client.GetContent("/controller/info"), "Not Found")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/controller/info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/controller/info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_DomainControllerMethod(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Domain("localhost, local").BindControllerMethod("/controller-info", new(DomainController), "Info")
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "Not Found")
gtest.Assert(client.GetContent("/controller/info"), "Not Found")
gtest.Assert(client.GetContent("/controller-info"), "Not Found")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "Not Found")
gtest.Assert(client.GetContent("/controller/info"), "Not Found")
gtest.Assert(client.GetContent("/controller-info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/controller"), "Not Found")
gtest.Assert(client.GetContent("/controller/init"), "Not Found")
gtest.Assert(client.GetContent("/controller/shut"), "Not Found")
gtest.Assert(client.GetContent("/controller/index"), "Not Found")
gtest.Assert(client.GetContent("/controller/show"), "Not Found")
gtest.Assert(client.GetContent("/controller/info"), "Not Found")
gtest.Assert(client.GetContent("/controller-info"), "1Controller Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,122 @@
// 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 (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type DomainObjectRest struct {}
func (o *DomainObjectRest) Init(r *ghttp.Request) {
r.Response.Write("1")
}
func (o *DomainObjectRest) Shut(r *ghttp.Request) {
r.Response.Write("2")
}
func (o *DomainObjectRest) Get(r *ghttp.Request) {
r.Response.Write("Object Get")
}
func (o *DomainObjectRest) Put(r *ghttp.Request) {
r.Response.Write("Object Put")
}
func (o *DomainObjectRest) Post(r *ghttp.Request) {
r.Response.Write("Object Post")
}
func (o *DomainObjectRest) Delete(r *ghttp.Request) {
r.Response.Write("Object Delete")
}
func (o *DomainObjectRest) Patch(r *ghttp.Request) {
r.Response.Write("Object Patch")
}
func (o *DomainObjectRest) Options(r *ghttp.Request) {
r.Response.Write("Object Options")
}
func (o *DomainObjectRest) Head(r *ghttp.Request) {
r.Response.Header().Set("head-ok", "1")
}
func Test_Router_DomainObjectRest(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
d := s.Domain("localhost, local")
d.BindObjectRest("/", new(DomainObjectRest))
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.PutContent("/"), "Not Found")
gtest.Assert(client.PostContent("/"), "Not Found")
gtest.Assert(client.DeleteContent("/"), "Not Found")
gtest.Assert(client.PatchContent("/"), "Not Found")
gtest.Assert(client.OptionsContent("/"), "Not Found")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "1Object Get2")
gtest.Assert(client.PutContent("/"), "1Object Put2")
gtest.Assert(client.PostContent("/"), "1Object Post2")
gtest.Assert(client.DeleteContent("/"), "1Object Delete2")
gtest.Assert(client.PatchContent("/"), "1Object Patch2")
gtest.Assert(client.OptionsContent("/"), "1Object Options2")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "1Object Get2")
gtest.Assert(client.PutContent("/"), "1Object Put2")
gtest.Assert(client.PostContent("/"), "1Object Post2")
gtest.Assert(client.DeleteContent("/"), "1Object Delete2")
gtest.Assert(client.PatchContent("/"), "1Object Patch2")
gtest.Assert(client.OptionsContent("/"), "1Object Options2")
resp1, err := client.Head("/")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,196 @@
// 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 (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type DomainObject struct {}
func (o *DomainObject) Init(r *ghttp.Request) {
r.Response.Write("1")
}
func (o *DomainObject) Shut(r *ghttp.Request) {
r.Response.Write("2")
}
func (o *DomainObject) Index(r *ghttp.Request) {
r.Response.Write("Object Index")
}
func (o *DomainObject) Show(r *ghttp.Request) {
r.Response.Write("Object Show")
}
func (o *DomainObject) Info(r *ghttp.Request) {
r.Response.Write("Object Info")
}
func Test_Router_DomainObject1(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Domain("localhost, local").BindObject("/", new(DomainObject))
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "Not Found")
gtest.Assert(client.GetContent("/show"), "Not Found")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "1Object Index2")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "1Object Index2")
gtest.Assert(client.GetContent("/show"), "1Object Show2")
gtest.Assert(client.GetContent("/info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "1Object Index2")
gtest.Assert(client.GetContent("/init"), "Not Found")
gtest.Assert(client.GetContent("/shut"), "Not Found")
gtest.Assert(client.GetContent("/index"), "1Object Index2")
gtest.Assert(client.GetContent("/show"), "1Object Show2")
gtest.Assert(client.GetContent("/info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_DomainObject2(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Domain("localhost, local").BindObject("/object", new(DomainObject), "Show, Info")
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "Not Found")
gtest.Assert(client.GetContent("/object/info"), "Not Found")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "1Object Show2")
gtest.Assert(client.GetContent("/object/info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "1Object Show2")
gtest.Assert(client.GetContent("/object/info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_DomainObjectMethod(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.Domain("localhost, local").BindObjectMethod("/object-info", new(DomainObject), "Info")
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "Not Found")
gtest.Assert(client.GetContent("/object/info"), "Not Found")
gtest.Assert(client.GetContent("/object-info"), "Not Found")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://localhost:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "Not Found")
gtest.Assert(client.GetContent("/object/info"), "Not Found")
gtest.Assert(client.GetContent("/object-info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://local:%d", p))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "Not Found")
gtest.Assert(client.GetContent("/object/info"), "Not Found")
gtest.Assert(client.GetContent("/object-info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,126 @@
// 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 (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Router_Exit(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindHookHandlerByMap("/*", map[string]ghttp.HandlerFunc{
"BeforeServe" : func(r *ghttp.Request){ r.Response.Write("1") },
"AfterServe" : func(r *ghttp.Request){ r.Response.Write("2") },
"BeforeOutput" : func(r *ghttp.Request){ r.Response.Write("3") },
"AfterOutput" : func(r *ghttp.Request){ r.Response.Write("4") },
"BeforeClose" : func(r *ghttp.Request){ r.Response.Write("5") },
"AfterClose" : func(r *ghttp.Request){ r.Response.Write("6") },
})
s.BindHandler("/test/test", func(r *ghttp.Request) {
r.Response.Write("test-start")
r.Exit()
r.Response.Write("test-end")
})
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))
gtest.Assert(client.GetContent("/"), "123")
gtest.Assert(client.GetContent("/test/test"), "1test-start23")
})
}
func Test_Router_ExitHook(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/priority/show", func(r *ghttp.Request) {
r.Response.Write("show")
})
s.BindHookHandlerByMap("/priority/:name", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("1")
},
})
s.BindHookHandlerByMap("/priority/*any", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("2")
},
})
s.BindHookHandlerByMap("/priority/show", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("3")
r.ExitHook()
},
})
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/priority/show"), "3show")
})
}
func Test_Router_ExitAll(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/priority/show", func(r *ghttp.Request) {
r.Response.Write("show")
})
s.BindHookHandlerByMap("/priority/:name", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("1")
},
})
s.BindHookHandlerByMap("/priority/*any", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("2")
},
})
s.BindHookHandlerByMap("/priority/show", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("3")
r.ExitAll()
},
})
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/priority/show"), "3")
})
}

View File

@ -0,0 +1,87 @@
// 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 (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Router_Hook_Basic(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindHookHandlerByMap("/*", map[string]ghttp.HandlerFunc{
"BeforeServe" : func(r *ghttp.Request){ r.Response.Write("1") },
"AfterServe" : func(r *ghttp.Request){ r.Response.Write("2") },
"BeforeOutput" : func(r *ghttp.Request){ r.Response.Write("3") },
"AfterOutput" : func(r *ghttp.Request){ r.Response.Write("4") },
"BeforeClose" : func(r *ghttp.Request){ r.Response.Write("5") },
"AfterClose" : func(r *ghttp.Request){ r.Response.Write("6") },
})
s.BindHandler("/test/test", func(r *ghttp.Request) {
r.Response.Write("test")
})
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))
gtest.Assert(client.GetContent("/"), "123")
gtest.Assert(client.GetContent("/test/test"), "1test23")
})
}
func Test_Router_Hook_Priority(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/priority/show", func(r *ghttp.Request) {
r.Response.Write("show")
})
s.BindHookHandlerByMap("/priority/:name", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("1")
},
})
s.BindHookHandlerByMap("/priority/*any", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("2")
},
})
s.BindHookHandlerByMap("/priority/show", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.Write("3")
},
})
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/priority/show"), "312show")
gtest.Assert(client.GetContent("/priority/any/any"), "2")
gtest.Assert(client.GetContent("/priority/name"), "12")
})
}

View File

@ -0,0 +1,112 @@
// 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 (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
type NamesObject struct {}
func (o *NamesObject) ShowName(r *ghttp.Request) {
r.Response.Write("Object Show Name")
}
func Test_NameToUri_FullName(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.SetNameToUriType(ghttp.NAME_TO_URI_TYPE_FULLNAME)
s.BindObject("/{.struct}/{.method}", new(NamesObject))
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/NamesObject"), "Not Found")
gtest.Assert(client.GetContent("/NamesObject/ShowName"), "Object Show Name")
})
}
func Test_NameToUri_AllLower(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.SetNameToUriType(ghttp.NAME_TO_URI_TYPE_ALLLOWER)
s.BindObject("/{.struct}/{.method}", new(NamesObject))
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/NamesObject"), "Not Found")
gtest.Assert(client.GetContent("/namesobject/showname"), "Object Show Name")
})
}
func Test_NameToUri_Camel(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.SetNameToUriType(ghttp.NAME_TO_URI_TYPE_CAMEL)
s.BindObject("/{.struct}/{.method}", new(NamesObject))
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/NamesObject"), "Not Found")
gtest.Assert(client.GetContent("/namesObject/showName"), "Object Show Name")
})
}
func Test_NameToUri_Default(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.SetNameToUriType(ghttp.NAME_TO_URI_TYPE_DEFAULT)
s.BindObject("/{.struct}/{.method}", new(NamesObject))
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/NamesObject"), "Not Found")
gtest.Assert(client.GetContent("/names-object/show-name"), "Object Show Name")
})
}

View File

@ -33,8 +33,11 @@ func (o *Object) Show(r *ghttp.Request) {
r.Response.Write("Object Show")
}
// 执行对象注册
func Test_Router_Object(t *testing.T) {
func (o *Object) Info(r *ghttp.Request) {
r.Response.Write("Object Info")
}
func Test_Router_Object1(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindObject("/", new(Object))
@ -65,3 +68,59 @@ func Test_Router_Object(t *testing.T) {
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_Object2(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindObject("/object", new(Object), "Show, Info")
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "1Object Show2")
gtest.Assert(client.GetContent("/object/info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}
func Test_Router_ObjectMethod(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindObjectMethod("/object-info", new(Object), "Info")
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))
gtest.Assert(client.GetContent("/"), "Not Found")
gtest.Assert(client.GetContent("/object"), "Not Found")
gtest.Assert(client.GetContent("/object/init"), "Not Found")
gtest.Assert(client.GetContent("/object/shut"), "Not Found")
gtest.Assert(client.GetContent("/object/index"), "Not Found")
gtest.Assert(client.GetContent("/object/show"), "Not Found")
gtest.Assert(client.GetContent("/object/info"), "Not Found")
gtest.Assert(client.GetContent("/object-info"), "1Object Info2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,272 @@
// 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 (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Static_ServerRoot(t *testing.T) {
// SetServerRoot with absolute path
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
defer gfile.Remove(path)
gfile.PutContents(path + "/index.htm", "index")
s.SetServerRoot(path)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "index")
gtest.Assert(client.GetContent("/index.htm"), "index")
})
// SetServerRoot with relative path
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`static/test/%d`, p)
defer gfile.Remove(path)
gfile.PutContents(path + "/index.htm", "index")
s.SetServerRoot(path)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "index")
gtest.Assert(client.GetContent("/index.htm"), "index")
})
}
func Test_Static_Folder_Forbidden(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
defer gfile.Remove(path)
gfile.PutContents(path + "/test.html", "test")
s.SetServerRoot(path)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/index.html"), "Not Found")
gtest.Assert(client.GetContent("/test.html"), "test")
})
}
func Test_Static_IndexFolder(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
defer gfile.Remove(path)
gfile.PutContents(path + "/test.html", "test")
s.SetIndexFolder(true)
s.SetServerRoot(path)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.AssertNE(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/index.html"), "Not Found")
gtest.Assert(client.GetContent("/test.html"), "test")
})
}
func Test_Static_IndexFiles1(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
defer gfile.Remove(path)
gfile.PutContents(path + "/index.html", "index")
gfile.PutContents(path + "/test.html", "test")
s.SetServerRoot(path)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "index")
gtest.Assert(client.GetContent("/index.html"), "index")
gtest.Assert(client.GetContent("/test.html"), "test")
})
}
func Test_Static_IndexFiles2(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
defer gfile.Remove(path)
gfile.PutContents(path + "/test.html", "test")
s.SetIndexFiles([]string{"index.html", "test.html"})
s.SetServerRoot(path)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "test")
gtest.Assert(client.GetContent("/index.html"), "Not Found")
gtest.Assert(client.GetContent("/test.html"), "test")
})
}
func Test_Static_AddSearchPath1(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path1 := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
path2 := fmt.Sprintf(`%s/ghttp/static/test/%d/%d`, gfile.TempDir(), p, p)
defer gfile.Remove(path1)
defer gfile.Remove(path2)
gfile.PutContents(path2 + "/test.html", "test")
s.SetServerRoot(path1)
s.AddSearchPath(path2)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/test.html"), "test")
})
}
func Test_Static_AddSearchPath2(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path1 := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
path2 := fmt.Sprintf(`%s/ghttp/static/test/%d/%d`, gfile.TempDir(), p, p)
defer gfile.Remove(path1)
defer gfile.Remove(path2)
gfile.PutContents(path1 + "/test.html", "test1")
gfile.PutContents(path2 + "/test.html", "test2")
s.SetServerRoot(path1)
s.AddSearchPath(path2)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/test.html"), "test1")
})
}
func Test_Static_AddStaticPath(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path1 := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
path2 := fmt.Sprintf(`%s/ghttp/static/test/%d/%d`, gfile.TempDir(), p, p)
defer gfile.Remove(path1)
defer gfile.Remove(path2)
gfile.PutContents(path1 + "/test.html", "test1")
gfile.PutContents(path2 + "/test.html", "test2")
s.SetServerRoot(path1)
s.AddStaticPath("/my-test", path2)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/test.html"), "test1")
gtest.Assert(client.GetContent("/my-test/test.html"), "test2")
})
}
func Test_Static_AddStaticPath_Priority(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path1 := fmt.Sprintf(`%s/ghttp/static/test/%d/test`, gfile.TempDir(), p)
path2 := fmt.Sprintf(`%s/ghttp/static/test/%d/%d/test`, gfile.TempDir(), p, p)
defer gfile.Remove(path1)
defer gfile.Remove(path2)
gfile.PutContents(path1 + "/test.html", "test1")
gfile.PutContents(path2 + "/test.html", "test2")
s.SetServerRoot(path1)
s.AddStaticPath("/test", path2)
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/test.html"), "test1")
gtest.Assert(client.GetContent("/test/test.html"), "test2")
})
}
func Test_Static_Rewrite(t *testing.T) {
gtest.Case(t, func() {
p := ports.PopRand()
s := g.Server(p)
path := fmt.Sprintf(`%s/ghttp/static/test/%d`, gfile.TempDir(), p)
defer gfile.Remove(path)
gfile.PutContents(path + "/test1.html", "test1")
gfile.PutContents(path + "/test2.html", "test2")
s.SetServerRoot(path)
s.SetRewrite("/test.html", "/test1.html")
s.SetRewriteMap(g.MapStrStr{
"/my-test1" : "/test1.html",
"/my-test2" : "/test2.html",
})
s.SetPort(p)
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
client := ghttp.NewClient()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent("/"), "Forbidden")
gtest.Assert(client.GetContent("/test.html"), "test1")
gtest.Assert(client.GetContent("/test1.html"), "test1")
gtest.Assert(client.GetContent("/test2.html"), "test2")
gtest.Assert(client.GetContent("/my-test1"), "test1")
gtest.Assert(client.GetContent("/my-test2"), "test2")
})
}

View File

@ -26,7 +26,8 @@ import (
)
const (
DEFAULT_CONFIG_FILE = "config.toml" // 默认的配置管理文件名称
// 默认的配置管理文件名称
DEFAULT_CONFIG_FILE = "config.toml"
)
// 配置管理对象
@ -37,6 +38,8 @@ type Config struct {
vc *gtype.Bool // 层级检索是否执行分隔符冲突检测(默认为false检测会比较影响检索效率)
}
// New returns a new configuration management object.
//
// 生成一个配置管理对象
func New(path string, file...string) *Config {
name := DEFAULT_CONFIG_FILE
@ -55,6 +58,8 @@ func New(path string, file...string) *Config {
return c
}
// filePath returns the absolute configuration file path for the given filename by <file>.
//
// 判断从哪个配置文件中获取内容,返回配置文件的绝对路径
func (c *Config) filePath(file...string) (path string) {
name := c.name.Val()

View File

@ -39,14 +39,14 @@ func TestCron_Add_Close(t *testing.T) {
gtest.AssertNE(err3, nil)
gtest.Assert(err4, nil)
gtest.Assert(cron.Size(), 3)
time.Sleep(1100*time.Millisecond)
time.Sleep(1200*time.Millisecond)
gtest.Assert(array.Len(), 2)
time.Sleep(1100*time.Millisecond)
time.Sleep(1200*time.Millisecond)
gtest.Assert(array.Len(), 5)
cron.Close()
time.Sleep(1100*time.Millisecond)
time.Sleep(1200*time.Millisecond)
fixedLength := array.Len()
time.Sleep(1100*time.Millisecond)
time.Sleep(1200*time.Millisecond)
gtest.Assert(array.Len(), fixedLength)
})
}

View File

@ -18,7 +18,7 @@ func (w *Watcher) startWatchLoop() {
// 关闭事件
case <- w.closeChan: return
// 监听事件
// 监听事件
case ev := <- w.watcher.Events:
//fmt.Println("ev:", ev.String())
w.cache.SetIfNotExist(ev.String(), func() interface{} {

View File

@ -235,7 +235,7 @@ func TestTimer_AddLeveledEntry1(t *testing.T) {
})
time.Sleep(1500*time.Millisecond)
gtest.Assert(array.Len(), 0)
time.Sleep(1200*time.Millisecond)
time.Sleep(1300*time.Millisecond)
//glog.Println("check")
gtest.Assert(array.Len(), 1)
})

View File

@ -181,14 +181,22 @@ func AssertLTE(value, expect interface{}) {
}
// 断言判断, value IN expect; 注意: expect必须为slice类型
// 断言判断, value IN expect; 注意: expect必须为slice类型
// 注意value参数可以为普通变量也可以为slice类型。
func AssertIN(value, expect interface{}) {
passed := false
passed := true
switch reflect.ValueOf(expect).Kind() {
case reflect.Slice, reflect.Array:
for _, v := range gconv.Interfaces(expect) {
if v == value {
passed = true
for _, v1 := range gconv.Interfaces(value) {
result := false
for _, v2 := range gconv.Interfaces(expect) {
if v1 == v2 {
result = true
break
}
}
if !result {
passed = false
break
}
}
@ -200,24 +208,31 @@ func AssertIN(value, expect interface{}) {
// 断言判断, value NOT IN expect; 注意: expect必须为slice类型
func AssertNI(value, expect interface{}) {
passed := false
passed := true
switch reflect.ValueOf(expect).Kind() {
case reflect.Slice, reflect.Array:
for _, v := range gconv.Interfaces(expect) {
if v == value {
passed = true
for _, v1 := range gconv.Interfaces(value) {
result := true
for _, v2 := range gconv.Interfaces(expect) {
if v1 == v2 {
result = false
break
}
}
if !result {
passed = false
break
}
}
}
if passed {
if !passed {
panic(fmt.Sprintf(`[ASSERT] EXPECT %v NOT IN %v`, value, expect))
}
}
// 提示错误不退出进程执行
func Error(message...interface{}) {
fmt.Fprintf(os.Stderr, "[ERROR] %s\n%s", fmt.Sprint(message...), getBacktrace())
panic(fmt.Sprintf("[ERROR] %s", fmt.Sprint(message...)))
}
// 提示错误并退出进程执行
@ -238,14 +253,21 @@ func compareMap(value, expect interface{}) error {
if rvExpect.Kind() == reflect.Map {
if rvValue.Kind() == reflect.Map {
if rvExpect.Len() == rvValue.Len() {
// 将两个map类型转换为同一个map类型, 才能执行比较,
// 直接使用 rvValue.MapIndex(key).Interface() 当key类型不一致时会报错。
mValue := make(map[string]string)
mExpect := make(map[string]string)
ksValue := rvValue.MapKeys()
ksExpect := rvExpect.MapKeys()
for _, key := range ksValue {
mValue[gconv.String(key.Interface())] = gconv.String(rvValue.MapIndex(key).Interface())
}
for _, key := range ksExpect {
if fmt.Sprintf("%v", rvValue.MapIndex(key).Interface()) != fmt.Sprintf("%v", rvExpect.MapIndex(key).Interface()) {
return fmt.Errorf(`[ASSERT] EXPECT VALUE map["%v"]:%v == %v`,
key,
rvValue.MapIndex(key).Interface(),
rvExpect.MapIndex(key).Interface(),
)
mExpect[gconv.String(key.Interface())] = gconv.String(rvExpect.MapIndex(key).Interface())
}
for k, v := range mExpect {
if v != mValue[k] {
return fmt.Errorf(`[ASSERT] EXPECT VALUE map["%v"]:%v == %v`, k, mValue[k], v)
}
}
} else {

View File

@ -59,6 +59,20 @@ func ReplaceI(origin, search, replace string, count...int) string {
return origin
}
// Count counts the number of <substr> appears in <s>. It returns 0 if no <substr> found in <s>.
//
// 计算字符串substr在字符串s中出现的次数如果没有在s中找到substr那么返回0。
func Count(s, substr string) int {
return strings.Count(s, substr)
}
// Count counts the number of <substr> appears in <s>, case-insensitive. It returns 0 if no <substr> found in <s>.
//
// (非大小写敏感)计算字符串substr在字符串s中出现的次数如果没有在s中找到substr那么返回0。
func CountI(s, substr string) int {
return strings.Count(ToLower(s), ToLower(substr))
}
// Replace string by array/slice.
//
// 使用map进行字符串替换(大小写敏感)

View File

@ -47,7 +47,16 @@ func Convert(i interface{}, t string, extraParams...interface{}) interface{} {
return Time(i, String(extraParams[0]))
}
return Time(i)
case "gtime.Time":
if len(extraParams) > 0 {
return GTime(i, String(extraParams[0]))
}
return *GTime(i)
case "*gtime.Time":
if len(extraParams) > 0 {
return GTime(i, String(extraParams[0]))
}
return GTime(i)
case "time.Duration": return TimeDuration(i)
default:
return i

View File

@ -15,68 +15,60 @@ import (
"strings"
)
// 将params键值对参数映射到对应的struct对象属性上第三个参数mapping为非必需表示自定义名称与属性名称的映射关系。
// 将params键值对参数映射到对应的struct对象属性上
// 第三个参数mapping为非必需表示自定义名称与属性名称的映射关系。
// 需要注意:
// 1、第二个参数应当为struct对象指针
// 2、struct对象的**公开属性(首字母大写)**才能被映射赋值;
// 3、map中的键名可以为小写映射转换时会自动将键名首字母转为大写做匹配映射如果无法匹配则忽略
func Struct(params interface{}, objPointer interface{}, attrMapping...map[string]string) error {
if params == nil {
return nil
return errors.New("params cannot be nil")
}
isParamMap := true
paramsMap := (map[string]interface{})(nil)
// 先将参数转为 map[string]interface{} 类型
if m, ok := params.(map[string]interface{}); ok {
paramsMap = m
} else {
paramsMap = make(map[string]interface{})
if reflect.ValueOf(params).Kind() == reflect.Map {
ks := reflect.ValueOf(params).MapKeys()
vs := reflect.ValueOf(params)
for _, k := range ks {
paramsMap[String(k.Interface())] = vs.MapIndex(k).Interface()
}
} else {
isParamMap = false
}
if objPointer == nil {
return errors.New("object pointer cannot be nil")
}
paramsMap := Map(params)
if paramsMap == nil {
return fmt.Errorf("invalid params: %v", params)
}
// struct的反射对象
elem := reflect.Value{}
if v, ok := objPointer.(reflect.Value); ok {
elem = v
} else {
elem = reflect.ValueOf(objPointer).Elem()
}
// 如果给定的参数不是map类型那么直接将参数值映射到第一个属性上
if !isParamMap {
if err := bindVarToStructByIndex(elem, 0, params); err != nil {
return err
rv := reflect.ValueOf(objPointer)
if kind := rv.Kind(); kind != reflect.Ptr {
return fmt.Errorf("object pointer should be type of: %v", kind)
}
return nil
if !rv.IsValid() || rv.IsNil() {
return errors.New("object pointer cannot be nil")
}
elem = rv.Elem()
}
// 已执行过转换的属性,只执行一次转换
dmap := make(map[string]bool)
// 已执行过转换的属性,只执行一次转换
// 或者是已经执行过转换检查的属性(即使不进行转换), 以便重复判断。
doneMap := make(map[string]bool)
// 首先按照传递的映射关系进行匹配
if len(attrMapping) > 0 && len(attrMapping[0]) > 0 {
for mappingk, mappingv := range attrMapping[0] {
if v, ok := paramsMap[mappingk]; ok {
dmap[mappingv] = true
if err := bindVarToStructAttr(elem, mappingv, v); err != nil {
for mapK, mapV := range attrMapping[0] {
if v, ok := paramsMap[mapK]; ok {
doneMap[mapV] = true
if err := bindVarToStructAttr(elem, mapV, v); err != nil {
return err
}
}
}
}
// 其次匹配对象定义时绑定的属性名称
// 其次匹配对象定义时绑定的属性名称,
// 标签映射关系map如果有的话
tagmap := getTagMapOfStruct(objPointer)
for tagk, tagv := range tagmap {
if _, ok := dmap[tagv]; ok {
tagMap := getTagMapOfStruct(objPointer)
for tagk, tagv := range tagMap {
if _, ok := doneMap[tagv]; ok {
continue
}
if v, ok := paramsMap[tagk]; ok {
dmap[tagv] = true
doneMap[tagv] = true
if err := bindVarToStructAttr(elem, tagv, v); err != nil {
return err
}
@ -88,19 +80,19 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
for i := 0; i < elem.NumField(); i++ {
attrMap[elemType.Field(i).Name] = struct{}{}
}
for mapk, mapv := range paramsMap {
for mapK, mapV := range paramsMap {
name := ""
for _, checkName := range []string {
gstr.UcFirst(mapk),
gstr.ReplaceByMap(mapk, map[string]string{
gstr.UcFirst(mapK),
gstr.ReplaceByMap(mapK, map[string]string{
"_" : "",
"-" : "",
" " : "",
})} {
if _, ok := dmap[checkName]; ok {
})} {
if _, ok := doneMap[checkName]; ok {
continue
}
if _, ok := tagmap[checkName]; ok {
if _, ok := tagMap[checkName]; ok {
continue
}
// 循环查找属性名称进行匹配
@ -114,6 +106,7 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
break
}
}
doneMap[checkName] = true
if name != "" {
break
}
@ -122,7 +115,7 @@ func Struct(params interface{}, objPointer interface{}, attrMapping...map[string
if name == "" {
continue
}
if err := bindVarToStructAttr(elem, name, mapv); err != nil {
if err := bindVarToStructAttr(elem, name, mapV); err != nil {
return err
}
}

View File

@ -22,7 +22,7 @@ func TimeDuration(i interface{}) time.Duration {
return time.Duration(Int64(i))
}
// 将变量i转换为time.Time类型
// 将变量i转换为time.Time类型, 自动识别i为时间戳或者标准化的时间字符串。
func GTime(i interface{}, format...string) *gtime.Time {
s := String(i)
if len(s) == 0 {

View File

@ -109,11 +109,8 @@ func Test_Struct_Attr_Slice(t *testing.T) {
type User struct {
Scores []int
}
user := new(User)
scores := []interface{}{99, 100, 60, 140}
// 通过map映射转换
user := new(User)
if err := gconv.Struct(g.Map{"Scores" : scores}, user); err != nil {
gtest.Error(err)
} else {
@ -121,15 +118,6 @@ func Test_Struct_Attr_Slice(t *testing.T) {
Scores : []int{99, 100, 60, 140},
})
}
// 通过变量映射转换直接slice赋值
if err := gconv.Struct(scores, user); err != nil {
gtest.Error(err)
} else {
gtest.Assert(user, &User{
Scores : []int{99, 100, 60, 140},
})
}
})
}

View File

@ -67,35 +67,4 @@ type CustomMsg = map[string]interface{}
func parseSequenceTag(tag string) (name, rule, msg string) {
match, _ := gregex.MatchString(`\s*((\w+)\s*@){0,1}\s*([^#]+)\s*(#\s*(.*)){0,1}\s*`, tag)
return strings.TrimSpace(match[2]), strings.TrimSpace(match[3]), strings.TrimSpace(match[5])
}
// 解析sequence tag为标准校验规则及自定义错误
func parseSequenceTags(tags []string) (rules map[string]string, msgs map[string]interface{}) {
rules = make(map[string]string)
msgs = make(map[string]interface{})
for _, tag := range tags {
name, rule, msg := parseSequenceTag(tag)
// 校验规则
if len(name) == 0 {
continue
}
rules[name] = rule
// 错误提示
if len(msg) > 0 {
ruleArray := strings.Split(rule, "|")
msgArray := strings.Split(msg, "|")
for k, v := range ruleArray {
if len(msgArray[k]) == 0 {
continue
}
// 关联校验会有":"符号
array := strings.Split(v, ":")
if _, ok := msgs[name]; !ok {
msgs[name] = make(map[string]string)
}
msgs[name].(map[string]string)[strings.TrimSpace(array[0])] = strings.TrimSpace(msgArray[k])
}
}
}
return
}

View File

@ -105,16 +105,20 @@ var (
)
// 检测单条数据的规则:
// value为需要校验的数据可以为任意基本数据类型
// msgs为自定义错误信息由于同一条数据的校验规则可能存在多条为方便调用参数类型支持 string/map[string]string 允许传递多个自定义的错误信息如果类型为string那么中间使用"|"符号分隔多个自定义错误
// params参数为联合校验参数对于需要联合校验的规则有效required-*、same、different
func Check(value interface{}, rules string, msgs interface{}, params...map[string]interface{}) *Error {
//
// 1. value为需要校验的数据可以为任意基本数据类型
//
// 2. msgs为自定义错误信息由于同一条数据的校验规则可能存在多条为方便调用参数类型支持 string/map/struct/*struct,
// 允许传递多个自定义的错误信息如果类型为string那么中间使用"|"符号分隔多个自定义错误;
//
// 3. params参数为联合校验参数支持任意的map/struct/*struct类型对于需要联合校验的规则有效required-*、same、different
func Check(value interface{}, rules string, msgs interface{}, params...interface{}) *Error {
// 内部会将参数全部转换为字符串类型进行校验
val := strings.TrimSpace(gconv.String(value))
data := make(map[string]string)
errorMsgs := make(map[string]string)
if len(params) > 0 {
for k, v := range params[0] {
for k, v := range gconv.Map(params[0]) {
data[k] = gconv.String(v)
}
}
@ -122,11 +126,12 @@ func Check(value interface{}, rules string, msgs interface{}, params...map[strin
msgArray := make([]string, 0)
customMsgMap := make(map[string]string)
switch v := msgs.(type) {
case map[string]string:
customMsgMap = v
case string:
msgArray = strings.Split(v, "|")
default:
for k, v := range gconv.Map(msgs) {
customMsgMap[k] = gconv.String(v)
}
}
ruleItems := strings.Split(strings.TrimSpace(rules), "|")
// 规则项预处理, 主要解决规则中存在的"|"关键字符号

View File

@ -400,6 +400,9 @@ func Test_QQ(t *testing.T) {
}
func Test_Ip(t *testing.T) {
if m := gvalid.Check("10.0.0.1", "ip", nil); m != nil {
t.Error(m)
}
if m := gvalid.Check("10.0.0.1", "ipv4", nil); m != nil {
t.Error(m)
}
@ -409,12 +412,21 @@ func Test_Ip(t *testing.T) {
if m := gvalid.Check("1920.0.0.0", "ipv4", nil); m == nil {
t.Error("ipv4校验失败")
}
if m := gvalid.Check("1920.0.0.0", "ip", nil); m == nil {
t.Error("ipv4校验失败")
}
if m := gvalid.Check("fe80::5484:7aff:fefe:9799", "ipv6", nil); m != nil {
t.Error(m)
}
if m := gvalid.Check("fe80::5484:7aff:fefe:9799123", "ipv6", nil); m == nil {
t.Error(m)
}
if m := gvalid.Check("fe80::5484:7aff:fefe:9799", "ip", nil); m != nil {
t.Error(m)
}
if m := gvalid.Check("fe80::5484:7aff:fefe:9799123", "ip", nil); m == nil {
t.Error(m)
}
}
func Test_IPv4(t *testing.T) {

View File

@ -7,6 +7,7 @@
package gvalid_test
import (
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gvalid"
"testing"
)
@ -65,3 +66,35 @@ func Test_CheckMapWithNilAndNotRequiredField(t *testing.T) {
}
}
func Test_Sequence(t *testing.T) {
gtest.Case(t, func() {
params := map[string]interface{} {
"passport" : "",
"password" : "123456",
"password2" : "1234567",
}
rules := []string {
"passport@required|length:6,16#账号不能为空|账号长度应当在:min到:max之间",
"password@required|length:6,16|same:password2#密码不能为空|密码长度应当在:min到:max之间|两次密码输入不相等",
"password2@required|length:6,16#",
}
err := gvalid.CheckMap(params, rules)
gtest.AssertNE(err, nil)
gtest.Assert(len(err.Map()), 2)
gtest.Assert(err.Map()["required"], "账号不能为空")
gtest.Assert(err.Map()["length"], "账号长度应当在6到16之间")
gtest.Assert(len(err.Maps()), 2)
gtest.Assert(err.String(), "账号不能为空; 账号长度应当在6到16之间; 两次密码输入不相等")
gtest.Assert(err.Strings(), []string{"账号不能为空", "账号长度应当在6到16之间", "两次密码输入不相等"})
k, m := err.FirstItem()
gtest.Assert(k, "passport")
gtest.Assert(m, err.Map())
r, s := err.FirstRule()
gtest.Assert(r, "required")
gtest.Assert(s, "账号不能为空")
})
}

View File

@ -7,28 +7,66 @@
package gvalid_test
import (
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gvalid"
"testing"
)
func Test_CheckStruct(t *testing.T) {
type Object struct {
Name string
Age int
}
rules := map[string]string {
"Name" : "required|length:6,16",
"Age" : "between:18,30",
}
msgs := map[string]interface{} {
"Name" : map[string]string {
"required" : "名称不能为空",
"length" : "名称长度为:min到:max个字符",
},
"Age" : "年龄为18到30周岁",
}
obj := &Object{"john", 16}
if m := gvalid.CheckStruct(obj, rules, msgs); m == nil {
t.Error("CheckObject校验失败")
}
gtest.Case(t, func() {
type Object struct {
Name string
Age int
}
rules := map[string]string {
"Name" : "required|length:6,16",
"Age" : "between:18,30",
}
msgs := map[string]interface{} {
"Name" : map[string]string {
"required" : "名称不能为空",
"length" : "名称长度为:min到:max个字符",
},
"Age" : "年龄为18到30周岁",
}
obj := &Object{"john", 16}
err := gvalid.CheckStruct(obj, rules, msgs)
gtest.AssertNE(err, nil)
gtest.Assert(len(err.Maps()), 2)
gtest.Assert(err.Maps()["Name"]["required"], "")
gtest.Assert(err.Maps()["Name"]["length"], "名称长度为6到16个字符")
gtest.Assert(err.Maps()["Age"]["between"], "年龄为18到30周岁")
})
gtest.Case(t, func() {
type LoginRequest struct {
Username string `json:"username" gvalid:"username@required#用户名不能为空"`
Password string `json:"password" gvalid:"password@required#登录密码不能为空"`
}
var login LoginRequest
err := gvalid.CheckStruct(login, nil)
gtest.AssertNE(err, nil)
gtest.Assert(len(err.Maps()), 2)
gtest.Assert(err.Maps()["username"]["required"], "用户名不能为空")
gtest.Assert(err.Maps()["password"]["required"], "登录密码不能为空")
})
gtest.Case(t, func() {
type User struct {
Id int `gvalid:"uid@required|min:10#|ID不能为空"`
Age int `gvalid:"age@required#年龄不能为空"`
Username string `json:"username" gvalid:"username@required#用户名不能为空"`
Password string `json:"password" gvalid:"password@required#登录密码不能为空"`
}
user := &User{
Id : 1,
Username : "john",
Password : "123456",
}
err := gvalid.CheckStruct(user, nil)
gtest.AssertNE(err, nil)
gtest.Assert(len(err.Maps()), 1)
gtest.Assert(err.Maps()["uid"]["min"], "ID不能为空")
})
}

View File

@ -13,7 +13,7 @@ func Hello2(r *ghttp.Request) {
func main() {
s := ghttp.GetServer()
s.Domain("127.0.0.1").BindHandler("/", Hello1)
s.Domain("localhost").BindHandler("/", Hello2)
s.Domain("localhost, local").BindHandler("/", Hello2)
s.SetPort(8199)
s.Run()
}

View File

@ -0,0 +1,33 @@
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/frame/gmvc"
)
type Controller struct {
gmvc.Controller
}
func (c *Controller) Login() {
c.Session.Id()
c.Response.Write("这个页面用户填写信息执行登录")
}
func (c *Controller) DoLogin() {
c.Session.Set("key", "value")
//c.Response.Header().Set("Set-Cookie", "myid=1B27UGQGCIBP0P70; Path=/; Domain=127.0.0.1; Expires=Wed, 04 Mar 2020 07:12:05 GMT")
c.Response.RedirectTo("/main")
}
func (c *Controller) Main() {
c.Response.WriteJson(c.Session.Data())
}
func main() {
s := g.Server()
s.BindController("/", new(Controller))
s.SetPort(8199)
s.Run()
}

View File

@ -1,13 +1,11 @@
package main
import "fmt"
import (
"fmt"
"github.com/gogf/gf/g/util/gconv"
)
func main() {
for i := 0; i < 10; i++ {
switch 1 {
default:
//continue
}
fmt.Println(i)
}
t := gconv.GTime("2010-10-10 00:00:01")
fmt.Println(t.String())
}

View File

@ -0,0 +1,23 @@
package main
import (
"fmt"
"github.com/gogf/gf/g/util/gconv"
)
func main() {
type User struct {
Uid int
Name string `gconv:"-"`
NickName string `gconv:"nickname, omitempty"`
Pass1 string `gconv:"password1"`
Pass2 string `gconv:"password2"`
}
user := User{
Uid : 100,
Name : "john",
Pass1 : "123",
Pass2 : "456",
}
fmt.Println(gconv.Map(user))
}

View File

@ -1,5 +1,5 @@
package gf
const VERSION = "v1.5.10"
const VERSION = "v1.5.15"
const AUTHORS = "john<john@goframe.org>"