Compare commits

...

28 Commits

Author SHA1 Message Date
5c9f0db903 version updates 2019-03-04 23:37:01 +08:00
28abf0c175 add ReplaceI/ReplaceIByArray/ReplaceIByMap case-insensetive replacing functions for gstr 2019-03-04 23:35:06 +08:00
13749feab4 add json features '-' and 'omitempty' for gconv.Map, like package 'json' from stdlib; add internal package 'empty', to check empty variable 2019-03-04 22:59:29 +08:00
c1e77b7e09 update example of gtcp 2019-03-03 20:40:36 +08:00
962a5e93f7 update example of gtcp 2019-03-03 20:20:31 +08:00
c3b9b8d5ae gconv updates 2019-03-03 00:53:35 +08:00
1ad076c522 travis updates 2019-03-03 00:31:16 +08:00
b01777fcd1 travis updates 2019-03-03 00:28:32 +08:00
55a5532c2e gdb updates 2019-03-03 00:14:20 +08:00
adb928941a issue template updates 2019-03-02 00:02:05 +08:00
f92c1fc527 update CORS feature of ghttp.Response; add more unit cases for ghttp.Server 2019-03-01 23:45:55 +08:00
3ae7279ebc issue template updates 2019-03-01 14:01:05 +08:00
66287c2d0e update the admin feature and unit test cases of ghttp 2019-02-28 23:57:20 +08:00
5d37626981 hot fix issue in router registry 2019-02-28 14:07:00 +08:00
d0ed3b979d release updates 2019-02-28 10:28:09 +08:00
fa256aec9f add example for layout using template engine; fix issue with config error output in gview when no config used 2019-02-27 22:53:39 +08:00
86834c5a15 version and comment updates 2019-02-27 22:17:09 +08:00
c2046157d6 add ghttp.Request.GetRawString function 2019-02-27 21:17:56 +08:00
cdb2cc89c0 remove password for unit test of gdb 2019-02-27 12:51:48 +08:00
4964c09a77 add more unit test cases for gdb 2019-02-27 12:38:12 +08:00
ef34b2c9ce gdb updates, add batch operation support for Insert/Save/Replace, change list param type from List to interface{} for Batch* functions 2019-02-27 09:38:10 +08:00
9afe242293 Merge branch 'master' into qiangg_gdb_map 2019-02-27 08:52:50 +08:00
136d93d373 add donate for gitee 2019-02-26 23:39:09 +08:00
3102cec5b8 issue template updates 2019-02-26 23:06:14 +08:00
e28eb9da04 add issue template 2019-02-26 22:58:52 +08:00
754ed86dfb add issue template 2019-02-26 22:56:52 +08:00
7f44f2f5e4 gdb updates 2019-02-26 14:23:29 +08:00
66efbe63f0 add struct support for *Insert/*Save/*Replace/*Update/Where/Data functions 2019-02-26 01:19:01 +08:00
63 changed files with 2224 additions and 685 deletions

34
.gitee/ISSUE_TEMPLATE.MD Normal file
View File

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

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

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

View File

@ -1,7 +1,8 @@
language: go
go:
- "1.11.x"
- "1.11.x"
- "1.12.x"
branches:
only:
@ -9,25 +10,25 @@ branches:
- develop
env:
- GO111MODULE=on
- GO111MODULE=on
services:
- mysql
- mysql
before_install:
- pwd
- pwd
install:
- pwd
- pwd
script:
- cd g
- GOARCH=386 go test -v ./...
- GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
- cd g
- GOARCH=386 go test -v ./...
- GOARCH=amd64 go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
after_success:
- bash <(curl -s https://codecov.io/bash)
- bash <(curl -s https://codecov.io/bash)

View File

@ -81,6 +81,14 @@ func main() {
`GF` 使用非常友好的 [MIT](LICENSE) 开源协议进行发布,永久`100%`开源免费。
# 捐赠
捐赠支持`GF`框架的研发,
请在捐赠时备注您的`github`/`gitee`账号名称。
<a href="https://goframe.org/images/donate.png" target="_blank">
<img src="https://goframe.org/images/donate.png" width="300"/>
</a>
# 贡献者(TOP 10)

View File

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

View File

@ -52,9 +52,9 @@
1. 从ghttp中剥离SESSION功能构成单独的模块gsession
1. 改进gproc进程间通信处理逻辑提高稳定性以应对进程间大批量的数据发送/接收;
1. gdb的Data方法支持struct参数传入
1. gfcache依旧使用gcache作为缓存控制对象不要使用gmap
1. 增加对ghttp路由注册的{.struct}/{.method}单元测试;
1. 更新跨域请求CORS相关功能文档

View File

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

View File

@ -14,8 +14,8 @@ import (
"github.com/gogf/gf/g/container/gvar"
"github.com/gogf/gf/g/os/gcache"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/util/gconv"
"reflect"
"strings"
)
@ -234,33 +234,60 @@ func (bs *dbBase) Begin() (*TX, error) {
}
}
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
func (bs *dbBase) Insert(table string, data Map) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_INSERT)
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
// 参数data支持map/struct/*struct/slice类型
// 当为slice(例如[]map/[]struct/[]*struct)类型时batch参数生效并自动切换为批量操作。
func (bs *dbBase) Insert(table string, data interface{}, batch...int) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_INSERT, batch...)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (bs *dbBase) Replace(table string, data Map) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_REPLACE)
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
// 参数data支持map/struct/*struct/slice类型
// 当为slice(例如[]map/[]struct/[]*struct)类型时batch参数生效并自动切换为批量操作。
func (bs *dbBase) Replace(table string, data interface{}, batch...int) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_REPLACE, batch...)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (bs *dbBase) Save(table string, data Map) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_SAVE)
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
// 参数data支持map/struct/*struct/slice类型
// 当为slice(例如[]map/[]struct/[]*struct)类型时batch参数生效并自动切换为批量操作。
func (bs *dbBase) Save(table string, data interface{}, batch...int) (sql.Result, error) {
return bs.db.doInsert(nil, table, data, OPTION_SAVE, batch...)
}
// insert、replace, save ignore操作
// 0: insert: 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
// 1: replace: 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
// 2: save: 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
// 3: ignore: 如果数据存在(主键或者唯一索引),那么什么也不做
func (bs *dbBase) doInsert(link dbLink, table string, data Map, option int) (result sql.Result, err error) {
var fields []string
var values []string
var params []interface{}
charl, charr := bs.db.getChars()
for k, v := range data {
fields = append(fields, charl + k + charr)
// 支持insert、replace, save ignore操作
// 0: insert: 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回;
// 1: replace: 如果数据存在(主键或者唯一索引),那么删除后重新写入一条;
// 2: save: 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据;
// 3: ignore: 如果数据存在(主键或者唯一索引),那么什么也不做;
//
// 参数data支持map/struct/*struct/slice类型
// 当为slice(例如[]map/[]struct/[]*struct)类型时batch参数生效并自动切换为批量操作。
func (bs *dbBase) doInsert(link dbLink, table string, data interface{}, option int, batch...int) (result sql.Result, err error) {
var fields []string
var values []string
var params []interface{}
var dataMap Map
// 使用反射判断data数据类型如果为slice类型那么自动转为批量操作
rv := reflect.ValueOf(data)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
case reflect.Slice: fallthrough
case reflect.Array:
return bs.db.doBatchInsert(link, table, data, option, batch...)
case reflect.Map: fallthrough
case reflect.Struct:
dataMap = Map(gconv.Map(data))
default:
return result, errors.New(fmt.Sprint("unsupported data type:", kind))
}
charL, charR := bs.db.getChars()
for k, v := range dataMap {
fields = append(fields, charL + k + charR)
values = append(values, "?")
params = append(params, v)
}
@ -268,11 +295,11 @@ func (bs *dbBase) doInsert(link dbLink, table string, data Map, option int) (res
updateStr := ""
if option == OPTION_SAVE {
var updates []string
for k, _ := range data {
for k, _ := range dataMap {
updates = append(updates,
fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
charl, k, charr,
charl, k, charr,
charL, k, charR,
charL, k, charR,
),
)
}
@ -290,28 +317,55 @@ func (bs *dbBase) doInsert(link dbLink, table string, data Map, option int) (res
}
// CURD操作:批量数据指定批次量写入
func (bs *dbBase) BatchInsert(table string, list List, batch int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, batch, OPTION_INSERT)
func (bs *dbBase) BatchInsert(table string, list interface{}, batch...int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, OPTION_INSERT, batch...)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (bs *dbBase) BatchReplace(table string, list List, batch int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, batch, OPTION_REPLACE)
func (bs *dbBase) BatchReplace(table string, list interface{}, batch...int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, OPTION_REPLACE, batch...)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (bs *dbBase) BatchSave(table string, list List, batch int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, batch, OPTION_SAVE)
func (bs *dbBase) BatchSave(table string, list interface{}, batch...int) (sql.Result, error) {
return bs.db.doBatchInsert(nil, table, list, OPTION_SAVE, batch...)
}
// 批量写入数据
func (bs *dbBase) doBatchInsert(link dbLink, table string, list List, batch int, option int) (result sql.Result, err error) {
var keys []string
var values []string
var bvalues []string
var params []interface{}
// 批量写入数据, 参数list支持slice类型例如: []map/[]struct/[]*struct。
func (bs *dbBase) doBatchInsert(link dbLink, table string, list interface{}, option int, batch...int) (result sql.Result, err error) {
var keys []string
var values []string
var params []interface{}
listMap := (List)(nil)
switch v := list.(type) {
case List:
listMap = v
case Map:
listMap = List{v}
default:
rv := reflect.ValueOf(list)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
// 如果是slice那么转换为List类型
case reflect.Slice: fallthrough
case reflect.Array:
listMap = make(List, rv.Len())
for i := 0; i < rv.Len(); i++ {
listMap[i] = gconv.Map(rv.Index(i).Interface())
}
case reflect.Map: fallthrough
case reflect.Struct:
listMap = List{Map(gconv.Map(list))}
default:
return result, errors.New(fmt.Sprint("unsupported list type:", kind))
}
}
// 判断长度
if len(list) < 1 {
if len(listMap) < 1 {
return result, errors.New("empty data list")
}
if link == nil {
@ -320,14 +374,15 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list List, batch int,
}
}
// 首先获取字段名称及记录长度
for k, _ := range list[0] {
keys = append(keys, k)
values = append(values, "?")
holders := []string(nil)
for k, _ := range listMap[0] {
keys = append(keys, k)
holders = append(holders, "?")
}
batchResult := new(batchSqlResult)
charl, charr := bs.db.getChars()
keyStr := charl + strings.Join(keys, charl + "," + charr) + charr
valueHolderStr := "(" + strings.Join(values, ",") + ")"
charL, charR := bs.db.getChars()
keyStr := charL + strings.Join(keys, charL + "," + charR) + charR
valueHolderStr := "(" + strings.Join(holders, ",") + ")"
// 操作判断
operation := getInsertOperationByOption(option)
updateStr := ""
@ -336,22 +391,26 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list List, batch int,
for _, k := range keys {
updates = append(updates,
fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
charl, k, charr,
charl, k, charr,
charL, k, charR,
charL, k, charR,
),
)
}
updateStr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
}
// 构造批量写入数据格式(注意map的遍历是无序的)
for i := 0; i < len(list); i++ {
batchNum := gDEFAULT_BATCH_NUM
if len(batch) > 0 {
batchNum = batch[0]
}
for i := 0; i < len(listMap); i++ {
for _, k := range keys {
params = append(params, list[i][k])
params = append(params, listMap[i][k])
}
bvalues = append(bvalues, valueHolderStr)
if len(bvalues) == batch {
values = append(values, valueHolderStr)
if len(values) == batchNum {
r, err := bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
operation, table, keyStr, strings.Join(values, ","),
updateStr),
params...)
if err != nil {
@ -363,14 +422,14 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list List, batch int,
batchResult.lastResult = r
batchResult.rowsAffected += n
}
params = params[:0]
bvalues = bvalues[:0]
params = params[:0]
values = values[:0]
}
}
// 处理最后不构成指定批量的数据
if len(bvalues) > 0 {
if len(values) > 0 {
r, err := bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
operation, table, keyStr, strings.Join(bvalues, ","),
operation, table, keyStr, strings.Join(values, ","),
updateStr),
params...)
if err != nil {
@ -386,8 +445,8 @@ func (bs *dbBase) doBatchInsert(link dbLink, table string, list List, batch int,
return batchResult, nil
}
// CURD操作:数据更新统一采用sql预处理
// data参数支持字符串或者关联数组类型,内部会自行做判断处理
// CURD操作:数据更新统一采用sql预处理
// data参数支持string/map/struct/*struct类型。
func (bs *dbBase) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
link, err := bs.db.Master()
if err != nil {
@ -396,23 +455,30 @@ func (bs *dbBase) Update(table string, data interface{}, condition interface{},
return bs.db.doUpdate(link, table, data, condition, args ...)
}
// CURD操作:数据更新统一采用sql预处理
// data参数支持字符串或者关联数组类型,内部会自行做判断处理
// 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)
updates := ""
charl, charr := bs.db.getChars()
refValue := reflect.ValueOf(data)
if refValue.Kind() == reflect.Map {
var fields []string
keys := refValue.MapKeys()
for _, k := range keys {
fields = append(fields, fmt.Sprintf("%s%s%s=?", charl, k, charr))
params = append(params, gconv.String(refValue.MapIndex(k).Interface()))
}
updates = strings.Join(fields, ",")
} else {
updates = gconv.String(data)
charL, charR := bs.db.getChars()
// 使用反射进行类型判断
rv := reflect.ValueOf(data)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
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))
}
updates = strings.Join(fields, ",")
default:
updates = gconv.String(data)
}
for _, v := range args {
params = append(params, gconv.String(v))

View File

@ -12,42 +12,44 @@ import (
"fmt"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/util/gconv"
"github.com/gogf/gf/g/text/gregex"
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
_ "github.com/gogf/gf/third/github.com/go-sql-driver/mysql"
"reflect"
"strings"
)
// 格式化SQL查询条件
func formatCondition(where interface{}, args []interface{}) (string, []interface{}) {
func formatCondition(where interface{}, args []interface{}) (newWhere string, newArgs []interface{}) {
// 条件字符串处理
buffer := bytes.NewBuffer(nil)
if reflect.ValueOf(where).Kind() == reflect.Map {
ks := reflect.ValueOf(where).MapKeys()
vs := reflect.ValueOf(where)
for _, k := range ks {
key := gconv.String(k.Interface())
value := gconv.String(vs.MapIndex(k).Interface())
if buffer.Len() > 0 {
buffer.WriteString(" AND ")
// 使用反射进行类型判断
rv := reflect.ValueOf(where)
kind := rv.Kind()
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
switch kind {
// 注意当where为map/struct类型时args参数必须为空。
case reflect.Map: fallthrough
case reflect.Struct:
for k, v := range gconv.Map(where) {
if buffer.Len() > 0 {
buffer.WriteString(" AND ")
}
buffer.WriteString(k + "=?")
newArgs = append(newArgs, v)
}
if gstr.IsNumeric(value) || value == "?" {
buffer.WriteString(key + "=" + value)
} else {
buffer.WriteString(key + "='" + value + "'")
}
}
} else {
buffer.Write(gconv.Bytes(where))
newWhere = buffer.String()
default:
buffer.WriteString(gconv.String(where))
}
if buffer.Len() == 0 {
buffer.WriteString("1=1")
}
// 查询条件处理
newWhere := buffer.String()
newArgs := make([]interface{}, 0)
// 查询条件参数处理主要处理slice参数类型
newWhere = buffer.String()
if len(args) > 0 {
for index, arg := range args {
rv := reflect.ValueOf(arg)
@ -57,6 +59,8 @@ func formatCondition(where interface{}, args []interface{}) (string, []interface
kind = rv.Kind()
}
switch kind {
// '?'占位符支持slice类型,
// 这里会将slice参数拆散并更新原有占位符'?'为多个'?',使用','符号连接。
case reflect.Slice: fallthrough
case reflect.Array:
for i := 0; i < rv.Len(); i++ {
@ -75,7 +79,7 @@ func formatCondition(where interface{}, args []interface{}) (string, []interface
}
}
}
return newWhere, newArgs
return
}
// 打印SQL对象(仅在debug=true时有效)
@ -109,13 +113,13 @@ func formatError(err error, query string, args ...interface{}) error {
// 根据insert选项获得操作名称
func getInsertOperationByOption(option int) string {
oper := "INSERT"
operator := "INSERT"
switch option {
case OPTION_REPLACE:
oper = "REPLACE"
operator = "REPLACE"
case OPTION_SAVE:
case OPTION_IGNORE:
oper = "INSERT IGNORE"
operator = "INSERT IGNORE"
}
return oper
return operator
}

View File

@ -200,13 +200,14 @@ func (md *Model) Cache(time int, name ... string) *Model {
return model
}
// 链式操作,操作数据记录项,可以是string/Map, 也可以是key,value,key,value,...
func (md *Model) Data(data ...interface{}) (*Model) {
// 链式操作,操作数据参数data类型支持 string/map/slice/struct/*struct ,
// 也可以是key,value,key,value,...。
func (md *Model) Data(data ...interface{}) *Model {
model := md.Clone()
if len(data) > 1 {
m := make(map[string]interface{})
for i := 0; i < len(data); i += 2 {
m[gconv.String(data[i])] = data[i+1]
m[gconv.String(data[i])] = data[i + 1]
}
model.data = m
} else {
@ -223,6 +224,7 @@ func (md *Model) Data(data ...interface{}) (*Model) {
kind = rv.Kind()
}
switch kind {
// 如果是slice那么转换为List类型
case reflect.Slice: fallthrough
case reflect.Array:
list := make(List, rv.Len())
@ -230,8 +232,9 @@ func (md *Model) Data(data ...interface{}) (*Model) {
list[i] = gconv.Map(rv.Index(i).Interface())
}
model.data = list
case reflect.Map:
model.data = gconv.Map(data[0])
case reflect.Map: fallthrough
case reflect.Struct:
model.data = Map(gconv.Map(data[0]))
default:
model.data = data[0]
}
@ -240,7 +243,9 @@ func (md *Model) Data(data ...interface{}) (*Model) {
return model
}
// 链式操作, CURD - Insert/BatchInsert
// 链式操作, CURD - Insert/BatchInsert
// 根据Data方法传递的参数类型决定该操作是单条操作还是批量操作
// 如果Data方法传递的是slice类型那么为批量操作。
func (md *Model) Insert() (result sql.Result, err error) {
defer func() {
if err == nil {
@ -279,7 +284,9 @@ func (md *Model) Insert() (result sql.Result, err error) {
return nil, errors.New("inserting into table with invalid data type")
}
// 链式操作, CURD - Replace/BatchReplace
// 链式操作, CURD - Replace/BatchReplace
// 根据Data方法传递的参数类型决定该操作是单条操作还是批量操作
// 如果Data方法传递的是slice类型那么为批量操作。
func (md *Model) Replace() (result sql.Result, err error) {
defer func() {
if err == nil {
@ -318,7 +325,9 @@ func (md *Model) Replace() (result sql.Result, err error) {
return nil, errors.New("replacing into table with invalid data type")
}
// 链式操作, CURD - Save/BatchSave
// 链式操作, CURD - Save/BatchSave
// 根据Data方法传递的参数类型决定该操作是单条操作还是批量操作
// 如果Data方法传递的是slice类型那么为批量操作。
func (md *Model) Save() (result sql.Result, err error) {
defer func() {
if err == nil {
@ -330,7 +339,7 @@ func (md *Model) Save() (result sql.Result, err error) {
}
// 批量操作
if list, ok := md.data.(List); ok {
batch := 10
batch := gDEFAULT_BATCH_NUM
if md.batch > 0 {
batch = md.batch
}

View File

@ -85,8 +85,8 @@ func (bs *dbBase) getTableFields(table string) (fields map[string]string, err er
// 缓存不存在时会查询数据表结构,缓存后不过期,直至程序重启(重新部署)
v := bs.cache.GetOrSetFunc("table_fields_" + table, func() interface{} {
result := (Result)(nil)
charl, charr := bs.db.getChars()
result, err = bs.GetAll(fmt.Sprintf(`SHOW COLUMNS FROM %s%s%s`, charl, table, charr))
charL, charR := bs.db.getChars()
result, err = bs.GetAll(fmt.Sprintf(`SHOW COLUMNS FROM %s%s%s`, charL, table, charR))
if err != nil {
return nil
}

View File

@ -100,33 +100,33 @@ func (tx *TX) GetCount(query string, args ...interface{}) (int, error) {
}
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
func (tx *TX) Insert(table string, data Map) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_INSERT)
func (tx *TX) Insert(table string, data interface{}, batch...int) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_INSERT, batch...)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (tx *TX) Replace(table string, data Map) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_REPLACE)
func (tx *TX) Replace(table string, data interface{}, batch...int) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_REPLACE, batch...)
}
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (tx *TX) Save(table string, data Map) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_SAVE)
func (tx *TX) Save(table string, data interface{}, batch...int) (sql.Result, error) {
return tx.db.doInsert(tx.tx, table, data, OPTION_SAVE, batch...)
}
// CURD操作:批量数据指定批次量写入
func (tx *TX) BatchInsert(table string, list List, batch int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, batch, OPTION_INSERT)
func (tx *TX) BatchInsert(table string, list interface{}, batch...int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, OPTION_INSERT, batch...)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
func (tx *TX) BatchReplace(table string, list List, batch int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, batch, OPTION_REPLACE)
func (tx *TX) BatchReplace(table string, list interface{}, batch...int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, OPTION_REPLACE, batch...)
}
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
func (tx *TX) BatchSave(table string, list List, batch int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, batch, OPTION_SAVE)
func (tx *TX) BatchSave(table string, list interface{}, batch...int) (sql.Result, error) {
return tx.db.doBatchInsert(tx.tx, table, list, OPTION_SAVE, batch...)
}
// CURD操作:数据更新统一采用sql预处理

View File

@ -3,6 +3,7 @@ package gdb_test
import (
"github.com/gogf/gf/g/database/gdb"
"github.com/gogf/gf/g/test/gtest"
"os"
)
var (
@ -13,7 +14,7 @@ var (
// 初始化连接参数。
// 测试前需要修改连接参数。
func init() {
gdb.AddDefaultConfigNode(gdb.ConfigNode{
node := gdb.ConfigNode{
Host: "127.0.0.1",
Port: "3306",
User: "root",
@ -22,8 +23,13 @@ func init() {
Type: "mysql",
Role: "master",
Charset: "utf8",
Priority: 1,
})
Priority: 1,
}
hostname, _ := os.Hostname()
if hostname == "ijohn" {
node.Pass = "12345678"
}
gdb.AddDefaultConfigNode(node)
if r, err := gdb.New(); err != nil {
gtest.Fatal(err)
} else {

View File

@ -56,6 +56,91 @@ func TestDbBase_Insert(t *testing.T) {
}); err != nil {
gtest.Fatal(err)
}
// normal map
result, err := db.Insert("user", map[interface{}]interface{} {
"id" : "2",
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
})
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 1)
// struct
type User struct {
Id int `gconv:"id"`
Passport string `json:"passport"`
Password string `gconv:"password"`
Nickname string `gconv:"nickname"`
CreateTime string `json:"create_time"`
}
result, err = db.Insert("user", User{
Id : 3,
Passport : "t3",
Password : "25d55ad283aa400af464c76d713c07ad",
Nickname : "T3",
CreateTime : gtime.Now().String(),
})
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 1)
value, err := db.GetValue("select `passport` from `user` where id=?", 3)
gtest.Assert(err, nil)
gtest.Assert(value.String(), "t3")
// *struct
result, err = db.Insert("user", &User{
Id : 4,
Passport : "t4",
Password : "25d55ad283aa400af464c76d713c07ad",
Nickname : "T4",
CreateTime : gtime.Now().String(),
})
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 1)
value, err = db.GetValue("select `passport` from `user` where id=?", 4)
gtest.Assert(err, nil)
gtest.Assert(value.String(), "t4")
// batch with Insert
if r, err := db.Insert("user", []interface{} {
map[interface{}]interface{} {
"id" : 200,
"passport" : "t200",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T200",
"create_time" : gtime.Now().String(),
},
map[interface{}]interface{} {
"id" : 300,
"passport" : "t300",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T300",
"create_time" : gtime.Now().String(),
},
}); err != nil {
gtest.Fatal(err)
} else {
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
}
// clear unnecessary data
result, err = db.Delete("user", "id>?", 1)
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 5)
}
func TestDbBase_BatchInsert(t *testing.T) {
@ -80,6 +165,36 @@ func TestDbBase_BatchInsert(t *testing.T) {
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
}
result, err := db.Delete("user", "id>?", 1)
if err != nil {
gtest.Fatal(err)
}
n, _ := result.RowsAffected()
gtest.Assert(n, 2)
// []interface{}
if r, err := db.BatchInsert("user", []interface{} {
map[interface{}]interface{} {
"id" : 2,
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
},
map[interface{}]interface{} {
"id" : 3,
"passport" : "t3",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T3",
"create_time" : gtime.Now().String(),
},
}, 1); err != nil {
gtest.Fatal(err)
} else {
n, _ := r.RowsAffected()
gtest.Assert(n, 2)
}
}
func TestDbBase_Save(t *testing.T) {

View File

@ -9,6 +9,7 @@ import (
"testing"
)
// 基本测试
func TestModel_Insert(t *testing.T) {
result, err := db.Table("user").Filter().Data(g.Map{
"id" : 1,
@ -23,6 +24,69 @@ func TestModel_Insert(t *testing.T) {
}
n, _ := result.LastInsertId()
gtest.Assert(n, 1)
result, err = db.Table("user").Filter().Data(map[interface{}]interface{} {
"id" : "2",
"uid" : "2",
"passport" : "t2",
"password" : "25d55ad283aa400af464c76d713c07ad",
"nickname" : "T2",
"create_time" : gtime.Now().String(),
}).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 1)
type User struct {
Id int `gconv:"id"`
Uid int `gconv:"uid"`
Passport string `json:"passport"`
Password string `gconv:"password"`
Nickname string `gconv:"nickname"`
CreateTime string `json:"create_time"`
}
result, err = db.Table("user").Filter().Data(User{
Id : 3,
Uid : 3,
Passport : "t3",
Password : "25d55ad283aa400af464c76d713c07ad",
Nickname : "T3",
CreateTime : gtime.Now().String(),
}).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 1)
value, err := db.Table("user").Fields("passport").Where("id=3").Value()
gtest.Assert(err, nil)
gtest.Assert(value.String(), "t3")
result, err = db.Table("user").Filter().Data(&User{
Id : 4,
Uid : 4,
Passport : "t4",
Password : "25d55ad283aa400af464c76d713c07ad",
Nickname : "T4",
CreateTime : gtime.Now().String(),
}).Insert()
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 1)
value, err = db.Table("user").Fields("passport").Where("id=4").Value()
gtest.Assert(err, nil)
gtest.Assert(value.String(), "t4")
result, err = db.Table("user").Where("id>?", 1).Delete()
if err != nil {
gtest.Fatal(err)
}
n, _ = result.RowsAffected()
gtest.Assert(n, 3)
}
func TestModel_Batch(t *testing.T) {
@ -191,7 +255,51 @@ func TestModel_GroupBy(t *testing.T) {
gtest.Assert(result[0]["nickname"].String(), "T111")
}
func TestModel_Where1(t *testing.T) {
// where string
func TestModel_WhereString(t *testing.T) {
gtest.Case(t, func() {
result, err := db.Table("user").Where("id=? and nickname=?", 3, "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(g.Map{"id" : 3, "nickname" : "T3"}).One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
}
// where struct
func TestModel_WhereStruct(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Id int `json:"id"`
Nickname string `gconv:"nickname"`
}
result, err := db.Table("user").Where(User{3, "T3"}).One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
result, err = db.Table("user").Where(&User{3, "T3"}).One()
if err != nil {
gtest.Fatal(err)
}
gtest.Assert(result["id"].Int(), 3)
})
}
// 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)
@ -201,7 +309,8 @@ func TestModel_Where1(t *testing.T) {
gtest.Assert(result[1]["id"].Int(), 3)
}
func TestModel_Where2(t *testing.T) {
// 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)

View File

@ -23,27 +23,36 @@ const (
LOG_LEVEL_CRIT = glog.LEVEL_CRIT
)
// NewVar creates a *Var.
//
// 动态变量
func NewVar(i interface{}, unsafe...bool) *Var {
return gvar.New(i, unsafe...)
}
// Wait blocks until all the web servers shutdown.
//
// 阻塞等待HTTPServer执行完成(同一进程多HTTPServer情况下)
func Wait() {
ghttp.Wait()
}
// Dump dumps a variable to stdout with more manually readable.
//
// 打印变量
func Dump(i...interface{}) {
gutil.Dump(i...)
}
// Throw throws a exception, which can be caught by Catch function.
// It always be used in TryCatch function.
//
// 抛出一个异常
func Throw(exception interface{}) {
gutil.Throw(exception)
}
// try...catch...
// TryCatch does the try...catch... logic.
func TryCatch(try func(), catch ... func(exception interface{})) {
gutil.TryCatch(try, catch...)
}

View File

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

View File

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

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

@ -0,0 +1,49 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
package empty
import (
"reflect"
)
// 判断给定的变量是否为空。
// 整型为0, 布尔为false, slice/map长度为0, 其他为nil的情况都为空。
// 为空时返回true否则返回false。
func IsEmpty(value interface{}) bool {
if value == nil {
return true
}
// 优先通过断言来进行常用类型判断
switch value := value.(type) {
case int: return value == 0
case int8: return value == 0
case int16: return value == 0
case int32: return value == 0
case int64: return value == 0
case uint: return value == 0
case uint8: return value == 0
case uint16: return value == 0
case uint32: return value == 0
case uint64: return value == 0
case float32: return value == 0
case float64: return value == 0
case bool: return value == false
case string: return value == ""
case []byte: return len(value) == 0
default:
// 最后通过反射来判断
rv := reflect.ValueOf(value)
kind := rv.Kind()
switch kind {
case reflect.Map: fallthrough
case reflect.Slice: fallthrough
case reflect.Array:
return rv.Len() == 0
}
}
return false
}

View File

@ -79,7 +79,7 @@ func (r *Request) GetVar(key string, def ... interface{}) gvar.VarRead {
return r.GetRequestVar(key, def...)
}
// 获取原始请求输入字符串
// 获取原始请求输入二进制。
func (r *Request) GetRaw() []byte {
if r.rawContent == nil {
r.rawContent, _ = ioutil.ReadAll(r.Body)
@ -87,6 +87,14 @@ func (r *Request) GetRaw() []byte {
return r.rawContent
}
// 获取原始请求输入字符串。
func (r *Request) GetRawString() string {
if r.rawContent == nil {
r.rawContent, _ = ioutil.ReadAll(r.Body)
}
return string(r.rawContent)
}
// 获取原始json请求输入字符串并解析为json对象
func (r *Request) GetJson() *gjson.Json {
data := r.GetRaw()

View File

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

View File

@ -118,15 +118,17 @@ func (r *Response) WriteXml(content interface{}, rootTag...string) error {
return nil
}
// 允许AJAX跨域访问
// Deprecated, please use CORSDefault instead.
//
// (已废弃请使用CORSDefault)允许AJAX跨域访问.
func (r *Response) SetAllowCrossDomainRequest(allowOrigin string, allowMethods string, maxAge...int) {
age := 3628800
if len(maxAge) > 0 {
age = maxAge[0]
}
r.Header().Set("Access-Control-Allow-Origin", allowOrigin);
r.Header().Set("Access-Control-Allow-Methods", allowMethods);
r.Header().Set("Access-Control-Max-Age", strconv.Itoa(age));
r.Header().Set("Access-Control-Allow-Origin", allowOrigin)
r.Header().Set("Access-Control-Allow-Methods", allowMethods)
r.Header().Set("Access-Control-Max-Age", strconv.Itoa(age))
}
// 返回HTTP Code状态码

View File

@ -0,0 +1,62 @@
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
//
package ghttp
import (
"github.com/gogf/gf/g/text/gstr"
"github.com/gogf/gf/g/util/gconv"
)
// See https://www.w3.org/TR/cors/ .
// 服务端允许跨域请求选项
type CORSOptions struct {
AllowOrigin string // Access-Control-Allow-Origin
AllowCredentials string // Access-Control-Allow-Credentials
ExposeHeaders string // Access-Control-Expose-Headers
MaxAge int // Access-Control-Max-Age
AllowMethods string // Access-Control-Allow-Methods
AllowHeaders string // Access-Control-Allow-Headers
}
// 默认的CORS配置
func (r *Response) DefaultCORSOptions() CORSOptions {
return CORSOptions {
AllowOrigin : gstr.TrimRight(r.request.Referer(), "/"),
AllowMethods : HTTP_METHODS,
AllowCredentials : "true",
MaxAge : 3628800,
}
}
// See https://www.w3.org/TR/cors/ .
// 允许请求跨域访问.
func (r *Response) CORS(options CORSOptions) {
if options.AllowOrigin != "" {
r.Header().Set("Access-Control-Allow-Origin", options.AllowOrigin)
}
if options.AllowCredentials != "" {
r.Header().Set("Access-Control-Allow-Credentials", options.AllowCredentials)
}
if options.ExposeHeaders != "" {
r.Header().Set("Access-Control-Expose-Headers", options.ExposeHeaders)
}
if options.MaxAge != 0 {
r.Header().Set("Access-Control-Max-Age", gconv.String(options.MaxAge))
}
if options.AllowMethods != "" {
r.Header().Set("Access-Control-Allow-Methods", options.AllowMethods)
}
if options.AllowHeaders != "" {
r.Header().Set("Access-Control-Allow-Headers", options.AllowHeaders)
}
}
// 允许请求跨域访问(使用more配置).
func (r *Response) CORSDefault() {
r.CORS(r.DefaultCORSOptions())
}

View File

@ -65,7 +65,6 @@ func (r *Response) buildInVars(params map[string]interface{}) map[string]interfa
if params == nil {
params = make(map[string]interface{})
}
c := gins.Config()
if c.GetFilePath() != "" {
params["Config"] = c.GetMap("")

View File

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

View File

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

View File

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

View File

@ -10,6 +10,7 @@ package ghttp
import (
"errors"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/text/gregex"
"strings"
"reflect"
"fmt"
@ -63,12 +64,17 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) e
fname : mname,
faddr : nil,
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
// 例如: pattern为/user, 那么会同时注册/user及/user/index,
// 这里处理新增/user路由绑定
if strings.EqualFold(mname, "Index") {
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
// 例如: pattern为/user, 那么会同时注册/user及/user/index
// 这里处理新增/user路由绑定
// 注意当pattern带有内置变量时不会自动加该路由。
if strings.EqualFold(mname, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
p := gstr.PosR(key, "/index")
m[key[0 : p] + key[p + 6 : ]] = &handlerItem {
k := key[0 : p] + key[p + 6 : ]
if len(k) == 0 {
k = "/"
}
m[k] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, mname),
rtype : gROUTE_REGISTER_CONTROLLER,
ctype : v.Elem().Type(),
@ -122,12 +128,13 @@ func (s *Server)BindControllerRest(pattern string, c Controller) error {
m := make(handlerMap)
v := reflect.ValueOf(c)
t := v.Type()
sname := t.Elem().Name()
pkgPath := t.Elem().PkgPath()
// 如果存在与HttpMethod对应名字的方法那么绑定这些方法
for i := 0; i < v.NumMethod(); i++ {
mname := t.Method(i).Name
method := strings.ToUpper(mname)
if _, ok := s.methodsMap[method]; !ok {
if _, ok := methodsMap[method]; !ok {
continue
}
if _, ok := v.Method(i).Interface().(func()); !ok {
@ -140,7 +147,7 @@ func (s *Server)BindControllerRest(pattern string, c Controller) error {
if ctlName[0] == '*' {
ctlName = fmt.Sprintf(`(%s)`, ctlName)
}
key := mname + ":" + pattern
key := s.mergeBuildInNameToPattern(mname + ":" + pattern, sname, mname, false)
m[key] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, mname),
rtype : gROUTE_REGISTER_CONTROLLER,

View File

@ -49,7 +49,7 @@ func (s *Server) bindHandlerByMap(m handlerMap) error {
// 将内置的名称按照设定的规则合并到pattern中内置名称按照{.xxx}规则命名。
// 规则1pattern中的URI包含{.struct}关键字,则替换该关键字为结构体名称;
// 规则1pattern中的URI包含{.method}关键字,则替换该关键字为方法名称;
// 规则2pattern中的URI包含{.method}关键字,则替换该关键字为方法名称;
// 规则2如果不满足规则1那么直接将防发明附加到pattern中的URI后面
func (s *Server) mergeBuildInNameToPattern(pattern string, structName, methodName string, allowAppend bool) string {
structName = s.nameToUrlPart(structName)

View File

@ -10,6 +10,7 @@ package ghttp
import (
"errors"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/text/gregex"
"strings"
"reflect"
"fmt"
@ -72,10 +73,15 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
finit : finit,
fshut : fshut,
}
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
if strings.EqualFold(mname, "Index") {
// 如果方法中带有Index方法那么额外自动增加一个路由规则匹配主URI
// 注意当pattern带有内置变量时不会自动加该路由。
if strings.EqualFold(mname, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
p := gstr.PosR(key, "/index")
m[key[0 : p] + key[p + 6 : ]] = &handlerItem {
k := key[0 : p] + key[p + 6 : ]
if len(k) == 0 {
k = "/"
}
m[k] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, mname),
rtype : gROUTE_REGISTER_OBJECT,
ctype : nil,
@ -141,6 +147,7 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
m := make(handlerMap)
v := reflect.ValueOf(obj)
t := v.Type()
sname := t.Elem().Name()
finit := (func(*Request))(nil)
fshut := (func(*Request))(nil)
if v.MethodByName("Init").IsValid() {
@ -153,7 +160,7 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
for i := 0; i < v.NumMethod(); i++ {
mname := t.Method(i).Name
method := strings.ToUpper(mname)
if _, ok := s.methodsMap[method]; !ok {
if _, ok := methodsMap[method]; !ok {
continue
}
faddr, ok := v.Method(i).Interface().(func(*Request))
@ -167,7 +174,7 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
if objName[0] == '*' {
objName = fmt.Sprintf(`(%s)`, objName)
}
key := mname + ":" + pattern
key := s.mergeBuildInNameToPattern(mname + ":" + pattern, sname, mname, false)
m[key] = &handlerItem {
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, mname),
rtype : gROUTE_REGISTER_OBJECT,

View File

@ -8,16 +8,17 @@
package ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Cookie(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/set", func(r *ghttp.Request){
r.Cookie.Set(r.Get("k"), r.Get("v"))
})
@ -28,19 +29,17 @@ func Test_Cookie(t *testing.T) {
s.BindHandler("/remove", func(r *ghttp.Request){
r.Cookie.Remove(r.Get("k"))
})
s.SetPort(8500)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetBrowserMode(true)
client.SetPrefix("http://127.0.0.1:8500")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
r1, e1 := client.Get("/set?k=key1&v=100")
if r1 != nil {
defer r1.Close()

View File

@ -0,0 +1,25 @@
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.
// 测试初始化
package ghttp_test
import (
"github.com/gogf/gf/g/container/garray"
)
var (
// 用于测试的端口数组,随机获取
ports = garray.NewIntArray()
)
func init() {
for i := 8000; i <= 8100; i++ {
ports.Append(i)
}
}

View File

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

View File

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

View File

@ -0,0 +1,106 @@
// 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 ControllerRest struct {
gmvc.Controller
}
func (c *ControllerRest) Init(r *ghttp.Request) {
c.Controller.Init(r)
c.Response.Write("1")
}
func (c *ControllerRest) Shut() {
c.Response.Write("2")
}
func (c *ControllerRest) Get() {
c.Response.Write("Controller Get")
}
func (c *ControllerRest) Put() {
c.Response.Write("Controller Put")
}
func (c *ControllerRest) Post() {
c.Response.Write("Controller Post")
}
func (c *ControllerRest) Delete() {
c.Response.Write("Controller Delete")
}
func (c *ControllerRest) Patch() {
c.Response.Write("Controller Patch")
}
func (c *ControllerRest) Options() {
c.Response.Write("Controller Options")
}
func (c *ControllerRest) Head() {
c.Response.Header().Set("head-ok", "1")
}
// 控制器注册测试
func Test_Router_ControllerRest(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindControllerRest("/", new(ControllerRest))
s.BindControllerRest("/{.struct}/{.method}", new(ControllerRest))
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("/"), "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("/controller-rest/get"), "1Controller Get2")
gtest.Assert(client.PutContent("/controller-rest/put"), "1Controller Put2")
gtest.Assert(client.PostContent("/controller-rest/post"), "1Controller Post2")
gtest.Assert(client.DeleteContent("/controller-rest/delete"), "1Controller Delete2")
gtest.Assert(client.PatchContent("/controller-rest/patch"), "1Controller Patch2")
gtest.Assert(client.OptionsContent("/controller-rest/options"), "1Controller Options2")
resp2, err := client.Head("/controller-rest/head")
if err == nil {
defer resp2.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp2.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,73 @@
// 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 Controller struct {
gmvc.Controller
}
func (c *Controller) Init(r *ghttp.Request) {
c.Controller.Init(r)
c.Response.Write("1")
}
func (c *Controller) Shut() {
c.Response.Write("2")
}
func (c *Controller) Index() {
c.Response.Write("Controller Index")
}
func (c *Controller) Show() {
c.Response.Write("Controller Show")
}
// 控制器注册测试
func Test_Router_Controller(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindController("/", new(Controller))
s.BindController("/{.struct}/{.method}", new(Controller))
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("/"), "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("/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"), "1Controller Index2")
gtest.Assert(client.GetContent("/controller/show"), "1Controller Show2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,173 @@
// 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 GroupCtlRest struct {
gmvc.Controller
}
func (c *GroupCtlRest) Init(r *ghttp.Request) {
c.Controller.Init(r)
c.Response.Write("1")
}
func (c *GroupCtlRest) Shut() {
c.Response.Write("2")
}
func (c *GroupCtlRest) Get() {
c.Response.Write("Controller Get")
}
func (c *GroupCtlRest) Put() {
c.Response.Write("Controller Put")
}
func (c *GroupCtlRest) Post() {
c.Response.Write("Controller Post")
}
func (c *GroupCtlRest) Delete() {
c.Response.Write("Controller Delete")
}
func (c *GroupCtlRest) Patch() {
c.Response.Write("Controller Patch")
}
func (c *GroupCtlRest) Options() {
c.Response.Write("Controller Options")
}
func (c *GroupCtlRest) Head() {
c.Response.Header().Set("head-ok", "1")
}
type GroupObjRest struct {}
func (o *GroupObjRest) Init(r *ghttp.Request) {
r.Response.Write("1")
}
func (o *GroupObjRest) Shut(r *ghttp.Request) {
r.Response.Write("2")
}
func (o *GroupObjRest) Get(r *ghttp.Request) {
r.Response.Write("Object Get")
}
func (o *GroupObjRest) Put(r *ghttp.Request) {
r.Response.Write("Object Put")
}
func (o *GroupObjRest) Post(r *ghttp.Request) {
r.Response.Write("Object Post")
}
func (o *GroupObjRest) Delete(r *ghttp.Request) {
r.Response.Write("Object Delete")
}
func (o *GroupObjRest) Patch(r *ghttp.Request) {
r.Response.Write("Object Patch")
}
func (o *GroupObjRest) Options(r *ghttp.Request) {
r.Response.Write("Object Options")
}
func (o *GroupObjRest) Head(r *ghttp.Request) {
r.Response.Header().Set("head-ok", "1")
}
func Test_Router_GroupRest(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
g := s.Group("/api")
ctl := new(GroupCtlRest)
obj := new(GroupObjRest)
g.REST("/ctl", ctl)
g.REST("/obj", obj)
g.REST("/{.struct}/{.method}", ctl)
g.REST("/{.struct}/{.method}", obj)
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("/api/ctl"), "1Controller Get2")
gtest.Assert(client.PutContent("/api/ctl"), "1Controller Put2")
gtest.Assert(client.PostContent("/api/ctl"), "1Controller Post2")
gtest.Assert(client.DeleteContent("/api/ctl"), "1Controller Delete2")
gtest.Assert(client.PatchContent("/api/ctl"), "1Controller Patch2")
gtest.Assert(client.OptionsContent("/api/ctl"), "1Controller Options2")
resp1, err := client.Head("/api/ctl")
if err == nil {
defer resp1.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp1.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/api/obj"), "1Object Get2")
gtest.Assert(client.PutContent("/api/obj"), "1Object Put2")
gtest.Assert(client.PostContent("/api/obj"), "1Object Post2")
gtest.Assert(client.DeleteContent("/api/obj"), "1Object Delete2")
gtest.Assert(client.PatchContent("/api/obj"), "1Object Patch2")
gtest.Assert(client.OptionsContent("/api/obj"), "1Object Options2")
resp2, err := client.Head("/api/obj")
if err == nil {
defer resp2.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp2.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/api/group-ctl-rest"), "Not Found")
gtest.Assert(client.GetContent("/api/group-ctl-rest/get"), "1Controller Get2")
gtest.Assert(client.PutContent("/api/group-ctl-rest/put"), "1Controller Put2")
gtest.Assert(client.PostContent("/api/group-ctl-rest/post"), "1Controller Post2")
gtest.Assert(client.DeleteContent("/api/group-ctl-rest/delete"), "1Controller Delete2")
gtest.Assert(client.PatchContent("/api/group-ctl-rest/patch"), "1Controller Patch2")
gtest.Assert(client.OptionsContent("/api/group-ctl-rest/options"), "1Controller Options2")
resp3, err := client.Head("/api/group-ctl-rest/head")
if err == nil {
defer resp3.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp3.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/api/group-obj-rest"), "Not Found")
gtest.Assert(client.GetContent("/api/group-obj-rest/get"), "1Object Get2")
gtest.Assert(client.PutContent("/api/group-obj-rest/put"), "1Object Put2")
gtest.Assert(client.PostContent("/api/group-obj-rest/post"), "1Object Post2")
gtest.Assert(client.DeleteContent("/api/group-obj-rest/delete"), "1Object Delete2")
gtest.Assert(client.PatchContent("/api/group-obj-rest/patch"), "1Object Patch2")
gtest.Assert(client.OptionsContent("/api/group-obj-rest/options"), "1Object Options2")
resp4, err := client.Head("/api/group-obj-rest/head")
if err == nil {
defer resp4.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp4.Header.Get("head-ok"), "1")
})
}

View File

@ -8,55 +8,73 @@
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/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
// 执行对象
type Object struct {}
type GroupObject struct {}
func (o *Object) Index(r *ghttp.Request) {
func (o *GroupObject) Init(r *ghttp.Request) {
r.Response.Write("1")
}
func (o *GroupObject) Shut(r *ghttp.Request) {
r.Response.Write("2")
}
func (o *GroupObject) Index(r *ghttp.Request) {
r.Response.Write("Object Index")
}
func (o *Object) Show(r *ghttp.Request) {
func (o *GroupObject) Show(r *ghttp.Request) {
r.Response.Write("Object Show")
}
func (o *Object) Delete(r *ghttp.Request) {
r.Response.Write("Object REST Delete")
func (o *GroupObject) Delete(r *ghttp.Request) {
r.Response.Write("Object Delete")
}
// 控制器
type Controller struct {
type GroupController struct {
gmvc.Controller
}
func (c *Controller) Index() {
func (c *GroupController) Init(r *ghttp.Request) {
c.Controller.Init(r)
c.Response.Write("1")
}
func (c *GroupController) Shut() {
c.Response.Write("2")
}
func (c *GroupController) Index() {
c.Response.Write("Controller Index")
}
func (c *Controller) Show() {
func (c *GroupController) Show() {
c.Response.Write("Controller Show")
}
func (c *Controller) Post() {
c.Response.Write("Controller REST Post")
func (c *GroupController) Post() {
c.Response.Write("Controller Post")
}
func Handler(r *ghttp.Request) {
r.Response.Write("Handler")
}
func Test_Router_Group1(t *testing.T) {
s := g.Server(gtime.Nanosecond())
obj := new(Object)
ctl := new(Controller)
func Test_Router_GroupBasic1(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
obj := new(GroupObject)
ctl := new(GroupController)
// 分组路由方法注册
g := s.Group("/api")
g.ALL ("/handler", Handler)
@ -66,48 +84,44 @@ func Test_Router_Group1(t *testing.T) {
g.ALL ("/obj", obj)
g.GET ("/obj/my-show", obj, "Show")
g.REST("/obj/rest", obj)
s.SetPort(8200)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8200")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent ("/api/handler"), "Handler")
gtest.Assert(client.GetContent ("/api/ctl"), "Controller Index")
gtest.Assert(client.GetContent ("/api/ctl/"), "Controller Index")
gtest.Assert(client.GetContent ("/api/ctl/index"), "Controller Index")
gtest.Assert(client.GetContent ("/api/ctl/my-show"), "Controller Show")
gtest.Assert(client.GetContent ("/api/ctl/post"), "Controller REST Post")
gtest.Assert(client.GetContent ("/api/ctl/show"), "Controller Show")
gtest.Assert(client.PostContent("/api/ctl/rest"), "Controller REST Post")
gtest.Assert(client.GetContent ("/api/ctl"), "1Controller Index2")
gtest.Assert(client.GetContent ("/api/ctl/"), "1Controller Index2")
gtest.Assert(client.GetContent ("/api/ctl/index"), "1Controller Index2")
gtest.Assert(client.GetContent ("/api/ctl/my-show"), "1Controller Show2")
gtest.Assert(client.GetContent ("/api/ctl/post"), "1Controller Post2")
gtest.Assert(client.GetContent ("/api/ctl/show"), "1Controller Show2")
gtest.Assert(client.PostContent("/api/ctl/rest"), "1Controller Post2")
gtest.Assert(client.GetContent ("/api/obj"), "Object Index")
gtest.Assert(client.GetContent ("/api/obj/"), "Object Index")
gtest.Assert(client.GetContent ("/api/obj/index"), "Object Index")
gtest.Assert(client.GetContent ("/api/obj/delete"), "Object REST Delete")
gtest.Assert(client.GetContent ("/api/obj/my-show"), "Object Show")
gtest.Assert(client.GetContent ("/api/obj/show"), "Object Show")
gtest.Assert(client.DeleteContent("/api/obj/rest"), "Object REST Delete")
gtest.Assert(client.GetContent ("/api/obj"), "1Object Index2")
gtest.Assert(client.GetContent ("/api/obj/"), "1Object Index2")
gtest.Assert(client.GetContent ("/api/obj/index"), "1Object Index2")
gtest.Assert(client.GetContent ("/api/obj/delete"), "1Object Delete2")
gtest.Assert(client.GetContent ("/api/obj/my-show"), "1Object Show2")
gtest.Assert(client.GetContent ("/api/obj/show"), "1Object Show2")
gtest.Assert(client.DeleteContent("/api/obj/rest"), "1Object Delete2")
// 测试404
resp, err := client.Get("/ThisDoesNotExist")
defer resp.Close()
gtest.Assert(err, nil)
gtest.Assert(resp.StatusCode, 404)
gtest.Assert(client.DeleteContent("/ThisDoesNotExist"), "Not Found")
gtest.Assert(client.DeleteContent("/api/ThisDoesNotExist"), "Not Found")
})
}
func Test_Router_Group2(t *testing.T) {
s := g.Server(gtime.Nanosecond())
obj := new(Object)
ctl := new(Controller)
func Test_Router_Basic2(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
obj := new(GroupObject)
ctl := new(GroupController)
// 分组路由批量注册
s.Group("/api").Bind("/api", []ghttp.GroupItem{
{"ALL", "/handler", Handler},
@ -118,34 +132,61 @@ func Test_Router_Group2(t *testing.T) {
{"GET", "/obj/my-show", obj, "Show"},
{"REST", "/obj/rest", obj},
})
s.SetPort(8300)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetPrefix("http://127.0.0.1:8300")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
gtest.Assert(client.GetContent ("/api/handler"), "Handler")
gtest.Assert(client.GetContent ("/api/ctl/my-show"), "Controller Show")
gtest.Assert(client.GetContent ("/api/ctl/post"), "Controller REST Post")
gtest.Assert(client.GetContent ("/api/ctl/show"), "Controller Show")
gtest.Assert(client.PostContent("/api/ctl/rest"), "Controller REST Post")
gtest.Assert(client.GetContent ("/api/ctl/my-show"), "1Controller Show2")
gtest.Assert(client.GetContent ("/api/ctl/post"), "1Controller Post2")
gtest.Assert(client.GetContent ("/api/ctl/show"), "1Controller Show2")
gtest.Assert(client.PostContent("/api/ctl/rest"), "1Controller Post2")
gtest.Assert(client.GetContent ("/api/obj/delete"), "Object REST Delete")
gtest.Assert(client.GetContent ("/api/obj/my-show"), "Object Show")
gtest.Assert(client.GetContent ("/api/obj/show"), "Object Show")
gtest.Assert(client.DeleteContent("/api/obj/rest"), "Object REST Delete")
gtest.Assert(client.GetContent ("/api/obj/delete"), "1Object Delete2")
gtest.Assert(client.GetContent ("/api/obj/my-show"), "1Object Show2")
gtest.Assert(client.GetContent ("/api/obj/show"), "1Object Show2")
gtest.Assert(client.DeleteContent("/api/obj/rest"), "1Object Delete2")
// 测试404
resp, err := client.Get("/ThisDoesNotExist")
defer resp.Close()
gtest.Assert(err, nil)
gtest.Assert(resp.StatusCode, 404)
gtest.Assert(client.DeleteContent("/ThisDoesNotExist"), "Not Found")
gtest.Assert(client.DeleteContent("/api/ThisDoesNotExist"), "Not Found")
})
}
func Test_Router_GroupBuildInVar(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
obj := new(GroupObject)
ctl := new(GroupController)
// 分组路由方法注册
g := s.Group("/api")
g.ALL ("/{.struct}/{.method}", ctl)
g.ALL ("/{.struct}/{.method}", obj)
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 ("/api/group-controller/index"), "1Controller Index2")
gtest.Assert(client.GetContent ("/api/group-controller/post"), "1Controller Post2")
gtest.Assert(client.GetContent ("/api/group-controller/show"), "1Controller Show2")
gtest.Assert(client.GetContent ("/api/group-object/index"), "1Object Index2")
gtest.Assert(client.GetContent ("/api/group-object/delete"), "1Object Delete2")
gtest.Assert(client.GetContent ("/api/group-object/show"), "1Object Show2")
gtest.Assert(client.DeleteContent("/ThisDoesNotExist"), "Not Found")
gtest.Assert(client.DeleteContent("/api/ThisDoesNotExist"), "Not Found")
})
}

View File

@ -0,0 +1,100 @@
// 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 ObjectRest struct {}
func (o *ObjectRest) Init(r *ghttp.Request) {
r.Response.Write("1")
}
func (o *ObjectRest) Shut(r *ghttp.Request) {
r.Response.Write("2")
}
func (o *ObjectRest) Get(r *ghttp.Request) {
r.Response.Write("Object Get")
}
func (o *ObjectRest) Put(r *ghttp.Request) {
r.Response.Write("Object Put")
}
func (o *ObjectRest) Post(r *ghttp.Request) {
r.Response.Write("Object Post")
}
func (o *ObjectRest) Delete(r *ghttp.Request) {
r.Response.Write("Object Delete")
}
func (o *ObjectRest) Patch(r *ghttp.Request) {
r.Response.Write("Object Patch")
}
func (o *ObjectRest) Options(r *ghttp.Request) {
r.Response.Write("Object Options")
}
func (o *ObjectRest) Head(r *ghttp.Request) {
r.Response.Header().Set("head-ok", "1")
}
func Test_Router_ObjectRest(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindObjectRest("/", new(ObjectRest))
s.BindObjectRest("/{.struct}/{.method}", new(ObjectRest))
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("/"), "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("/object-rest/get"), "1Object Get2")
gtest.Assert(client.PutContent("/object-rest/put"), "1Object Put2")
gtest.Assert(client.PostContent("/object-rest/post"), "1Object Post2")
gtest.Assert(client.DeleteContent("/object-rest/delete"), "1Object Delete2")
gtest.Assert(client.PatchContent("/object-rest/patch"), "1Object Patch2")
gtest.Assert(client.OptionsContent("/object-rest/options"), "1Object Options2")
resp2, err := client.Head("/object-rest/head")
if err == nil {
defer resp2.Close()
}
gtest.Assert(err, nil)
gtest.Assert(resp2.Header.Get("head-ok"), "1")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -0,0 +1,67 @@
// 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 Object struct {}
func (o *Object) Init(r *ghttp.Request) {
r.Response.Write("1")
}
func (o *Object) Shut(r *ghttp.Request) {
r.Response.Write("2")
}
func (o *Object) Index(r *ghttp.Request) {
r.Response.Write("Object Index")
}
func (o *Object) Show(r *ghttp.Request) {
r.Response.Write("Object Show")
}
// 执行对象注册
func Test_Router_Object(t *testing.T) {
p := ports.PopRand()
s := g.Server(p)
s.BindObject("/", new(Object))
s.BindObject("/{.struct}/{.method}", new(Object))
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("/"), "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("/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"), "1Object Index2")
gtest.Assert(client.GetContent("/object/show"), "1Object Show2")
gtest.Assert(client.GetContent("/none-exist"), "Not Found")
})
}

View File

@ -8,16 +8,17 @@
package ghttp_test
import (
"fmt"
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
"github.com/gogf/gf/g/os/gtime"
"github.com/gogf/gf/g/test/gtest"
"testing"
"time"
)
func Test_Session(t *testing.T) {
s := g.Server(gtime.Nanosecond())
p := ports.PopRand()
s := g.Server(p)
s.BindHandler("/set", func(r *ghttp.Request){
r.Session.Set(r.Get("k"), r.Get("v"))
})
@ -30,19 +31,17 @@ func Test_Session(t *testing.T) {
s.BindHandler("/clear", func(r *ghttp.Request){
r.Session.Clear()
})
s.SetPort(8600)
s.SetPort(p)
s.SetDumpRouteMap(false)
go s.Run()
defer func() {
s.Shutdown()
time.Sleep(time.Second)
}()
s.Start()
defer s.Shutdown()
// 等待启动完成
time.Sleep(time.Second)
gtest.Case(t, func() {
client := ghttp.NewClient()
client.SetBrowserMode(true)
client.SetPrefix("http://127.0.0.1:8600")
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
r1, e1 := client.Get("/set?k=key1&v=100")
if r1 != nil {
defer r1.Close()

View File

@ -63,24 +63,23 @@ func (c *Config) filePath(file...string) (path string) {
}
c.paths.RLockFunc(func(array []string) {
for _, v := range array {
//fmt.Println("search:", v, name)
if path, _ = gspath.Search(v, name); path != "" {
break
} else {
//if strings.EqualFold(v, "/Users/john/Temp/config") {
// gutil.Dump(gspath.Get(v).AllPaths())
//}
}
}
})
if path == "" {
buffer := bytes.NewBuffer(nil)
buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" in following paths:", name))
c.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
if c.paths.Len() > 0 {
buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" in following paths:", name))
c.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
} else {
buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" with no path set/add", name))
}
glog.Error(buffer.String())
}
return path
@ -94,10 +93,14 @@ func (c *Config) SetPath(path string) error {
glog.Error(fmt.Sprintf(`[gcfg] SetPath failed: %s`, err.Error()))
return err
}
// 重复判断
if c.paths.Search(realPath) != -1 {
return nil
}
c.jsons.Clear()
c.paths.Clear()
c.paths.Append(realPath)
glog.Debug("[gcfg] SetPath:", realPath)
//glog.Debug("[gcfg] SetPath:", realPath)
return nil
}
@ -116,19 +119,34 @@ func (c *Config) AddPath(path string) error {
glog.Error(fmt.Sprintf(`[gcfg] AddPath failed: %s`, err.Error()))
return err
}
// 重复判断
if c.paths.Search(realPath) != -1 {
return nil
}
c.paths.Append(realPath)
glog.Debug("[gcfg] AddPath:", realPath)
//glog.Debug("[gcfg] AddPath:", realPath)
return nil
}
// 获取指定文件的绝对路径,默认获取默认的配置文件路径
func (c *Config) GetFilePath(file...string) string {
return c.filePath(file...)
// 获取指定文件的绝对路径,默认获取默认的配置文件路径,当指定的配置文件不存在时,返回空字符串,并且不会报错。
func (c *Config) GetFilePath(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
}
}
})
return
}
// 设置配置管理对象的默认文件名称
func (c *Config) SetFileName(name string) {
glog.Debug("[gcfg] SetFileName:", name)
//glog.Debug("[gcfg] SetFileName:", name)
c.name.Set(name)
}

View File

@ -115,9 +115,13 @@ func (view *View) SetPath(path string) error {
glog.Error(fmt.Sprintf(`[gview] SetPath failed: %s`, err.Error()))
return err
}
// 重复判断
if view.paths.Search(realPath) != -1 {
return nil
}
view.paths.Clear()
view.paths.Append(realPath)
glog.Debug("[gview] SetPath:", realPath)
//glog.Debug("[gview] SetPath:", realPath)
return nil
}
@ -132,8 +136,12 @@ func (view *View) AddPath(path string) error {
glog.Error(fmt.Sprintf(`[gview] AddPath failed: %s`, err.Error()))
return err
}
// 重复判断
if view.paths.Search(realPath) != -1 {
return nil
}
view.paths.Append(realPath)
glog.Debug("[gview] AddPath:", realPath)
//glog.Debug("[gview] AddPath:", realPath)
return nil
}
@ -165,12 +173,16 @@ func (view *View) Parse(file string, params Params, funcmap...map[string]interfa
})
if path == "" {
buffer := bytes.NewBuffer(nil)
buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" in following paths:", file))
view.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
if view.paths.Len() > 0 {
buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" in following paths:", file))
view.paths.RLockFunc(func(array []string) {
for k, v := range array {
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
}
})
} else {
buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" with no path set/add", file))
}
glog.Error(buffer.String())
return nil, errors.New(fmt.Sprintf(`tpl "%s" not found`, file))
}

View File

@ -31,15 +31,78 @@ func Replace(origin, search, replace string, count...int) string {
return strings.Replace(origin, search, replace, n)
}
// Replace returns a copy of the string <origin> with string <search> replaced by <replace>
// with case-insensitive.
//
// 字符串替换(大小写不敏感)
func ReplaceI(origin, search, replace string, count...int) string {
n := -1
if len(count) > 0 {
n = count[0]
}
if n == 0 {
return origin
}
length := len(search)
searchLower := strings.ToLower(search)
for {
originLower := strings.ToLower(origin)
if pos := strings.Index(originLower, searchLower); pos != -1 {
origin = origin[ : pos] + replace + origin[pos + length : ]
if n -= 1; n == 0 {
break
}
} else {
break
}
}
return origin
}
// Replace string by array/slice.
//
// 使用map进行字符串替换(大小写敏感)
func ReplaceByArray(origin string, array []string) string {
for i := 0; i < len(array); i += 2 {
if i + 1 >= len(array) {
break
}
origin = Replace(origin, array[i], array[i + 1])
}
return origin
}
// Replace string by array/slice with case-insensitive.
//
// 使用map进行字符串替换(大小写不敏感)
func ReplaceIByArray(origin string, array []string) string {
for i := 0; i < len(array); i += 2 {
if i + 1 >= len(array) {
break
}
origin = ReplaceI(origin, array[i], array[i + 1])
}
return origin
}
// Replace string by map.
//
// 使用map进行字符串替换(大小写敏感)
func ReplaceByMap(origin string, replaces map[string]string) string {
result := origin
for k, v := range replaces {
result = strings.Replace(result, k, v, -1)
origin = Replace(origin, k, v)
}
return result
return origin
}
// Replace string by map with case-insensitive.
//
// 使用map进行字符串替换(大小写不敏感)
func ReplaceIByMap(origin string, replaces map[string]string) string {
for k, v := range replaces {
origin = ReplaceI(origin, k, v)
}
return origin
}
// ToLower returns a copy of the string s with all Unicode letters mapped to their lower case.

View File

@ -9,6 +9,7 @@ package gstr
import "strings"
// Find the position of the first occurrence of a substring in a string.
// It returns -1, if none found.
//
// 返回 needle 在 haystack 中首次出现的数字位置,找不到返回-1。
func Pos(haystack, needle string, startOffset...int) int {
@ -32,6 +33,7 @@ func Pos(haystack, needle string, startOffset...int) int {
}
// Find the position of the first occurrence of a case-insensitive substring in a string.
// It returns -1, if none found.
//
// 返回在字符串 haystack 中 needle 首次出现的数字位置(不区分大小写),找不到返回-1。
func PosI(haystack, needle string, startOffset...int) int {
@ -56,6 +58,7 @@ func PosI(haystack, needle string, startOffset...int) int {
}
// Find the position of the last occurrence of a substring in a string.
// It returns -1, if none found.
//
// 查找指定字符串在目标字符串中最后一次出现的位置,找不到返回-1。
func PosR(haystack, needle string, startOffset...int) int {
@ -81,6 +84,7 @@ func PosR(haystack, needle string, startOffset...int) int {
}
// Find the position of the last occurrence of a case-insensitive substring in a string.
// It returns -1, if none found.
//
// 以不区分大小写的方式查找指定字符串在目标字符串中最后一次出现的位置,找不到返回-1。
func PosRI(haystack, needle string, startOffset...int) int {

View File

@ -21,6 +21,13 @@ func Test_Replace(t *testing.T) {
gtest.Assert(gstr.Replace(s1, "ab", "AB"), "ABcdEFG乱入的中文ABcdefg")
gtest.Assert(gstr.Replace(s1, "EF", "ef"), "abcdefG乱入的中文abcdefg")
gtest.Assert(gstr.Replace(s1, "MN", "mn"), s1)
gtest.Assert(gstr.ReplaceByArray(s1, g.ArrayStr {
"a" , "A",
"A" , "-",
"a",
}), "-bcdEFG乱入的中文-bcdefg")
gtest.Assert(gstr.ReplaceByMap(s1, g.MapStrStr{
"a" : "A",
"G" : "g",
@ -28,6 +35,36 @@ func Test_Replace(t *testing.T) {
})
}
func Test_ReplaceI_1(t *testing.T) {
gtest.Case(t, func() {
s1 := "abcd乱入的中文ABCD"
s2 := "a"
gtest.Assert(gstr.ReplaceI(s1, "ab", "aa"), "aacd乱入的中文aaCD")
gtest.Assert(gstr.ReplaceI(s1, "ab", "aa", 0), "abcd乱入的中文ABCD")
gtest.Assert(gstr.ReplaceI(s1, "ab", "aa", 1), "aacd乱入的中文ABCD")
gtest.Assert(gstr.ReplaceI(s1, "abcd", "-"), "-乱入的中文-")
gtest.Assert(gstr.ReplaceI(s1, "abcd", "-", 1), "-乱入的中文ABCD")
gtest.Assert(gstr.ReplaceI(s1, "abcd乱入的", ""), "中文ABCD")
gtest.Assert(gstr.ReplaceI(s1, "ABCD乱入的", ""), "中文ABCD")
gtest.Assert(gstr.ReplaceI(s2, "A", "-"), "-")
gtest.Assert(gstr.ReplaceI(s2, "a", "-"), "-")
gtest.Assert(gstr.ReplaceIByArray(s1, g.ArrayStr {
"abcd乱入的" , "-",
"-" , "=",
"a",
}), "=中文ABCD")
gtest.Assert(gstr.ReplaceIByMap(s1, g.MapStrStr {
"ab" : "-",
"CD" : "=",
}), "-=乱入的中文-=")
})
}
func Test_ToLower(t *testing.T) {
gtest.Case(t, func() {
s1 := "abcdEFG乱入的中文abcdefg"

View File

@ -7,108 +7,135 @@
package gconv
import (
"github.com/gogf/gf/g/internal/empty"
"reflect"
"strings"
)
// 任意类型转换为 map[string]interface{} 类型,
// 如果给定的输入参数i不是map类型那么转换会失败返回nil.
// 当i为struct对象时第二个参数noTagCheck表示不检测json标签否则将会使用json tag作为map的键名。
func Map(i interface{}, noTagCheck...bool) map[string]interface{} {
if i == nil {
func Map(value interface{}, noTagCheck...bool) map[string]interface{} {
if value == nil {
return nil
}
if r, ok := i.(map[string]interface{}); ok {
if r, ok := value.(map[string]interface{}); ok {
return r
} else {
// 仅对常见的几种map组合进行断言最后才会使用反射
m := make(map[string]interface{})
switch i.(type) {
switch value.(type) {
case map[interface{}]interface{}:
for k, v := range i.(map[interface{}]interface{}) {
for k, v := range value.(map[interface{}]interface{}) {
m[String(k)] = v
}
case map[interface{}]string:
for k, v := range i.(map[interface{}]string) {
for k, v := range value.(map[interface{}]string) {
m[String(k)] = v
}
case map[interface{}]int:
for k, v := range i.(map[interface{}]int) {
for k, v := range value.(map[interface{}]int) {
m[String(k)] = v
}
case map[interface{}]uint:
for k, v := range i.(map[interface{}]uint) {
for k, v := range value.(map[interface{}]uint) {
m[String(k)] = v
}
case map[interface{}]float32:
for k, v := range i.(map[interface{}]float32) {
for k, v := range value.(map[interface{}]float32) {
m[String(k)] = v
}
case map[interface{}]float64:
for k, v := range i.(map[interface{}]float64) {
for k, v := range value.(map[interface{}]float64) {
m[String(k)] = v
}
case map[string]bool:
for k, v := range i.(map[string]bool) {
for k, v := range value.(map[string]bool) {
m[k] = v
}
case map[string]int:
for k, v := range i.(map[string]int) {
for k, v := range value.(map[string]int) {
m[k] = v
}
case map[string]uint:
for k, v := range i.(map[string]uint) {
for k, v := range value.(map[string]uint) {
m[k] = v
}
case map[string]float32:
for k, v := range i.(map[string]float32) {
for k, v := range value.(map[string]float32) {
m[k] = v
}
case map[string]float64:
for k, v := range i.(map[string]float64) {
for k, v := range value.(map[string]float64) {
m[k] = v
}
case map[int]interface{}:
for k, v := range i.(map[int]interface{}) {
for k, v := range value.(map[int]interface{}) {
m[String(k)] = v
}
case map[int]string:
for k, v := range i.(map[int]string) {
for k, v := range value.(map[int]string) {
m[String(k)] = v
}
case map[uint]string:
for k, v := range i.(map[uint]string) {
for k, v := range value.(map[uint]string) {
m[String(k)] = v
}
// 不是常见类型,则使用反射
default:
rv := reflect.ValueOf(i)
rv := reflect.ValueOf(value)
kind := rv.Kind()
// 如果是指针,那么需要转换到指针对应的数据项,以便识别真实的类型
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
if kind == reflect.Map {
ks := rv.MapKeys()
for _, k := range ks {
m[String(k.Interface())] = rv.MapIndex(k).Interface()
}
} else if kind == reflect.Struct {
rt := rv.Type()
name := ""
for i := 0; i < rv.NumField(); i++ {
// 检查json tag
if len(noTagCheck) == 0 || !noTagCheck[0] {
if name = rt.Field(i).Tag.Get("json"); name == "" {
name = rt.Field(i).Name
}
switch kind {
case reflect.Map:
ks := rv.MapKeys()
for _, k := range ks {
m[String(k.Interface())] = rv.MapIndex(k).Interface()
}
m[name] = rv.Field(i).Interface()
}
} else {
return nil
case reflect.Struct:
rt := rv.Type()
name := ""
for i := 0; i < rv.NumField(); i++ {
name = ""
// 检查tag, 支持gconv, json标签, 优先使用gconv
if len(noTagCheck) == 0 || !noTagCheck[0] {
tag := rt.Field(i).Tag
if name = tag.Get("gconv"); name == "" {
name = tag.Get("json")
}
}
if name == "" {
name = strings.TrimSpace(rt.Field(i).Name)
} else {
// 支持标准库json特性: -, omitempty
name = strings.TrimSpace(name)
if name == "-" {
continue
}
array := strings.Split(name, ",")
if len(array) > 1 {
switch strings.TrimSpace(array[1]) {
case "omitempty":
if empty.IsEmpty(rv.Field(i).Interface()) {
continue
} else {
name = strings.TrimSpace(array[0])
}
default:
name = strings.TrimSpace(array[0])
}
}
}
m[name] = rv.Field(i).Interface()
}
default:
return nil
}
}
return m

View File

@ -14,7 +14,7 @@ import (
)
func Test_Map(t *testing.T) {
func Test_Map_Basic(t *testing.T) {
gtest.Case(t, func() {
m1 := map[string]string{
"k" : "v",
@ -36,3 +36,79 @@ func Test_Map(t *testing.T) {
})
})
}
func Test_Map_StructWithGconvTag(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Uid int
Name string
SiteUrl string `gconv:"-"`
NickName string `gconv:"nickname, omitempty"`
Pass1 string `gconv:"password1"`
Pass2 string `gconv:"password2"`
}
user1 := User{
Uid : 100,
Name : "john",
SiteUrl : "https://goframe.org",
Pass1 : "123",
Pass2 : "456",
}
user2 := &user1
map1 := gconv.Map(user1)
map2 := gconv.Map(user2)
gtest.Assert(map1["Uid"], 100)
gtest.Assert(map1["Name"], "john")
gtest.Assert(map1["SiteUrl"], nil)
gtest.Assert(map1["NickName"], nil)
gtest.Assert(map1["nickname"], nil)
gtest.Assert(map1["password1"], "123")
gtest.Assert(map1["password2"], "456")
gtest.Assert(map2["Uid"], 100)
gtest.Assert(map2["Name"], "john")
gtest.Assert(map2["SiteUrl"], nil)
gtest.Assert(map2["NickName"], nil)
gtest.Assert(map2["nickname"], nil)
gtest.Assert(map2["password1"], "123")
gtest.Assert(map2["password2"], "456")
})
}
func Test_Map_StructWithJsonTag(t *testing.T) {
gtest.Case(t, func() {
type User struct {
Uid int
Name string
SiteUrl string `json:"-"`
NickName string `json:"nickname, omitempty"`
Pass1 string `json:"password1"`
Pass2 string `json:"password2"`
}
user1 := User{
Uid : 100,
Name : "john",
SiteUrl : "https://goframe.org",
Pass1 : "123",
Pass2 : "456",
}
user2 := &user1
map1 := gconv.Map(user1)
map2 := gconv.Map(user2)
gtest.Assert(map1["Uid"], 100)
gtest.Assert(map1["Name"], "john")
gtest.Assert(map1["SiteUrl"], nil)
gtest.Assert(map1["NickName"], nil)
gtest.Assert(map1["nickname"], nil)
gtest.Assert(map1["password1"], "123")
gtest.Assert(map1["password2"], "456")
gtest.Assert(map2["Uid"], 100)
gtest.Assert(map2["Name"], "john")
gtest.Assert(map2["SiteUrl"], nil)
gtest.Assert(map2["NickName"], nil)
gtest.Assert(map2["nickname"], nil)
gtest.Assert(map2["password1"], "123")
gtest.Assert(map2["password2"], "456")
})
}

View File

@ -0,0 +1,27 @@
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/frame/gmvc"
"github.com/gogf/gf/g/net/ghttp"
)
type Order2 struct {
gmvc.Controller
}
func (o *Order2) Get() {
o.Response.Write("GET")
}
func main() {
s := g.Server()
s.BindHookHandlerByMap("/api.v2/*any", map[string]ghttp.HandlerFunc {
"BeforeServe" : func(r *ghttp.Request) {
r.Response.CORSDefault()
},
})
s.BindControllerRest("/api.v2/{.struct}", new(Order2))
s.SetPort(8199)
s.Run()
}

View File

@ -1,23 +0,0 @@
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Writeln("hello")
})
s.BindHandler("/restart", func(r *ghttp.Request){
r.Response.Writeln("restart server")
r.Server.Restart()
})
s.BindHandler("/shutdown", func(r *ghttp.Request){
r.Response.Writeln("shutdown server")
r.Server.Shutdown()
})
s.SetPort(8199, 8200)
s.Run()
}

View File

@ -0,0 +1,23 @@
package main
import (
"github.com/gogf/gf/g"
"github.com/gogf/gf/g/net/ghttp"
)
func main() {
s := g.Server()
s.BindHandler("/main1", func(r *ghttp.Request) {
r.Response.WriteTpl("layout.html", g.Map{
"mainTpl" : "main/main1.html",
})
})
s.BindHandler("/main2", func(r *ghttp.Request) {
r.Response.WriteTpl("layout.html", g.Map{
"mainTpl" : "main/main2.html",
})
})
g.View().SetPath("template")
s.SetPort(8199)
s.Run()
}

View File

@ -0,0 +1 @@
<h1>FOOTER</h1>

View File

@ -0,0 +1 @@
<h1>HEADER</h1>

View File

@ -0,0 +1,3 @@
{{include "header.html" .}}
{{include .mainTpl .}}
{{include "footer.html" .}}

View File

@ -0,0 +1 @@
<h1>MAIN1</h1>

View File

@ -0,0 +1 @@
<h1>MAIN2</h1>

View File

@ -15,9 +15,11 @@ func main() {
for {
data, err := conn.Recv(-1)
if len(data) > 0 {
fmt.Println(string(data))
fmt.Println(string(data))
}
if err != nil {
// client closed, err will be: EOF
fmt.Println(err)
break
}
}
@ -36,5 +38,12 @@ func main() {
glog.Error(err)
}
time.Sleep(time.Second)
if i == 5 {
conn.Close()
break
}
}
// exit after 5 seconds
time.Sleep(5*time.Second)
}

View File

@ -0,0 +1,23 @@
package main
import (
"github.com/gogf/gf/g/net/gtcp"
"github.com/gogf/gf/g/os/glog"
"github.com/gogf/gf/g/util/gconv"
"time"
)
func main() {
// Client
conn, err := gtcp.NewConn("127.0.0.1:8999")
if err != nil {
panic(err)
}
defer conn.Close()
for i := 0; i < 3; i++ {
if err := conn.Send([]byte(gconv.String(i))); err != nil {
glog.Error(err)
}
time.Sleep(time.Second)
}
}

View File

@ -0,0 +1,24 @@
package main
import (
"fmt"
"github.com/gogf/gf/g/net/gtcp"
)
func main() {
// Server
gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) {
defer conn.Close()
for {
data, err := conn.Recv(-1)
if len(data) > 0 {
fmt.Println(string(data))
}
if err != nil {
// client closed, err will be: EOF
fmt.Println(err)
break
}
}
}).Run()
}

View File

@ -1,14 +1,13 @@
package main
import (
"fmt"
)
import "fmt"
func main() {
s := "abc我是中国人é"
fmt.Println(len(s))
for i := 0; i < len(s); i++ {
fmt.Println(s[i])
for i := 0; i < 10; i++ {
switch 1 {
default:
//continue
}
fmt.Println(i)
}
}

View File

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