mirror of
https://gitee.com/johng/gf
synced 2026-06-08 18:47:45 +08:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d37626981 | |||
| d0ed3b979d | |||
| fa256aec9f | |||
| 86834c5a15 | |||
| c2046157d6 | |||
| cdb2cc89c0 | |||
| 4964c09a77 | |||
| ef34b2c9ce | |||
| 9afe242293 | |||
| 136d93d373 | |||
| 3102cec5b8 | |||
| e28eb9da04 | |||
| 754ed86dfb | |||
| 7058e4f2c4 | |||
| 704a5dbd73 | |||
| fbd4ce8c2e | |||
| cb3ce71cdc | |||
| 7f44f2f5e4 | |||
| 66efbe63f0 | |||
| 49a1308875 | |||
| 4332580c01 | |||
| 3dd8b6ad33 | |||
| 3e0a975a88 | |||
| 6aa1c5b1eb | |||
| 1fb5a8cd6f | |||
| 8925460718 | |||
| 9797701881 | |||
| 8a50b180c0 | |||
| 159190d187 | |||
| 989d543a1f | |||
| fcc3a1b2f6 |
33
.gitee/ISSUE_TEMPLATE.MD
Normal file
33
.gitee/ISSUE_TEMPLATE.MD
Normal file
@ -0,0 +1,33 @@
|
||||
<!-- 为更高效率地交流并解决问题,请按照以下模板提交issue,感谢! -->
|
||||
### 1. 您当前使用的`Go`版本(将终端`go version`指令结果粘贴到下面)?
|
||||
|
||||
<pre>
|
||||
$ go version
|
||||
|
||||
</pre>
|
||||
|
||||
### 2. 您当前使用的`GoFrame`版本(可以查看`go.mod`/`version.go`/`gf.VERSION`)?
|
||||
|
||||
|
||||
|
||||
### 3. 更新到最新的框架版本是否能够解决问题?
|
||||
|
||||
|
||||
|
||||
### 4. 问题描述?
|
||||
|
||||
<!--
|
||||
请您尽可能地提供一份最短的,可复现问题的代码。
|
||||
代码尽可能地完整,最好是可以直接编译运行。
|
||||
-->
|
||||
|
||||
|
||||
|
||||
### 5. 您期望得到的结果?
|
||||
|
||||
|
||||
|
||||
### 6. 您实际得到的结果?
|
||||
|
||||
|
||||
|
||||
33
.github/ISSUE_TEMPLATE.MD
vendored
Normal file
33
.github/ISSUE_TEMPLATE.MD
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
<!-- Please answer these questions before submitting your issue. Thanks! -->
|
||||
### 1. What version of `Go` are you using (`go version`)?
|
||||
|
||||
<pre>
|
||||
$ go version
|
||||
|
||||
</pre>
|
||||
|
||||
### 2. What version of `GoFrame` are you using?
|
||||
|
||||
|
||||
|
||||
### 3. Does this issue reproduce 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?
|
||||
|
||||
|
||||
|
||||
@ -14,13 +14,13 @@
|
||||
[](https://www.codetriage.com/gogf/gf)
|
||||
-->
|
||||
|
||||
`GF(GoFrame)` is a modular, lightweight, loosely coupled, high performance application development framework written in Go. Supporting graceful server, hot updates, multi-domain, multi-port, multi-service, HTTP/HTTPS, dynamic/hook routing and many more features. Providing a series of core components and dozens of practical modules.
|
||||
`GF(GoFrame)` is a modular, loose-coupled and production-ready application development framework written in Go. Providing a series of core components and dozens of practical modules, such as: cache, logging, array/queue/set/map, timer/timing tasks, file/memory lock, object pool, validator, database ORM, etc. Supporting web server with graceful server, hot updates, multi-domain, multi-port, multi-service, HTTP/HTTPS, dynamic/hook routing, rewrite rules and many more features.
|
||||
|
||||
# Installation
|
||||
```
|
||||
go get -u github.com/gogf/gf
|
||||
```
|
||||
or use `go.mod`
|
||||
or use `go.mod`:
|
||||
```
|
||||
require github.com/gogf/gf latest
|
||||
```
|
||||
@ -32,6 +32,7 @@ golang version >= 1.9.2
|
||||
|
||||
# Documentation
|
||||
|
||||
* [GoDoc](https://godoc.org/github.com/gogf/gf)
|
||||
* [中文文档](https://goframe.org)
|
||||
|
||||
# Architecture
|
||||
@ -92,6 +93,8 @@ func main() {
|
||||
|
||||
# Donators
|
||||
|
||||
<a href="https://gitee.com/tiangenglan" target="_blank" title="zhuhuan12"><img src="https://images.gitee.com/uploads/99/1167099_tiangenglan.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zhuhuan12" target="_blank" title="zhuhuan12"><img src="https://gitee.com/uploads/39/751839_zhuhuan12.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>
|
||||
|
||||
12
README_ZH.MD
12
README_ZH.MD
@ -32,7 +32,7 @@
|
||||
go get -u github.com/gogf/gf
|
||||
```
|
||||
或者
|
||||
`go.mod`
|
||||
`go.mod`:
|
||||
```
|
||||
require github.com/gogf/gf latest
|
||||
```
|
||||
@ -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)
|
||||
|
||||
@ -100,6 +108,8 @@ func main() {
|
||||
|
||||
# 捐赠者
|
||||
|
||||
<a href="https://gitee.com/tiangenglan" target="_blank" title="zhuhuan12"><img src="https://images.gitee.com/uploads/99/1167099_tiangenglan.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zhuhuan12" target="_blank" title="zhuhuan12"><img src="https://gitee.com/uploads/39/751839_zhuhuan12.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>
|
||||
|
||||
35
RELEASE.MD
35
RELEASE.MD
@ -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)
|
||||
|
||||
## 新特性
|
||||
|
||||
2
TODO.MD
2
TODO.MD
@ -52,7 +52,7 @@
|
||||
1. 从ghttp中剥离SESSION功能构成单独的模块gsession;
|
||||
1. 改进gproc进程间通信处理逻辑,提高稳定性,以应对进程间大批量的数据发送/接收;
|
||||
1. gdb的Data方法支持struct参数传入;
|
||||
|
||||
1. gfcache依旧使用gcache作为缓存控制对象,不要使用gmap;
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -24,23 +24,30 @@ import (
|
||||
func formatCondition(where interface{}, args []interface{}) (string, []interface{}) {
|
||||
// 条件字符串处理
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if reflect.ValueOf(where).Kind() == reflect.Map {
|
||||
ks := reflect.ValueOf(where).MapKeys()
|
||||
vs := reflect.ValueOf(where)
|
||||
for _, k := range ks {
|
||||
key := gconv.String(k.Interface())
|
||||
value := gconv.String(vs.MapIndex(k).Interface())
|
||||
if buffer.Len() > 0 {
|
||||
buffer.WriteString(" AND ")
|
||||
// 使用反射进行类型判断
|
||||
rv := reflect.ValueOf(where)
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Map: fallthrough
|
||||
case reflect.Struct:
|
||||
for k, v := range gconv.Map(where) {
|
||||
key := gconv.String(k)
|
||||
value := gconv.String(v)
|
||||
if buffer.Len() > 0 {
|
||||
buffer.WriteString(" AND ")
|
||||
}
|
||||
if gstr.IsNumeric(value) || value == "?" {
|
||||
buffer.WriteString(key + "=" + value)
|
||||
} else {
|
||||
buffer.WriteString(key + "='" + value + "'")
|
||||
}
|
||||
}
|
||||
if gstr.IsNumeric(value) || value == "?" {
|
||||
buffer.WriteString(key + "=" + value)
|
||||
} else {
|
||||
buffer.WriteString(key + "='" + value + "'")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buffer.Write(gconv.Bytes(where))
|
||||
default:
|
||||
buffer.WriteString(gconv.String(where))
|
||||
}
|
||||
if buffer.Len() == 0 {
|
||||
buffer.WriteString("1=1")
|
||||
@ -57,6 +64,7 @@ func formatCondition(where interface{}, args []interface{}) (string, []interface
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
// Where条件参数支持slice类型
|
||||
case reflect.Slice: fallthrough
|
||||
case reflect.Array:
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
@ -109,13 +117,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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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预处理
|
||||
|
||||
@ -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) {
|
||||
@ -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,6 +255,7 @@ func TestModel_GroupBy(t *testing.T) {
|
||||
gtest.Assert(result[0]["nickname"].String(), "T111")
|
||||
}
|
||||
|
||||
// slice
|
||||
func TestModel_Where1(t *testing.T) {
|
||||
result, err := db.Table("user").Where("id IN(?)", g.Slice{1,3}).OrderBy("id ASC").All()
|
||||
if err != nil {
|
||||
@ -201,6 +266,7 @@ func TestModel_Where1(t *testing.T) {
|
||||
gtest.Assert(result[1]["id"].Int(), 3)
|
||||
}
|
||||
|
||||
// slice
|
||||
func TestModel_Where2(t *testing.T) {
|
||||
result, err := db.Table("user").Where("nickname=? AND id IN(?)", "T3", g.Slice{1,3}).OrderBy("id ASC").All()
|
||||
if err != nil {
|
||||
11
g/g_func.go
11
g/g_func.go
@ -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...)
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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...)
|
||||
|
||||
16
g/g_setting.go
Normal file
16
g/g_setting.go
Normal file
@ -0,0 +1,16 @@
|
||||
// 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 g
|
||||
|
||||
import "github.com/gogf/gf/g/net/ghttp"
|
||||
|
||||
// SetServerGraceful enables/disables graceful reload feature of ghttp Web Server.
|
||||
//
|
||||
// 是否开启WebServer的平滑重启特性。
|
||||
func SetServerGraceful(enabled bool) {
|
||||
ghttp.SetGraceful(enabled)
|
||||
}
|
||||
@ -9,44 +9,44 @@
|
||||
package ghttp
|
||||
|
||||
func Get(url string) (*ClientResponse, error) {
|
||||
return DoRequest("GET", url, []byte(""))
|
||||
return DoRequest("GET", url)
|
||||
}
|
||||
|
||||
func Put(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("PUT", url, []byte(data))
|
||||
func Put(url string, data...string) (*ClientResponse, error) {
|
||||
return DoRequest("PUT", url, data...)
|
||||
}
|
||||
|
||||
func Post(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("POST", url, []byte(data))
|
||||
func Post(url string, data...string) (*ClientResponse, error) {
|
||||
return DoRequest("POST", url, data...)
|
||||
}
|
||||
|
||||
func Delete(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("DELETE", url, []byte(data))
|
||||
func Delete(url string, data...string) (*ClientResponse, error) {
|
||||
return DoRequest("DELETE", url, data...)
|
||||
}
|
||||
|
||||
func Head(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("HEAD", url, []byte(data))
|
||||
func Head(url string, data...string) (*ClientResponse, error) {
|
||||
return DoRequest("HEAD", url, data...)
|
||||
}
|
||||
|
||||
func Patch(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("PATCH", url, []byte(data))
|
||||
func Patch(url string, data...string) (*ClientResponse, error) {
|
||||
return DoRequest("PATCH", url, data...)
|
||||
}
|
||||
|
||||
func Connect(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("CONNECT", url, []byte(data))
|
||||
func Connect(url string, data...string) (*ClientResponse, error) {
|
||||
return DoRequest("CONNECT", url, data...)
|
||||
}
|
||||
|
||||
func Options(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("OPTIONS", url, []byte(data))
|
||||
func Options(url string, data...string) (*ClientResponse, error) {
|
||||
return DoRequest("OPTIONS", url, data...)
|
||||
}
|
||||
|
||||
func Trace(url, data string) (*ClientResponse, error) {
|
||||
return DoRequest("TRACE", url, []byte(data))
|
||||
func Trace(url string, data...string) (*ClientResponse, error) {
|
||||
return DoRequest("TRACE", url, data...)
|
||||
}
|
||||
|
||||
// 该方法支持二进制提交数据
|
||||
func DoRequest(method, url string, data []byte) (*ClientResponse, error) {
|
||||
return NewClient().DoRequest(method, url, data)
|
||||
func DoRequest(method, url string, data...string) (*ClientResponse, error) {
|
||||
return NewClient().DoRequest(method, url, data...)
|
||||
}
|
||||
|
||||
// GET请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
|
||||
@ -24,11 +24,13 @@ import (
|
||||
|
||||
// http客户端
|
||||
type Client struct {
|
||||
http.Client // 底层http client对象
|
||||
header map[string]string // HEADER信息Map
|
||||
prefix string // 设置请求的URL前缀
|
||||
authUser string // HTTP基本权限设置:名称
|
||||
authPass string // HTTP基本权限设置:密码
|
||||
http.Client // 底层http client对象
|
||||
header map[string]string // HEADER信息Map
|
||||
cookies map[string]string // 自定义COOKIE
|
||||
prefix string // 设置请求的URL前缀
|
||||
authUser string // HTTP基本权限设置:名称
|
||||
authPass string // HTTP基本权限设置:密码
|
||||
browserMode bool // 是否模拟浏览器模式(自动保存提交COOKIE)
|
||||
}
|
||||
|
||||
// http客户端对象指针
|
||||
@ -39,10 +41,16 @@ func NewClient() (*Client) {
|
||||
DisableKeepAlives: true,
|
||||
},
|
||||
},
|
||||
header : make(map[string]string),
|
||||
header : make(map[string]string),
|
||||
cookies : make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// 是否模拟浏览器模式(自动保存提交COOKIE)
|
||||
func (c *Client) SetBrowserMode(enabled bool) {
|
||||
c.browserMode = enabled
|
||||
}
|
||||
|
||||
// 设置HTTP Header
|
||||
func (c *Client) SetHeader(key, value string) {
|
||||
c.header[key] = value
|
||||
@ -58,6 +66,18 @@ func (c *Client) SetHeaderRaw(header string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置COOKIE
|
||||
func (c *Client) SetCookie(key, value string) {
|
||||
c.cookies[key] = value
|
||||
}
|
||||
|
||||
// 使用Map设置COOKIE
|
||||
func (c *Client) SetCookieMap(cookieMap map[string]string) {
|
||||
for k, v := range cookieMap {
|
||||
c.cookies[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// 设置请求的URL前缀
|
||||
func (c *Client) SetPrefix(prefix string) {
|
||||
c.prefix = prefix
|
||||
@ -76,26 +96,30 @@ func (c *Client) SetBasicAuth(user, pass string) {
|
||||
|
||||
// GET请求
|
||||
func (c *Client) Get(url string) (*ClientResponse, error) {
|
||||
return c.DoRequest("GET", url, []byte(""))
|
||||
return c.DoRequest("GET", url)
|
||||
}
|
||||
|
||||
// PUT请求
|
||||
func (c *Client) Put(url, data string) (*ClientResponse, error) {
|
||||
return c.DoRequest("PUT", url, []byte(data))
|
||||
func (c *Client) Put(url string, data...string) (*ClientResponse, error) {
|
||||
return c.DoRequest("PUT", url, data...)
|
||||
}
|
||||
|
||||
// POST请求提交数据,默认使用表单方式提交数据(绝大部分场景下也是如此)。
|
||||
// 如果服务端对Content-Type有要求,可使用Client对象进行请求,单独设置相关属性。
|
||||
// 支持文件上传,需要字段格式为:FieldName=@file:
|
||||
func (c *Client) Post(url, data string) (*ClientResponse, error) {
|
||||
func (c *Client) Post(url string, data...string) (*ClientResponse, error) {
|
||||
if len(c.prefix) > 0 {
|
||||
url = c.prefix + url
|
||||
}
|
||||
param := ""
|
||||
if len(data) > 0 {
|
||||
param = data[0]
|
||||
}
|
||||
req := (*http.Request)(nil)
|
||||
if strings.Contains(data, "@file:") {
|
||||
if strings.Contains(param, "@file:") {
|
||||
buffer := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(buffer)
|
||||
for _, item := range strings.Split(data, "&") {
|
||||
for _, item := range strings.Split(param, "&") {
|
||||
array := strings.Split(item, "=")
|
||||
if len(array[1]) > 6 && strings.Compare(array[1][0:6], "@file:") == 0 {
|
||||
path := array[1][6:]
|
||||
@ -126,7 +150,7 @@ func (c *Client) Post(url, data string) (*ClientResponse, error) {
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
}
|
||||
} else {
|
||||
if r, err := http.NewRequest("POST", url, bytes.NewReader([]byte(data))); err != nil {
|
||||
if r, err := http.NewRequest("POST", url, bytes.NewReader([]byte(param))); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
req = r
|
||||
@ -139,6 +163,19 @@ func (c *Client) Post(url, data string) (*ClientResponse, error) {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
// COOKIE
|
||||
if len(c.cookies) > 0 {
|
||||
headerCookie := ""
|
||||
for k, v := range c.cookies {
|
||||
if len(headerCookie) > 0 {
|
||||
headerCookie += ";"
|
||||
}
|
||||
headerCookie += k + "=" + v
|
||||
}
|
||||
if len(headerCookie) > 0 {
|
||||
req.Header.Set("Cookie", headerCookie)
|
||||
}
|
||||
}
|
||||
// HTTP账号密码
|
||||
if len(c.authUser) > 0 {
|
||||
req.SetBasicAuth(c.authUser, c.authPass)
|
||||
@ -148,34 +185,36 @@ func (c *Client) Post(url, data string) (*ClientResponse, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &ClientResponse{}
|
||||
r.Response = *resp
|
||||
r := &ClientResponse{
|
||||
cookies : make(map[string]string),
|
||||
}
|
||||
r.Response = resp
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// DELETE请求
|
||||
func (c *Client) Delete(url, data string) (*ClientResponse, error) {
|
||||
return c.DoRequest("DELETE", url, []byte(data))
|
||||
func (c *Client) Delete(url string, data...string) (*ClientResponse, error) {
|
||||
return c.DoRequest("DELETE", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) Head(url, data string) (*ClientResponse, error) {
|
||||
return c.DoRequest("HEAD", url, []byte(data))
|
||||
func (c *Client) Head(url string, data...string) (*ClientResponse, error) {
|
||||
return c.DoRequest("HEAD", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) Patch(url, data string) (*ClientResponse, error) {
|
||||
return c.DoRequest("PATCH", url, []byte(data))
|
||||
func (c *Client) Patch(url string, data...string) (*ClientResponse, error) {
|
||||
return c.DoRequest("PATCH", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) Connect(url, data string) (*ClientResponse, error) {
|
||||
return c.DoRequest("CONNECT", url, []byte(data))
|
||||
func (c *Client) Connect(url string, data...string) (*ClientResponse, error) {
|
||||
return c.DoRequest("CONNECT", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) Options(url, data string) (*ClientResponse, error) {
|
||||
return c.DoRequest("OPTIONS", url, []byte(data))
|
||||
func (c *Client) Options(url string, data...string) (*ClientResponse, error) {
|
||||
return c.DoRequest("OPTIONS", url, data...)
|
||||
}
|
||||
|
||||
func (c *Client) Trace(url, data string) (*ClientResponse, error) {
|
||||
return c.DoRequest("TRACE", url, []byte(data))
|
||||
func (c *Client) Trace(url string, data...string) (*ClientResponse, error) {
|
||||
return c.DoRequest("TRACE", url, data...)
|
||||
}
|
||||
|
||||
// GET请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
@ -220,11 +259,7 @@ func (c *Client) TraceContent(url string, data...string) string {
|
||||
|
||||
// 请求并返回服务端结果(内部会自动读取服务端返回结果并关闭缓冲区指针)
|
||||
func (c *Client) DoRequestContent(method string, url string, data...string) string {
|
||||
content := ""
|
||||
if len(data) > 0 {
|
||||
content = data[0]
|
||||
}
|
||||
response, err := c.DoRequest(method, url, []byte(content))
|
||||
response, err := c.DoRequest(method, url, data...)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
@ -233,14 +268,18 @@ func (c *Client) DoRequestContent(method string, url string, data...string) stri
|
||||
}
|
||||
|
||||
// 请求并返回response对象,该方法支持二进制提交数据
|
||||
func (c *Client) DoRequest(method, url string, data []byte) (*ClientResponse, error) {
|
||||
func (c *Client) DoRequest(method, url string, data...string) (*ClientResponse, error) {
|
||||
if strings.EqualFold("POST", method) {
|
||||
return c.Post(url, string(data))
|
||||
return c.Post(url, data...)
|
||||
}
|
||||
if len(c.prefix) > 0 {
|
||||
url = c.prefix + url
|
||||
}
|
||||
req, err := http.NewRequest(strings.ToUpper(method), url, bytes.NewReader(data))
|
||||
param := ""
|
||||
if len(data) > 0 {
|
||||
param = data[0]
|
||||
}
|
||||
req, err := http.NewRequest(strings.ToUpper(method), url, bytes.NewReader([]byte(param)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -250,13 +289,40 @@ func (c *Client) DoRequest(method, url string, data []byte) (*ClientResponse, er
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
}
|
||||
// COOKIE
|
||||
if len(c.cookies) > 0 {
|
||||
headerCookie := ""
|
||||
for k, v := range c.cookies {
|
||||
if len(headerCookie) > 0 {
|
||||
headerCookie += ";"
|
||||
}
|
||||
headerCookie += k + "=" + v
|
||||
}
|
||||
if len(headerCookie) > 0 {
|
||||
req.Header.Set("Cookie", headerCookie)
|
||||
}
|
||||
}
|
||||
// 执行请求
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := &ClientResponse{}
|
||||
r.Response = *resp
|
||||
r := &ClientResponse{
|
||||
cookies : make(map[string]string),
|
||||
}
|
||||
r.Response = resp
|
||||
// 浏览器模式
|
||||
if c.browserMode {
|
||||
now := time.Now()
|
||||
for _, v := range r.Cookies() {
|
||||
if v.Expires.UnixNano() < now.UnixNano() {
|
||||
delete(c.cookies, v.Name)
|
||||
} else {
|
||||
c.cookies[v.Name] = v.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
//fmt.Println(url, c.cookies)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
|
||||
@ -10,14 +10,30 @@ package ghttp
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 客户端请求结果对象
|
||||
type ClientResponse struct {
|
||||
http.Response
|
||||
*http.Response
|
||||
cookies map[string]string
|
||||
}
|
||||
|
||||
// 获取返回的数据
|
||||
// 获得返回的指定COOKIE值
|
||||
func (r *ClientResponse) GetCookie(key string) string {
|
||||
if r.cookies == nil {
|
||||
now := time.Now()
|
||||
for _, v := range r.Cookies() {
|
||||
if v.Expires.UnixNano() < now.UnixNano() {
|
||||
continue
|
||||
}
|
||||
r.cookies[v.Name] = v.Value
|
||||
}
|
||||
}
|
||||
return r.cookies[key]
|
||||
}
|
||||
|
||||
// 获取返回的数据(二进制).
|
||||
func (r *ClientResponse) ReadAll() []byte {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
@ -26,6 +42,11 @@ func (r *ClientResponse) ReadAll() []byte {
|
||||
return body
|
||||
}
|
||||
|
||||
// 获取返回的数据(字符串).
|
||||
func (r *ClientResponse) ReadAllString() string {
|
||||
return string(r.ReadAll())
|
||||
}
|
||||
|
||||
// 关闭返回的HTTP链接
|
||||
func (r *ClientResponse) Close() {
|
||||
r.Response.Close = true
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,6 @@ func newResponse(s *Server, w http.ResponseWriter) *Response {
|
||||
Server : s,
|
||||
ResponseWriter : ResponseWriter {
|
||||
ResponseWriter : w,
|
||||
Status : http.StatusOK,
|
||||
buffer : bytes.NewBuffer(nil),
|
||||
},
|
||||
}
|
||||
@ -137,9 +136,8 @@ func (r *Response) WriteStatus(status int, content...string) {
|
||||
if status != http.StatusOK {
|
||||
if f := r.request.Server.getStatusHandler(status, r.request); f != nil {
|
||||
f(r.request)
|
||||
// 如果是http.StatusOK那么表示回调函数内部没有设置header status,
|
||||
// 那么这里就可以设置status,防止多次设置(http: multiple response.WriteHeader calls)
|
||||
if r.Status == http.StatusOK {
|
||||
// 防止多次设置(http: multiple response.WriteHeader calls)
|
||||
if r.Status == 0 {
|
||||
r.WriteHeader(status)
|
||||
}
|
||||
return
|
||||
|
||||
@ -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("")
|
||||
|
||||
@ -140,17 +140,24 @@ var (
|
||||
doneChan = make(chan struct{}, 1000)
|
||||
|
||||
// 用于服务进程初始化,只能初始化一次,采用“懒初始化”(在server运行时才初始化)
|
||||
serverProcInited = gtype.NewBool()
|
||||
serverProcessInited = gtype.NewBool()
|
||||
|
||||
// 是否开启WebServer平滑重启特性, 会开启额外的本地端口监听,用于进程管理通信
|
||||
gracefulEnabled = true
|
||||
)
|
||||
|
||||
// 是否开启平滑重启特性
|
||||
func SetGraceful(enabled bool) {
|
||||
gracefulEnabled = enabled
|
||||
}
|
||||
|
||||
// Web Server进程初始化.
|
||||
// 注意该方法不能放置于包初始化方法init中,不使用ghttp.Server的功能便不能初始化对应的协程goroutine逻辑.
|
||||
func serverProcInit() {
|
||||
if serverProcInited.Val() {
|
||||
func serverProcessInit() {
|
||||
if serverProcessInited.Val() {
|
||||
return
|
||||
}
|
||||
serverProcInited.Set(true)
|
||||
serverProcessInited.Set(true)
|
||||
// 如果是完整重启,那么需要等待主进程销毁后,才开始执行监听,防止端口冲突
|
||||
if genv.Get(gADMIN_ACTION_RESTART_ENVKEY) != "" {
|
||||
if p, e := os.FindProcess(gproc.PPid()); e == nil {
|
||||
@ -164,7 +171,9 @@ func serverProcInit() {
|
||||
// 信号量管理操作监听
|
||||
go handleProcessSignal()
|
||||
// 异步监听进程间消息
|
||||
go handleProcessMessage()
|
||||
if gracefulEnabled {
|
||||
go handleProcessMessage()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取/创建一个默认配置的HTTP Server(默认监听端口是80)
|
||||
@ -207,7 +216,7 @@ func GetServer(name...interface{}) (*Server) {
|
||||
// 需要结合Wait方式一起使用
|
||||
func (s *Server) Start() error {
|
||||
// 服务进程初始化,只会初始化一次
|
||||
serverProcInit()
|
||||
serverProcessInit()
|
||||
|
||||
// 当前Web Server状态判断
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
@ -253,7 +262,7 @@ func (s *Server) Start() error {
|
||||
if gproc.IsChild() {
|
||||
gtimer.SetTimeout(2*time.Second, func() {
|
||||
if err := gproc.Send(gproc.PPid(), []byte("exit"), gADMIN_GPROC_COMM_GROUP); err != nil {
|
||||
panic(err)
|
||||
glog.Error("ghttp server error in process communication:", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ func GetCookie(r *Request) *Cookie {
|
||||
}
|
||||
return &Cookie {
|
||||
request : r,
|
||||
server : r.Server,
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +53,6 @@ func (c *Cookie) init() {
|
||||
c.path = c.request.Server.GetCookiePath()
|
||||
c.domain = c.request.Server.GetCookieDomain()
|
||||
c.maxage = c.request.Server.GetCookieMaxAge()
|
||||
c.server = c.request.Server
|
||||
c.response = c.request.Response
|
||||
// 如果没有设置COOKIE有效域名,那么设置HOST为默认有效域名
|
||||
if c.domain == "" {
|
||||
@ -115,6 +115,11 @@ func (c *Cookie) SetCookie(key, value, domain, path string, maxAge int, httpOnly
|
||||
}
|
||||
}
|
||||
|
||||
// 获得客户端提交的SessionId
|
||||
func (c *Cookie) GetSessionId() string {
|
||||
return c.Get(c.server.GetSessionIdName())
|
||||
}
|
||||
|
||||
// 设置SessionId
|
||||
func (c *Cookie) SetSessionId(id string) {
|
||||
c.Set(c.server.GetSessionIdName(), id)
|
||||
@ -133,9 +138,14 @@ func (c *Cookie) Get(key string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 删除COOKIE,使用默认的domain&path
|
||||
func (c *Cookie) Remove(key string) {
|
||||
c.SetCookie(key, "", c.domain, c.path, -86400)
|
||||
}
|
||||
|
||||
// 标记该cookie在对应的域名和路径失效
|
||||
// 删除cookie的重点是需要通知浏览器客户端cookie已过期
|
||||
func (c *Cookie) Remove(key, domain, path string) {
|
||||
func (c *Cookie) RemoveCookie(key, domain, path string) {
|
||||
c.SetCookie(key, "", domain, path, -86400)
|
||||
}
|
||||
|
||||
|
||||
@ -46,14 +46,28 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
request := newRequest(s, r, w)
|
||||
|
||||
defer func() {
|
||||
if request.LeaveTime == 0 {
|
||||
request.LeaveTime = gtime.Microsecond()
|
||||
// 设置请求完成时间
|
||||
request.LeaveTime = gtime.Microsecond()
|
||||
// 事件 - BeforeOutput
|
||||
if !request.IsExited() {
|
||||
s.callHookHandler(HOOK_BEFORE_OUTPUT, request)
|
||||
}
|
||||
// 输出Cookie
|
||||
request.Cookie.Output()
|
||||
// 输出缓冲区
|
||||
request.Response.OutputBuffer()
|
||||
// 事件 - AfterOutput
|
||||
if !request.IsExited() {
|
||||
s.callHookHandler(HOOK_AFTER_OUTPUT, request)
|
||||
}
|
||||
|
||||
// 事件 - BeforeClose
|
||||
s.callHookHandler(HOOK_BEFORE_CLOSE, request)
|
||||
// access log
|
||||
s.handleAccessLog(request)
|
||||
// error log使用recover进行判断
|
||||
if e := recover(); e != nil {
|
||||
request.Response.WriteStatus(http.StatusInternalServerError)
|
||||
s.handleErrorLog(e, request)
|
||||
}
|
||||
// 更新Session会话超时时间
|
||||
@ -125,22 +139,6 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
if !request.IsExited() {
|
||||
s.callHookHandler(HOOK_AFTER_SERVE, request)
|
||||
}
|
||||
|
||||
// 设置请求完成时间
|
||||
request.LeaveTime = gtime.Microsecond()
|
||||
|
||||
// 事件 - BeforeOutput
|
||||
if !request.IsExited() {
|
||||
s.callHookHandler(HOOK_BEFORE_OUTPUT, request)
|
||||
}
|
||||
// 输出Cookie
|
||||
request.Cookie.Output()
|
||||
// 输出缓冲区
|
||||
request.Response.OutputBuffer()
|
||||
// 事件 - AfterOutput
|
||||
if !request.IsExited() {
|
||||
s.callHookHandler(HOOK_AFTER_OUTPUT, request)
|
||||
}
|
||||
}
|
||||
|
||||
// 查找静态文件的绝对路径
|
||||
|
||||
@ -10,7 +10,6 @@ package ghttp
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// 处理服务错误信息,主要是panic,http请求的status由access log进行管理
|
||||
@ -34,8 +33,6 @@ func (s *Server) handleAccessLog(r *Request) {
|
||||
|
||||
// 处理服务错误信息,主要是panic,http请求的status由access log进行管理
|
||||
func (s *Server) handleErrorLog(error interface{}, r *Request) {
|
||||
r.Response.WriteStatus(http.StatusInternalServerError)
|
||||
|
||||
// 错误输出默认是开启的
|
||||
if !s.IsErrorLogEnabled() && gfile.MainPkgPath() == "" {
|
||||
return
|
||||
|
||||
@ -64,15 +64,15 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) e
|
||||
faddr : nil,
|
||||
}
|
||||
// 如果方法中带有Index方法,那么额外自动增加一个路由规则匹配主URI
|
||||
// 例如: pattern为/user, 那么会同时注册/user及/user/index,
|
||||
// 这里处理新增/user路由绑定
|
||||
if strings.EqualFold(mname, "Index") {
|
||||
p := key
|
||||
if strings.EqualFold(p[len(p) - 6:], "/index") {
|
||||
p = p[0 : len(p) - 6]
|
||||
if len(p) == 0 {
|
||||
p = "/"
|
||||
}
|
||||
p := gstr.PosR(key, "/index")
|
||||
k := key[0 : p] + key[p + 6 : ]
|
||||
if len(k) == 0 {
|
||||
k = "/"
|
||||
}
|
||||
m[p] = &handlerItem {
|
||||
m[k] = &handlerItem {
|
||||
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, mname),
|
||||
rtype : gROUTE_REGISTER_CONTROLLER,
|
||||
ctype : v.Elem().Type(),
|
||||
|
||||
@ -74,14 +74,12 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
|
||||
}
|
||||
// 如果方法中带有Index方法,那么额外自动增加一个路由规则匹配主URI
|
||||
if strings.EqualFold(mname, "Index") {
|
||||
p := key
|
||||
if strings.EqualFold(p[len(p) - 6:], "/index") {
|
||||
p = p[0 : len(p) - 6]
|
||||
if len(p) == 0 {
|
||||
p = "/"
|
||||
}
|
||||
p := gstr.PosR(key, "/index")
|
||||
k := key[0 : p] + key[p + 6 : ]
|
||||
if len(k) == 0 {
|
||||
k = "/"
|
||||
}
|
||||
m[p] = &handlerItem {
|
||||
m[k] = &handlerItem {
|
||||
name : fmt.Sprintf(`%s.%s.%s`, pkgPath, objName, mname),
|
||||
rtype : gROUTE_REGISTER_OBJECT,
|
||||
ctype : nil,
|
||||
|
||||
@ -52,7 +52,7 @@ func (s *Session) init() {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取SessionId
|
||||
// 获取/创建SessionId
|
||||
func (s *Session) Id() string {
|
||||
s.init()
|
||||
return s.id
|
||||
@ -60,8 +60,11 @@ func (s *Session) Id() string {
|
||||
|
||||
// 获取当前session所有数据
|
||||
func (s *Session) Data() map[string]interface{} {
|
||||
s.init()
|
||||
return s.data.Map()
|
||||
if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" {
|
||||
s.init()
|
||||
return s.data.Map()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 设置session
|
||||
@ -84,70 +87,158 @@ func (s *Session) BatchSet(m map[string]interface{}) {
|
||||
|
||||
// 判断键名是否存在
|
||||
func (s *Session) Contains (key string) bool {
|
||||
s.init()
|
||||
return s.data.Contains(key)
|
||||
if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" {
|
||||
s.init()
|
||||
return s.data.Contains(key)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 获取SESSION
|
||||
func (s *Session) Get (key string) interface{} {
|
||||
s.init()
|
||||
return s.data.Get(key)
|
||||
if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" {
|
||||
s.init()
|
||||
return s.data.Get(key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取SESSION,建议都用该方法获取参数
|
||||
func (s *Session) GetVar(key string) gvar.VarRead {
|
||||
s.init()
|
||||
return gvar.NewRead(s.data.Get(key), true)
|
||||
}
|
||||
|
||||
|
||||
func (s *Session) GetString (key string) string { return gconv.String(s.Get(key)) }
|
||||
func (s *Session) GetBool(key string) bool { return gconv.Bool(s.Get(key)) }
|
||||
|
||||
func (s *Session) GetInt(key string) int { return gconv.Int(s.Get(key)) }
|
||||
func (s *Session) GetInt8(key string) int8 { return gconv.Int8(s.Get(key)) }
|
||||
func (s *Session) GetInt16(key string) int16 { return gconv.Int16(s.Get(key)) }
|
||||
func (s *Session) GetInt32(key string) int32 { return gconv.Int32(s.Get(key)) }
|
||||
func (s *Session) GetInt64(key string) int64 { return gconv.Int64(s.Get(key)) }
|
||||
|
||||
func (s *Session) GetUint(key string) uint { return gconv.Uint(s.Get(key)) }
|
||||
func (s *Session) GetUint8(key string) uint8 { return gconv.Uint8(s.Get(key)) }
|
||||
func (s *Session) GetUint16(key string) uint16 { return gconv.Uint16(s.Get(key)) }
|
||||
func (s *Session) GetUint32(key string) uint32 { return gconv.Uint32(s.Get(key)) }
|
||||
func (s *Session) GetUint64(key string) uint64 { return gconv.Uint64(s.Get(key)) }
|
||||
|
||||
func (s *Session) GetFloat32 (key string) float32 { return gconv.Float32(s.Get(key)) }
|
||||
func (s *Session) GetFloat64 (key string) float64 { return gconv.Float64(s.Get(key)) }
|
||||
|
||||
func (s *Session) GetBytes(key string) []byte { return gconv.Bytes(s.Get(key)) }
|
||||
func (s *Session) GetInts(key string) []int { return gconv.Ints(s.Get(key)) }
|
||||
func (s *Session) GetFloats(key string) []float64 { return gconv.Floats(s.Get(key)) }
|
||||
func (s *Session) GetStrings(key string) []string { return gconv.Strings(s.Get(key)) }
|
||||
func (s *Session) GetInterfaces(key string) []interface{} { return gconv.Interfaces(s.Get(key)) }
|
||||
|
||||
func (s *Session) GetTime(key string, format...string) time.Time { return gconv.Time(s.Get(key), format...) }
|
||||
func (s *Session) GetTimeDuration(key string) time.Duration { return gconv.TimeDuration(s.Get(key)) }
|
||||
|
||||
// 将变量转换为对象,注意 objPointer 参数必须为struct指针
|
||||
func (s *Session) GetStruct(key string, objPointer interface{}, attrMapping...map[string]string) error {
|
||||
return gconv.Struct(s.Get(key), objPointer, attrMapping...)
|
||||
return gvar.NewRead(s.Get(key), true)
|
||||
}
|
||||
|
||||
// 删除session
|
||||
func (s *Session) Remove(key string) {
|
||||
s.init()
|
||||
s.data.Remove(key)
|
||||
if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" {
|
||||
s.init()
|
||||
s.data.Remove(key)
|
||||
}
|
||||
}
|
||||
|
||||
// 清空session
|
||||
func (s *Session) Clear() {
|
||||
s.init()
|
||||
s.data.Clear()
|
||||
if len(s.id) > 0 || s.request.Cookie.GetSessionId() != "" {
|
||||
s.init()
|
||||
s.data.Clear()
|
||||
}
|
||||
}
|
||||
|
||||
// 更新过期时间(如果用在守护进程中长期使用,需要手动调用进行更新,防止超时被清除)
|
||||
func (s *Session) UpdateExpire() {
|
||||
if len(s.id) > 0 {
|
||||
if len(s.id) > 0 && s.data.Size() > 0 {
|
||||
s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetString(key string) string {
|
||||
return gconv.String(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetBool(key string) bool {
|
||||
return gconv.Bool(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetInt(key string) int {
|
||||
return gconv.Int(s.Get(key)) }
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetInt8(key string) int8 {
|
||||
return gconv.Int8(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetInt16(key string) int16 {
|
||||
return gconv.Int16(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetInt32(key string) int32 {
|
||||
return gconv.Int32(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetInt64(key string) int64 {
|
||||
return gconv.Int64(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetUint(key string) uint {
|
||||
return gconv.Uint(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetUint8(key string) uint8 {
|
||||
return gconv.Uint8(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetUint16(key string) uint16 {
|
||||
return gconv.Uint16(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetUint32(key string) uint32 {
|
||||
return gconv.Uint32(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetUint64(key string) uint64 {
|
||||
return gconv.Uint64(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetFloat32(key string) float32 {
|
||||
return gconv.Float32(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetFloat64(key string) float64 {
|
||||
return gconv.Float64(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetBytes(key string) []byte {
|
||||
return gconv.Bytes(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetInts(key string) []int {
|
||||
return gconv.Ints(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetFloats(key string) []float64 {
|
||||
return gconv.Floats(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetStrings(key string) []string {
|
||||
return gconv.Strings(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetInterfaces(key string) []interface{} {
|
||||
return gconv.Interfaces(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetTime(key string, format...string) time.Time {
|
||||
return gconv.Time(s.Get(key), format...)
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
func (s *Session) GetTimeDuration(key string) time.Duration {
|
||||
return gconv.TimeDuration(s.Get(key))
|
||||
}
|
||||
|
||||
// Deprecated, use GetVar instead.
|
||||
// (已废弃, 请使用GetVar) 将变量转换为对象,注意 objPointer 参数必须为struct指针
|
||||
func (s *Session) GetStruct(key string, objPointer interface{}, attrMapping...map[string]string) error {
|
||||
return gconv.Struct(s.Get(key), objPointer, attrMapping...)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// 基本路由功能以及优先级测试
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/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())
|
||||
s.BindHandler("/:name", func(r *ghttp.Request){
|
||||
r.Response.Write("/:name")
|
||||
})
|
||||
s.BindHandler("/:name/update", func(r *ghttp.Request){
|
||||
r.Response.Write(r.Get("name"))
|
||||
})
|
||||
s.BindHandler("/:name/:action", func(r *ghttp.Request){
|
||||
r.Response.Write(r.Get("action"))
|
||||
})
|
||||
s.BindHandler("/:name/*any", func(r *ghttp.Request){
|
||||
r.Response.Write(r.Get("any"))
|
||||
})
|
||||
s.BindHandler("/user/list/{field}.html", func(r *ghttp.Request){
|
||||
r.Response.Write(r.Get("field"))
|
||||
})
|
||||
s.SetPort(8100)
|
||||
s.SetDumpRouteMap(false)
|
||||
go s.Run()
|
||||
defer func() {
|
||||
s.Shutdown()
|
||||
time.Sleep(time.Second)
|
||||
}()
|
||||
// 等待启动完成
|
||||
time.Sleep(time.Second)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix("http://127.0.0.1:8100")
|
||||
|
||||
gtest.Assert(client.GetContent("/john"), "")
|
||||
gtest.Assert(client.GetContent("/john/update"), "john")
|
||||
gtest.Assert(client.GetContent("/john/edit"), "edit")
|
||||
gtest.Assert(client.GetContent("/user/list/100.html"), "100")
|
||||
})
|
||||
}
|
||||
62
g/net/ghttp/ghttp_unit_cookie_test.go
Normal file
62
g/net/ghttp/ghttp_unit_cookie_test.go
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// COOKIE测试
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"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())
|
||||
s.BindHandler("/set", func(r *ghttp.Request){
|
||||
r.Cookie.Set(r.Get("k"), r.Get("v"))
|
||||
})
|
||||
s.BindHandler("/get", func(r *ghttp.Request){
|
||||
//fmt.Println(r.Cookie.Map())
|
||||
r.Response.Write(r.Cookie.Get(r.Get("k")))
|
||||
})
|
||||
s.BindHandler("/remove", func(r *ghttp.Request){
|
||||
r.Cookie.Remove(r.Get("k"))
|
||||
})
|
||||
s.SetPort(8500)
|
||||
s.SetDumpRouteMap(false)
|
||||
go s.Run()
|
||||
defer func() {
|
||||
s.Shutdown()
|
||||
time.Sleep(time.Second)
|
||||
}()
|
||||
// 等待启动完成
|
||||
time.Sleep(time.Second)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetBrowserMode(true)
|
||||
client.SetPrefix("http://127.0.0.1:8500")
|
||||
r1, e1 := client.Get("/set?k=key1&v=100")
|
||||
if r1 != nil {
|
||||
defer r1.Close()
|
||||
}
|
||||
gtest.Assert(e1, nil)
|
||||
gtest.Assert(r1.ReadAllString(), "")
|
||||
|
||||
gtest.Assert(client.GetContent("/set?k=key2&v=200"), "")
|
||||
|
||||
gtest.Assert(client.GetContent("/get?k=key1"), "100")
|
||||
gtest.Assert(client.GetContent("/get?k=key2"), "200")
|
||||
gtest.Assert(client.GetContent("/get?k=key3"), "")
|
||||
gtest.Assert(client.GetContent("/remove?k=key1"), "")
|
||||
gtest.Assert(client.GetContent("/remove?k=key3"), "")
|
||||
gtest.Assert(client.GetContent("/remove?k=key4"), "")
|
||||
gtest.Assert(client.GetContent("/get?k=key1"), "")
|
||||
gtest.Assert(client.GetContent("/get?k=key2"), "200")
|
||||
})
|
||||
}
|
||||
@ -16,7 +16,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_Params(t *testing.T) {
|
||||
func Test_Params_Basic(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Name string
|
||||
208
g/net/ghttp/ghttp_unit_router_basic_test.go
Normal file
208
g/net/ghttp/ghttp_unit_router_basic_test.go
Normal file
@ -0,0 +1,208 @@
|
||||
// Copyright 2018 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// 基本路由功能以及优先级测试
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g"
|
||||
"github.com/gogf/gf/g/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())
|
||||
s.BindHandler("/:name", func(r *ghttp.Request){
|
||||
r.Response.Write("/:name")
|
||||
})
|
||||
s.BindHandler("/:name/update", func(r *ghttp.Request){
|
||||
r.Response.Write(r.Get("name"))
|
||||
})
|
||||
s.BindHandler("/:name/:action", func(r *ghttp.Request){
|
||||
r.Response.Write(r.Get("action"))
|
||||
})
|
||||
s.BindHandler("/:name/*any", func(r *ghttp.Request){
|
||||
r.Response.Write(r.Get("any"))
|
||||
})
|
||||
s.BindHandler("/user/list/{field}.html", func(r *ghttp.Request){
|
||||
r.Response.Write(r.Get("field"))
|
||||
})
|
||||
s.SetPort(8100)
|
||||
s.SetDumpRouteMap(false)
|
||||
go s.Run()
|
||||
defer func() {
|
||||
s.Shutdown()
|
||||
time.Sleep(time.Second)
|
||||
}()
|
||||
// 等待启动完成
|
||||
time.Sleep(time.Second)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix("http://127.0.0.1:8100")
|
||||
|
||||
gtest.Assert(client.GetContent("/john"), "")
|
||||
gtest.Assert(client.GetContent("/john/update"), "john")
|
||||
gtest.Assert(client.GetContent("/john/edit"), "edit")
|
||||
gtest.Assert(client.GetContent("/user/list/100.html"), "100")
|
||||
})
|
||||
}
|
||||
|
||||
// 测试HTTP Method注册.
|
||||
func Test_Router_Method(t *testing.T) {
|
||||
s := g.Server(gtime.Nanosecond())
|
||||
s.BindHandler("GET:/get", func(r *ghttp.Request){
|
||||
|
||||
})
|
||||
s.BindHandler("POST:/post", func(r *ghttp.Request){
|
||||
|
||||
})
|
||||
s.SetPort(8105)
|
||||
s.SetDumpRouteMap(false)
|
||||
go s.Run()
|
||||
defer func() {
|
||||
s.Shutdown()
|
||||
time.Sleep(time.Second)
|
||||
}()
|
||||
// 等待启动完成
|
||||
time.Sleep(time.Second)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix("http://127.0.0.1:8105")
|
||||
|
||||
resp1, err := client.Get("/get")
|
||||
defer resp1.Close()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(resp1.StatusCode, 200)
|
||||
|
||||
resp2, err := client.Post("/get")
|
||||
defer resp2.Close()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(resp2.StatusCode, 404)
|
||||
|
||||
resp3, err := client.Get("/post")
|
||||
defer resp3.Close()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(resp3.StatusCode, 404)
|
||||
|
||||
resp4, err := client.Post("/post")
|
||||
defer resp4.Close()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(resp4.StatusCode, 200)
|
||||
})
|
||||
}
|
||||
|
||||
// 测试状态返回.
|
||||
func Test_Router_Status(t *testing.T) {
|
||||
s := g.Server(gtime.Nanosecond())
|
||||
s.BindHandler("/200", func(r *ghttp.Request){
|
||||
r.Response.WriteStatus(200)
|
||||
})
|
||||
s.BindHandler("/300", func(r *ghttp.Request){
|
||||
r.Response.WriteStatus(300)
|
||||
})
|
||||
s.BindHandler("/400", func(r *ghttp.Request){
|
||||
r.Response.WriteStatus(400)
|
||||
})
|
||||
s.BindHandler("/500", func(r *ghttp.Request){
|
||||
r.Response.WriteStatus(500)
|
||||
})
|
||||
s.SetPort(8110)
|
||||
s.SetDumpRouteMap(false)
|
||||
go s.Run()
|
||||
defer func() {
|
||||
s.Shutdown()
|
||||
time.Sleep(time.Second)
|
||||
}()
|
||||
// 等待启动完成
|
||||
time.Sleep(time.Second)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix("http://127.0.0.1:8110")
|
||||
|
||||
resp1, err := client.Get("/200")
|
||||
defer resp1.Close()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(resp1.StatusCode, 200)
|
||||
|
||||
resp2, err := client.Get("/300")
|
||||
defer resp2.Close()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(resp2.StatusCode, 300)
|
||||
|
||||
resp3, err := client.Get("/400")
|
||||
defer resp3.Close()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(resp3.StatusCode, 400)
|
||||
|
||||
resp4, err := client.Get("/500")
|
||||
defer resp4.Close()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(resp4.StatusCode, 500)
|
||||
})
|
||||
}
|
||||
|
||||
// 自定义状态码处理.
|
||||
func Test_Router_CustomStatusHandler(t *testing.T) {
|
||||
s := g.Server(gtime.Nanosecond())
|
||||
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.SetDumpRouteMap(false)
|
||||
go s.Run()
|
||||
defer func() {
|
||||
s.Shutdown()
|
||||
time.Sleep(time.Second)
|
||||
}()
|
||||
// 等待启动完成
|
||||
time.Sleep(time.Second)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix("http://127.0.0.1:8120")
|
||||
|
||||
gtest.Assert(client.GetContent("/"), "hello")
|
||||
resp, err := client.Get("/ThisDoesNotExist")
|
||||
defer resp.Close()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(resp.StatusCode, 404)
|
||||
gtest.Assert(resp.ReadAllString(), "404 page")
|
||||
})
|
||||
}
|
||||
|
||||
// 测试不存在的路由.
|
||||
func Test_Router_404(t *testing.T) {
|
||||
s := g.Server(gtime.Nanosecond())
|
||||
s.BindHandler("/", func(r *ghttp.Request){
|
||||
r.Response.Write("hello")
|
||||
})
|
||||
s.SetPort(8130)
|
||||
s.SetDumpRouteMap(false)
|
||||
go s.Run()
|
||||
defer func() {
|
||||
s.Shutdown()
|
||||
time.Sleep(time.Second)
|
||||
}()
|
||||
// 等待启动完成
|
||||
time.Sleep(time.Second)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetPrefix("http://127.0.0.1:8130")
|
||||
|
||||
gtest.Assert(client.GetContent("/"), "hello")
|
||||
resp, err := client.Get("/ThisDoesNotExist")
|
||||
defer resp.Close()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(resp.StatusCode, 404)
|
||||
})
|
||||
}
|
||||
@ -20,6 +20,10 @@ import (
|
||||
// 执行对象
|
||||
type Object struct {}
|
||||
|
||||
func (o *Object) Index(r *ghttp.Request) {
|
||||
r.Response.Write("Object Index")
|
||||
}
|
||||
|
||||
func (o *Object) Show(r *ghttp.Request) {
|
||||
r.Response.Write("Object Show")
|
||||
}
|
||||
@ -33,6 +37,10 @@ type Controller struct {
|
||||
gmvc.Controller
|
||||
}
|
||||
|
||||
func (c *Controller) Index() {
|
||||
c.Response.Write("Controller Index")
|
||||
}
|
||||
|
||||
func (c *Controller) Show() {
|
||||
c.Response.Write("Controller Show")
|
||||
}
|
||||
@ -72,16 +80,27 @@ func Test_Router_Group1(t *testing.T) {
|
||||
|
||||
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/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")
|
||||
|
||||
// 测试404
|
||||
resp, err := client.Get("/ThisDoesNotExist")
|
||||
defer resp.Close()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(resp.StatusCode, 404)
|
||||
})
|
||||
}
|
||||
|
||||
@ -122,5 +141,11 @@ func Test_Router_Group2(t *testing.T) {
|
||||
gtest.Assert(client.GetContent ("/api/obj/my-show"), "Object Show")
|
||||
gtest.Assert(client.GetContent ("/api/obj/show"), "Object Show")
|
||||
gtest.Assert(client.DeleteContent("/api/obj/rest"), "Object REST Delete")
|
||||
|
||||
// 测试404
|
||||
resp, err := client.Get("/ThisDoesNotExist")
|
||||
defer resp.Close()
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(resp.StatusCode, 404)
|
||||
})
|
||||
}
|
||||
66
g/net/ghttp/ghttp_unit_session_test.go
Normal file
66
g/net/ghttp/ghttp_unit_session_test.go
Normal file
@ -0,0 +1,66 @@
|
||||
// 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.
|
||||
|
||||
// SESSION测试
|
||||
package ghttp_test
|
||||
|
||||
import (
|
||||
"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())
|
||||
s.BindHandler("/set", func(r *ghttp.Request){
|
||||
r.Session.Set(r.Get("k"), r.Get("v"))
|
||||
})
|
||||
s.BindHandler("/get", func(r *ghttp.Request){
|
||||
r.Response.Write(r.Session.Get(r.Get("k")))
|
||||
})
|
||||
s.BindHandler("/remove", func(r *ghttp.Request){
|
||||
r.Session.Remove(r.Get("k"))
|
||||
})
|
||||
s.BindHandler("/clear", func(r *ghttp.Request){
|
||||
r.Session.Clear()
|
||||
})
|
||||
s.SetPort(8600)
|
||||
s.SetDumpRouteMap(false)
|
||||
go s.Run()
|
||||
defer func() {
|
||||
s.Shutdown()
|
||||
time.Sleep(time.Second)
|
||||
}()
|
||||
// 等待启动完成
|
||||
time.Sleep(time.Second)
|
||||
gtest.Case(t, func() {
|
||||
client := ghttp.NewClient()
|
||||
client.SetBrowserMode(true)
|
||||
client.SetPrefix("http://127.0.0.1:8600")
|
||||
r1, e1 := client.Get("/set?k=key1&v=100")
|
||||
if r1 != nil {
|
||||
defer r1.Close()
|
||||
}
|
||||
gtest.Assert(e1, nil)
|
||||
gtest.Assert(r1.ReadAllString(), "")
|
||||
|
||||
gtest.Assert(client.GetContent("/set?k=key2&v=200"), "")
|
||||
|
||||
gtest.Assert(client.GetContent("/get?k=key1"), "100")
|
||||
gtest.Assert(client.GetContent("/get?k=key2"), "200")
|
||||
gtest.Assert(client.GetContent("/get?k=key3"), "")
|
||||
gtest.Assert(client.GetContent("/remove?k=key1"), "")
|
||||
gtest.Assert(client.GetContent("/remove?k=key3"), "")
|
||||
gtest.Assert(client.GetContent("/remove?k=key4"), "")
|
||||
gtest.Assert(client.GetContent("/get?k=key1"), "")
|
||||
gtest.Assert(client.GetContent("/get?k=key2"), "200")
|
||||
gtest.Assert(client.GetContent("/clear"), "")
|
||||
gtest.Assert(client.GetContent("/get?k=key2"), "")
|
||||
})
|
||||
}
|
||||
@ -63,13 +63,8 @@ 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())
|
||||
//}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -94,6 +89,10 @@ 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)
|
||||
@ -116,14 +115,29 @@ 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)
|
||||
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
|
||||
}
|
||||
|
||||
// 设置配置管理对象的默认文件名称
|
||||
|
||||
@ -117,23 +117,23 @@ func newSchedule(pattern string) (*cronSchedule, error) {
|
||||
schedule.hour = m
|
||||
}
|
||||
// 天
|
||||
if m, err := parseItem(match[4], 1, 31, false); err != nil {
|
||||
if m, err := parseItem(match[4], 1, 31, true); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.day = m
|
||||
}
|
||||
// 周
|
||||
if m, err := parseItem(match[5], 0, 6, false); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.week = m
|
||||
}
|
||||
// 月
|
||||
if m, err := parseItem(match[6], 1, 12, false); err != nil {
|
||||
if m, err := parseItem(match[5], 1, 12, false); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.month = m
|
||||
}
|
||||
// 周
|
||||
if m, err := parseItem(match[6], 0, 6, true); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
schedule.week = m
|
||||
}
|
||||
return schedule, nil
|
||||
} else {
|
||||
return nil, errors.New(fmt.Sprintf(`invalid pattern: "%s"`, pattern))
|
||||
@ -200,14 +200,14 @@ func parseItemValue(value string, valueType byte) (int, error) {
|
||||
} else {
|
||||
// 英文字母
|
||||
switch valueType {
|
||||
case 'w':
|
||||
if i, ok := weekMap[strings.ToLower(value)]; ok {
|
||||
return int(i), nil
|
||||
}
|
||||
case 'm':
|
||||
if i, ok := monthMap[strings.ToLower(value)]; ok {
|
||||
return int(i), nil
|
||||
}
|
||||
case 'w':
|
||||
if i, ok := weekMap[strings.ToLower(value)]; ok {
|
||||
return int(i), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, errors.New(fmt.Sprintf(`invalid pattern value: "%s"`, value))
|
||||
@ -234,10 +234,10 @@ func (s *cronSchedule) meet(t time.Time) bool {
|
||||
if _, ok := s.day[t.Day()]; !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := s.week[int(t.Weekday())]; !ok {
|
||||
if _, ok := s.month[int(t.Month())]; !ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := s.month[int(t.Month())]; !ok {
|
||||
if _, ok := s.week[int(t.Weekday())]; !ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
@ -47,31 +47,16 @@ func (c *Cache) GetBinContents(path string) []byte {
|
||||
return b
|
||||
}
|
||||
|
||||
// 添加文件监控
|
||||
// 添加文件监控,一旦文件有变化立即清除缓存,下一次读取的时候再执行缓存。
|
||||
func (c *Cache) addMonitor(path string) {
|
||||
// 防止多goroutine同时调用
|
||||
if c.cache.Contains(path) {
|
||||
return
|
||||
}
|
||||
gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
//glog.Debug("gfcache:", event)
|
||||
length := 0
|
||||
if r := c.cache.Get(path); r != nil {
|
||||
length = len(r.([]byte))
|
||||
}
|
||||
// 是否删除
|
||||
if event.IsRemove() {
|
||||
c.cache.Remove(path)
|
||||
c.size.Add(-length)
|
||||
return
|
||||
}
|
||||
// 更新缓存内容
|
||||
if c.cap.Val() == 0 || c.size.Val() < c.cap.Val() {
|
||||
b := gfile.GetBinContents(path)
|
||||
if len(b) > 0 {
|
||||
c.size.Add(len(b) - length)
|
||||
c.cache.Set(path, b)
|
||||
}
|
||||
c.size.Add(-len(r.([]byte)))
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -130,7 +130,9 @@ func (p *Pool) File() (*File, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !p.inited.Val() || p.inited.Set(true) == false {
|
||||
// !p.inited.Val() 使用原子读取操作判断,保证该操作判断的效率;
|
||||
// p.inited.Set(true) == false 使用原子写入操作,保证该操作的原子性;
|
||||
if !p.inited.Val() && p.inited.Set(true) == false {
|
||||
gfsnotify.Add(f.path, func(event *gfsnotify.Event) {
|
||||
// 如果文件被删除或者重命名,立即重建指针池
|
||||
if event.IsRemove() || event.IsRename() {
|
||||
|
||||
@ -7,8 +7,7 @@
|
||||
|
||||
// Package glog implements powerful and easy-to-use levelled logging functionality.
|
||||
//
|
||||
// 日志模块,
|
||||
// 直接文件/输出操作,没有异步逻辑,没有使用缓存或者通道
|
||||
// 日志模块, 直接文件/输出操作,没有异步逻辑,没有使用缓存或者通道
|
||||
package glog
|
||||
|
||||
import (
|
||||
@ -28,10 +27,10 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// glog默认的日志等级,影响全局
|
||||
// default level for log
|
||||
defaultLevel = gtype.NewInt(LEVEL_ALL)
|
||||
|
||||
// 默认的日志对象
|
||||
// default logger object, for package method usage
|
||||
logger = New()
|
||||
)
|
||||
|
||||
@ -39,101 +38,157 @@ func init() {
|
||||
SetDebug(cmdenv.Get("gf.glog.debug", true).Bool())
|
||||
}
|
||||
|
||||
// 日志日志目录绝对路径
|
||||
// SetPath sets the directory path for file logging.
|
||||
//
|
||||
// 日志日志目录绝对路径.
|
||||
func SetPath(path string) {
|
||||
logger.SetPath(path)
|
||||
}
|
||||
|
||||
// 日志文件名称
|
||||
func SetFile(file string) {
|
||||
logger.SetFile(file)
|
||||
// SetFile sets the file name <pattern> for file logging.
|
||||
// Datetime pattern can be used in <pattern>, eg: access-{Ymd}.log.
|
||||
// The default file name pattern is: Y-m-d.log, eg: 2018-01-01.log
|
||||
//
|
||||
// 日志文件名称.
|
||||
func SetFile(pattern string) {
|
||||
logger.SetFile(pattern)
|
||||
}
|
||||
|
||||
// 设置全局的日志记录等级
|
||||
// SetLevel sets the default logging level.
|
||||
//
|
||||
// 设置全局的日志记录等级.
|
||||
func SetLevel(level int) {
|
||||
logger.SetLevel(level)
|
||||
defaultLevel.Set(level)
|
||||
}
|
||||
|
||||
// 可自定义IO接口,IO可以是文件输出、标准输出、网络输出
|
||||
// SetWriter sets the customed logging <writer> for logging.
|
||||
// The <writer> object should implements the io.Writer interface.
|
||||
// Developer can use customed logging <writer> to redirect logging output to another service,
|
||||
// eg: kafka, mysql, mongodb, etc.
|
||||
//
|
||||
// 可自定义IO接口,IO可以是文件输出、标准输出、网络输出.
|
||||
func SetWriter(writer io.Writer) {
|
||||
logger.SetWriter(writer)
|
||||
}
|
||||
|
||||
// 返回自定义的IO,默认为nil
|
||||
// GetWriter returns the customed writer object, which implements the io.Writer interface.
|
||||
// It returns nil if no customed writer set.
|
||||
//
|
||||
// 返回自定义的IO,默认为nil.
|
||||
func GetWriter() io.Writer {
|
||||
return logger.GetWriter()
|
||||
}
|
||||
|
||||
// 获取全局的日志记录等级
|
||||
// GetLevel returns the default logging level value.
|
||||
//
|
||||
// 获取全局的日志记录等级.
|
||||
func GetLevel() int {
|
||||
return defaultLevel.Val()
|
||||
}
|
||||
|
||||
// 设置是否允许输出DEBUG信息
|
||||
// SetDebug enables/disables the debug level for default logger.
|
||||
// The debug level is enbaled in default.
|
||||
//
|
||||
// 设置是否允许输出DEBUG信息.
|
||||
func SetDebug(debug bool) {
|
||||
logger.SetDebug(debug)
|
||||
}
|
||||
|
||||
// SetStdPrint sets whether ouptput the logging contents to stdout, which is false indefault.
|
||||
//
|
||||
// 设置写日志的同时开启or关闭控制台打印,默认是关闭的
|
||||
func SetStdPrint(open bool) {
|
||||
logger.SetStdPrint(open)
|
||||
}
|
||||
|
||||
// GetPath returns the logging directory path for file logging.
|
||||
// It returns empty string if no directory path set.
|
||||
//
|
||||
// 获取日志目录绝对路径
|
||||
func GetPath() string {
|
||||
return logger.GetPath()
|
||||
}
|
||||
|
||||
// PrintBacktrace prints the caller backtrace,
|
||||
// the optional parameter <skip> specify the skipped backtraces offset from the end point.
|
||||
//
|
||||
// 打印文件调用回溯信息
|
||||
func PrintBacktrace(skip...int) {
|
||||
logger.PrintBacktrace(skip...)
|
||||
}
|
||||
|
||||
// 获取文件调用回溯信息
|
||||
// GetBacktrace returns the caller backtrace content,
|
||||
// the optional parameter <skip> specify the skipped backtraces offset from the end point.
|
||||
//
|
||||
// 获取文件调用回溯信息.
|
||||
func GetBacktrace(skip...int) string {
|
||||
return logger.GetBacktrace(skip...)
|
||||
}
|
||||
|
||||
// SetBacktrace enables/disables the backtrace feature in failure logging outputs.
|
||||
//
|
||||
// 是否关闭全局的backtrace信息
|
||||
func SetBacktrace(enabled bool) {
|
||||
logger.SetBacktrace(enabled)
|
||||
}
|
||||
|
||||
// To is a chaining function,
|
||||
// which redirects current logging content output to the sepecified <writer>.
|
||||
//
|
||||
// 链式操作,设置下一次写入日志内容的Writer
|
||||
func To(writer io.Writer) *Logger {
|
||||
return logger.To(writer)
|
||||
}
|
||||
|
||||
// 设置下一次输出的分类,支持多级分类设置
|
||||
// Cat is a chaining function,
|
||||
// which sets the category to <category> for current logging content output.
|
||||
//
|
||||
// 设置下一次输出的分类,支持多级分类设置.
|
||||
func Cat(category string) *Logger {
|
||||
return logger.Cat(category)
|
||||
}
|
||||
|
||||
// File is a chaining function,
|
||||
// which sets file name <pattern> for the current logging content output.
|
||||
//
|
||||
// 设置日志输出文件名称格式
|
||||
func File(file string) *Logger {
|
||||
return logger.File(file)
|
||||
func File(pattern string) *Logger {
|
||||
return logger.File(pattern)
|
||||
}
|
||||
|
||||
// 设置日志打印等级
|
||||
// Level is a chaining function,
|
||||
// which sets logging level for the current logging content output.
|
||||
//
|
||||
// 设置日志打印等级.
|
||||
func Level(level int) *Logger {
|
||||
return logger.Level(level)
|
||||
}
|
||||
|
||||
// 设置文件调用回溯信息
|
||||
// Backtrace is a chaining function,
|
||||
// which sets backtrace options for the current logging content output .
|
||||
//
|
||||
// 设置文件调用回溯信息.
|
||||
func Backtrace(enabled bool, skip...int) *Logger {
|
||||
return logger.Backtrace(enabled, skip...)
|
||||
}
|
||||
|
||||
// StdPrint is a chaining function,
|
||||
// which enables/disables stdout for the current logging content output.
|
||||
//
|
||||
// 是否允许在设置输出文件时同时也输出到终端
|
||||
func StdPrint(enabled bool) *Logger {
|
||||
return logger.StdPrint(enabled)
|
||||
}
|
||||
|
||||
// Header is a chaining function,
|
||||
// which enables/disables log header for the current logging content output.
|
||||
//
|
||||
// 是否打印每行日志头信息(默认开启)
|
||||
func Header(enabled bool) *Logger {
|
||||
return logger.Header(enabled)
|
||||
}
|
||||
|
||||
func Print(v ...interface{}) {
|
||||
logger.Print(v ...)
|
||||
}
|
||||
@ -150,14 +205,17 @@ func Printfln(format string, v ...interface{}) {
|
||||
logger.Printfln(format, v ...)
|
||||
}
|
||||
|
||||
// Fatal prints the logging content with [FATA] header and newline, then exit the current process.
|
||||
func Fatal(v ...interface{}) {
|
||||
logger.Fatal(v ...)
|
||||
}
|
||||
|
||||
// Fatalf prints the logging content with [FATA] header and custom format, then exit the current process.
|
||||
func Fatalf(format string, v ...interface{}) {
|
||||
logger.Fatalf(format, v ...)
|
||||
}
|
||||
|
||||
// Fatalf prints the logging content with [FATA] header, custom format and newline, then exit the current process.
|
||||
func Fatalfln(format string, v ...interface{}) {
|
||||
logger.Fatalfln(format, v ...)
|
||||
}
|
||||
|
||||
@ -58,6 +58,8 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a custom logger.
|
||||
//
|
||||
// 新建自定义的日志操作对象
|
||||
func New() *Logger {
|
||||
return &Logger {
|
||||
@ -72,7 +74,9 @@ func New() *Logger {
|
||||
}
|
||||
}
|
||||
|
||||
// Logger深拷贝
|
||||
// Clone returns a new logger, which is the clone the current logger.
|
||||
//
|
||||
// Logger拷贝.
|
||||
func (l *Logger) Clone() *Logger {
|
||||
return &Logger {
|
||||
pr : l,
|
||||
@ -87,16 +91,23 @@ func (l *Logger) Clone() *Logger {
|
||||
}
|
||||
}
|
||||
|
||||
// SetLevel sets the logging level.
|
||||
//
|
||||
// 设置日志记录等级
|
||||
func (l *Logger) SetLevel(level int) {
|
||||
l.level.Set(level)
|
||||
}
|
||||
|
||||
// GetLevel returns the logging level value.
|
||||
//
|
||||
// 获取日志记录等级
|
||||
func (l *Logger) GetLevel() int {
|
||||
return l.level.Val()
|
||||
}
|
||||
|
||||
// SetDebug enables/disables the debug level for logger.
|
||||
// The debug level is enbaled in default.
|
||||
//
|
||||
// 快捷方法,打开或关闭DEBU日志信息
|
||||
func (l *Logger) SetDebug(debug bool) {
|
||||
if debug {
|
||||
@ -106,6 +117,7 @@ func (l *Logger) SetDebug(debug bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// SetBacktrace enables/disables the backtrace feature in failure logging outputs.
|
||||
func (l *Logger) SetBacktrace(enabled bool) {
|
||||
if enabled {
|
||||
l.btStatus.Set(1)
|
||||
@ -115,11 +127,16 @@ func (l *Logger) SetBacktrace(enabled bool) {
|
||||
|
||||
}
|
||||
|
||||
// 设置BacktraceSkip
|
||||
// SetBacktraceSkip sets the backtrace offset from the end point.
|
||||
func (l *Logger) SetBacktraceSkip(skip int) {
|
||||
l.btSkip.Set(skip)
|
||||
}
|
||||
|
||||
// SetWriter sets the customed logging <writer> for logging.
|
||||
// The <writer> object should implements the io.Writer interface.
|
||||
// Developer can use customed logging <writer> to redirect logging output to another service,
|
||||
// eg: kafka, mysql, mongodb, etc.
|
||||
//
|
||||
// 可自定义IO接口,IO可以是文件输出、标准输出、网络输出
|
||||
func (l *Logger) SetWriter(writer io.Writer) {
|
||||
l.mu.Lock()
|
||||
@ -127,6 +144,9 @@ func (l *Logger) SetWriter(writer io.Writer) {
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// GetWriter returns the customed writer object, which implements the io.Writer interface.
|
||||
// It returns nil if no customed writer set.
|
||||
//
|
||||
// 返回自定义的IO,默认为nil
|
||||
func (l *Logger) GetWriter() io.Writer {
|
||||
l.mu.RLock()
|
||||
@ -135,7 +155,10 @@ func (l *Logger) GetWriter() io.Writer {
|
||||
return r
|
||||
}
|
||||
|
||||
// 获取默认的文件IO
|
||||
// getFilePointer returns the file pinter for file logging.
|
||||
// It returns nil if file logging disabled, or file open fails.
|
||||
//
|
||||
// 获取默认的文件IO.
|
||||
func (l *Logger) getFilePointer() *gfpool.File {
|
||||
if path := l.path.Val(); path != "" {
|
||||
// 文件名称中使用"{}"包含的内容使用gtime格式化
|
||||
@ -159,7 +182,9 @@ func (l *Logger) getFilePointer() *gfpool.File {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 设置日志文件的存储目录路径
|
||||
// SetPath sets the directory path for file logging.
|
||||
//
|
||||
// 设置日志文件的存储目录路径.
|
||||
func (l *Logger) SetPath(path string) error {
|
||||
// path必须有值
|
||||
if path == "" {
|
||||
@ -176,16 +201,25 @@ func (l *Logger) SetPath(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPath returns the logging directory path for file logging.
|
||||
// It returns empty string if no directory path set.
|
||||
//
|
||||
// 获取设置的日志目录路径
|
||||
func (l *Logger) GetPath() string {
|
||||
return l.path.Val()
|
||||
}
|
||||
|
||||
// 日志文件名称
|
||||
func (l *Logger) SetFile(file string) {
|
||||
l.file.Set(file)
|
||||
// SetFile sets the file name <pattern> for file logging.
|
||||
// Datetime pattern can be used in <pattern>, eg: access-{Ymd}.log.
|
||||
// The default file name pattern is: Y-m-d.log, eg: 2018-01-01.log
|
||||
//
|
||||
// 设置日志文件名称格式.
|
||||
func (l *Logger) SetFile(pattern string) {
|
||||
l.file.Set(pattern)
|
||||
}
|
||||
|
||||
// SetStdPrint sets whether ouptput the logging contents to stdout, which is false indefault.
|
||||
//
|
||||
// 设置写日志时开启or关闭控制台打印,默认是关闭的
|
||||
func (l *Logger) SetStdPrint(enabled bool) {
|
||||
l.alsoStdPrint.Set(enabled)
|
||||
@ -263,11 +297,17 @@ func (l *Logger) appendBacktrace(s string, skip...int) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// PrintBacktrace prints the caller backtrace,
|
||||
// the optional parameter <skip> specify the skipped backtraces offset from the end point.
|
||||
//
|
||||
// 直接打印回溯信息,参数skip表示调用端往上多少级开始回溯
|
||||
func (l *Logger) PrintBacktrace(skip...int) {
|
||||
l.Println(l.appendBacktrace("", skip...))
|
||||
}
|
||||
|
||||
// GetBacktrace returns the caller backtrace content,
|
||||
// the optional parameter <skip> specify the skipped backtraces offset from the end point.
|
||||
//
|
||||
// 获取文件调用回溯字符串,参数skip表示调用端往上多少级开始回溯
|
||||
func (l *Logger) GetBacktrace(skip...int) string {
|
||||
customSkip := 0
|
||||
@ -322,16 +362,19 @@ func (l *Logger) Printfln(format string, v ...interface{}) {
|
||||
l.stdPrint(fmt.Sprintf(format + ln, v...))
|
||||
}
|
||||
|
||||
// Fatal prints the logging content with [FATA] header and newline, then exit the current process.
|
||||
func (l *Logger) Fatal(v ...interface{}) {
|
||||
l.errPrint("[FATA] " + fmt.Sprintln(v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalf prints the logging content with [FATA] header and custom format, then exit the current process.
|
||||
func (l *Logger) Fatalf(format string, v ...interface{}) {
|
||||
l.errPrint("[FATA] " + fmt.Sprintf(format, v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Fatalf prints the logging content with [FATA] header, custom format and newline, then exit the current process.
|
||||
func (l *Logger) Fatalfln(format string, v ...interface{}) {
|
||||
l.errPrint("[FATA] " + fmt.Sprintf(format + ln, v...))
|
||||
os.Exit(1)
|
||||
@ -463,6 +506,8 @@ func (l *Logger) Criticalfln(format string, v ...interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// checkLevel checks whether the given <level> could be output.
|
||||
//
|
||||
// 判断给定level是否满足
|
||||
func (l *Logger) checkLevel(level int) bool {
|
||||
return l.level.Val() & level > 0
|
||||
|
||||
@ -11,6 +11,9 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// To is a chaining function,
|
||||
// which redirects current logging content output to the sepecified <writer>.
|
||||
//
|
||||
// 链式操作,设置下一次写入日志内容的Writer
|
||||
func (l *Logger) To(writer io.Writer) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
@ -23,6 +26,9 @@ func (l *Logger) To(writer io.Writer) *Logger {
|
||||
return logger
|
||||
}
|
||||
|
||||
// Cat is a chaining function,
|
||||
// which sets the category to <category> for current logging content output.
|
||||
//
|
||||
// 链式操作,设置下一次输出的日志分类(可以按照文件目录层级设置),在当前logpath或者当前工作目录下创建category目录,
|
||||
// 这是一个链式操作,可以设置多个分类,将会创建层级的日志分类目录。
|
||||
func (l *Logger) Cat(category string) *Logger {
|
||||
@ -39,6 +45,9 @@ func (l *Logger) Cat(category string) *Logger {
|
||||
return logger
|
||||
}
|
||||
|
||||
// File is a chaining function,
|
||||
// which sets file name <pattern> for the current logging content output.
|
||||
//
|
||||
// 日志文件格式
|
||||
func (l *Logger) File(file string) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
@ -51,6 +60,9 @@ func (l *Logger) File(file string) *Logger {
|
||||
return logger
|
||||
}
|
||||
|
||||
// Level is a chaining function,
|
||||
// which sets logging level for the current logging content output.
|
||||
//
|
||||
// 设置日志打印等级
|
||||
func (l *Logger) Level(level int) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
@ -63,6 +75,9 @@ func (l *Logger) Level(level int) *Logger {
|
||||
return logger
|
||||
}
|
||||
|
||||
// Backtrace is a chaining function,
|
||||
// which sets backtrace options for the current logging content output .
|
||||
//
|
||||
// 设置文件调用回溯信息
|
||||
func (l *Logger) Backtrace(enabled bool, skip...int) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
@ -78,6 +93,9 @@ func (l *Logger) Backtrace(enabled bool, skip...int) *Logger {
|
||||
return logger
|
||||
}
|
||||
|
||||
// StdPrint is a chaining function,
|
||||
// which enables/disables stdout for the current logging content output.
|
||||
//
|
||||
// 是否允许在设置输出文件时同时也输出到终端
|
||||
func (l *Logger) StdPrint(enabled bool) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
@ -90,6 +108,9 @@ func (l *Logger) StdPrint(enabled bool) *Logger {
|
||||
return logger
|
||||
}
|
||||
|
||||
// Header is a chaining function,
|
||||
// which enables/disables log header for the current logging content output.
|
||||
//
|
||||
// 是否打印每行日志头信息(默认开启)
|
||||
func (l *Logger) Header(enabled bool) *Logger {
|
||||
logger := (*Logger)(nil)
|
||||
@ -27,8 +27,10 @@ const (
|
||||
gPROC_TEMP_DIR_ENV_KEY = "GPROC_TEMP_DIR"
|
||||
)
|
||||
|
||||
// 进程开始执行时间
|
||||
var processStartTime = time.Now()
|
||||
var (
|
||||
// 进程开始执行时间
|
||||
processStartTime = time.Now()
|
||||
)
|
||||
|
||||
// 获取当前进程ID
|
||||
func Pid() int {
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
// 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.
|
||||
|
||||
// "不要通过共享内存来通信,而应该通过通信来共享内存"
|
||||
|
||||
|
||||
@ -26,16 +27,43 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// 是否已开启TCP端口监听服务(使用int而非bool,以便于使用原子操作判断是否开启)
|
||||
tcpListeningCount = gtype.NewInt()
|
||||
// 是否已开启TCP端口监听服务
|
||||
tcpListened = gtype.NewBool()
|
||||
)
|
||||
|
||||
// 获取其他进程传递到当前进程的消息包,阻塞执行。
|
||||
// 进程只有在执行该方法后才会打开请求端口,默认情况下不允许进程间通信。
|
||||
func Receive(group...string) *Msg {
|
||||
// 一个进程只能开启一个监听goroutine
|
||||
if tcpListened.Set(true) == false {
|
||||
go startTcpListening()
|
||||
}
|
||||
queue := (*gqueue.Queue)(nil)
|
||||
groupName := gPROC_COMM_DEAFULT_GRUOP_NAME
|
||||
if len(group) > 0 {
|
||||
groupName = group[0]
|
||||
}
|
||||
if v := commReceiveQueues.Get(groupName); v == nil {
|
||||
commReceiveQueues.LockFunc(func(m map[string]interface{}) {
|
||||
if v, ok := m[groupName]; ok {
|
||||
queue = v.(*gqueue.Queue)
|
||||
} else {
|
||||
queue = gqueue.New(gPROC_MSG_QUEUE_MAX_LENGTH)
|
||||
m[groupName] = queue
|
||||
}
|
||||
})
|
||||
} else {
|
||||
queue = v.(*gqueue.Queue)
|
||||
}
|
||||
|
||||
if v := queue.Pop(); v != nil {
|
||||
return v.(*Msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 创建本地进程TCP通信服务
|
||||
func startTcpListening() {
|
||||
// 一个进程只能开启一个监听goroutine
|
||||
if tcpListeningCount.Add(1) != 1 {
|
||||
return
|
||||
}
|
||||
var listen *net.TCPListener
|
||||
for i := gPROC_DEFAULT_TCP_PORT; ; i++ {
|
||||
addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", i))
|
||||
@ -48,7 +76,6 @@ func startTcpListening() {
|
||||
}
|
||||
// 将监听的端口保存到通信文件中(字符串类型存放)
|
||||
gfile.PutContents(getCommFilePath(Pid()), gconv.String(i))
|
||||
//glog.Printfln("%d: gproc listening on [%s]", Pid(), addr)
|
||||
break
|
||||
}
|
||||
for {
|
||||
@ -133,32 +160,3 @@ func bufferToMsgs(buffer []byte) []*Msg {
|
||||
return msgs
|
||||
}
|
||||
|
||||
// 获取其他进程传递到当前进程的消息包,阻塞执行。
|
||||
func Receive(group...string) *Msg {
|
||||
// 开启接收协程时才会开启端口监听
|
||||
go startTcpListening()
|
||||
|
||||
var queue *gqueue.Queue
|
||||
groupName := gPROC_COMM_DEAFULT_GRUOP_NAME
|
||||
if len(group) > 0 {
|
||||
groupName = group[0]
|
||||
}
|
||||
|
||||
if v := commReceiveQueues.Get(groupName); v == nil {
|
||||
commReceiveQueues.LockFunc(func(m map[string]interface{}) {
|
||||
if v, ok := m[groupName]; ok {
|
||||
queue = v.(*gqueue.Queue)
|
||||
} else {
|
||||
queue = gqueue.New(gPROC_MSG_QUEUE_MAX_LENGTH)
|
||||
m[groupName] = queue
|
||||
}
|
||||
})
|
||||
} else {
|
||||
queue = v.(*gqueue.Queue)
|
||||
}
|
||||
|
||||
if v := queue.Pop(); v != nil {
|
||||
return v.(*Msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -7,16 +7,16 @@
|
||||
package gproc
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/net/gtcp"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"github.com/gogf/gf/g/encoding/gbinary"
|
||||
"fmt"
|
||||
"errors"
|
||||
"time"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/encoding/gbinary"
|
||||
"github.com/gogf/gf/g/net/gtcp"
|
||||
"github.com/gogf/gf/g/os/gfcache"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"github.com/gogf/gf/g/util/gconv"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -26,7 +26,7 @@ const (
|
||||
gPROC_COMM_DEAFULT_GRUOP_NAME = "" // 默认分组名称
|
||||
)
|
||||
|
||||
// 向指定gproc进程发送数据
|
||||
// 向指定gproc进程发送数据.
|
||||
// 数据格式:总长度(24bit)|发送进程PID(24bit)|接收进程PID(24bit)|分组长度(8bit)|分组名称(变长)|校验(32bit)|参数(变长)
|
||||
func Send(pid int, data []byte, group...string) error {
|
||||
groupName := gPROC_COMM_DEAFULT_GRUOP_NAME
|
||||
@ -85,6 +85,6 @@ func getConnByPid(pid int) (*gtcp.Conn, error) {
|
||||
// 获取指定进程监听的端口号
|
||||
func getPortByPid(pid int) int {
|
||||
path := getCommFilePath(pid)
|
||||
content := gfile.GetContents(path)
|
||||
content := gfcache.GetContents(path)
|
||||
return gconv.Int(content)
|
||||
}
|
||||
@ -115,6 +115,10 @@ 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)
|
||||
@ -132,6 +136,10 @@ 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)
|
||||
return nil
|
||||
|
||||
@ -13,17 +13,25 @@ import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Quote quotes <s> by replacing special chars in <s>
|
||||
// to match the rules of regular expression pattern.
|
||||
// And returns the copy.
|
||||
//
|
||||
// 转移正则规则字符串,例如:Quote(`[foo]`) 返回 `\[foo\]`
|
||||
func Quote(s string) string {
|
||||
return regexp.QuoteMeta(s)
|
||||
}
|
||||
|
||||
// Validate checks whether given regular expression pattern <pattern> valid.
|
||||
//
|
||||
// 校验所给定的正则表达式是否符合规范
|
||||
func Validate(pattern string) error {
|
||||
_, err := getRegexp(pattern)
|
||||
return err
|
||||
}
|
||||
|
||||
// IsMatch checks whether given bytes <src> matches <pattern>.
|
||||
//
|
||||
// 正则表达式是否匹配
|
||||
func IsMatch(pattern string, src []byte) bool {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
@ -32,10 +40,15 @@ func IsMatch(pattern string, src []byte) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsMatchString checks whether given string <src> matches <pattern>.
|
||||
//
|
||||
// 判断给定的字符串<src>是否满足正则表达式<pattern>.
|
||||
func IsMatchString(pattern string, src string) bool {
|
||||
return IsMatch(pattern, []byte(src))
|
||||
}
|
||||
|
||||
// MatchString return bytes slice that matched <pattern>.
|
||||
//
|
||||
// 正则匹配,并返回匹配的列表(参数[]byte)
|
||||
func Match(pattern string, src []byte) ([][]byte, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
@ -45,6 +58,8 @@ func Match(pattern string, src []byte) ([][]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// MatchString return strings that matched <pattern>.
|
||||
//
|
||||
// 正则匹配,并返回匹配的列表(参数[]string)
|
||||
func MatchString(pattern string, src string) ([]string, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
@ -54,6 +69,8 @@ func MatchString(pattern string, src string) ([]string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// MatchAll return all bytes slices that matched <pattern>.
|
||||
//
|
||||
// 正则匹配,并返回所有匹配的列表(参数[]string)
|
||||
func MatchAll(pattern string, src []byte) ([][][]byte, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
@ -63,7 +80,9 @@ func MatchAll(pattern string, src []byte) ([][][]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 正则匹配,并返回所有匹配的列表(参数[][]string)
|
||||
// MatchAllString return all strings that matched <pattern>.
|
||||
//
|
||||
// 正则匹配,并返回所有匹配的列表(参数[][]string).
|
||||
func MatchAllString(pattern string, src string) ([][]string, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.FindAllStringSubmatch(src, -1), nil
|
||||
@ -72,7 +91,9 @@ func MatchAllString(pattern string, src string) ([][]string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 正则替换(全部替换)
|
||||
// ReplaceString replace all matched <pattern> in bytes <src> with bytes <replace>.
|
||||
//
|
||||
// 正则替换(全部替换).
|
||||
func Replace(pattern string, replace, src []byte) ([]byte, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.ReplaceAll(src, replace), nil
|
||||
@ -81,12 +102,17 @@ func Replace(pattern string, replace, src []byte) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceString replace all matched <pattern> in string <src> with string <replace>.
|
||||
//
|
||||
// 正则替换(全部替换),字符串
|
||||
func ReplaceString(pattern, replace, src string) (string, error) {
|
||||
r, e := Replace(pattern, []byte(replace), []byte(src))
|
||||
return string(r), e
|
||||
}
|
||||
|
||||
// ReplaceFunc replace all matched <pattern> in bytes <src>
|
||||
// with custom replacement function <replaceFunc>.
|
||||
//
|
||||
// 正则替换(全部替换),给定自定义替换方法
|
||||
func ReplaceFunc(pattern string, src []byte, replaceFunc func(b []byte) []byte) ([]byte, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
@ -96,6 +122,9 @@ func ReplaceFunc(pattern string, src []byte, replaceFunc func(b []byte) []byte)
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceStringFunc replace all matched <pattern> in string <src>
|
||||
// with custom replacement function <replaceFunc>.
|
||||
//
|
||||
// 正则替换(全部替换),给定自定义替换方法
|
||||
func ReplaceStringFunc(pattern string, src string, replaceFunc func(s string) string) (string, error) {
|
||||
bytes, err := ReplaceFunc(pattern, []byte(src), func(bytes []byte) []byte {
|
||||
|
||||
@ -6,9 +6,10 @@
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gregex
|
||||
package gregex_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/g/text/gregex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -18,42 +19,42 @@ var replace = "johng.cn"
|
||||
|
||||
func BenchmarkValidate(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Validate(pattern)
|
||||
gregex.Validate(pattern)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsMatch(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
IsMatch(pattern, []byte(src))
|
||||
gregex.IsMatch(pattern, []byte(src))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsMatchString(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
IsMatchString(pattern, src)
|
||||
gregex.IsMatchString(pattern, src)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMatchString(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
MatchString(pattern, src)
|
||||
gregex.MatchString(pattern, src)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMatchAllString(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
MatchAllString(pattern, src)
|
||||
gregex.MatchAllString(pattern, src)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReplace(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Replace(pattern, []byte(replace), []byte(src))
|
||||
gregex.Replace(pattern, []byte(replace), []byte(src))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReplaceString(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ReplaceString(pattern, replace, src)
|
||||
gregex.ReplaceString(pattern, replace, src)
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,25 +88,6 @@ func UcWords(str string) string {
|
||||
return strings.Title(str)
|
||||
}
|
||||
|
||||
// Traverse the array to find the string index position, if not exist, return-1.
|
||||
//
|
||||
// 遍历数组查找字符串索引位置,如果不存在则返回-1,使用完整遍历查找.
|
||||
func SearchArray (a []string, s string) int {
|
||||
for i, v := range a {
|
||||
if s == v {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// InArray tests whether the given string s is in string array a.
|
||||
//
|
||||
// 判断字符串是否在数组中
|
||||
func InArray (a []string, s string) bool {
|
||||
return SearchArray(a, s) != -1
|
||||
}
|
||||
|
||||
// IsLetterLower tests whether the given byte b is in lower case.
|
||||
//
|
||||
// 判断给定字符是否小写
|
||||
@ -490,15 +471,7 @@ func Explode(delimiter, str string) []string {
|
||||
//
|
||||
// 用glue将字符串数组pieces连接为一个字符串。
|
||||
func Implode(glue string, pieces []string) string {
|
||||
var buf bytes.Buffer
|
||||
l := len(pieces)
|
||||
for _, str := range pieces {
|
||||
buf.WriteString(str)
|
||||
if l--; l > 0 {
|
||||
buf.WriteString(glue)
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
return strings.Join(pieces, glue)
|
||||
}
|
||||
|
||||
// Generate a single-byte string from a number.
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -68,26 +68,6 @@ func Test_UcWords(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SearchArray(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
array := []string{"a", "b", "c"}
|
||||
gtest.Assert(gstr.SearchArray(array, "a"), 0)
|
||||
gtest.Assert(gstr.SearchArray(array, "b"), 1)
|
||||
gtest.Assert(gstr.SearchArray(array, "c"), 2)
|
||||
gtest.Assert(gstr.SearchArray(array, "d"), -1)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_InArray(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
array := []string{"a", "b", "c"}
|
||||
gtest.Assert(gstr.InArray(array, "a"), true)
|
||||
gtest.Assert(gstr.InArray(array, "b"), true)
|
||||
gtest.Assert(gstr.InArray(array, "c"), true)
|
||||
gtest.Assert(gstr.InArray(array, "d"), false)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_IsLetterLower(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(gstr.IsLetterLower('a'), true)
|
||||
|
||||
@ -99,10 +99,13 @@ func Map(i interface{}, noTagCheck...bool) map[string]interface{} {
|
||||
rt := rv.Type()
|
||||
name := ""
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
// 检查json tag
|
||||
// 检查tag, 支持gconv, json标签, 优先使用gconv
|
||||
if len(noTagCheck) == 0 || !noTagCheck[0] {
|
||||
if name = rt.Field(i).Tag.Get("json"); name == "" {
|
||||
name = rt.Field(i).Name
|
||||
tag := rt.Field(i).Tag
|
||||
if name = tag.Get("gconv"); name == "" {
|
||||
if name = tag.Get("json"); name == "" {
|
||||
name = rt.Field(i).Name
|
||||
}
|
||||
}
|
||||
}
|
||||
m[name] = rv.Field(i).Interface()
|
||||
|
||||
23
geg/net/ghttp/server/template/layout/main.go
Normal file
23
geg/net/ghttp/server/template/layout/main.go
Normal 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()
|
||||
}
|
||||
@ -0,0 +1 @@
|
||||
<h1>FOOTER</h1>
|
||||
@ -0,0 +1 @@
|
||||
<h1>HEADER</h1>
|
||||
@ -0,0 +1,3 @@
|
||||
{{include "header.html" .}}
|
||||
{{include .mainTpl .}}
|
||||
{{include "footer.html" .}}
|
||||
@ -0,0 +1 @@
|
||||
<h1>MAIN1</h1>
|
||||
@ -0,0 +1 @@
|
||||
<h1>MAIN2</h1>
|
||||
@ -1,17 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/os/gcron"
|
||||
"github.com/gogf/gf/g/os/glog"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cron := gcron.New()
|
||||
glog.Println("start")
|
||||
cron.DelayAddOnce(1, "* * * * * *", func() {
|
||||
glog.Println("run")
|
||||
})
|
||||
func test() {
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
_, err := gcron.Add("*/10 * * * * ?", test)
|
||||
fmt.Println(err)
|
||||
fmt.Println(gcron.Entries())
|
||||
time.Sleep(10*time.Second)
|
||||
}
|
||||
@ -2,11 +2,13 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/g/os/gfile"
|
||||
)
|
||||
|
||||
func main() {
|
||||
f, e := gfile.Open("/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/third")
|
||||
fmt.Println(e)
|
||||
fmt.Println(f)
|
||||
s := "abc我是中国人é"
|
||||
fmt.Println(len(s))
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
fmt.Println(s[i])
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
package gf
|
||||
|
||||
const VERSION = "v1.5.0"
|
||||
const VERSION = "v1.5.8"
|
||||
const AUTHORS = "john<john@goframe.org>"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user