Compare commits

...

54 Commits

Author SHA1 Message Date
bca5532df8 version updates 2019-03-17 22:36:58 +08:00
72eeadd9aa Merge branch 'master' into develop 2019-03-17 22:31:15 +08:00
0af55794f6 add more unit test cases for gdb 2019-03-17 22:26:41 +08:00
25a6c53533 add unit test cases for gfsnotify 2019-03-15 14:54:01 +08:00
9f9172c775 update unit test cases of package gconv 2019-03-15 09:05:56 +08:00
320e0db417 fix int-overflow issue in gconv.String when converting int64 to string; add more unit test cases for package gconv 2019-03-15 00:22:39 +08:00
cb8362d447 update unit test cases of package gins 2019-03-14 23:28:56 +08:00
45a83fc53c unit test cases update for package gins 2019-03-14 00:23:46 +08:00
3411bd1c1d merge master 2019-03-13 22:41:00 +08:00
6ab0a77364 add more defaulr searching paths for g.Config() 2019-03-13 22:12:59 +08:00
281bae4116 unit test cases update for package gins 2019-03-13 09:11:50 +08:00
218c692fe0 update unit test cases 2019-03-12 23:56:09 +08:00
fa69b581e1 update unit test cases 2019-03-12 23:50:30 +08:00
bd0baceeca update unit test cases 2019-03-12 23:47:27 +08:00
e71c837472 update unit test cases 2019-03-12 23:45:44 +08:00
782aaabd07 add more unit test cases for gins; update text/template for gview 2019-03-12 23:26:10 +08:00
8ae9276732 add disable cache feature option for package gspath 2019-03-12 00:24:31 +08:00
79a3aa5916 add more unit test cases for package gvalid 2019-03-11 22:58:21 +08:00
733c5db228 version updates 2019-03-11 22:33:43 +08:00
6171c621a7 fix issue in missing customed error messages in package gvalid 2019-03-11 22:33:20 +08:00
802568856c version updates 2019-03-11 16:35:44 +08:00
127fb67185 add session id check in session object initialzing 2019-03-11 16:33:51 +08:00
5db039bbce gdb.Model updates, rename Alterable function to Safe, change gdb.Model alterable in default, update Where function to support more cases when using map as its param, add more unit test cases for gdb.Model 2019-03-11 16:14:55 +08:00
8a3365d18e ghttp comment updates 2019-03-10 00:39:34 +08:00
2ae5b1a4f8 add more unit test cases for ghttp.Server 2019-03-10 00:35:03 +08:00
f9515d7126 version updates 2019-03-09 10:20:11 +08:00
b56679a97c revert g.Map to map[string]interface{}, add g.MapAnyAny 2019-03-09 10:17:21 +08:00
d1b123964a README updates 2019-03-08 21:07:18 +08:00
991f7c4958 ghttp.Response updates 2019-03-08 19:03:22 +08:00
770619c39e update unit test cases og gcron 2019-03-08 18:27:11 +08:00
4ad5450b80 gtest updates; remove one unit test case of gconv.Struct 2019-03-08 18:18:29 +08:00
49ce7fe885 fix data race issue of gqueue 2019-03-08 17:54:50 +08:00
e66f63262b add more unit test cases for ghttp.Server 2019-03-08 17:31:30 +08:00
94bd5da68a add map key operator support in Where function for gdb 2019-03-08 11:12:52 +08:00
3eee95caf2 VERSION updates 2019-03-08 10:35:35 +08:00
5c638c630a add select in support for slice type of arguments in Where function of gdb 2019-03-08 10:33:36 +08:00
a6ec9d7a1c update gdb.Model.Where, orverwrite args if multiple calls Where function 2019-03-08 10:18:38 +08:00
374c70c0e3 README updates 2019-03-08 09:03:32 +08:00
40771066d4 travis updates 2019-03-08 08:48:53 +08:00
22a7ef43ce Merge branch 'master' into develop 2019-03-08 00:21:52 +08:00
05f22d1cee README updates 2019-03-08 00:15:57 +08:00
ebf56a86ab add Alterable function for gdb.Model 2019-03-07 23:53:56 +08:00
2ba59e8943 fix issue in gtime convert in package gconv; add Structs/Scan functions for gdb.Model; add GetStructs/GetScan functions for gdb.Base and gdb.TX 2019-03-07 23:36:45 +08:00
83be1de04c remove error returns from router registry functions of WebServer; add more unit test cases for WebServer 2019-03-06 15:21:00 +08:00
c8251ed82f g.Map updates 2019-03-06 10:38:50 +08:00
2335ea0c4d merge master 2019-03-05 21:07:54 +08:00
5d874e9063 add example code for gconv.Map; comment updates of gdb 2019-03-05 17:52:34 +08:00
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
116 changed files with 5245 additions and 1201 deletions

View File

@ -14,19 +14,23 @@ env:
services:
- mysql
- redis-server
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时判断重复性不能添加重复的路径
1. gdb执行数据写入时如果参数为struct/[]struct自动映射与表字段对应关系不再使用gconv标签标识

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -39,15 +39,17 @@ type DB interface {
doPrepare(link dbLink, query string) (*sql.Stmt, error)
doInsert(link dbLink, table string, data interface{}, option int, batch...int) (result sql.Result, err error)
doBatchInsert(link dbLink, table string, list interface{}, option int, batch...int) (result sql.Result, err error)
doUpdate(link dbLink, table string, data interface{}, condition interface{}, args ...interface{}) (result sql.Result, err error)
doDelete(link dbLink, table string, condition interface{}, args ...interface{}) (result sql.Result, err error)
doUpdate(link dbLink, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error)
doDelete(link dbLink, table string, condition string, args ...interface{}) (result sql.Result, err error)
// 数据库查询
GetAll(query string, args ...interface{}) (Result, error)
GetOne(query string, args ...interface{}) (Record, error)
GetValue(query string, args ...interface{}) (Value, error)
GetCount(query string, args ...interface{}) (int, error)
GetStruct(obj interface{}, query string, args ...interface{}) error
GetStruct(objPointer interface{}, query string, args ...interface{}) error
GetStructs(objPointerSlice interface{}, query string, args ...interface{}) error
GetScan(objPointer interface{}, query string, args ...interface{}) error
// 创建底层数据库master/slave链接对象
Master() (*sql.DB, error)

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
}
// 数据库查询,获取查询字段值
@ -281,7 +312,7 @@ func (bs *dbBase) doInsert(link dbLink, table string, data interface{}, option i
return bs.db.doBatchInsert(link, table, data, option, batch...)
case reflect.Map: fallthrough
case reflect.Struct:
dataMap = Map(gconv.Map(data))
dataMap = gconv.Map(data)
default:
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
}
@ -289,7 +320,7 @@ func (bs *dbBase) doInsert(link dbLink, table string, data interface{}, option i
for k, v := range dataMap {
fields = append(fields, charL + k + charR)
values = append(values, "?")
params = append(params, v)
params = append(params, convertParam(v))
}
operation := getInsertOperationByOption(option)
updateStr := ""
@ -338,6 +369,10 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt
var params []interface{}
listMap := (List)(nil)
switch v := list.(type) {
case Result:
listMap = v.ToList()
case Record:
listMap = List{v.ToMap()}
case List:
listMap = v
case Map:
@ -405,7 +440,7 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt
}
for i := 0; i < len(listMap); i++ {
for _, k := range keys {
params = append(params, listMap[i][k])
params = append(params, convertParam(listMap[i][k]))
}
values = append(values, valueHolderStr)
if len(values) == batchNum {
@ -448,17 +483,13 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, opt
// CURD操作:数据更新统一采用sql预处理。
// data参数支持string/map/struct/*struct类型。
func (bs *dbBase) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
link, err := bs.db.Master()
if err != nil {
return nil, err
}
return bs.db.doUpdate(link, table, data, condition, args ...)
newWhere, newArgs := formatCondition(condition, args)
return bs.db.doUpdate(nil, table, data, newWhere, newArgs ...)
}
// CURD操作:数据更新统一采用sql预处理。
// data参数支持string/map/struct/*struct类型类型。
func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, condition interface{}, args ...interface{}) (result sql.Result, err error) {
params := ([]interface{})(nil)
func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, condition string, args ...interface{}) (result sql.Result, err error) {
updates := ""
charL, charR := bs.db.getChars()
// 使用反射进行类型判断
@ -468,43 +499,51 @@ func (bs *dbBase) doUpdate(link dbLink, table string, data interface{}, conditio
rv = rv.Elem()
kind = rv.Kind()
}
params := []interface{}(nil)
switch kind {
case reflect.Map: fallthrough
case reflect.Struct:
var fields []string
for k, v := range gconv.Map(data) {
fields = append(fields, fmt.Sprintf("%s%s%s=?", charL, k, charR))
params = append(params, gconv.String(v))
params = append(params, convertParam(v))
}
updates = strings.Join(fields, ",")
default:
updates = gconv.String(data)
}
for _, v := range args {
params = append(params, gconv.String(v))
if len(params) > 0 {
args = append(params, args...)
}
// 如果没有传递link那么使用默认的写库对象
if link == nil {
if link, err = bs.db.Master(); err != nil {
return nil, err
}
}
newWhere, newArgs := formatCondition(condition, params)
return bs.db.doExec(link, fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, newWhere), newArgs...)
if len(condition) == 0 {
return bs.db.doExec(link, fmt.Sprintf("UPDATE %s SET %s", table, updates), args...)
}
return bs.db.doExec(link, fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, condition), args...)
}
// CURD操作:删除数据
func (bs *dbBase) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
link, err := bs.db.Master()
if err != nil {
return nil, err
}
return bs.db.doDelete(link, table, condition, args ...)
newWhere, newArgs := formatCondition(condition, args)
return bs.db.doDelete(nil, table, newWhere, newArgs ...)
}
// CURD操作:删除数据
func (bs *dbBase) doDelete(link dbLink, table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
newWhere, newArgs := formatCondition(condition, args)
return bs.db.doExec(link, fmt.Sprintf("DELETE FROM %s WHERE %s", table, newWhere), newArgs...)
func (bs *dbBase) doDelete(link dbLink, table string, condition string, args ...interface{}) (result sql.Result, err error) {
if link == nil {
if link, err = bs.db.Master(); err != nil {
return nil, err
}
}
if len(condition) == 0 {
return bs.db.doExec(link, fmt.Sprintf("DELETE FROM %s", table), args...)
}
return bs.db.doExec(link, fmt.Sprintf("DELETE FROM %s WHERE %s", table, condition), args...)
}
// 获得缓存对象

View File

@ -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,62 @@ 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")
return "", args
}
// 查询条件参数处理主要处理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 +101,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 +111,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)
}
}
@ -82,6 +126,22 @@ func formatCondition(where interface{}, args []interface{}) (newWhere string, ne
return
}
// 将预处理参数转换为底层数据库引擎支持的格式。
// 主要是判断参数是否为复杂数据类型,如果是,那么转换为基础类型。
func convertParam(value interface{}) interface{} {
rv := reflect.ValueOf(value)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Struct:
return gconv.String(value)
}
return value
}
// 打印SQL对象(仅在debug=true时有效)
func printSql(v *Sql) {
s := fmt.Sprintf("%s, %v, %s, %s, %d ms, %s", v.Sql, v.Args,

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 {
@ -211,13 +247,17 @@ func (md *Model) Data(data ...interface{}) *Model {
}
model.data = m
} else {
switch data[0].(type) {
switch params := data[0].(type) {
case Result:
model.data = params.ToList()
case Record:
model.data = params.ToMap()
case List:
model.data = data[0]
model.data = params
case Map:
model.data = data[0]
model.data = params
default:
rv := reflect.ValueOf(data[0])
rv := reflect.ValueOf(params)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
@ -384,9 +424,9 @@ func (md *Model) Update() (result sql.Result, err error) {
}
}
if md.tx == nil {
return md.db.Update(md.tables, md.data, md.where, md.whereArgs ...)
return md.db.doUpdate(nil, md.tables, md.data, md.where, md.whereArgs ...)
} else {
return md.tx.Update(md.tables, md.data, md.where, md.whereArgs ...)
return md.tx.doUpdate(md.tables, md.data, md.where, md.whereArgs ...)
}
}
@ -398,9 +438,9 @@ func (md *Model) Delete() (result sql.Result, err error) {
}
}()
if md.tx == nil {
return md.db.Delete(md.tables, md.where, md.whereArgs...)
return md.db.doDelete(nil, md.tables, md.where, md.whereArgs...)
} else {
return md.tx.Delete(md.tables, md.where, md.whereArgs...)
return md.tx.doDelete(md.tables, md.where, md.whereArgs...)
}
}
@ -438,13 +478,44 @@ func (md *Model) Value() (Value, error) {
return nil, nil
}
// 链式操作查询单条记录并自动转换为struct对象
func (md *Model) Struct(obj interface{}) error {
// 链式操作查询单条记录并自动转换为struct对象, 参数必须为对象的指针,不能为空指针。
func (md *Model) Struct(objPointer interface{}) error {
one, err := md.One()
if err != nil {
return err
}
return one.ToStruct(obj)
return one.ToStruct(objPointer)
}
// 链式操作查询多条记录并自动转换为指定的slice对象, 如: []struct/[]*struct。
func (md *Model) Structs(objPointerSlice interface{}) error {
r, err := md.All()
if err != nil {
return err
}
return r.ToStructs(objPointerSlice)
}
// 链式操作将结果转换为指定的struct/*struct/[]struct/[]*struct,
// 参数应该为指针类型,否则返回失败。
// 该方法自动识别参数类型调用Struct/Structs方法。
func (md *Model) Scan(objPointer interface{}) error {
t := reflect.TypeOf(objPointer)
k := t.Kind()
if k != reflect.Ptr {
return fmt.Errorf("params should be type of pointer, but got: %v", k)
}
k = t.Elem().Kind()
switch k {
case reflect.Array:
case reflect.Slice:
return md.Structs(objPointer)
case reflect.Struct:
return md.Struct(objPointer)
default:
return fmt.Errorf("element type should be type of struct/slice, unsupported: %v", k)
}
return nil
}
// 链式操作查询数量fields可以为空也可以自定义查询字段
@ -532,14 +603,13 @@ func (md *Model) getFormattedSql() string {
return s
}
// 组块结果集
// @author ymrjqyy
// @author 2018-08-15
// 组块结果集
func (md *Model) Chunk(limit int, callback func(result Result, err error) bool) {
page := 1
page := 1
model := md
for {
md.ForPage(page, limit)
data, err := md.getAll(md.getFormattedSql(), md.whereArgs...)
model = model.ForPage(page, limit)
data, err := model.All()
if err != nil {
callback(nil, err)
break

View File

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

View File

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

View File

@ -4,7 +4,6 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gdb
import (

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 ...)
@ -129,14 +162,28 @@ func (tx *TX) BatchSave(table string, list interface{}, batch...int) (sql.Result
return tx.db.doBatchInsert(tx.tx, table, list, OPTION_SAVE, batch...)
}
// CURD操作:数据更新统一采用sql预处理
// data参数支持字符串或者关联数组类型内部会自行做判断处理
// CURD操作:数据更新统一采用sql预处理,
// data参数支持字符串或者关联数组类型内部会自行做判断处理.
func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
newWhere, newArgs := formatCondition(condition, args)
return tx.doUpdate(table, data, newWhere, newArgs ...)
}
// 与Update方法的区别是不处理条件参数
func (tx *TX) doUpdate(table string, data interface{}, condition string, args ...interface{}) (sql.Result, error) {
return tx.db.doUpdate(tx.tx, table, data, condition, args ...)
}
// CURD操作:删除数据
func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
newWhere, newArgs := formatCondition(condition, args)
return tx.doDelete(table, newWhere, newArgs ...)
}
// 与Delete方法的区别是不处理条件参数
func (tx *TX) doDelete(table string, condition string, args ...interface{}) (sql.Result, error) {
return tx.db.doDelete(tx.tx, table, condition, args ...)
}

View File

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

View File

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

View File

@ -1,11 +1,20 @@
package gdb_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/database/gdb"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"os"
)
const (
// 初始化表数据量
INIT_DATA_SIZE = 10
)
var (
// 数据库对象/接口
db gdb.DB
@ -26,10 +35,12 @@ func init() {
Priority: 1,
}
hostname, _ := os.Hostname()
// 本地测试hack
if hostname == "ijohn" {
node.Pass = "12345678"
}
gdb.AddDefaultConfigNode(node)
gdb.AddConfigNode("test", node)
gdb.AddConfigNode(gdb.DEFAULT_GROUP_NAME, node)
if r, err := gdb.New(); err != nil {
gtest.Fatal(err)
} else {
@ -39,12 +50,25 @@ func init() {
if _, err := db.Exec("CREATE DATABASE IF NOT EXISTS `test` CHARACTER SET UTF8"); err != nil {
gtest.Fatal(err)
}
// 选择操作数据库
db.SetSchema("test")
if _, err := db.Exec("DROP TABLE IF EXISTS `user`"); err != nil {
gtest.Fatal(err)
// 创建默认用户表
createTable("user")
}
// 创建指定名称的user测试表当table为空时创建随机的表名。
// 创建的测试表默认没有任何数据。
// 执行完成后返回该表名。
// TODO 支持更多数据库
func createTable(table...string) (name string) {
if len(table) > 0 {
name = table[0]
} else {
name = fmt.Sprintf(`user_%d`, gtime.Nanosecond())
}
if _, err := db.Exec(`
CREATE TABLE user (
dropTable(name)
if _, err := db.Exec(fmt.Sprintf(`
CREATE TABLE %s (
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID',
passport varchar(45) NOT NULL COMMENT '账号',
password char(32) NOT NULL COMMENT '密码',
@ -52,7 +76,38 @@ func init() {
create_time timestamp NOT NULL COMMENT '创建时间/注册时间',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
`); err != nil {
`, name)); err != nil {
gtest.Fatal(err)
}
return
}
// 删除指定表.
func dropTable(table string) {
if _, err := db.Exec(fmt.Sprintf("DROP TABLE IF EXISTS `%s`", table)); err != nil {
gtest.Fatal(err)
}
}
// See createTable.
// 创建测试表,并初始化默认数据。
func createInitTable(table...string) (name string) {
name = createTable(table...)
array := garray.New(true)
for i := 1; i <= INIT_DATA_SIZE; i++ {
array.Append(g.Map{
"id" : i,
"passport" : fmt.Sprintf(`t%d`, i),
"password" : fmt.Sprintf(`p%d`, i),
"nickname" : fmt.Sprintf(`T%d`, i),
"create_time" : gtime.Now().String(),
})
}
result, err := db.Table(name).Data(array.Slice()).Insert()
gtest.Assert(err, nil)
n, e := result.RowsAffected()
gtest.Assert(e, nil)
gtest.Assert(n, INIT_DATA_SIZE)
return
}

View File

@ -9,6 +9,15 @@ import (
"testing"
)
func TestDbBase_Ping(t *testing.T) {
gtest.Case(t, func() {
err1 := db.PingMaster()
err2 := db.PingSlave()
gtest.Assert(err1, nil)
gtest.Assert(err2, nil)
})
}
func TestDbBase_Query(t *testing.T) {
if _, err := db.Query("SELECT ?", 1); err != nil {
gtest.Fatal(err)
@ -144,57 +153,102 @@ func TestDbBase_Insert(t *testing.T) {
}
func TestDbBase_BatchInsert(t *testing.T) {
if r, err := db.BatchInsert("user", g.List {
{
"id" : 2,
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
},
{
"id" : 3,
"passport" : "t3",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T3",
"create_time" : gtime.Now().String(),
},
}, 1); err != nil {
gtest.Fatal(err)
} else {
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
}
gtest.Case(t, func() {
if r, err := db.BatchInsert("user", g.List {
{
"id" : 2,
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
},
{
"id" : 3,
"passport" : "t3",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T3",
"create_time" : gtime.Now().String(),
},
}, 1); err != nil {
gtest.Fatal(err)
} else {
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
}
result, err := db.Delete("user", "id>?", 1)
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 2)
// []interface{}
if r, err := db.BatchInsert("user", []interface{} {
map[interface{}]interface{} {
"id" : 2,
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
},
map[interface{}]interface{} {
"id" : 3,
"passport" : "t3",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T3",
"create_time" : gtime.Now().String(),
},
}, 1); err != nil {
gtest.Fatal(err)
} else {
n, _ := r.RowsAffected()
result, err := db.Delete("user", "id>?", 1)
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 2)
}
// []interface{}
if r, err := db.BatchInsert("user", []interface{} {
map[interface{}]interface{} {
"id" : 2,
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
},
map[interface{}]interface{} {
"id" : 3,
"passport" : "t3",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T3",
"create_time" : gtime.Now().String(),
},
}, 1); err != nil {
gtest.Fatal(err)
} else {
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
}
})
// batch insert map
gtest.Case(t, func() {
table := createTable()
defer dropTable(table)
result, err := db.BatchInsert(table, g.Map{
"id" : 1,
"passport" : "t1",
"password" : "p1",
"nickname" : "T1",
"create_time" : gtime.Now().String(),
})
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
})
// batch insert struct
gtest.Case(t, func() {
table := createTable()
defer dropTable(table)
type User struct {
Id int `gconv:"id"`
Passport string `gconv:"passport"`
Password string `gconv:"password"`
NickName string `gconv:"nickname"`
CreateTime *gtime.Time `gconv:"create_time"`
}
user := &User{
Id : 1,
Passport : "t1",
Password : "p1",
NickName : "T1",
CreateTime : gtime.Now(),
}
result, err := db.BatchInsert(table, user)
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
})
}
func TestDbBase_Save(t *testing.T) {
@ -266,19 +320,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

@ -90,29 +90,66 @@ func TestModel_Insert(t *testing.T) {
}
func TestModel_Batch(t *testing.T) {
result, err := db.Table("user").Filter().Data(g.List{
{
"id" : 2,
"uid" : 2,
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
},
{
"id" : 3,
"uid" : 3,
"passport" : "t3",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T3",
"create_time" : gtime.Now().String(),
},
}).Batch(1).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 2)
// batch insert
gtest.Case(t, func() {
result, err := db.Table("user").Filter().Data(g.List{
{
"id" : 2,
"uid" : 2,
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
},
{
"id" : 3,
"uid" : 3,
"passport" : "t3",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T3",
"create_time" : gtime.Now().String(),
},
}).Batch(1).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 2)
})
// batch save
gtest.Case(t, func() {
table := createInitTable()
defer dropTable(table)
result, err := db.Table(table).All()
gtest.Assert(err, nil)
gtest.Assert(len(result), INIT_DATA_SIZE)
for _, v := range result {
v["nickname"].Set(v["nickname"].String() + v["id"].String())
}
r, e := db.Table(table).Data(result).Save()
gtest.Assert(e, nil)
n, e := r.RowsAffected()
gtest.Assert(e, nil)
gtest.Assert(n, INIT_DATA_SIZE*2)
})
// batch replace
gtest.Case(t, func() {
table := createInitTable()
defer dropTable(table)
result, err := db.Table(table).All()
gtest.Assert(err, nil)
gtest.Assert(len(result), INIT_DATA_SIZE)
for _, v := range result {
v["nickname"].Set(v["nickname"].String() + v["id"].String())
}
r, e := db.Table(table).Data(result).Replace()
gtest.Assert(e, nil)
n, e := r.RowsAffected()
gtest.Assert(e, nil)
gtest.Assert(n, INIT_DATA_SIZE*2)
})
}
func TestModel_Replace(t *testing.T) {
@ -121,7 +158,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 +173,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)
@ -146,12 +183,23 @@ func TestModel_Save(t *testing.T) {
}
func TestModel_Update(t *testing.T) {
result, err := db.Table("user").Data("passport", "t22").Where("passport=?", "t2").Update()
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
gtest.Case(t, func() {
result, err := db.Table("user").Data("passport", "t22").Where("passport=?", "t2").Update()
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
})
gtest.Case(t, func() {
result, err := db.Table("user").Data("passport", "t2").Where("passport='t22'").Update()
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
})
}
func TestModel_Clone(t *testing.T) {
@ -168,13 +216,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 +301,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 +479,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 +536,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 +590,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

@ -8,21 +8,21 @@
package gjson
import (
"errors"
"github.com/gogf/gf/g/text/gregex"
"strings"
"strconv"
"io/ioutil"
"encoding/json"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/util/gconv"
"errors"
"fmt"
"github.com/gogf/gf/g/encoding/gtoml"
"github.com/gogf/gf/g/encoding/gxml"
"github.com/gogf/gf/g/encoding/gyaml"
"github.com/gogf/gf/g/encoding/gtoml"
"github.com/gogf/gf/g/text/gstr"
"time"
"github.com/gogf/gf/g/internal/rwmutex"
"fmt"
"github.com/gogf/gf/g/os/gfcache"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
"strconv"
"strings"
"time"
)
const (
@ -110,11 +110,7 @@ func DecodeToJson (b []byte) (*Json, error) {
// 支持多种配置文件类型转换为json格式内容并解析为gjson.Json对象
func Load (path string) (*Json, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
return LoadContent(data, gfile.Ext(path))
return LoadContent(gfcache.GetBinContents(path), gfile.Ext(path))
}
// 支持的配置文件格式xml, json, yaml/yml, toml,
@ -546,6 +542,11 @@ func (j *Json) Get(pattern...string) interface{} {
return nil
}
// 判断锁给定pattern是否数据存在
func (j *Json) Contains(pattern...string) bool {
return j.Get(pattern...) != nil
}
// 计算指定pattern的元素长度(pattern对应数据类型为map[string]interface{}/[]interface{}时有效)
func (j *Json) Len(pattern string) int {
p := j.getPointerByPattern(pattern)

View File

@ -73,7 +73,7 @@ func View(name...string) *gview.View {
}
key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_VIEW, group)
return instances.GetOrSetFuncLock(key, func() interface{} {
path := cmdenv.Get("gf.gview.path", gfile.SelfDir()).String()
path := cmdenv.Get("gf.gview.path", gfile.Pwd()).String()
view := gview.New(path)
// 添加基于源码的搜索目录检索地址,常用于开发环境调试,只添加入口文件目录
if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) {
@ -94,11 +94,22 @@ func Config(file...string) *gcfg.Config {
}
return instances.GetOrSetFuncLock(fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_CONFIG, configFile),
func() interface{} {
path := cmdenv.Get("gf.gcfg.path", gfile.SelfDir()).String()
config := gcfg.New(path, configFile)
// 添加基于源码的搜索目录检索地址,常用于开发环境调试,只添加入口文件目录
if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) {
config.AddPath(p)
pwdPath := gfile.Pwd()
envPath := cmdenv.Get("gf.gcfg.path").String()
selfPath := gfile.SelfDir()
mainPath := gfile.MainPkgPath()
config := gcfg.New(pwdPath, configFile)
// 自定义的环境变量/启动参数路径,优先级最高,覆盖默认的工作目录
if envPath != "" && gfile.Exists(envPath) {
config.SetPath(envPath)
}
// 二进制文件执行目录
if selfPath != "" && gfile.Exists(selfPath) {
config.AddPath(selfPath)
}
// 开发环境源码main包目录
if mainPath != "" && gfile.Exists(mainPath) {
config.AddPath(mainPath)
}
return config
}).(*gcfg.Config)

View File

@ -0,0 +1,43 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gins_test
import (
"github.com/gogf/gf/g/frame/gins"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func Test_SetGet(t *testing.T) {
gtest.Case(t, func() {
gins.Set("test-user", 1)
gtest.Assert(gins.Get("test-user"), 1)
gtest.Assert(gins.Get("none-exists"), nil)
})
gtest.Case(t, func() {
gtest.Assert(gins.GetOrSet("test-1", 1), 1)
gtest.Assert(gins.Get("test-1"), 1)
})
gtest.Case(t, func() {
gtest.Assert(gins.GetOrSetFunc("test-2", func() interface{} {
return 2
}), 2)
gtest.Assert(gins.Get("test-2"), 2)
})
gtest.Case(t, func() {
gtest.Assert(gins.GetOrSetFuncLock("test-3", func() interface{} {
return 3
}), 3)
gtest.Assert(gins.Get("test-3"), 3)
})
gtest.Case(t, func() {
gtest.Assert(gins.SetIfNotExist("test-4", 4), true)
gtest.Assert(gins.Get("test-4"), 4)
gtest.Assert(gins.SetIfNotExist("test-4", 5), false)
gtest.Assert(gins.Get("test-4"), 4)
})
}

View File

@ -0,0 +1,166 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gins_test
import (
"fmt"
"github.com/gogf/gf/g/frame/gins"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Config(t *testing.T) {
config := `
# 模板引擎目录
viewpath = "/home/www/templates/"
test = "v=1"
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = "8692651"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
# Redis数据库配置
[redis]
disk = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"
`
gtest.Case(t, func() {
gtest.AssertNE(gins.Config(), nil)
})
// relative path
gtest.Case(t, func() {
path := "config.toml"
err := gfile.PutContents(path, config)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer gins.Config().Reload()
gtest.Assert(gins.Config().Get("test"), "v=1")
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500*time.Millisecond)
// relative path, config folder
gtest.Case(t, func() {
path := "config/config.toml"
err := gfile.PutContents(path, config)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer gins.Config().Reload()
gtest.Assert(gins.Config().Get("test"), "v=1")
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
path := "test.toml"
err := gfile.PutContents(path, config)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer gins.Config().Reload()
gtest.Assert(gins.Config("test.toml").Get("test"), "v=1")
gtest.Assert(gins.Config("test.toml").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config("test.toml").Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
path := "config/test.toml"
err := gfile.PutContents(path, config)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer gins.Config().Reload()
gtest.Assert(gins.Config("test.toml").Get("test"), "v=1")
gtest.Assert(gins.Config("test.toml").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config("test.toml").Get("redis.disk"), "127.0.0.1:6379,0")
})
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500*time.Millisecond)
// absolute path
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.Nanosecond())
file := fmt.Sprintf(`%s/%s`, path, "config.toml")
err := gfile.PutContents(file, config)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer gins.Config().Reload()
gtest.Assert(gins.Config().AddPath(path), nil)
gtest.Assert(gins.Config().Get("test"), "v=1")
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d/config`, gfile.TempDir(), gtime.Nanosecond())
file := fmt.Sprintf(`%s/%s`, path, "config.toml")
err := gfile.PutContents(file, config)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer gins.Config().Reload()
gtest.Assert(gins.Config().AddPath(path), nil)
gtest.Assert(gins.Config().Get("test"), "v=1")
gtest.Assert(gins.Config().Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config().Get("redis.disk"), "127.0.0.1:6379,0")
})
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.Nanosecond())
file := fmt.Sprintf(`%s/%s`, path, "test.toml")
err := gfile.PutContents(file, config)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer gins.Config("test.toml").Reload()
gtest.Assert(gins.Config("test.toml").AddPath(path), nil)
gtest.Assert(gins.Config("test.toml").Get("test"), "v=1")
gtest.Assert(gins.Config("test.toml").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config("test.toml").Get("redis.disk"), "127.0.0.1:6379,0")
})
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d/config`, gfile.TempDir(), gtime.Nanosecond())
file := fmt.Sprintf(`%s/%s`, path, "test.toml")
err := gfile.PutContents(file, config)
gtest.Assert(err, nil)
defer gfile.Remove(file)
defer gins.Config("test.toml").Reload()
gtest.Assert(gins.Config("test.toml").AddPath(path), nil)
gtest.Assert(gins.Config("test.toml").Get("test"), "v=1")
gtest.Assert(gins.Config("test.toml").Get("database.default.1.host"), "127.0.0.1")
gtest.Assert(gins.Config("test.toml").Get("redis.disk"), "127.0.0.1:6379,0")
})
}

View File

@ -0,0 +1,75 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gins_test
import (
"fmt"
"github.com/gogf/gf/g/frame/gins"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Database(t *testing.T) {
config := `
# 模板引擎目录
viewpath = "/home/www/templates/"
test = "v=2"
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
# pass = "12345678"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
[[database.test]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
# pass = "12345678"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
# Redis数据库配置
[redis]
default = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"
`
path := "config.toml"
err := gfile.PutContents(path, config)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer gins.Config().Reload()
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
fmt.Println("gins Test_Database", gins.Config().Get("test"))
dbDefault := gins.Database()
dbTest := gins.Database("test")
gtest.AssertNE(dbDefault, nil)
gtest.AssertNE(dbTest, nil)
gtest.Assert(dbDefault.PingMaster(), nil)
gtest.Assert(dbDefault.PingSlave(), nil)
gtest.Assert(dbTest.PingMaster(), nil)
gtest.Assert(dbTest.PingSlave(), nil)
})
}

View File

@ -0,0 +1,78 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gins_test
import (
"fmt"
"github.com/gogf/gf/g/frame/gins"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Redis(t *testing.T) {
config := `
# 模板引擎目录
viewpath = "/home/www/templates/"
test = "v=3"
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
# pass = "12345678"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
[[database.test]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = ""
# pass = "12345678"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"
# Redis数据库配置
[redis]
default = "127.0.0.1:6379,0"
cache = "127.0.0.1:6379,1"
`
path := "config.toml"
err := gfile.PutContents(path, config)
gtest.Assert(err, nil)
defer gfile.Remove(path)
defer gins.Config().Reload()
// for gfsnotify callbacks to refresh cache of config file
time.Sleep(500*time.Millisecond)
gtest.Case(t, func() {
fmt.Println("gins Test_Redis", gins.Config().Get("test"))
redisDefault := gins.Redis()
redisCache := gins.Redis("cache")
gtest.AssertNE(redisDefault, nil)
gtest.AssertNE(redisCache, nil)
r, err := redisDefault.Do("PING")
gtest.Assert(err, nil)
gtest.Assert(r, "PONG")
r, err = redisCache.Do("PING")
gtest.Assert(err, nil)
gtest.Assert(r, "PONG")
})
}

View File

@ -0,0 +1,49 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gins_test
import (
"fmt"
"github.com/gogf/gf/g/frame/gins"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
)
func Test_View(t *testing.T) {
gtest.Case(t, func() {
gtest.AssertNE(gins.View(), nil)
b, e := gins.View().ParseContent(`{{"我是中国人" | substr 2 -1}}`, nil)
gtest.Assert(e, nil)
gtest.Assert(string(b), "中国人")
})
gtest.Case(t, func() {
tpl := "t.tpl"
err := gfile.PutContents(tpl, `{{"我是中国人" | substr 2 -1}}`)
gtest.Assert(err, nil)
defer gfile.Remove(tpl)
b, e := gins.View().Parse("t.tpl", nil)
gtest.Assert(e, nil)
gtest.Assert(string(b), "中国人")
})
gtest.Case(t, func() {
path := fmt.Sprintf(`%s/%d`, gfile.TempDir(), gtime.Nanosecond())
tpl := fmt.Sprintf(`%s/%s`, path, "t.tpl")
err := gfile.PutContents(tpl, `{{"我是中国人" | substr 2 -1}}`)
gtest.Assert(err, nil)
defer gfile.Remove(tpl)
err = gins.View().AddPath(path)
gtest.Assert(err, nil)
b, e := gins.View().Parse("t.tpl", nil)
gtest.Assert(e, nil)
gtest.Assert(string(b), "中国人")
})
}

31
g/g.go
View File

@ -3,34 +3,51 @@
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// 常用数据类型以及对象封装
package g
import "github.com/gogf/gf/g/container/gvar"
// 框架动态变量可以用该类型替代interface{}类型
type Var = gvar.Var
// Universal variable type, like generics.
//
// 动态变量类型可以用该类型替代interface{}类型
type Var = gvar.Var
// Frequently-used map type alias.
//
// 常用map数据结构(使用别名)
type Map = map[string]interface{}
type MapAnyAny = map[interface{}]interface{}
type MapAnyStr = map[interface{}]string
type MapAnyInt = map[interface{}]int
type MapStrAny = map[string]interface{}
type MapStrStr = map[string]string
type MapStrInt = map[string]int
type MapIntAny = map[int]interface{}
type MapIntStr = map[int]string
type MapIntInt = map[int]int
// Frequently-used slice type alias.
//
// 常用list数据结构(使用别名)
type List = []Map
type ListAnyStr = []map[interface{}]string
type ListAnyInt = []map[interface{}]int
type ListStrAny = []map[string]interface{}
type ListStrStr = []map[string]string
type ListStrInt = []map[string]int
type ListIntAny = []map[int]interface{}
type ListIntStr = []map[int]string
type ListIntInt = []map[int]int
// Frequently-used slice type alias.
//
// 常用slice数据结构(使用别名)
type Slice = []interface{}
type SliceAny = []interface{}
type SliceStr = []string
type SliceInt = []int
type Array = Slice
type ArrayStr = SliceStr
type ArrayInt = SliceInt
type Array = []interface{}
type ArrayAny = []interface{}
type ArrayStr = []string
type ArrayInt = []int

View File

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

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

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

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

@ -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())
}
}
@ -94,8 +104,8 @@ func (s *Session) Contains (key string) bool {
return false
}
// 获取SESSION
func (s *Session) Get (key string) interface{} {
// 获取SESSION变量
func (s *Session) Get(key string) interface{} {
if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" {
s.init()
return s.data.Get(key)
@ -131,112 +141,95 @@ func (s *Session) UpdateExpire() {
}
}
// Deprecated, use GetVar instead.
func (s *Session) GetString(key string) string {
return gconv.String(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetBool(key string) bool {
return gconv.Bool(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetInt(key string) int {
return gconv.Int(s.Get(key)) }
// Deprecated, use GetVar instead.
func (s *Session) GetInt8(key string) int8 {
return gconv.Int8(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetInt16(key string) int16 {
return gconv.Int16(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetInt32(key string) int32 {
return gconv.Int32(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetInt64(key string) int64 {
return gconv.Int64(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetUint(key string) uint {
return gconv.Uint(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetUint8(key string) uint8 {
return gconv.Uint8(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetUint16(key string) uint16 {
return gconv.Uint16(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetUint32(key string) uint32 {
return gconv.Uint32(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetUint64(key string) uint64 {
return gconv.Uint64(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetFloat32(key string) float32 {
return gconv.Float32(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetFloat64(key string) float64 {
return gconv.Float64(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetBytes(key string) []byte {
return gconv.Bytes(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetInts(key string) []int {
return gconv.Ints(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetFloats(key string) []float64 {
return gconv.Floats(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetStrings(key string) []string {
return gconv.Strings(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetInterfaces(key string) []interface{} {
return gconv.Interfaces(s.Get(key))
}
// Deprecated, use GetVar instead.
func (s *Session) GetTime(key string, format...string) time.Time {
return gconv.Time(s.Get(key), format...)
}
// Deprecated, use GetVar instead.
func (s *Session) GetGTime(key string, format...string) *gtime.Time {
return gconv.GTime(s.Get(key), format...)
}
func (s *Session) GetTimeDuration(key string) time.Duration {
return gconv.TimeDuration(s.Get(key))
}
// Deprecated, use GetVar instead.
// (已废弃, 请使用GetVar) 将变量转换为对象,注意 objPointer 参数必须为struct指针
// 将变量转换为对象,注意 objPointer 参数必须为struct指针
func (s *Session) GetStruct(key string, objPointer interface{}, attrMapping...map[string]string) error {
return gconv.Struct(s.Get(key), objPointer, attrMapping...)
}

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

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

@ -31,15 +31,15 @@ var serverMapping = gmap.NewStringInterfaceMap()
// 获取/创建一个空配置的TCP Server
// 单例模式请保证name的唯一性
func GetServer(name...interface{}) (*Server) {
sname := gDEFAULT_SERVER
serverName := gDEFAULT_SERVER
if len(name) > 0 {
sname = gconv.String(name[0])
serverName = gconv.String(name[0])
}
if s := serverMapping.Get(sname); s != nil {
if s := serverMapping.Get(serverName); s != nil {
return s.(*Server)
}
s := NewServer("", nil)
serverMapping.Set(sname, s)
serverMapping.Set(serverName, s)
return s
}
@ -65,19 +65,24 @@ func (s *Server) SetHandler (handler func (*Conn)) {
// 执行监听
func (s *Server) Run() error {
if s.handler == nil {
return errors.New("start running failed: socket handler not defined")
}
tcpaddr, err := net.ResolveTCPAddr("tcp", s.address)
if err != nil {
err := errors.New("start running failed: socket handler not defined")
glog.Error(err)
return err
}
listen, err := net.ListenTCP("tcp", tcpaddr)
addr, err := net.ResolveTCPAddr("tcp", s.address)
if err != nil {
glog.Error(err)
return err
}
listen, err := net.ListenTCP("tcp", addr)
if err != nil {
glog.Error(err)
return err
}
for {
if conn, err := listen.Accept(); err != nil {
glog.Error(err)
return err
} else if conn != nil {
go s.handler(NewConnByNetConn(conn))
}

View File

@ -8,6 +8,7 @@
package gudp
import (
"github.com/gogf/gf/g/os/glog"
"net"
"errors"
"github.com/gogf/gf/g/container/gmap"
@ -30,15 +31,15 @@ var serverMapping = gmap.NewStringInterfaceMap()
// 获取/创建一个空配置的UDP Server
// 单例模式请保证name的唯一性
func GetServer(name...interface{}) (*Server) {
sname := gDEFAULT_SERVER
serverName := gDEFAULT_SERVER
if len(name) > 0 {
sname = gconv.String(name[0])
serverName = gconv.String(name[0])
}
if s := serverMapping.Get(sname); s != nil {
if s := serverMapping.Get(serverName); s != nil {
return s.(*Server)
}
s := NewServer("", nil)
serverMapping.Set(sname, s)
serverMapping.Set(serverName, s)
return s
}
@ -64,14 +65,18 @@ func (s *Server) SetHandler (handler func (*Conn)) {
// 执行监听
func (s *Server) Run() error {
if s.handler == nil {
return errors.New("start running failed: socket handler not defined")
err := errors.New("start running failed: socket handler not defined")
glog.Error(err)
return err
}
addr, err := net.ResolveUDPAddr("udp", s.address)
if err != nil {
glog.Error(err)
return err
}
conn, err := net.ListenUDP("udp", addr)
if err != nil {
glog.Error(err)
return err
}
for {

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,19 +58,15 @@ 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()
if len(file) > 0 {
name = file[0]
}
c.paths.RLockFunc(func(array []string) {
for _, v := range array {
if path, _ = gspath.Search(v, name); path != "" {
break
}
}
})
path = c.GetFilePath(name)
if path == "" {
buffer := bytes.NewBuffer(nil)
if c.paths.Len() > 0 {
@ -128,7 +127,8 @@ func (c *Config) AddPath(path string) error {
return nil
}
// 获取指定文件的绝对路径,默认获取默认的配置文件路径,当指定的配置文件不存在时,返回空字符串,并且不会报错。
// 查找配置文件,获取指定配置文件的绝对路径,默认获取默认的配置文件路径
// 当指定的配置文件不存在时,返回空字符串,并且不会报错。
func (c *Config) GetFilePath(file...string) (path string) {
name := c.name.Val()
if len(file) > 0 {
@ -136,9 +136,14 @@ func (c *Config) GetFilePath(file...string) (path string) {
}
c.paths.RLockFunc(func(array []string) {
for _, v := range array {
// 查找当前目录
if path, _ = gspath.Search(v, name); path != "" {
break
}
// 查找当前目录下的config子目录
if path, _ = gspath.Search(v, "config" + gfile.Separator + name); path != "" {
break
}
}
})
return
@ -186,6 +191,14 @@ func (c *Config) GetVar(pattern string, file...string) gvar.VarRead {
return gvar.New(nil, true)
}
// 判断指定的配置项是否存在
func (c *Config) Contains(pattern string, file...string) bool {
if j := c.getJson(file...); j != nil {
return j.Contains(pattern)
}
return false
}
// 获得一个键值对关联数组/哈希表,方便操作,不需要自己做类型转换
// 注意如果获取的值不存在或者类型与json类型不匹配那么将会返回nil
func (c *Config) GetMap(pattern string, file...string) map[string]interface{} {

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

@ -10,52 +10,52 @@
package gfcache
import (
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/internal/cmdenv"
"github.com/gogf/gf/g/os/gcache"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gfsnotify"
)
type Cache struct {
cap *gtype.Int // 缓存容量(byte)设置为0表示不限制
size *gtype.Int // 缓存大小(Byte)
cache *gmap.StringInterfaceMap // 缓存对象
}
const (
// 默认的缓存容量(10MB)
gDEFAULT_CACHE_CAP = 10*1024*1024
// 默认的缓存超时时间(60秒)
gDEFAULT_CACHE_EXPIRE = 60
)
var (
// 默认的缓存容量
cacheCap = cmdenv.Get("gf.gfcache.cap", gDEFAULT_CACHE_CAP).Int()
// 默认的文件缓存对象
cache = New()
// 默认的缓存时间(秒)
cacheExpire = cmdenv.Get("gf.gfcache.expire", gDEFAULT_CACHE_EXPIRE).Int()*1000
)
func New(cap ... int) *Cache {
c := cacheCap
if len(cap) > 0 {
c = cap[0]
// 获得文件内容 stringexpire参数为缓存过期时间单位为秒。
func GetContents(path string, expire...int) string {
return string(GetBinContents(path, expire...))
}
// 获得文件内容 []byteexpire参数为缓存过期时间单位为秒。
func GetBinContents(path string, expire...int) []byte {
k := cacheKey(path)
e := cacheExpire
if len(expire) > 0 {
e = expire[0]
}
return &Cache {
cap : gtype.NewInt(c),
size : gtype.NewInt(),
cache : gmap.NewStringInterfaceMap(),
r := gcache.GetOrSetFuncLock(k, func() interface{} {
b := gfile.GetBinContents(path)
if b != nil {
// 添加文件监控,如果文件有任何变化,立即清空缓存
gfsnotify.Add(path, func(event *gfsnotify.Event) {
gcache.Remove(k)
gfsnotify.Exit()
})
}
return b
}, e*1000)
if r != nil {
return r.([]byte)
}
return nil
}
// 获得已缓存的文件大小(byte)
func GetSize() int {
return cache.GetSize()
}
// 获得文件内容 string
func GetContents(path string) string {
return cache.GetContents(path)
}
// 获得文件内容 []byte
func GetBinContents(path string) []byte {
return cache.GetBinContents(path)
// 生成缓存键名
func cacheKey(path string) string {
return "gf.gfcache:" + path
}

View File

@ -1,62 +0,0 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// 文件缓存.
package gfcache
import (
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gfsnotify"
)
// 设置容量大小(byte)
func (c *Cache) SetCap(cap int) {
c.cap.Set(cap)
}
// 获得缓存容量大小(byte)
func (c *Cache) GetCap() int {
return c.cap.Val()
}
// 获得已缓存的文件大小(byte)
func (c *Cache) GetSize() int {
return c.size.Val()
}
// 获得文件内容 string
func (c *Cache) GetContents(path string) string {
return string(c.GetBinContents(path))
}
// 获得文件内容 []byte
func (c *Cache) GetBinContents(path string) []byte {
if v := c.cache.Get(path); v != nil {
return v.([]byte)
}
b := gfile.GetBinContents(path)
// 读取到内容,并且没有超过缓存容量限制时才会执行缓存
if len(b) > 0 && (c.cap.Val() == 0 || c.size.Val() < c.cap.Val()) {
c.size.Add(len(b))
c.addMonitor(path)
c.cache.Set(path, b)
}
return b
}
// 添加文件监控,一旦文件有变化立即清除缓存,下一次读取的时候再执行缓存。
func (c *Cache) addMonitor(path string) {
// 防止多goroutine同时调用
if c.cache.Contains(path) {
return
}
gfsnotify.Add(path, func(event *gfsnotify.Event) {
if r := c.cache.Get(path); r != nil {
c.cache.Remove(path)
c.size.Add(-len(r.([]byte)))
}
})
}

View File

@ -122,11 +122,12 @@ func IsDir(path string) bool {
return s.IsDir()
}
// Get current working absolute directory path.
// Get current working directory absolute path.
//
// 获取当前工作目录(SelfDir()方法的别名)
// 获取当前工作目录(注意与SelfDir的区别).
func Pwd() string {
return SelfDir()
path, _ := os.Getwd()
return path
}
// Check whether given path a file(not a directory).
@ -140,11 +141,18 @@ func IsFile(path string) bool {
return !s.IsDir()
}
// Info returns a FileInfo describing the named file.
// See Stat.
//
// Stat 方法的别名。
func Info(path string) (os.FileInfo, error) {
return Stat(path)
}
// Stat returns a FileInfo describing the named file.
// If there is an error, it will be of type *PathError.
//
// 获取文件或目录信息.
func Info(path string) (os.FileInfo, error) {
func Stat(path string) (os.FileInfo, error) {
return os.Stat(path)
}

View File

@ -24,7 +24,7 @@ func GetContents(path string) string {
return string(GetBinContents(path))
}
// (二进制)读取文件内容
// (二进制)读取文件内容如果文件不存在或者读取失败返回nil。
func GetBinContents(path string) []byte {
data, err := ioutil.ReadFile(path)
if err != nil {

View File

@ -59,7 +59,8 @@ const (
)
const (
REPEAT_EVENT_FILTER_INTERVAL = 1 // (毫秒)重复事件过滤间隔
REPEAT_EVENT_FILTER_INTERVAL = 1 // (毫秒)重复事件过滤间隔
gFSNOTIFY_EVENT_EXIT = "exit" // 是否退出回调执行
)
var (
@ -113,3 +114,8 @@ func RemoveCallback(callbackId int) error {
defaultWatcher.RemoveCallback(callbackId)
return nil
}
// 在回调方法中调用该方法退出回调注册
func Exit() {
panic(gFSNOTIFY_EVENT_EXIT)
}

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{} {
@ -74,7 +74,7 @@ func (w *Watcher) getCallbacks(path string) (callbacks []*Callback) {
return
}
// 事件循环
// 事件循环(核心逻辑)
func (w *Watcher) startEventLoop() {
go func() {
for {
@ -126,10 +126,22 @@ func (w *Watcher) startEventLoop() {
}
// 执行回调处理,异步处理
for _, callback := range callbacks {
go callback.Func(event)
for _, v := range callbacks {
go func(callback *Callback) {
defer func() {
// 是否退出监控
if err := recover(); err != nil {
switch err {
case gFSNOTIFY_EVENT_EXIT:
w.RemoveCallback(callback.Id)
default:
panic(err)
}
}
}()
callback.Func(event)
}(v)
}
} else {
break
}

View File

@ -0,0 +1,155 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*" -benchmem
package gfsnotify_test
import (
"github.com/gogf/gf/g/container/gtype"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gfsnotify"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/util/gconv"
"testing"
"time"
)
func TestWatcher_AddRemove(t *testing.T) {
gtest.Case(t, func() {
path1 := gconv.String(gtime.Nanosecond())
path2 := gconv.String(gtime.Nanosecond()) + "2"
gfile.PutContents(path1, "1")
defer func() {
gfile.Remove(path1)
gfile.Remove(path2)
}()
v := gtype.NewInt(1)
callback, err := gfsnotify.Add(path1, func(event *gfsnotify.Event) {
if event.IsWrite() {
v.Set(2)
return
}
if event.IsRename() {
v.Set(3)
gfsnotify.Exit()
return
}
})
gtest.Assert(err, nil)
gtest.AssertNE(callback, nil)
gfile.PutContents(path1, "2")
time.Sleep(100*time.Millisecond)
gtest.Assert(v.Val(), 2)
gfile.Rename(path1, path2)
time.Sleep(100*time.Millisecond)
gtest.Assert(v.Val(), 3)
})
gtest.Case(t, func() {
path1 := gconv.String(gtime.Nanosecond())
gfile.PutContents(path1, "1")
defer func() {
gfile.Remove(path1)
}()
v := gtype.NewInt(1)
callback, err := gfsnotify.Add(path1, func(event *gfsnotify.Event) {
if event.IsWrite() {
v.Set(2)
return
}
if event.IsRemove() {
v.Set(4)
return
}
})
gtest.Assert(err, nil)
gtest.AssertNE(callback, nil)
gfile.PutContents(path1, "2")
time.Sleep(100*time.Millisecond)
gtest.Assert(v.Val(), 2)
gfile.Remove(path1)
time.Sleep(100*time.Millisecond)
gtest.Assert(v.Val(), 4)
gfile.PutContents(path1, "1")
time.Sleep(100*time.Millisecond)
gtest.Assert(v.Val(), 4)
})
}
func TestWatcher_Callback(t *testing.T) {
gtest.Case(t, func() {
path1 := gconv.String(gtime.Nanosecond())
gfile.PutContents(path1, "1")
defer func() {
gfile.Remove(path1)
}()
v := gtype.NewInt(1)
callback, err := gfsnotify.Add(path1, func(event *gfsnotify.Event) {
if event.IsWrite() {
v.Set(2)
return
}
})
gtest.Assert(err, nil)
gtest.AssertNE(callback, nil)
gfile.PutContents(path1, "2")
time.Sleep(100*time.Millisecond)
gtest.Assert(v.Val(), 2)
v.Set(3)
gfsnotify.RemoveCallback(callback.Id)
gfile.PutContents(path1, "3")
time.Sleep(100*time.Millisecond)
gtest.Assert(v.Val(), 3)
})
// multiple callbacks
gtest.Case(t, func() {
path1 := gconv.String(gtime.Nanosecond())
gfile.PutContents(path1, "1")
defer func() {
gfile.Remove(path1)
}()
v1 := gtype.NewInt(1)
v2 := gtype.NewInt(1)
callback1, err1 := gfsnotify.Add(path1, func(event *gfsnotify.Event) {
if event.IsWrite() {
v1.Set(2)
return
}
})
callback2, err2 := gfsnotify.Add(path1, func(event *gfsnotify.Event) {
if event.IsWrite() {
v2.Set(2)
return
}
})
gtest.Assert(err1, nil)
gtest.Assert(err2, nil)
gtest.AssertNE(callback1, nil)
gtest.AssertNE(callback2, nil)
gfile.PutContents(path1, "2")
time.Sleep(100*time.Millisecond)
gtest.Assert(v1.Val(), 2)
gtest.Assert(v2.Val(), 2)
v1.Set(3)
v2.Set(3)
gfsnotify.RemoveCallback(callback1.Id)
gfile.PutContents(path1, "3")
time.Sleep(100*time.Millisecond)
gtest.Assert(v1.Val(), 3)
gtest.Assert(v2.Val(), 2)
})
}

View File

@ -7,7 +7,8 @@
// Package gspath implements file index and search for folders.
//
// 搜索目录管理,
// 可以添加搜索目录,按照添加的优先级进行文件检索,并在内部进行高效缓存处理。
// 可以添加搜索目录,按照添加的优先级进行文件检索,并在内部进行高效缓存处理(可选)
// 注意:当开启缓存功能后,在新增/删除文件时,会存在检索延迟。
package gspath
import (
@ -16,9 +17,8 @@ import (
"github.com/gogf/gf/g/container/garray"
"github.com/gogf/gf/g/container/gmap"
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gfsnotify"
"github.com/gogf/gf/g/text/gstr"
"runtime"
"os"
"sort"
"strings"
)
@ -26,7 +26,7 @@ import (
// 文件目录搜索管理对象
type SPath struct {
paths *garray.StringArray // 搜索路径,按照优先级进行排序
cache *gmap.StringStringMap // 搜索结果缓存map
cache *gmap.StringStringMap // 搜索结果缓存map(如果未nil表示未启用缓存功能)
}
// 文件搜索缓存项
@ -37,17 +37,20 @@ type SPathCacheItem struct {
var (
// 单个目录路径对应的SPath对象指针用于路径检索对象复用
pathsMap = gmap.NewStringInterfaceMap()
pathsMap = gmap.NewStringInterfaceMap()
pathsCacheMap = gmap.NewStringInterfaceMap()
)
// 创建一个搜索对象
func New(path...string) *SPath {
func New(path string, cache bool) *SPath {
sp := &SPath {
paths : garray.NewStringArray(),
cache : gmap.NewStringStringMap(),
}
if cache {
sp.cache = gmap.NewStringStringMap()
}
if len(path) > 0 {
if _, err := sp.Add(path[0]); err != nil {
if _, err := sp.Add(path); err != nil {
//fmt.Errorf(err.Error())
}
}
@ -55,17 +58,21 @@ func New(path...string) *SPath {
}
// 创建/获取一个单例的搜索对象, root必须为目录的绝对路径
func Get(root string) *SPath {
func Get(root string, cache bool) *SPath {
return pathsMap.GetOrSetFuncLock(root, func() interface{} {
return New(root)
return New(root, cache)
}).(*SPath)
}
// 检索root目录(必须为绝对路径)下面的name文件的绝对路径indexFiles用于指定当检索到的结果为目录时同时检索是否存在这些indexFiles文件
func Search(root string, name string, indexFiles...string) (filePath string, isDir bool) {
return Get(root).Search(name, indexFiles...)
return Get(root, false).Search(name, indexFiles...)
}
// 检索root目录(必须为绝对路径)下面的name文件的绝对路径indexFiles用于指定当检索到的结果为目录时同时检索是否存在这些indexFiles文件
func SearchWithCache(root string, name string, indexFiles...string) (filePath string, isDir bool) {
return Get(root, true).Search(name, indexFiles...)
}
// 设置搜索路径,只保留当前设置项,其他搜索路径被清空
func (sp *SPath) Set(path string) (realPath string, err error) {
@ -88,8 +95,9 @@ func (sp *SPath) Set(path string) (realPath string, err error) {
}
}
sp.paths.Clear()
sp.cache.Clear()
if sp.cache != nil {
sp.cache.Clear()
}
sp.paths.Append(realPath)
sp.updateCacheByPath(realPath)
sp.addMonitorByPath(realPath)
@ -128,9 +136,39 @@ func (sp *SPath) Add(path string) (realPath string, err error) {
}
// 给定的name只是相对文件路径找不到该文件时返回空字符串;
// 当给定indexFiles时如果name一个目录那么会进一步检索其下对应的indexFiles文件是否存在存在则返回indexFile绝对路径
// 当给定indexFiles时如果name一个目录那么会进一步检索其下对应的indexFiles文件是否存在存在则返回indexFile绝对路径
// 否则返回name目录绝对路径。
func (sp *SPath) Search(name string, indexFiles...string) (filePath string, isDir bool) {
// 不使用缓存
if sp.cache == nil {
sp.paths.LockFunc(func(array []string) {
path := ""
for _, v := range array {
path = v + gfile.Separator + name
if stat, err := os.Stat(path); !os.IsNotExist(err) {
filePath = path
isDir = stat.IsDir()
break
}
}
})
if len(indexFiles) > 0 && isDir {
if name == "/" {
name = ""
}
path := ""
for _, file := range indexFiles {
path = filePath + gfile.Separator + file
if gfile.Exists(path) {
filePath = path
isDir = false
break
}
}
}
return
}
// 使用缓存功能
name = sp.formatCacheName(name)
if v := sp.cache.Get(name); v != "" {
filePath, isDir = sp.parseCacheValue(v)
@ -151,6 +189,9 @@ func (sp *SPath) Search(name string, indexFiles...string) (filePath string, isDi
// 从搜索路径中移除指定的文件,这样该文件无法给搜索。
// path可以是绝对路径也可以相对路径。
func (sp *SPath) Remove(path string) {
if sp.cache == nil {
return
}
if gfile.Exists(path) {
for _, v := range sp.paths.Slice() {
name := gstr.Replace(path, v, "")
@ -170,6 +211,9 @@ func (sp *SPath) Paths() []string {
// 返回当前对象缓存的所有路径列表
func (sp *SPath) AllPaths() []string {
if sp.cache == nil {
return nil
}
paths := sp.cache.Keys()
if len(paths) > 0 {
sort.Strings(paths)
@ -181,82 +225,3 @@ func (sp *SPath) AllPaths() []string {
func (sp *SPath) Size() int {
return sp.paths.Len()
}
// 递归添加目录下的文件
func (sp *SPath) updateCacheByPath(path string) {
sp.addToCache(path, path)
}
// 格式化name返回符合规范的缓存名称分隔符号统一为'/',且前缀必须以'/'开头(类似HTTP URI).
func (sp *SPath) formatCacheName(name string) string {
if runtime.GOOS != "linux" {
name = gstr.Replace(name, "\\", "/")
}
return "/" + strings.Trim(name, "./")
}
// 根据path计算出对应的缓存name, dirPath为检索根目录路径
func (sp *SPath) nameFromPath(filePath, rootPath string) string {
name := gstr.Replace(filePath, rootPath, "")
name = sp.formatCacheName(name)
return name
}
// 按照一定数据结构生成缓存的数据项字符串
func (sp *SPath) makeCacheValue(filePath string, isDir bool) string {
if isDir {
return filePath + "_D_"
}
return filePath + "_F_"
}
// 按照一定数据结构解析数据项字符串
func (sp *SPath) parseCacheValue(value string) (filePath string, isDir bool) {
if value[len(value) - 2 : len(value) - 1][0] == 'F' {
return value[: len(value) - 3], false
}
return value[: len(value) - 3], true
}
// 添加path到缓存中(递归)
func (sp *SPath) addToCache(filePath, rootPath string) {
// 首先添加自身
idDir := gfile.IsDir(filePath)
sp.cache.SetIfNotExist(sp.nameFromPath(filePath, rootPath), sp.makeCacheValue(filePath, idDir))
// 如果添加的是目录,那么需要递归添加
if idDir {
if files, err := gfile.ScanDir(filePath, "*", true); err == nil {
//fmt.Println("gspath add to cache:", filePath, files)
for _, path := range files {
sp.cache.SetIfNotExist(sp.nameFromPath(path, rootPath), sp.makeCacheValue(path, gfile.IsDir(path)))
}
} else {
//fmt.Errorf(err.Error())
}
}
}
// 添加文件目录监控(递归),当目录下的文件有更新时,会同时更新缓存。
// 这里需要注意的点是,由于添加监听是递归添加的,那么假如删除一个目录,那么该目录下的文件(包括目录)也会产生一条删除事件总共会产生N条事件。
func (sp *SPath) addMonitorByPath(path string) {
gfsnotify.Add(path, func(event *gfsnotify.Event) {
//glog.Debug(event.String())
switch {
case event.IsRemove():
sp.cache.Remove(sp.nameFromPath(event.Path, path))
case event.IsRename():
if !gfile.Exists(event.Path) {
sp.cache.Remove(sp.nameFromPath(event.Path, path))
}
case event.IsCreate():
sp.addToCache(event.Path, path)
}
}, true)
}
// 删除监听(递归)
func (sp *SPath) removeMonitorByPath(path string) {
gfsnotify.Remove(path)
}

109
g/os/gspath/gspath_cache.go Normal file
View File

@ -0,0 +1,109 @@
// 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 gspath implements file index and search for folders.
//
package gspath
import (
"github.com/gogf/gf/g/os/gfile"
"github.com/gogf/gf/g/os/gfsnotify"
"github.com/gogf/gf/g/text/gstr"
"runtime"
"strings"
)
// 递归添加目录下的文件
func (sp *SPath) updateCacheByPath(path string) {
if sp.cache == nil {
return
}
sp.addToCache(path, path)
}
// 格式化name返回符合规范的缓存名称分隔符号统一为'/',且前缀必须以'/'开头(类似HTTP URI).
func (sp *SPath) formatCacheName(name string) string {
if runtime.GOOS != "linux" {
name = gstr.Replace(name, "\\", "/")
}
return "/" + strings.Trim(name, "./")
}
// 根据path计算出对应的缓存name, dirPath为检索根目录路径
func (sp *SPath) nameFromPath(filePath, rootPath string) string {
name := gstr.Replace(filePath, rootPath, "")
name = sp.formatCacheName(name)
return name
}
// 按照一定数据结构生成缓存的数据项字符串
func (sp *SPath) makeCacheValue(filePath string, isDir bool) string {
if isDir {
return filePath + "_D_"
}
return filePath + "_F_"
}
// 按照一定数据结构解析数据项字符串
func (sp *SPath) parseCacheValue(value string) (filePath string, isDir bool) {
if value[len(value) - 2 : len(value) - 1][0] == 'F' {
return value[: len(value) - 3], false
}
return value[: len(value) - 3], true
}
// 添加path到缓存中(递归)
func (sp *SPath) addToCache(filePath, rootPath string) {
if sp.cache == nil {
return
}
// 首先添加自身
idDir := gfile.IsDir(filePath)
sp.cache.SetIfNotExist(sp.nameFromPath(filePath, rootPath), sp.makeCacheValue(filePath, idDir))
// 如果添加的是目录,那么需要递归添加
if idDir {
if files, err := gfile.ScanDir(filePath, "*", true); err == nil {
//fmt.Println("gspath add to cache:", filePath, files)
for _, path := range files {
sp.cache.SetIfNotExist(sp.nameFromPath(path, rootPath), sp.makeCacheValue(path, gfile.IsDir(path)))
}
} else {
//fmt.Errorf(err.Error())
}
}
}
// 添加文件目录监控(递归),当目录下的文件有更新时,会同时更新缓存。
// 这里需要注意的点是,由于添加监听是递归添加的,那么假如删除一个目录,那么该目录下的文件(包括目录)也会产生一条删除事件总共会产生N条事件。
func (sp *SPath) addMonitorByPath(path string) {
if sp.cache == nil {
return
}
gfsnotify.Add(path, func(event *gfsnotify.Event) {
//glog.Debug(event.String())
switch {
case event.IsRemove():
sp.cache.Remove(sp.nameFromPath(event.Path, path))
case event.IsRename():
if !gfile.Exists(event.Path) {
sp.cache.Remove(sp.nameFromPath(event.Path, path))
}
case event.IsCreate():
sp.addToCache(event.Path, path)
}
}, true)
}
// 删除监听(递归)
func (sp *SPath) removeMonitorByPath(path string) {
if sp.cache == nil {
return
}
gfsnotify.Remove(path)
}

View File

@ -1,37 +0,0 @@
// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package gspath
import (
"testing"
)
var (
sp = New()
)
func init() {
sp.Add("/Users/john/Temp")
}
func Benchmark_Search(b *testing.B) {
for i := 0; i < b.N; i++ {
sp.Search("1")
}
}
func Benchmark_Search_None(b *testing.B) {
for i := 0; i < b.N; i++ {
sp.Search("1000")
}
}
func Benchmark_Search_IndexFiles(b *testing.B) {
for i := 0; i < b.N; i++ {
sp.Search("1", "index.html")
}
}

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

@ -54,7 +54,7 @@ func checkAndInitDefaultView() {
// gfile.MainPkgPath() 用以判断是否开发环境
mainPkgPath := gfile.MainPkgPath()
if gfile.MainPkgPath() == "" {
viewObj = New(gfile.SelfDir())
viewObj = New(gfile.Pwd())
} else {
viewObj = New(mainPkgPath)
}

View File

@ -1,3 +1,3 @@
// from golang-1.11.2
// from golang-1.11.2 text/template
// 1. remove "<no value>" when template variable does not exist;
package text

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

@ -235,39 +235,6 @@ func Test_Shuffle(t *testing.T) {
})
}
func Test_Trim(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gstr.Trim(" 123456\n "), "123456")
gtest.Assert(gstr.Trim("#123456#;", "#;"), "123456")
})
}
func Test_TrimRight(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gstr.TrimRight(" 123456\n "), " 123456")
gtest.Assert(gstr.TrimRight("#123456#;", "#;"), "#123456")
})
}
func Test_TrimRightStr(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gstr.TrimRightStr("gogo我爱gogo", "go"), "gogo我爱")
})
}
func Test_TrimLeft(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gstr.TrimLeft(" \r123456\n "), "123456\n ")
gtest.Assert(gstr.TrimLeft("#;123456#;", "#;"), "123456#;")
})
}
func Test_TrimLeftStr(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gstr.TrimLeftStr("gogo我爱gogo", "go"), "我爱gogo")
})
}
func Test_Split(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gstr.Split("1.2", "."), []string{"1", "2"})
@ -335,4 +302,24 @@ func Test_QuoteMeta(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gstr.QuoteMeta(`.\+*?[^]($)`), `\.\\\+\*\?\[\^\]\(\$\)`)
})
}
func Test_Count(t *testing.T) {
gtest.Case(t, func() {
s := "abcdaAD"
gtest.Assert(gstr.Count(s, "0"), 0)
gtest.Assert(gstr.Count(s, "a"), 2)
gtest.Assert(gstr.Count(s, "b"), 1)
gtest.Assert(gstr.Count(s, "d"), 1)
})
}
func Test_CountI(t *testing.T) {
gtest.Case(t, func() {
s := "abcdaAD"
gtest.Assert(gstr.CountI(s, "0"), 0)
gtest.Assert(gstr.CountI(s, "a"), 3)
gtest.Assert(gstr.CountI(s, "b"), 1)
gtest.Assert(gstr.CountI(s, "d"), 2)
})
}

View File

@ -0,0 +1,48 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// go test *.go -bench=".*"
package gstr_test
import (
"github.com/gogf/gf/g/test/gtest"
"github.com/gogf/gf/g/text/gstr"
"testing"
)
func Test_Trim(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gstr.Trim(" 123456\n "), "123456")
gtest.Assert(gstr.Trim("#123456#;", "#;"), "123456")
})
}
func Test_TrimRight(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gstr.TrimRight(" 123456\n "), " 123456")
gtest.Assert(gstr.TrimRight("#123456#;", "#;"), "#123456")
})
}
func Test_TrimRightStr(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gstr.TrimRightStr("gogo我爱gogo", "go"), "gogo我爱")
})
}
func Test_TrimLeft(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gstr.TrimLeft(" \r123456\n "), "123456\n ")
gtest.Assert(gstr.TrimLeft("#;123456#;", "#;"), "123456#;")
})
}
func Test_TrimLeftStr(t *testing.T) {
gtest.Case(t, func() {
gtest.Assert(gstr.TrimLeftStr("gogo我爱gogo", "go"), "我爱gogo")
})
}

View File

@ -13,6 +13,7 @@ package gconv
import (
"encoding/json"
"github.com/gogf/gf/g/encoding/gbinary"
"reflect"
"strconv"
"strings"
)
@ -22,6 +23,16 @@ type apiString interface {
String() string
}
var (
// 为空的字符串
emptyStringMap = map[string]struct{}{
"" : struct {}{},
"0" : struct {}{},
"off" : struct {}{},
"false" : struct {}{},
}
)
// 将变量i转换为字符串指定的类型t非必须参数extraParams用以额外的参数传递
func Convert(i interface{}, t string, extraParams...interface{}) interface{} {
switch t {
@ -47,13 +58,23 @@ 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
}
}
// 转换为二进制[]byte
func Bytes(i interface{}) []byte {
if i == nil {
return nil
@ -71,11 +92,11 @@ func String(i interface{}) string {
return ""
}
switch value := i.(type) {
case int: return strconv.Itoa(value)
case int: return strconv.FormatInt(int64(value), 10)
case int8: return strconv.Itoa(int(value))
case int16: return strconv.Itoa(int(value))
case int32: return strconv.Itoa(int(value))
case int64: return strconv.Itoa(int(value))
case int64: return strconv.FormatInt(int64(value), 10)
case uint: return strconv.FormatUint(uint64(value), 10)
case uint8: return strconv.FormatUint(uint64(value), 10)
case uint16: return strconv.FormatUint(uint64(value), 10)
@ -98,7 +119,7 @@ func String(i interface{}) string {
}
}
//false: "", 0, false, off
//false: false, "", 0, "false", "off", empty slice/map
func Bool(i interface{}) bool {
if i == nil {
return false
@ -106,10 +127,27 @@ func Bool(i interface{}) bool {
if v, ok := i.(bool); ok {
return v
}
if s := String(i); s != "" && s != "0" && s != "false" && s != "off" {
if s, ok := i.(string); ok {
if _, ok := emptyStringMap[s]; ok {
return false
}
return true
}
return false
rv := reflect.ValueOf(i)
switch rv.Kind() {
case reflect.Ptr: return !rv.IsNil()
case reflect.Map: fallthrough
case reflect.Array: fallthrough
case reflect.Slice: return rv.Len() != 0
case reflect.Struct: return true
default:
s := String(i)
if _, ok := emptyStringMap[s]; ok {
return false
}
return true
}
}
func Int(i interface{}) int {

View File

@ -320,4 +320,24 @@ func Interfaces(i interface{}) []interface{} {
}
return array
}
}
// 将类型转换为[]map[string]interface{}类型.
func Maps(i interface{}) []map[string]interface{} {
if i == nil {
return nil
}
if r, ok := i.([]map[string]interface{}); ok {
return r
} else {
array := Interfaces(i)
if len(array) == 0 {
return nil
}
list := make([]map[string]interface{}, len(array))
for k, v := range array {
list[k] = Map(v)
}
return list
}
}

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