mirror of
https://gitee.com/johng/gf
synced 2026-06-21 16:01:11 +08:00
Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d604d198ab | |||
| 36791d2f48 | |||
| 08f9cffed9 | |||
| 783c0ba846 | |||
| 7ad4f61564 | |||
| adf06a2b0d | |||
| d6aa2b2512 | |||
| 0a8af94610 | |||
| 2c27c0f58a | |||
| 4172eae87e | |||
| 26f2c61068 | |||
| f97bed2607 | |||
| 8ef7155c70 | |||
| 2c6e8f88fb | |||
| 25068b1e83 | |||
| 1f36eb3a9a | |||
| a9ed577d05 | |||
| 782d614082 | |||
| 0629c00b07 | |||
| b90d5bb205 | |||
| cbc824c80a | |||
| 0c9be40b86 | |||
| c96abd706d | |||
| 0ae5872783 | |||
| 2cff10e0d2 | |||
| cab78f557d | |||
| 04353aa1a5 | |||
| 35121a66e9 | |||
| e726ed2c19 | |||
| 503446afc7 | |||
| 2063f662d3 | |||
| d7381399aa | |||
| d05b497cdb | |||
| ef919be587 | |||
| fff31e0f4f | |||
| cdd6fc7c1e | |||
| 74bc36a2dc | |||
| 48328ae52c | |||
| a86f4f8e23 | |||
| 0a1e048268 | |||
| 6fc5efd6ba | |||
| 2d795b593d | |||
| 20628ec75c | |||
| 10d1ccb009 | |||
| fcc37c9581 | |||
| 43cd391543 | |||
| 18d2df33f7 | |||
| a85daa5617 | |||
| 48dc4ce3e2 | |||
| d07bac89a0 | |||
| 5d32ad6bc4 | |||
| 397b0a3e7e | |||
| 259961632d | |||
| cb1d6382ec | |||
| 8714a69a13 | |||
| 3ae0ea2de7 | |||
| 1879a9f4c7 | |||
| 3938717b04 | |||
| 1208b688f1 | |||
| 0ad7ee5a32 | |||
| 7a4e68e6b9 | |||
| 71222b247f | |||
| 95db811943 | |||
| 2dbc817132 | |||
| 7a8bd96edc | |||
| c5e9686a95 | |||
| c914edf616 | |||
| 656bfcb6bd | |||
| 7434dfe6fa | |||
| e67aa63a50 | |||
| 06fc786416 | |||
| d5e46f2b42 | |||
| c003a92408 | |||
| 09e6f10b60 | |||
| c961c22cd7 | |||
| 105a821069 | |||
| 670993f769 | |||
| 60a571f291 | |||
| 20a0cb2cd9 | |||
| 51e70be04d | |||
| ac65b808c6 | |||
| eb9ddf3c47 | |||
| 80993e9f77 | |||
| b7a6d257d5 | |||
| 7022486e93 | |||
| 83f5a9d34e | |||
| 431e1051b8 | |||
| f8ab5c3842 | |||
| 101d095f45 | |||
| 8481de2b47 | |||
| c973f133de | |||
| d23cdcbe57 | |||
| 9a52175bd6 | |||
| 4275218841 | |||
| 663a2c2a16 | |||
| da58a60ad5 | |||
| 2c26063f4b | |||
| b19e47783b | |||
| aee266eea0 | |||
| 8304769953 | |||
| 914a74bca9 | |||
| b965dbff70 | |||
| 9f9bcd2467 | |||
| b3353afe3c | |||
| 4e3081afee | |||
| 578a6a2df3 | |||
| aa42ddd3f1 | |||
| 69738c337f | |||
| 12f099fd54 | |||
| 38932f306d | |||
| 5e7e1077a1 | |||
| 8f85311332 | |||
| 6eb2887a5a | |||
| 54f4fd3101 | |||
| 64a22acf84 | |||
| 4e5877923d | |||
| ceaa1a4dd1 | |||
| 9e1ad46c90 | |||
| d5a3fefd8b | |||
| d85332aca1 | |||
| 10c3f6d85a | |||
| ea4764f1f9 | |||
| fe753b0bc8 | |||
| 04608269fe | |||
| 6addd64cf0 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,3 +16,4 @@ cbuild
|
||||
**/.DS_Store
|
||||
.vscode/
|
||||
go.sum
|
||||
|
||||
|
||||
33
.travis.yml
Normal file
33
.travis.yml
Normal file
@ -0,0 +1,33 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.11.x"
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- develop
|
||||
|
||||
env:
|
||||
- GITEE_GF=$GOPATH/src/gitee.com/johng/gf GO111MODULE=on
|
||||
|
||||
services:
|
||||
- mysql
|
||||
|
||||
before_install:
|
||||
- pwd
|
||||
|
||||
install:
|
||||
- pwd
|
||||
- mkdir -p $GITEE_GF
|
||||
- cp * $GITEE_GF -R
|
||||
- cd $GITEE_GF
|
||||
|
||||
script:
|
||||
- cd g && go test -v ./... -race -coverprofile=coverage.txt -covermode=atomic
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
|
||||
|
||||
2
LICENSE
2
LICENSE
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
|
||||
221
README.MD
221
README.MD
@ -1,49 +1,46 @@
|
||||
<div align=center>
|
||||
<img src="http://cover.kancloud.cn/johng/gf" width="150"/>
|
||||
</div>
|
||||
# GoFrame
|
||||
<img align="right" height="150px" src="https://gfer.me/cover.png">
|
||||
|
||||
[](https://godoc.org/github.com/johng-cn/gf)
|
||||
[](https://travis-ci.org/johng-cn/gf)
|
||||
[](https://goreportcard.com/report/github.com/johng-cn/gf)
|
||||
[](https://gfer.me)
|
||||
[](https://github.com/johng-cn/gf)
|
||||
[](https://github.com/johng-cn/gf)
|
||||
[](https://github.com/johng-cn/gf/releases)
|
||||
|
||||
GF(Go Frame)是一款模块化、松耦合、轻量级、高性能的Go语言Web开发框架。支持热重启、热更新、多域名、多端口、多服务、HTTP/HTTPS、动态路由等特性
|
||||
,并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、服务注册、配置管理、模板引擎、数据校验、分页管理、数据库ORM等等等等,
|
||||
并且提供了数十个实用开发模块集,如:缓存、日志、时间、命令行、二进制、文件锁、对象池、连接池、数据编码、进程管理、进程通信、TCP/UDP组件、
|
||||
并发安全容器、Goroutine池等等等等等等。
|
||||
<!--
|
||||
[](https://codecov.io/gh/johng-cn/gf)
|
||||
[](https://www.codetriage.com/johng-cn/gf)
|
||||
-->
|
||||
|
||||
开源项目地址(仓库保持实时同步):
|
||||
[Gitee](https://gitee.com/johng/gf),[Github](https://github.com/johng-cn/gf)。
|
||||
使用中有任何问题/建议,欢迎加入技术QQ群交流:**116707870**。
|
||||
如有优秀的框架使用案例,欢迎联系作者将地址展示到项目库中,您的牛逼将被世人所瞻仰。
|
||||
`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.
|
||||
|
||||
# 安装
|
||||
```html
|
||||
# Installation
|
||||
```
|
||||
go get -u gitee.com/johng/gf
|
||||
```
|
||||
|
||||
# 限制
|
||||
```shell
|
||||
golang版本 >= 1.9.2
|
||||
or use `go.mod`
|
||||
```
|
||||
require gitee.com/johng/gf latest
|
||||
```
|
||||
|
||||
# 特点
|
||||
1. 轻量级、高性能,模块化、松耦合设计,丰富的开发模块;
|
||||
1. 热重启、热更新特性,并支持Web界面及命令行管理接口;
|
||||
1. 专业的技术交流群,完善的开发文档及示例代码,良好的中文化支持;
|
||||
1. 支持多种形式的服务注册特性,灵活高效的路由控制管理;
|
||||
1. 支持服务事件回调注册功能,可供选择的pprof性能分析模块;
|
||||
1. 支持配置文件及模板文件的自动检测更新机制,即修改即生效;
|
||||
1. 支持自定义日期时间格式的时间模块,类似PHP日期时间格式化;
|
||||
1. 强大的数据/表单校验模块,支持常用的40种及自定义校验规则;
|
||||
1. 强大的网络通信TCP/UDP组件,并提供TCP连接池特性,简便高效;
|
||||
1. 提供了对基本数据类型的并发安全封装,提供了常用的数据结构容器;
|
||||
1. 支持Go变量/Json/Xml/Yml/Toml任意数据格式之间的相互转换及创建;
|
||||
1. 强大的数据库ORM,支持应用层级的集群管理、读写分离、负载均衡,查询缓存、方法及链式ORM操作;
|
||||
1. 更多特点请查阅框架[手册](https://gfer.me)和[源码](https://godoc.org/github.com/johng-cn/gf);
|
||||
# Limitation
|
||||
```
|
||||
golang version >= 1.9.2
|
||||
```
|
||||
|
||||
# 文档
|
||||
GoFrame开发文档:[gfer.me](https://gfer.me)
|
||||
# Documentation
|
||||
|
||||
* [中文文档](https://gfer.me/)
|
||||
|
||||
# Architecture
|
||||
<div align=center>
|
||||
<img src="https://gfer.me/images/arch.png"/>
|
||||
</div>
|
||||
|
||||
# Quick Start
|
||||
|
||||
# 使用
|
||||
## Hello World
|
||||
```go
|
||||
package main
|
||||
|
||||
@ -60,122 +57,48 @@ func main() {
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
## 多域名支持
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.Domain("localhost1,localhost2").BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Write("localhostx")
|
||||
})
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
## 多端口支持
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/", func(r *ghttp.Request){
|
||||
r.Response.Writeln("go frame!")
|
||||
})
|
||||
s.SetPort(8080, 8081, 8082)
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
## 路由控制
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/order/:action/{page}.html", func(r *ghttp.Request){
|
||||
r.Response.Writef("action:%s, page:%s", r.Get("action"), r.Get("page"))
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
|
||||
## 数据库ORM
|
||||
|
||||
### ORM创建/关闭
|
||||
|
||||
```go
|
||||
// 获取默认配置的单例数据库对象(配置名称为"default")
|
||||
db, err := gdb.DB()
|
||||
// 获取配置分组名称为"user-center"的单例数据库对象
|
||||
db, err := gdb.DB("user-center")
|
||||
// 无须显示Close,数据库引擎底层采用了链接池设计,当链接不再使用时会自动关闭
|
||||
```
|
||||
### 单表/联表查询
|
||||
```go
|
||||
// 查询多条记录并使用Limit分页
|
||||
r, err := db.Table("user").Where("u.uid > ?", 1).Limit(0, 10).Select()
|
||||
// 查询符合条件的单条记录(第一条)
|
||||
r, err := db.Table("user u").LeftJoin("user_detail ud", "u.uid=ud.uid").Fields("u.*,ud.site").Where("u.uid=?", 1).One()
|
||||
// 查询指定字段值
|
||||
r, err := db.Table("user u").RightJoin("user_detail ud", "u.uid=ud.uid").Fields("ud.site").Where("u.uid=?", 1).Value()
|
||||
// 分组及排序
|
||||
r, err := db.Table("user u").InnerJoin("user_detail ud", "u.uid=ud.uid").Fields("u.*,ud.city").GroupBy("city").OrderBy("register_time asc").Select()
|
||||
// 不使用john的联表查询
|
||||
r, err := db.Table("user u,user_detail ud").Where("u.uid=ud.uid").Fields("u.*,ud.city").All()
|
||||
// 不使用Fields方法指定查询字段时,默认查询为"*"
|
||||
r, err := db.Table("user").Where("u.uid=1",).One()
|
||||
```
|
||||
|
||||
### 更新/删除
|
||||
```go
|
||||
// 更新
|
||||
r, err := db.Table("user").Data(gdb.Map{"name" : "john2"}).Where("name=?", "john").Update()
|
||||
r, err := db.Table("user").Data("name='john3'").Where("name=?", "john2").Update()
|
||||
// 删除
|
||||
r, err := db.Table("user").Where("uid=?", 10).Delete()
|
||||
// Data数值方法的参数形式比较灵活
|
||||
r, err := db.Table("user").Data(`name="john"`).Update()
|
||||
r, err := db.Table("user").Data("name", "john").Update()
|
||||
r, err := db.Table("user").Data(g.Map{"name" : "john"}).Update()
|
||||
```
|
||||
### 写入/保存
|
||||
```go
|
||||
r, err := db.Table("user").Data(gdb.Map{"name": "john"}).Insert()
|
||||
r, err := db.Table("user").Data(gdb.Map{"uid": 10000, "name": "john"}).Replace()
|
||||
r, err := db.Table("user").Data(gdb.Map{"uid": 10001, "name": "john"}).Save()
|
||||
```
|
||||
|
||||
### 事务操作
|
||||
|
||||
```go
|
||||
if tx, err := db.Begin(); err == nil {
|
||||
if r, err := tx.Table("user").Data(gdb.Map{"uid":1, "name": "john"}).Save(); err == nil {
|
||||
tx.Commit()
|
||||
} else {
|
||||
tx.Rollback()
|
||||
}
|
||||
|
||||
fmt.Println(r, err)
|
||||
}
|
||||
```
|
||||
[View More..](https://gfer.me/start/index)
|
||||
|
||||
|
||||
# License
|
||||
|
||||
`GF` is licensed under the [MIT License](LICENSE), 100% free and open-source, forever.
|
||||
|
||||
# Contributors(TOP 10)
|
||||
|
||||
<a href="https://gitee.com/johng" target="_blank" title="John"><img src="https://gitee.com/uploads/27/1309327_johng.png?1530630243" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/wenzi1" target="_blank" title="蚊子"><img src="https://images.gitee.com/uploads/22/1923122_wenzi1.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://gfer.me/images/contributors/zseeker.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/ymrjqyy" target="_blank" title="一墨染尽青衣颜"><img src="https://images.gitee.com/uploads/27/876827_ymrjqyy.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://github.com/chenyang351" target="_blank" title="chenyang351"><img src="https://avatars1.githubusercontent.com/u/30063958?s=60&v=4" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/wxkj" target="_blank" title="wxkj"><img src="https://gitee.com/uploads/56/91356_wxkj.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://github.com/wxkj001" target="_blank" title="3wxkj001
|
||||
"><img src="https://avatars0.githubusercontent.com/u/7794279?s=60&v=4" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/zhangjinfu" target="_blank" title="张金富"><img src="https://images.gitee.com/uploads/63/356163_zhangjinfu.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://gfer.me/images/contributors/garfieldkwong.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/qq1054000800" target="_blank" title="hello"><img src="https://gitee.com/uploads/9/2209_qq1054000800.jpg" width="60" align="left"></a>
|
||||
|
||||
<br /><br /><br />
|
||||
|
||||
# Donators
|
||||
|
||||
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/hailaz" target="_blank" title="HaiLaz"><img src="https://gitee.com/uploads/87/1273187_hailaz.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/mg91" target="_blank" title="mg91"><img src="https://images.gitee.com/uploads/30/1410930_mg91.png" width="60" align="left"></a>
|
||||
|
||||
|
||||
|
||||
...
|
||||
|
||||
|
||||
更多特性及示例请查看官方开发文档:[gfer.me](https://gfer.me)
|
||||
|
||||
104
README_ZH.MD
Normal file
104
README_ZH.MD
Normal file
@ -0,0 +1,104 @@
|
||||
# GoFrame
|
||||
<img align="right" height="150px" src="https://gfer.me/cover.png">
|
||||
|
||||
[](https://godoc.org/github.com/johng-cn/gf)
|
||||
[](https://travis-ci.org/johng-cn/gf)
|
||||
[](https://goreportcard.com/report/github.com/johng-cn/gf)
|
||||
[](https://gfer.me)
|
||||
[](https://github.com/johng-cn/gf)
|
||||
[](https://github.com/johng-cn/gf)
|
||||
[](https://github.com/johng-cn/gf/releases)
|
||||
|
||||
<!--
|
||||
[](https://codecov.io/gh/johng-cn/gf)
|
||||
[](https://www.codetriage.com/johng-cn/gf)
|
||||
-->
|
||||
|
||||
`GF(Go Frame)`是一款模块化、松耦合、轻量级、高性能的Go应用开发框架。支持热重启、热更新、多域名、多端口、多服务、HTTP/HTTPS、动态路由等特性
|
||||
,并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、服务注册、配置管理、模板引擎、数据校验、分页管理、数据库ORM等等等等,
|
||||
并且提供了数十个内置核心开发模块集,如:缓存、日志、时间、命令行、二进制、文件锁、内存锁、对象池、连接池、数据编码、进程管理、进程通信、文件监控、定时任务、TCP/UDP组件、
|
||||
并发安全容器等等等等等等。
|
||||
|
||||
|
||||
# 特点
|
||||
* 模块化、松耦合设计;
|
||||
* 丰富实用的开发模块;
|
||||
* 详尽的开发文档及示例;
|
||||
* 完善的本地中文化支持;
|
||||
* 致力于项目的通用方案;
|
||||
* 更适合企业及团队使用;
|
||||
* 更多请查阅文档及源码;
|
||||
|
||||
# 安装
|
||||
```html
|
||||
go get -u gitee.com/johng/gf
|
||||
```
|
||||
或者
|
||||
`go.mod`
|
||||
```
|
||||
require gitee.com/johng/gf latest
|
||||
```
|
||||
# 限制
|
||||
```shell
|
||||
golang版本 >= 1.9.2
|
||||
```
|
||||
|
||||
# 架构
|
||||
<div align=center>
|
||||
<img src="https://gfer.me/images/arch.png"/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
# 文档
|
||||
[https://gfer.me](https://gfer.me)
|
||||
|
||||
|
||||
# 使用
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/", func(r *ghttp.Request) {
|
||||
r.Response.Write("Hello World")
|
||||
})
|
||||
s.Run()
|
||||
}
|
||||
```
|
||||
|
||||
[更多..](https://gfer.me/start/index)
|
||||
|
||||
|
||||
# 协议
|
||||
|
||||
`GF` 使用非常友好的 [MIT](LICENSE) 开源协议进行发布,永久`100%`开源免费。
|
||||
|
||||
# 贡献者(TOP 10)
|
||||
|
||||
<a href="https://gitee.com/johng" target="_blank" title="John"><img src="https://gitee.com/uploads/27/1309327_johng.png" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/wenzi1" target="_blank" title="蚊子"><img src="https://images.gitee.com/uploads/22/1923122_wenzi1.png" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/zseeker" target="_blank" title="zseeker"><img src="https://gfer.me/images/contributors/zseeker.png" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/ymrjqyy" target="_blank" title="一墨染尽青衣颜"><img src="https://images.gitee.com/uploads/27/876827_ymrjqyy.png" width="60" align="left"></a>
|
||||
<a href="https://github.com/chenyang351" target="_blank" title="chenyang351"><img src="https://avatars1.githubusercontent.com/u/30063958?s=60&v=4" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/wxkj" target="_blank" title="wxkj"><img src="https://gitee.com/uploads/56/91356_wxkj.png" width="60" align="left"></a>
|
||||
<a href="https://github.com/wxkj001" target="_blank" title="3wxkj001
|
||||
"><img src="https://avatars0.githubusercontent.com/u/7794279?s=60&v=4" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/zhangjinfu" target="_blank" title="张金富"><img src="https://images.gitee.com/uploads/63/356163_zhangjinfu.png" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/garfieldkwong" target="_blank" title="GarfieldKwong"><img src="https://gfer.me/images/contributors/garfieldkwong.png" width="60" align="left"></a>
|
||||
<a href="https://gitee.com/qq1054000800" target="_blank" title="hello"><img src="https://gitee.com/uploads/9/2209_qq1054000800.jpg" width="60" align="left"></a>
|
||||
|
||||
<br /><br /><br />
|
||||
|
||||
# 捐赠者
|
||||
|
||||
<a href="https://gitee.com/zfan_codes" target="_blank" title="范钟"><img src="https://images.gitee.com/uploads/32/2044832_zfan_codes.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/hailaz" target="_blank" title="HaiLaz"><img src="https://gitee.com/uploads/87/1273187_hailaz.png" width="60" align="left"></a>
|
||||
|
||||
<a href="https://gitee.com/mg91" target="_blank" title="mg91"><img src="https://images.gitee.com/uploads/30/1410930_mg91.png" width="60" align="left"></a>
|
||||
328
RELEASE.MD
328
RELEASE.MD
@ -1,136 +1,86 @@
|
||||
# `v0.97.399 beta` (2018-04-23)
|
||||
1、 增加gfsnotify文件监控模块;
|
||||
2、 配置管理模块增加配置文件自动检测更新机制;
|
||||
3、 模板引擎增加对模板文件的自动检测更新机制;
|
||||
4、 改进gconv包基本类型转换功能,提高转换性能;
|
||||
5、 增加gpage分页管理包,支持动态分页、静态分页以及自定义分页样式特性;
|
||||
6、 ghttp.Request增加Exit方法,用以标记服务退出,当在服务执行前调用后,服务将不再执行;
|
||||
7、 ghttp.Response去掉WriteString方法,统一使用Write方法返回数据流,是使用灵活的参数形式;
|
||||
8、 模板引擎增加模板变量暴露接口LockFunc/RLockFunc,以便支持开发者灵活处理模板变量;
|
||||
9、 ghttp.Server增加access & error log功能,并支持开发者自定义日志处理回调函数注册;
|
||||
10、增加gredis包,支持对redis的客户端操作封装,并将gredis.Redis对象加入到gins单例管理器中进行统一配置管理维护;
|
||||
11、gins单例管理器增加对单例对象配置文件的自动检测更新机制,当配置文件在外部发生变更时,自动刷新单例管理器中的单例对象;
|
||||
12、gdb数据库ORM包增加And/Or条件链式方法,并改进Where/Data方法参数灵活性;
|
||||
13、对于新增加的模块,同时也增加了对应的开发文档,并梳理完善了现有的其他模块开发文档;
|
||||
14、修复ISSUE:
|
||||
#IISWI gitee.com/johng/gf/issues/IISWI,
|
||||
#IISMY gitee.com/johng/gf/issues/IISMY,
|
||||
反馈并跟踪完成第三方依赖mxj包的ISSUE修复(github.com/clbanning/mxj/issues/48);
|
||||
# `v1.3.8` (2018-12-26)
|
||||
|
||||
|
||||
|
||||
# `v0.98.503 beta` (2018-05-21)
|
||||
## 新特性
|
||||
1、平滑重启特性( http://gf.johng.cn/625833 );
|
||||
2、gflock文件锁模块( http://gf.johng.cn/626062 );
|
||||
3、gproc进程管理及通信模块( http://gf.johng.cn/626063 );
|
||||
4、gpage分页管理模块,强大的动态分页及静态分页功能,并为开发者自定义分页样式提供了极高的灵活度( http://gf.johng.cn/597431 );
|
||||
5、ghttp.Server增加多端口监听特性,并支持HTTP/HTTPS( http://gf.johng.cn/494366 , http://gf.johng.cn/598802 );
|
||||
6、增加gspath目录检索包管理工具,支持对多目录下的文件检索特性;
|
||||
7、ghttp包控制器及执行对象注册增加更灵活的动态路由特性,路由规则增加{method}变量支持;
|
||||
1. 对`gform`完成重构,以提高扩展性,并修复部分细节问题、完善单元测试用例([https://gfer.me/database/orm/index](https://gfer.me/database/orm/index));
|
||||
1. `WebServer`路由注册新增分组路由特性([https://gfer.me/net/ghttp/group](https://gfer.me/net/ghttp/group));
|
||||
1. `WebServer`新增`Rewrite`路由重写特性([https://gfer.me/net/ghttp/static](https://gfer.me/net/ghttp/static));
|
||||
1. 增加框架运行时对开发环境的自动识别;
|
||||
1. 增加了`Travis CI`自动化构建/测试;
|
||||
|
||||
## 新功能
|
||||
1、gutil包增加MapToStruct方法,支持将map数据类型映射为struct对象;
|
||||
2、gconv
|
||||
1)、gconv包增加按照类型名称字符串进行类型转换;
|
||||
2)、gconv包新增Time/TimeDuration类型转换方法;
|
||||
3、ghttp
|
||||
1)、增加Web Server目录安全访问控制机制;
|
||||
2)、ghttp.Server增加自定义状态码回调函数注册处理;
|
||||
4、gdb
|
||||
1)、gdb包增加gdb.GetStruct/gdb.Model.Struct方法,获取查询结果记录自动转换为指定对象;
|
||||
2)、gdb增加Value/Record/Result类型,增加对Value类型的系列类型转换方法;
|
||||
3)、gdb包增加db.GetCount,tx.GetCount,model.Count数量查询方法;
|
||||
1. 改进`WebServer`静态文件服务功能,增加`SetStaticPath`/`AddStaticPath`方法([https://gfer.me/net/ghttp/static](https://gfer.me/net/ghttp/static));
|
||||
1. `gform`新增`Filter`链式操作方法,用于过滤参数中的非表字段键值对([https://gfer.me/database/orm/linkop](https://gfer.me/database/orm/linkop));
|
||||
1. `gcache`新增`Data`方法,用以获取所有的缓存数据项;
|
||||
1. `gredis`增加`GetConn`方法获取原生redis连接对象;
|
||||
|
||||
## 功能改进
|
||||
1、改进gredis客户端功能封装;
|
||||
2、改进grand包随机数生成性能;
|
||||
3、grand/gdb/gredis包增加benchmark性能测试脚本;
|
||||
4、改进gjson/gparser包的ToStruct方法实现;
|
||||
5、gdb :改进gdb.New获取ORM操作对象性能;
|
||||
6、gcfg :改进配置文件检索功能;
|
||||
7、gview:模板引擎增加多目录检索功能;
|
||||
8、gfile:增加源码main包目录获取方法MainPkgPath;
|
||||
9、ghttp
|
||||
1)、ghttp.Request增加请求进入和完成时间记录,并增加到默认日志内容中;
|
||||
2)、ghttp.Server事件回调之间支持通过ghttp.Request.Param自定义参数进行流程传参;
|
||||
10、gdb
|
||||
1)、改进gdb.Result与gdb.List, gdb.Record与gdb.Map之间的类型转换,便于业务层数据编码处理(如json/xml);
|
||||
2)、改进gdb.Tx.GetValue返回值类型;
|
||||
3)、gdb.Model.Data参数支持更加灵活的map参数;
|
||||
1. 改进`gform`的`Where`方法,支持`slice`类型的参数,并更方便地支持`in`操作查询([https://gfer.me/database/orm/linkop](https://gfer.me/database/orm/linkop));
|
||||
1. 改进`gproc`进程间通信数据结构,将`pid`字段从`16bit`扩展为`24bit`;
|
||||
1. 改进`gconv`/`gmap`/`garray`,增加若干操作方法;
|
||||
1. 改进`gview`模板引擎中的`date`内置函数,当给定的时间戳为空时打印当前的系统时间;
|
||||
1. 改进`gview`模板引擎中,当打印的变量不存在时,显示为空(标准库默认显示为`<no value>`);
|
||||
1. 改进`WebServer`,去掉`HANGUP`的信号监听,避免程序通过`nohup`运行时产生异常退出问题;
|
||||
1. 改进`gcache`性能,并完善基准测试;
|
||||
|
||||
## 问题修复
|
||||
1、ghttp
|
||||
1)、修复ghttp包路由缓存问题;
|
||||
2)、修复服务注册时的控制器及执行对象方法丢失问题;
|
||||
2、gconv
|
||||
1)、修正gconv.Float64方法位大小设置问题;
|
||||
2)、修复gconv.Int64(float64(xxx))问题;
|
||||
2、gdb
|
||||
1)、修复gdb.GetAll针对返回数据列表的for..range...的返回结果slice相同指针问题;
|
||||
2)、修复gdb.Delete方法错误;
|
||||
3)、修复gdb.Model.And/Or方法;
|
||||
4)、修复gdb.Model.Where方法参数处理问题;
|
||||
3、garray:修复garray包Remove方法锁机制问题;
|
||||
4、gtype :修复gtype.Float32/gtype.Float64对象类型的方法逻辑错误;
|
||||
5、gfsnotify:修复在windows下文件参数中不同文件分隔符引起的热更新机制失效问题;
|
||||
6、修复gvalid包验证问题:如果值为nil,并且不需要require*验证时,其他验证失效。并增加单元测试项,测试通过。
|
||||
## Bug Fix
|
||||
1. 修复`gcache`在非LRU特性开启时的缓存关闭资源竞争问题,并修复`doSetWithLockCheck`内部方法的返回值问题;
|
||||
1. 修复`grand.intn`内部方法在`x86`架构下的随机数位溢出问题;
|
||||
1. 修复`gbinary`中`Int`方法针对`[]byte`参数长度自动匹配造成的字节长度溢出问题;
|
||||
1. 修复`gjson`由于官方标准库`json`不支持`map[interface{}]*`类型造成的Go变量编码问题;
|
||||
1. 修复`garray`中部分方法的数据竞争问题,修复二分查找排序问题;
|
||||
1. 修复`ghttp.Request.GetVar`方法获取参数问题;
|
||||
1. 修复`gform`的数据库连接池不起作用的问题;
|
||||
|
||||
# `v0.99.682 beta` (2018-08-07)
|
||||
|
||||
|
||||
|
||||
|
||||
# `v1.2.11` (2018-11-26)
|
||||
## 新特性
|
||||
1、新增gdes包,用于DES加密/加密算法处理;
|
||||
2、新增gkafka包,kafka的golang客户端;
|
||||
3、新增gpool对象复用池,比较于标准库的sync.Pool更加灵活强大,可自定义对象的缓存时间、创建方法、销毁方法(http://gf.johng.cn/686654);
|
||||
4、完成网络通信gtcp/gudp包的重构,并进行了大量的改进工作,新增了详尽的开发文档及示例代码(http://gf.johng.cn/494382);
|
||||
5、增加gring并发安全环,标准库container/ring包的并发安全版本,并做了易用性的封装(http://gf.johng.cn/686655);
|
||||
6、gtime包新增了自定义日期格式话的支持,格式化语法类似PHP的date语法(http://gf.johng.cn/494387);
|
||||
7、gdb增加调试模式特性,使用SetDebug方法实现,在调试模式下可以获取详细的SQL执行记录,增加了详细的开发文档及示例代码(http://gf.johng.cn/702801);
|
||||
8、gdb增加查询缓存特性,使用Cache方法实现,增加了详细的开发文档及示例代码(http://gf.johng.cn/702801);
|
||||
9、ghttp.Server路由功能增加字段匹配规则特性,支持如:/order/list/{page}.html 动态路由规则特性(http://gf.johng.cn/702766);
|
||||
10、gpage分页包增加分页URL规则生成模板特性,内部可使用{.page}变量指定页码位置(http://gf.johng.cn/716438);
|
||||
11、增加gmap.Map对象,这是gmap.InterfaceInterfaceMap的别名;
|
||||
1. `ORM`新增对`SQLServer`及`Oracle`的支持([https://gfer.me/database/orm/database](https://gfer.me/database/orm/database));
|
||||
1. 完成`gvalid`模块校验结果的顺序特性([https://gfer.me/util/gvalid/checkmap](https://gfer.me/util/gvalid/checkmap));
|
||||
1. 改进`ghttp.Request.Exit`,使得调用该方法时立即退出业务执行,开发者无需调用`Exit`方法时再使用`return`返回([https://gfer.me/net/ghttp/service/object](https://gfer.me/net/ghttp/service/object));
|
||||
1. 模板引擎新增若干内置函数:`text/html/htmldecode/url/urldecode/date/compare/substr/strlimit/hidestr/highlight/toupper/tolower/nl2br` ([https://gfer.me/os/gview/funcs](https://gfer.me/os/gview/funcs));
|
||||
1. 模板引擎新增内置变量`Config` ([https://gfer.me/os/gview/vars](https://gfer.me/os/gview/vars));
|
||||
1. 改进`gconv.Struct`转换默认规则,支持不区分大小写的键名与属性名称匹配;
|
||||
1. `gform`配置文件支持`linkinfo`自定义数据库连接字段([https://gfer.me/database/orm/config](https://gfer.me/database/orm/config));
|
||||
1. `gfsnotify`模块增加对特定回调的取消注册功能([https://gfer.me/os/gfsnotify/index](https://gfer.me/os/gfsnotify/index));
|
||||
|
||||
|
||||
|
||||
## 新功能
|
||||
1、gdb增加MaxIdleConnCount/MaxOpenConnCount/MaxConnLifetime三项配置,并增加SetMaxConnLifetime方法;
|
||||
2、ghttp.Client增加HTTP账号密码设置功能(SetBasicAuth);
|
||||
3、glog新增对系统换行符号的自适配调整(\n|\r\n);
|
||||
4、增加glog控制台调试模式打印开关(SetDebug);
|
||||
5、gcfg增加SetFileName方法设置默认读取的配置文件名称;
|
||||
6、gcfg/gjson/gparser包新增Int8/16/32/64,Uint8/16/32/64方法;
|
||||
7、增加gzip方法的封装(Zip/Unzip);
|
||||
8、gview增加模板变量分隔符设置方法SetDelimiters;
|
||||
9、ghttp.Response增加Writef、Writefln方法;
|
||||
1. 改进`ghttp.Request`,增加`SetParam/GetParam`请求流程自定义变量设置/获取方法,用于在请求流程中的回调函数共享变量([https://gfer.me/net/ghttp/request](https://gfer.me/net/ghttp/request));
|
||||
1. 改进`ghttp.Response`,增加`ServeFileDownload`方法,用于WebServer引导客户端下载文件([https://gfer.me/net/ghttp/response](https://gfer.me/net/ghttp/response));
|
||||
1. `gvar`模块新增`gvar.VarRead`只读接口,用于控制对外只暴露数据读取功能;
|
||||
1. 增加`g.Throw`抛异常方法,`g.TryCatch`异常捕获方法封装;
|
||||
1. 改进`gcron`模块,增加自定义的Cron管理对象,增加`New/Start/Stop`方法;
|
||||
|
||||
|
||||
## 功能改进
|
||||
1、改进gfilepool文件指针池设计;改进gfile文本内容写入,增加指针池使用
|
||||
2、gdb包增加调试模式特性,并支持在调试模式下获得已执行的SQL列表结果
|
||||
3、改进gproc进程间通信机制,增加进程消息分组特性,并限定队列大小
|
||||
4、gdb结果方法处理增加ToXml/ToJson方法
|
||||
5、gregx包名修改为gregex
|
||||
6、改进gtime.StrToTime方法,新增对常见标准时间日期的自动转换,以及对时区的自动识别支持,并调整gconv,gvalid对该包的引用
|
||||
7、增加对字符集转换的封装,gxml包中使用新增的字符集转换包来做处理
|
||||
8、ghttp.Server.EnableAdmin页面Restart接口支持GET参数newExeFilePath支持
|
||||
9、ghttp.Server平滑重启机制增加可自定义重启可执行文件路径,特别是针对windows系统特别有用(因为windows下不支持可执行文件覆盖更新)
|
||||
10、改进ghttp.Server静态文件检索设计,增加开发环境时的main包源码目录查找机制;改进gcfg/gview的main包源码目录查找机制
|
||||
11、优化gcache设计,LRU特性非默认开启;优化gtype/gcache基准测试脚本;新增gregx基准测试脚本,改进设计,提升性能
|
||||
12、gfile包增加GoRootOfBuild方法,用于获取编译时的GOROOT数值;并改进glog包中backtrace的GOROOT路径过滤处理;
|
||||
13、改进grpool代码质量,并改进对池化goroutine数量的限制设计
|
||||
14、改进gdb.Map/List及g.Map/List的类型定义,改用别名特性以便支持原生类型输入(map/slice),并修复gdb.Model.Update方法参数处理问题
|
||||
15、调整ghttp包示例代码目录结构,增加ghttp.Client自定义Header方法,ghttp.Cookie增加Map方法用于获得客户端提交的所有cookie值,构造成map返回
|
||||
16、删除gcharset中的getcharset方法
|
||||
17、去掉gmap中常用的基本数据类型转换获取方法
|
||||
18、改进gconv.String方法,当无法使用基本类型进行字符串转换时,使用json.Marshal进行转换
|
||||
19、gvalid.CheckObject方法名称修改为gvalid.CheckStruct
|
||||
|
||||
1. WebServer添加`RouterCacheExpire`配置参数,用于设置路由检索缓存过期时间;
|
||||
1. WebServer允许同一`HOOK`事件被多次绑定注册,先注册的回调函数优先级更高([https://gfer.me/net/ghttp/service/hook](https://gfer.me/net/ghttp/service/hook));
|
||||
1. 当前工作目录为系统临时目录时,`gcfg`/`gview`/`ghttp`模块默认不添加工作目录到搜索路径;
|
||||
1. 改进`WebSocket`默认支持跨域请求([https://gfer.me/net/ghttp/websocket](https://gfer.me/net/ghttp/websocket));
|
||||
1. 改进`gtime.Format`支持中文;
|
||||
1. 改进`gfsnotify`,支持编辑器对文件非执行标准编辑时(RENAME+CHMOD)的热更新问题;
|
||||
1. 改进`gtype.Set`方法,增加Set原子操作返回旧的变量值;
|
||||
1. `gfile.ScanDir`增加支持`pattern`多个文件模式匹配,使用'`,`'符号分隔多个匹配模式;
|
||||
1. `gcfg`模块增加获取配置变量为`*gvar.Var`;
|
||||
1. `gstr`模块增加对中文截取方法;
|
||||
1. 改进`gtime.StrToTime`对常用时间格式匹配模式,新增`gtime.ParseTimeFromContent`方法;
|
||||
1. 修改配置管理、模板引擎、调试模式的环境变量名称为大写下划线标准格式;
|
||||
1. 改进`grand`模块随机数生成设计,底层使用`crypto/rand`+缓冲区实现高速的随机数生成([https://gfer.me/util/grand/index](https://gfer.me/util/grand/index));
|
||||
|
||||
## 问题修复
|
||||
1、修正gstr.IsNumeric错误
|
||||
2、修复当xml中encoding字符集为非UTF-8字符集时报错的问题
|
||||
3、修正gconv包float32->float64精度问题
|
||||
4、修复gpage包分页计数问题
|
||||
5、修复gdb批量数据Save错误
|
||||
6、去掉gpool中math.MAXINT64常量的使用,以修复int64到int类型的转换错误,兼容32位系统
|
||||
7、修正ghttp包没有使用Server仍然初始化相关异步goroutine的问题
|
||||
1. 修复`gspath`模块在`windows`下搜索失效问题;
|
||||
1. 修复`gspath`模块Search时带有indexFiles的检索问题;
|
||||
1. bug fix INZS1([https://gitee.com/johng/gf/issues/INZS1](https://gitee.com/johng/gf/issues/INZS1));
|
||||
1. 修复`gproc.ShellRun`在windows下的执行问题;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# `v1.0.898 stable` (2018-10-24)
|
||||
|
||||
@ -227,3 +177,145 @@
|
||||
1. 其他一些改动;
|
||||
|
||||
|
||||
|
||||
# `v0.99.682 beta` (2018-08-07)
|
||||
## 新特性
|
||||
1、新增gdes包,用于DES加密/加密算法处理;
|
||||
2、新增gkafka包,kafka的golang客户端;
|
||||
3、新增gpool对象复用池,比较于标准库的sync.Pool更加灵活强大,可自定义对象的缓存时间、创建方法、销毁方法(http://gf.johng.cn/686654);
|
||||
4、完成网络通信gtcp/gudp包的重构,并进行了大量的改进工作,新增了详尽的开发文档及示例代码(http://gf.johng.cn/494382);
|
||||
5、增加gring并发安全环,标准库container/ring包的并发安全版本,并做了易用性的封装(http://gf.johng.cn/686655);
|
||||
6、gtime包新增了自定义日期格式话的支持,格式化语法类似PHP的date语法(http://gf.johng.cn/494387);
|
||||
7、gdb增加调试模式特性,使用SetDebug方法实现,在调试模式下可以获取详细的SQL执行记录,增加了详细的开发文档及示例代码(http://gf.johng.cn/702801);
|
||||
8、gdb增加查询缓存特性,使用Cache方法实现,增加了详细的开发文档及示例代码(http://gf.johng.cn/702801);
|
||||
9、ghttp.Server路由功能增加字段匹配规则特性,支持如:/order/list/{page}.html 动态路由规则特性(http://gf.johng.cn/702766);
|
||||
10、gpage分页包增加分页URL规则生成模板特性,内部可使用{.page}变量指定页码位置(http://gf.johng.cn/716438);
|
||||
11、增加gmap.Map对象,这是gmap.InterfaceInterfaceMap的别名;
|
||||
|
||||
## 新功能
|
||||
1、gdb增加MaxIdleConnCount/MaxOpenConnCount/MaxConnLifetime三项配置,并增加SetMaxConnLifetime方法;
|
||||
2、ghttp.Client增加HTTP账号密码设置功能(SetBasicAuth);
|
||||
3、glog新增对系统换行符号的自适配调整(\n|\r\n);
|
||||
4、增加glog控制台调试模式打印开关(SetDebug);
|
||||
5、gcfg增加SetFileName方法设置默认读取的配置文件名称;
|
||||
6、gcfg/gjson/gparser包新增Int8/16/32/64,Uint8/16/32/64方法;
|
||||
7、增加gzip方法的封装(Zip/Unzip);
|
||||
8、gview增加模板变量分隔符设置方法SetDelimiters;
|
||||
9、ghttp.Response增加Writef、Writefln方法;
|
||||
|
||||
## 功能改进
|
||||
1、改进gfilepool文件指针池设计;改进gfile文本内容写入,增加指针池使用
|
||||
2、gdb包增加调试模式特性,并支持在调试模式下获得已执行的SQL列表结果
|
||||
3、改进gproc进程间通信机制,增加进程消息分组特性,并限定队列大小
|
||||
4、gdb结果方法处理增加ToXml/ToJson方法
|
||||
5、gregx包名修改为gregex
|
||||
6、改进gtime.StrToTime方法,新增对常见标准时间日期的自动转换,以及对时区的自动识别支持,并调整gconv,gvalid对该包的引用
|
||||
7、增加对字符集转换的封装,gxml包中使用新增的字符集转换包来做处理
|
||||
8、ghttp.Server.EnableAdmin页面Restart接口支持GET参数newExeFilePath支持
|
||||
9、ghttp.Server平滑重启机制增加可自定义重启可执行文件路径,特别是针对windows系统特别有用(因为windows下不支持可执行文件覆盖更新)
|
||||
10、改进ghttp.Server静态文件检索设计,增加开发环境时的main包源码目录查找机制;改进gcfg/gview的main包源码目录查找机制
|
||||
11、优化gcache设计,LRU特性非默认开启;优化gtype/gcache基准测试脚本;新增gregx基准测试脚本,改进设计,提升性能
|
||||
12、gfile包增加GoRootOfBuild方法,用于获取编译时的GOROOT数值;并改进glog包中backtrace的GOROOT路径过滤处理;
|
||||
13、改进grpool代码质量,并改进对池化goroutine数量的限制设计
|
||||
14、改进gdb.Map/List及g.Map/List的类型定义,改用别名特性以便支持原生类型输入(map/slice),并修复gdb.Model.Update方法参数处理问题
|
||||
15、调整ghttp包示例代码目录结构,增加ghttp.Client自定义Header方法,ghttp.Cookie增加Map方法用于获得客户端提交的所有cookie值,构造成map返回
|
||||
16、删除gcharset中的getcharset方法
|
||||
17、去掉gmap中常用的基本数据类型转换获取方法
|
||||
18、改进gconv.String方法,当无法使用基本类型进行字符串转换时,使用json.Marshal进行转换
|
||||
19、gvalid.CheckObject方法名称修改为gvalid.CheckStruct
|
||||
|
||||
|
||||
## 问题修复
|
||||
1、修正gstr.IsNumeric错误
|
||||
2、修复当xml中encoding字符集为非UTF-8字符集时报错的问题
|
||||
3、修正gconv包float32->float64精度问题
|
||||
4、修复gpage包分页计数问题
|
||||
5、修复gdb批量数据Save错误
|
||||
6、去掉gpool中math.MAXINT64常量的使用,以修复int64到int类型的转换错误,兼容32位系统
|
||||
7、修正ghttp包没有使用Server仍然初始化相关异步goroutine的问题
|
||||
|
||||
|
||||
|
||||
|
||||
# `v0.98.503 beta` (2018-05-21)
|
||||
## 新特性
|
||||
1、平滑重启特性( http://gf.johng.cn/625833 );
|
||||
2、gflock文件锁模块( http://gf.johng.cn/626062 );
|
||||
3、gproc进程管理及通信模块( http://gf.johng.cn/626063 );
|
||||
4、gpage分页管理模块,强大的动态分页及静态分页功能,并为开发者自定义分页样式提供了极高的灵活度( http://gf.johng.cn/597431 );
|
||||
5、ghttp.Server增加多端口监听特性,并支持HTTP/HTTPS( http://gf.johng.cn/494366 , http://gf.johng.cn/598802 );
|
||||
6、增加gspath目录检索包管理工具,支持对多目录下的文件检索特性;
|
||||
7、ghttp包控制器及执行对象注册增加更灵活的动态路由特性,路由规则增加{method}变量支持;
|
||||
|
||||
## 新功能
|
||||
1、gutil包增加MapToStruct方法,支持将map数据类型映射为struct对象;
|
||||
2、gconv
|
||||
1)、gconv包增加按照类型名称字符串进行类型转换;
|
||||
2)、gconv包新增Time/TimeDuration类型转换方法;
|
||||
3、ghttp
|
||||
1)、增加Web Server目录安全访问控制机制;
|
||||
2)、ghttp.Server增加自定义状态码回调函数注册处理;
|
||||
4、gdb
|
||||
1)、gdb包增加gdb.GetStruct/gdb.Model.Struct方法,获取查询结果记录自动转换为指定对象;
|
||||
2)、gdb增加Value/Record/Result类型,增加对Value类型的系列类型转换方法;
|
||||
3)、gdb包增加db.GetCount,tx.GetCount,model.Count数量查询方法;
|
||||
|
||||
## 功能改进
|
||||
1、改进gredis客户端功能封装;
|
||||
2、改进grand包随机数生成性能;
|
||||
3、grand/gdb/gredis包增加benchmark性能测试脚本;
|
||||
4、改进gjson/gparser包的ToStruct方法实现;
|
||||
5、gdb :改进gdb.New获取ORM操作对象性能;
|
||||
6、gcfg :改进配置文件检索功能;
|
||||
7、gview:模板引擎增加多目录检索功能;
|
||||
8、gfile:增加源码main包目录获取方法MainPkgPath;
|
||||
9、ghttp
|
||||
1)、ghttp.Request增加请求进入和完成时间记录,并增加到默认日志内容中;
|
||||
2)、ghttp.Server事件回调之间支持通过ghttp.Request.Param自定义参数进行流程传参;
|
||||
10、gdb
|
||||
1)、改进gdb.Result与gdb.List, gdb.Record与gdb.Map之间的类型转换,便于业务层数据编码处理(如json/xml);
|
||||
2)、改进gdb.Tx.GetValue返回值类型;
|
||||
3)、gdb.Model.Data参数支持更加灵活的map参数;
|
||||
|
||||
## 问题修复
|
||||
1、ghttp
|
||||
1)、修复ghttp包路由缓存问题;
|
||||
2)、修复服务注册时的控制器及执行对象方法丢失问题;
|
||||
2、gconv
|
||||
1)、修正gconv.Float64方法位大小设置问题;
|
||||
2)、修复gconv.Int64(float64(xxx))问题;
|
||||
2、gdb
|
||||
1)、修复gdb.GetAll针对返回数据列表的for..range...的返回结果slice相同指针问题;
|
||||
2)、修复gdb.Delete方法错误;
|
||||
3)、修复gdb.Model.And/Or方法;
|
||||
4)、修复gdb.Model.Where方法参数处理问题;
|
||||
3、garray:修复garray包Remove方法锁机制问题;
|
||||
4、gtype :修复gtype.Float32/gtype.Float64对象类型的方法逻辑错误;
|
||||
5、gfsnotify:修复在windows下文件参数中不同文件分隔符引起的热更新机制失效问题;
|
||||
6、修复gvalid包验证问题:如果值为nil,并且不需要require*验证时,其他验证失效。并增加单元测试项,测试通过。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# `v0.97.399 beta` (2018-04-23)
|
||||
1、 增加gfsnotify文件监控模块;
|
||||
2、 配置管理模块增加配置文件自动检测更新机制;
|
||||
3、 模板引擎增加对模板文件的自动检测更新机制;
|
||||
4、 改进gconv包基本类型转换功能,提高转换性能;
|
||||
5、 增加gpage分页管理包,支持动态分页、静态分页以及自定义分页样式特性;
|
||||
6、 ghttp.Request增加Exit方法,用以标记服务退出,当在服务执行前调用后,服务将不再执行;
|
||||
7、 ghttp.Response去掉WriteString方法,统一使用Write方法返回数据流,是使用灵活的参数形式;
|
||||
8、 模板引擎增加模板变量暴露接口LockFunc/RLockFunc,以便支持开发者灵活处理模板变量;
|
||||
9、 ghttp.Server增加access & error log功能,并支持开发者自定义日志处理回调函数注册;
|
||||
10、增加gredis包,支持对redis的客户端操作封装,并将gredis.Redis对象加入到gins单例管理器中进行统一配置管理维护;
|
||||
11、gins单例管理器增加对单例对象配置文件的自动检测更新机制,当配置文件在外部发生变更时,自动刷新单例管理器中的单例对象;
|
||||
12、gdb数据库ORM包增加And/Or条件链式方法,并改进Where/Data方法参数灵活性;
|
||||
13、对于新增加的模块,同时也增加了对应的开发文档,并梳理完善了现有的其他模块开发文档;
|
||||
14、修复ISSUE:
|
||||
#IISWI gitee.com/johng/gf/issues/IISWI,
|
||||
#IISMY gitee.com/johng/gf/issues/IISMY,
|
||||
反馈并跟踪完成第三方依赖mxj包的ISSUE修复(github.com/clbanning/mxj/issues/48);
|
||||
|
||||
|
||||
|
||||
|
||||
20
TODO.MD
20
TODO.MD
@ -34,16 +34,24 @@
|
||||
- glog分类&日志等级&链式操作、gdb debug自动输出调试信息、gmlock内存锁、
|
||||
1. 服务注册域名增加对泛域名的支持;
|
||||
1. Cookie设置中文失效问题;
|
||||
1. ghttp hook回调使用方式在注册路由比较多的时候,优先级可能使得开发者混乱,考虑方式便于管理;
|
||||
1. 使用gconv将slice映射到struct属性上,例如redis hscan的结果集;
|
||||
1. 项目参考:
|
||||
- https://github.com/namreg/godown
|
||||
- https://github.com/Masterminds/sprig
|
||||
1. gform参考 https://gohouse.github.io/gorose/dist/index.html 进行改进
|
||||
1. 模板引擎增加对对象的支持(参考https://segmentfault.com/q/1010000016829214);
|
||||
1. 改进gfpool在文件指针变化时的更新;
|
||||
1. gtcp提供简便的包发送/接收方法(SendPkg/RecvPkg)以解决常见的TCP通信粘包问题,并完善文档(参考:https://www.cnblogs.com/kex1n/p/6502002.html);
|
||||
1. gfile对于文件的读写强行使用了gfpool,在某些场景下不合适,需要考虑剥离开,并为开发者提供单独的指针池文件操作特性;
|
||||
1. 路由增加不区分大小写得匹配方式;
|
||||
1. str_ireplace: http://php.net/manual/en/function.str-ireplace.php
|
||||
1. strpos/stripos/strrpos/strripos: http://php.net/manual/en/function.stripos.php
|
||||
1. 改进WebServer获取POST参数处理逻辑,当提交非form数据时,例如json数据,针对某些方法可以直接解析;
|
||||
1. WebServer增加可选择的路由覆盖配置,默认情况下不覆盖;
|
||||
1. gkafka这个包比较重,未来从框架中剥离出来;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# DONE
|
||||
@ -96,4 +104,8 @@
|
||||
1. `gfsnotify`增加添加监听文件时的监听ID返回,以便调用端删除监听时只删除自己添加的监听,而不影响其他对该同一文件的监听回调;
|
||||
1. `gfsnotify`针对添加目录监听时无法使用多个`Watcher`,考虑改进,并考虑动态扩容全局`Watcher`方案;
|
||||
1. 由于系统对inotify实例数量(`fs.inotify.max_user_instances`)以及队列大小(`fs.inotify.max_user_watches`)有限制,需要改进`gfsnotify`;
|
||||
|
||||
1. WebServer事件回调允许对同一个路由规则绑定多个事件回调;
|
||||
1. gcfg/gview/ghttp等模块加上对临时文件目录的自动添加监听判断(基本是开发环境下,特别是windows环境),去掉临时文件的监听,避免临时文件过大引起的运行缓慢占用内存问题;
|
||||
1. 改进gfpool在文件指针变化时的更新;
|
||||
1. ghttp hook回调使用方式在注册路由比较多的时候,优先级可能使得开发者混乱,考虑方式便于管理;
|
||||
1. gform对于MySQL字段类型为datetime类型的时区问题分析;
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package garray provides kinds of concurrent-safe(alternative) arrays.
|
||||
// 并发安全的数组.
|
||||
package garray
|
||||
|
||||
|
||||
@ -69,6 +69,17 @@ func (a *IntArray) InsertAfter(index int, value int) {
|
||||
func (a *IntArray) Remove(index int) int {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
} else if index == len(a.array) - 1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
@ -136,6 +147,20 @@ func (a *IntArray) Search(value int) int {
|
||||
return result
|
||||
}
|
||||
|
||||
// 清理数组中重复的元素项
|
||||
func (a *IntArray) Unique() *IntArray {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array) - 1; i++ {
|
||||
for j := i + 1; j < len(a.array); j++ {
|
||||
if a.array[i] == a.array[j] {
|
||||
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (a *IntArray) LockFunc(f func(array []int)) {
|
||||
a.mu.Lock(true)
|
||||
|
||||
@ -68,11 +68,41 @@ func (a *Array) InsertAfter(index int, value interface{}) {
|
||||
func (a *Array) Remove(index int) interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
} else if index == len(a.array) - 1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
}
|
||||
|
||||
// 将最左端(索引为0)的数据项移出数组,并返回该数据项
|
||||
func (a *Array) PopLeft() interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
}
|
||||
|
||||
// 将最右端(索引为length - 1)的数据项移出数组,并返回该数据项
|
||||
func (a *Array) PopRight() interface{} {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
index := len(a.array) - 1
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
|
||||
// 追加数据项
|
||||
func (a *Array) Append(value...interface{}) {
|
||||
a.mu.Lock()
|
||||
@ -135,6 +165,20 @@ func (a *Array) Search(value interface{}) int {
|
||||
return result
|
||||
}
|
||||
|
||||
// 清理数组中重复的元素项
|
||||
func (a *Array) Unique() *Array {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array) - 1; i++ {
|
||||
for j := i + 1; j < len(a.array); j++ {
|
||||
if a.array[i] == a.array[j] {
|
||||
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (a *Array) LockFunc(f func(array []interface{})) {
|
||||
a.mu.Lock(true)
|
||||
|
||||
@ -16,7 +16,7 @@ type SortedIntArray struct {
|
||||
mu *rwmutex.RWMutex // 互斥锁
|
||||
cap int // 初始化设置的数组容量
|
||||
array []int // 底层数组
|
||||
unique *gtype.Bool // 是否要求不能重复
|
||||
unique *gtype.Bool // 是否要求不能重复(默认false)
|
||||
compareFunc func(v1, v2 int) int // 比较函数,返回值 -1: v1 < v2;0: v1 == v2;1: v1 > v2
|
||||
}
|
||||
|
||||
@ -153,16 +153,14 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int)
|
||||
max := len(a.array) - 1
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for {
|
||||
for min <= max {
|
||||
mid = int((min + max) / 2)
|
||||
cmp = a.compareFunc(value, a.array[mid])
|
||||
switch cmp {
|
||||
case -1 : max = mid - 1
|
||||
case 0 :
|
||||
case 1 : min = mid + 1
|
||||
}
|
||||
if cmp == 0 || min > max {
|
||||
break
|
||||
case 0 :
|
||||
return mid, cmp
|
||||
}
|
||||
}
|
||||
return mid, cmp
|
||||
|
||||
@ -146,16 +146,14 @@ func (a *SortedArray) binSearch(value interface{}, lock bool)(index int, result
|
||||
max := len(a.array) - 1
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for {
|
||||
for min <= max {
|
||||
mid = int((min + max) / 2)
|
||||
cmp = a.compareFunc(value, a.array[mid])
|
||||
switch cmp {
|
||||
case -1 : max = mid - 1
|
||||
case 0 :
|
||||
case 1 : min = mid + 1
|
||||
}
|
||||
if cmp == 0 || min > max {
|
||||
break
|
||||
case 0 :
|
||||
return mid, cmp
|
||||
}
|
||||
}
|
||||
return mid, cmp
|
||||
|
||||
@ -147,16 +147,14 @@ func (a *SortedStringArray) binSearch(value string, lock bool) (index int, resul
|
||||
max := len(a.array) - 1
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for {
|
||||
for min <= max {
|
||||
mid = int((min + max) / 2)
|
||||
cmp = a.compareFunc(value, a.array[mid])
|
||||
switch cmp {
|
||||
case -1 : max = mid - 1
|
||||
case 0 :
|
||||
case 1 : min = mid + 1
|
||||
}
|
||||
if cmp == 0 || min > max {
|
||||
break
|
||||
case 0 :
|
||||
return mid, cmp
|
||||
}
|
||||
}
|
||||
return mid, cmp
|
||||
|
||||
@ -68,7 +68,18 @@ func (a *StringArray) InsertAfter(index int, value string) {
|
||||
// 删除指定索引的数据项, 调用方注意判断数组边界
|
||||
func (a *StringArray) Remove(index int) string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.RUnlock()
|
||||
defer a.mu.Unlock()
|
||||
// 边界删除判断,以提高删除效率
|
||||
if index == 0 {
|
||||
value := a.array[0]
|
||||
a.array = a.array[1 : ]
|
||||
return value
|
||||
} else if index == len(a.array) - 1 {
|
||||
value := a.array[index]
|
||||
a.array = a.array[: index]
|
||||
return value
|
||||
}
|
||||
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
|
||||
value := a.array[index]
|
||||
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
|
||||
return value
|
||||
@ -135,6 +146,20 @@ func (a *StringArray) Search(value string) int {
|
||||
return result
|
||||
}
|
||||
|
||||
// 清理数组中重复的元素项
|
||||
func (a *StringArray) Unique() *StringArray {
|
||||
a.mu.Lock()
|
||||
for i := 0; i < len(a.array) - 1; i++ {
|
||||
for j := i + 1; j < len(a.array); j++ {
|
||||
if a.array[i] == a.array[j] {
|
||||
a.array = append(a.array[ : j], a.array[j + 1 : ]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
// 使用自定义方法执行加锁修改操作
|
||||
func (a *StringArray) LockFunc(f func(array []string)) {
|
||||
a.mu.Lock(true)
|
||||
|
||||
84
g/container/garray/garray_test.go
Normal file
84
g/container/garray/garray_test.go
Normal file
@ -0,0 +1,84 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
|
||||
|
||||
// go test *.go
|
||||
|
||||
package garray_test
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/garray"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/util/gtest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func Test_IntArray_Unique(t *testing.T) {
|
||||
expect := []int{1, 2, 3, 4, 5, 6}
|
||||
array := garray.NewIntArray(0, 0)
|
||||
array.Append(1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6)
|
||||
array.Unique()
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
}
|
||||
|
||||
func Test_SortedIntArray1(t *testing.T) {
|
||||
expect := []int{0,1,2,3,4,5,6,7,8,9,10}
|
||||
array := garray.NewSortedIntArray(0)
|
||||
for i := 10; i > -1; i-- {
|
||||
array.Add(i)
|
||||
}
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
}
|
||||
|
||||
func Test_SortedIntArray2(t *testing.T) {
|
||||
expect := []int{0,1,2,3,4,5,6,7,8,9,10}
|
||||
array := garray.NewSortedIntArray(0)
|
||||
for i := 0; i <= 10; i++ {
|
||||
array.Add(i)
|
||||
}
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
}
|
||||
|
||||
func Test_SortedStringArray1(t *testing.T) {
|
||||
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
|
||||
array := garray.NewSortedStringArray(0)
|
||||
for i := 10; i > -1; i-- {
|
||||
array.Add(gconv.String(i))
|
||||
}
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
}
|
||||
|
||||
func Test_SortedStringArray2(t *testing.T) {
|
||||
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
|
||||
array := garray.NewSortedStringArray(0)
|
||||
for i := 0; i <= 10; i++ {
|
||||
array.Add(gconv.String(i))
|
||||
}
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
}
|
||||
|
||||
func Test_SortedArray1(t *testing.T) {
|
||||
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
|
||||
array := garray.NewSortedArray(0, func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
})
|
||||
for i := 10; i > -1; i-- {
|
||||
array.Add(gconv.String(i))
|
||||
}
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
}
|
||||
|
||||
func Test_SortedArray2(t *testing.T) {
|
||||
expect := []string{"0","1","10","2","3","4","5","6","7","8","9"}
|
||||
array := garray.NewSortedArray(0, func(v1, v2 interface{}) int {
|
||||
return strings.Compare(gconv.String(v1), gconv.String(v2))
|
||||
})
|
||||
for i := 0; i <= 10; i++ {
|
||||
array.Add(gconv.String(i))
|
||||
}
|
||||
gtest.Assert(array.Slice(), expect)
|
||||
}
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gchan provides graceful operations for channel.
|
||||
// 优雅的Channel操作.
|
||||
package gchan
|
||||
|
||||
@ -40,8 +41,7 @@ func (q *Chan) Pop() interface{} {
|
||||
|
||||
// 关闭队列(通知所有通过Pop阻塞的协程退出)
|
||||
func (q *Chan) Close() {
|
||||
if !q.closed.Val() {
|
||||
q.closed.Set(true)
|
||||
if !q.closed.Set(true) {
|
||||
close(q.list)
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
// Package glist provides a concurrent-safe(alternative) doubly linked list.
|
||||
// 并发安全的双向链表.
|
||||
package glist
|
||||
|
||||
@ -21,7 +22,7 @@ type List struct {
|
||||
|
||||
// 获得一个变长链表指针
|
||||
func New(safe...bool) *List {
|
||||
return &List{
|
||||
return &List {
|
||||
mu : rwmutex.New(safe...),
|
||||
list : list.New(),
|
||||
}
|
||||
|
||||
@ -14,31 +14,31 @@ import (
|
||||
|
||||
var l = New()
|
||||
|
||||
func BenchmarkPushBack(b *testing.B) {
|
||||
func Benchmark_PushBack(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PushBack(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPopFront(b *testing.B) {
|
||||
func Benchmark_PopFront(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PopFront()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPushFront(b *testing.B) {
|
||||
func Benchmark_PushFront(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PushFront(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPopBack(b *testing.B) {
|
||||
func Benchmark_PopBack(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.PopBack()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLen(b *testing.B) {
|
||||
func Benchmark_Len(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
l.Len()
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gmap provides kinds of concurrent-safe(alternative) maps.
|
||||
// 并发安全的哈希MAP.
|
||||
package gmap
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gpool provides a object-reusable concurrent-safe pool.
|
||||
// 对象复用池.
|
||||
package gpool
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gqueue provides a dynamic/static concurrent-safe(alternative) queue.
|
||||
// 并发安全的动态队列.
|
||||
// 特点:
|
||||
// 1、动态队列初始化速度快;
|
||||
@ -29,26 +30,22 @@ type Queue struct {
|
||||
}
|
||||
|
||||
const (
|
||||
// 默认临时队列大小,注意是临时的
|
||||
gDEFAULT_QUEUE_SIZE = 10000
|
||||
// 动态队列缓冲区大小
|
||||
gQUEUE_SIZE = 10000
|
||||
)
|
||||
|
||||
// 队列大小为非必须参数,默认不限制
|
||||
func New(limit...int) *Queue {
|
||||
size := gDEFAULT_QUEUE_SIZE
|
||||
if len(limit) > 0 {
|
||||
size = limit[0]
|
||||
}
|
||||
q := &Queue {
|
||||
list : glist.New(),
|
||||
queue : make(chan interface{}, size),
|
||||
events : make(chan struct{}, math.MaxInt32),
|
||||
closeChan : make(chan struct{}, 0),
|
||||
}
|
||||
if len(limit) > 0 {
|
||||
q.limit = size
|
||||
q.limit = limit[0]
|
||||
q.queue = make(chan interface{}, limit[0])
|
||||
} else {
|
||||
// 如果是动态队列大小,那么额外会运行一个goroutine
|
||||
q.list = glist.New()
|
||||
q.queue = make(chan interface{}, gQUEUE_SIZE)
|
||||
q.events = make(chan struct{}, math.MaxInt32)
|
||||
go q.startAsyncLoop()
|
||||
}
|
||||
return q
|
||||
|
||||
@ -13,36 +13,38 @@ import (
|
||||
"gitee.com/johng/gf/g/container/gqueue"
|
||||
)
|
||||
|
||||
var length = 10000000
|
||||
var bn = 20000000
|
||||
var length = 1000000
|
||||
var qstatic = gqueue.New(length)
|
||||
var qdynamic = gqueue.New()
|
||||
var cany = make(chan interface{}, length)
|
||||
var cint = make(chan int, length)
|
||||
|
||||
func Benchmark_GqueueStaticPushAndPop(b *testing.B) {
|
||||
func Benchmark_Gqueue_StaticPushAndPop(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
qstatic.Push(i)
|
||||
qstatic.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_GqueueDynamicPush(b *testing.B) {
|
||||
func Benchmark_Gqueue_DynamicPush(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
qdynamic.Push(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_ChannelInterfacePushAndPop(b *testing.B) {
|
||||
func Benchmark_Gqueue_DynamicPop(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
qdynamic.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Channel_PushAndPop(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
cany <- i
|
||||
<- cany
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_ChannelIntPushAndPop(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
cint <- i
|
||||
<- cint
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gring provides a concurrent-safe(alternative) ring(circular lists).
|
||||
// 并发安全的环.
|
||||
package gring
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gset provides kinds of concurrent-safe(alternative) sets.
|
||||
// 并发安全的集合SET.
|
||||
package gset
|
||||
|
||||
|
||||
@ -18,55 +18,55 @@ var ints = gset.NewIntSet()
|
||||
var itfs = gset.NewInterfaceSet()
|
||||
var strs = gset.NewStringSet()
|
||||
|
||||
func BenchmarkIntSet_Add(b *testing.B) {
|
||||
func Benchmark_IntSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ints.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIntSet_Contains(b *testing.B) {
|
||||
func Benchmark_IntSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ints.Contains(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIntSet_Remove(b *testing.B) {
|
||||
func Benchmark_IntSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ints.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInterfaceSet_Add(b *testing.B) {
|
||||
func Benchmark_InterfaceSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfs.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInterfaceSet_Contains(b *testing.B) {
|
||||
func Benchmark_InterfaceSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfs.Contains(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkInterfaceSet_Remove(b *testing.B) {
|
||||
func Benchmark_InterfaceSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfs.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringSet_Add(b *testing.B) {
|
||||
func Benchmark_StringSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strs.Add(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringSet_Contains(b *testing.B) {
|
||||
func Benchmark_StringSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strs.Contains(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStringSet_Remove(b *testing.B) {
|
||||
func Benchmark_StringSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strs.Remove(strconv.Itoa(i))
|
||||
}
|
||||
|
||||
73
g/container/gset/gset_unsafe_test.go
Normal file
73
g/container/gset/gset_unsafe_test.go
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
|
||||
|
||||
// go test *.go -bench=".*"
|
||||
|
||||
package gset_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"strconv"
|
||||
"gitee.com/johng/gf/g/container/gset"
|
||||
)
|
||||
|
||||
var intsUnsafe = gset.NewIntSet(false)
|
||||
var itfsUnsafe = gset.NewInterfaceSet(false)
|
||||
var strsUnsafe = gset.NewStringSet(false)
|
||||
|
||||
func Benchmark_Unsafe_IntSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
intsUnsafe.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
intsUnsafe.Contains(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_IntSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
intsUnsafe.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_InterfaceSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfsUnsafe.Add(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_InterfaceSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfsUnsafe.Contains(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_InterfaceSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
itfsUnsafe.Remove(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringSet_Add(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strsUnsafe.Add(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringSet_Contains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strsUnsafe.Contains(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Unsafe_StringSet_Remove(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
strsUnsafe.Remove(strconv.Itoa(i))
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gtype provides kinds of concurrent-safe basic-types.
|
||||
// 并发安全的基本类型.
|
||||
package gtype
|
||||
|
||||
|
||||
@ -4,13 +4,15 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gvar provides a universal variable type.
|
||||
// 通用动态变量.
|
||||
package gvar
|
||||
|
||||
import (
|
||||
"time"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Var struct {
|
||||
@ -30,6 +32,16 @@ func New(value interface{}, safe...bool) *Var {
|
||||
return v
|
||||
}
|
||||
|
||||
// 创建一个只读动态变量,value参数可以为nil
|
||||
func NewRead(value interface{}, safe...bool) VarRead {
|
||||
return VarRead(New(value, safe...))
|
||||
}
|
||||
|
||||
// 返回动态变量的只读接口
|
||||
func (v *Var) ReadOnly() VarRead {
|
||||
return VarRead(v)
|
||||
}
|
||||
|
||||
func (v *Var) Set(value interface{}) (old interface{}) {
|
||||
if v.safe {
|
||||
old = v.value.(*gtype.Interface).Set(value)
|
||||
@ -48,6 +60,7 @@ func (v *Var) Val() interface{} {
|
||||
}
|
||||
}
|
||||
|
||||
// Val() 别名
|
||||
func (v *Var) Interface() interface{} {
|
||||
return v.Val()
|
||||
}
|
||||
@ -80,6 +93,8 @@ func (v *Var) Interfaces() []interface{} { return gconv.Interfaces(v.Val()
|
||||
func (v *Var) Time(format...string) time.Time { return gconv.Time(v.Val(), format...) }
|
||||
func (v *Var) TimeDuration() time.Duration { return gconv.TimeDuration(v.Val()) }
|
||||
|
||||
func (v *Var) GTime(format...string) *gtime.Time { return gconv.GTime(v.Val(), format...) }
|
||||
|
||||
// 将变量转换为对象,注意 objPointer 参数必须为struct指针
|
||||
func (v *Var) Struct(objPointer interface{}, attrMapping...map[string]string) error {
|
||||
return gconv.Struct(v.Val(), objPointer, attrMapping...)
|
||||
|
||||
42
g/container/gvar/gvar_read.go
Normal file
42
g/container/gvar/gvar_read.go
Normal file
@ -0,0 +1,42 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
|
||||
|
||||
package gvar
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 只读变量接口
|
||||
type VarRead interface {
|
||||
Val() interface{}
|
||||
IsNil() bool
|
||||
Bytes() []byte
|
||||
String() string
|
||||
Bool() bool
|
||||
Int() int
|
||||
Int8() int8
|
||||
Int16() int16
|
||||
Int32() int32
|
||||
Int64() int64
|
||||
Uint() uint
|
||||
Uint8() uint8
|
||||
Uint16() uint16
|
||||
Uint32() uint32
|
||||
Uint64() uint64
|
||||
Float32() float32
|
||||
Float64() float64
|
||||
Interface() interface{}
|
||||
Ints() []int
|
||||
Floats() []float64
|
||||
Strings() []string
|
||||
Interfaces() []interface{}
|
||||
Time(format ...string) time.Time
|
||||
TimeDuration() time.Duration
|
||||
GTime(format...string) *gtime.Time
|
||||
Struct(objPointer interface{}, attrMapping ...map[string]string) error
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
package gdes
|
||||
package gdes_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/encoding/gdes"
|
||||
"gitee.com/johng/gf/g/crypto/gdes"
|
||||
)
|
||||
|
||||
func TestDesECB(t *testing.T){
|
||||
|
||||
@ -35,6 +35,7 @@ func EncryptFile(path string) string {
|
||||
if e != nil {
|
||||
return ""
|
||||
}
|
||||
defer f.Close()
|
||||
h := md5.New()
|
||||
_, e = io.Copy(h, f)
|
||||
if e != nil {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 数据库ORM.
|
||||
// Package gdb provides ORM features for popular relationship databases/数据库ORM.
|
||||
// 默认内置支持MySQL, 其他数据库需要手动import对应的数据库引擎第三方包.
|
||||
package gdb
|
||||
|
||||
@ -12,7 +12,6 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"gitee.com/johng/gf/g/container/gring"
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"gitee.com/johng/gf/g/container/gvar"
|
||||
@ -22,39 +21,42 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
OPTION_INSERT = 0
|
||||
OPTION_REPLACE = 1
|
||||
OPTION_SAVE = 2
|
||||
OPTION_IGNORE = 3
|
||||
)
|
||||
|
||||
// 数据库操作接口
|
||||
type Link interface {
|
||||
// 打开数据库连接,建立数据库操作对象
|
||||
Open(c *ConfigNode) (*sql.DB, error)
|
||||
type DB interface {
|
||||
// 建立数据库连接方法(开发者一般不需要直接调用)
|
||||
Open(config *ConfigNode) (*sql.DB, error)
|
||||
|
||||
// SQL操作方法
|
||||
Query(q string, args ...interface{}) (*sql.Rows, error)
|
||||
Exec(q string, args ...interface{}) (sql.Result, error)
|
||||
Prepare(q string) (*sql.Stmt, error)
|
||||
// SQL操作方法 API
|
||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
Exec(sql string, args ...interface{}) (sql.Result, error)
|
||||
Prepare(sql string, execOnMaster...bool) (*sql.Stmt, error)
|
||||
|
||||
// 内部实现API的方法(不同数据库可覆盖这些方法实现自定义的操作)
|
||||
doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error)
|
||||
doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error)
|
||||
doPrepare(link dbLink, query string) (*sql.Stmt, error)
|
||||
doInsert(link dbLink, table string, data Map, option int) (result sql.Result, err error)
|
||||
doBatchInsert(link dbLink, table string, list List, batch int, option int) (result sql.Result, err error)
|
||||
doUpdate(link dbLink, table string, data interface{}, condition interface{}, args ...interface{}) (result sql.Result, err error)
|
||||
doDelete(link dbLink, table string, condition interface{}, args ...interface{}) (result sql.Result, err error)
|
||||
|
||||
// 数据库查询
|
||||
GetAll(q string, args ...interface{}) (Result, error)
|
||||
GetOne(q string, args ...interface{}) (Record, error)
|
||||
GetValue(q string, args ...interface{}) (Value, error)
|
||||
GetAll(query string, args ...interface{}) (Result, error)
|
||||
GetOne(query string, args ...interface{}) (Record, error)
|
||||
GetValue(query string, args ...interface{}) (Value, error)
|
||||
GetCount(query string, args ...interface{}) (int, error)
|
||||
GetStruct(obj interface{}, query string, args ...interface{}) error
|
||||
|
||||
// Ping
|
||||
// 创建底层数据库master/slave链接对象
|
||||
Master() (*sql.DB, error)
|
||||
Slave() (*sql.DB, error)
|
||||
|
||||
// Ping
|
||||
PingMaster() error
|
||||
PingSlave() error
|
||||
|
||||
// 连接属性设置
|
||||
SetMaxIdleConns(n int)
|
||||
SetMaxOpenConns(n int)
|
||||
SetConnMaxLifetime(n int)
|
||||
|
||||
// 开启事务操作
|
||||
Begin() (*Tx, error)
|
||||
Begin() (*TX, error)
|
||||
|
||||
// 数据表插入/更新/保存操作
|
||||
Insert(table string, data Map) (sql.Result, error)
|
||||
@ -74,25 +76,40 @@ type Link interface {
|
||||
Table(tables string) *Model
|
||||
From(tables string) *Model
|
||||
|
||||
// 内部方法
|
||||
insert(table string, data Map, option uint8) (sql.Result, error)
|
||||
batchInsert(table string, list List, batch int, option uint8) (sql.Result, error)
|
||||
// 设置管理
|
||||
SetDebug(debug bool)
|
||||
SetSchema(schema string)
|
||||
GetQueriedSqls() []*Sql
|
||||
PrintQueriedSqls()
|
||||
SetMaxIdleConns(n int)
|
||||
SetMaxOpenConns(n int)
|
||||
SetConnMaxLifetime(n int)
|
||||
|
||||
getQuoteCharLeft() string
|
||||
getQuoteCharRight() string
|
||||
handleSqlBeforeExec(q *string) *string
|
||||
// 内部方法接口
|
||||
getCache() (*gcache.Cache)
|
||||
getChars() (charLeft string, charRight string)
|
||||
getDebug() bool
|
||||
filterFields(table string, data map[string]interface{}) map[string]interface{}
|
||||
getTableFields(table string) (map[string]string, error)
|
||||
handleSqlBeforeExec(sql string) string
|
||||
}
|
||||
|
||||
// 执行底层数据库操作的核心接口
|
||||
type dbLink interface {
|
||||
Query(query string, args ...interface{}) (*sql.Rows, error)
|
||||
Exec(sql string, args ...interface{}) (sql.Result, error)
|
||||
Prepare(sql string) (*sql.Stmt, error)
|
||||
}
|
||||
|
||||
// 数据库链接对象
|
||||
type Db struct {
|
||||
link Link // 底层数据库类型管理对象
|
||||
type dbBase struct {
|
||||
db DB // 数据库对象
|
||||
group string // 配置分组名称
|
||||
charl string // SQL安全符号(左)
|
||||
charr string // SQL安全符号(右)
|
||||
debug *gtype.Bool // (默认关闭)是否开启调试模式,当开启时会启用一些调试特性
|
||||
sqls *gring.Ring // (debug=true时有效)已执行的SQL列表
|
||||
cache *gcache.Cache // 查询缓存,需要注意的是,事务查询不支持缓存
|
||||
maxIdleConnCount *gtype.Int // 连接池最大限制的连接数
|
||||
cache *gcache.Cache // 数据库缓存,包括底层连接池对象缓存及查询缓存;需要注意的是,事务查询不支持查询缓存
|
||||
schema *gtype.String // 手动切换的数据库名称
|
||||
maxIdleConnCount *gtype.Int // 连接池最大限制的连接数
|
||||
maxOpenConnCount *gtype.Int // 连接池最大打开的连接数
|
||||
maxConnLifetime *gtype.Int // (单位秒)连接对象可重复使用的时间长度
|
||||
}
|
||||
@ -104,11 +121,11 @@ type Sql struct {
|
||||
Error error // 执行结果(nil为成功)
|
||||
Start int64 // 执行开始时间(毫秒)
|
||||
End int64 // 执行结束时间(毫秒)
|
||||
Func string // 执行方法名称
|
||||
Func string // 执行方法
|
||||
}
|
||||
|
||||
// 返回数据表记录值
|
||||
type Value = *gvar.Var
|
||||
type Value = gvar.VarRead
|
||||
|
||||
// 返回数据表记录Map
|
||||
type Record map[string]Value
|
||||
@ -117,26 +134,22 @@ type Record map[string]Value
|
||||
type Result []Record
|
||||
|
||||
// 关联数组,绑定一条数据表记录(使用别名)
|
||||
type Map = map[string]interface{}
|
||||
type Map = map[string]interface{}
|
||||
|
||||
// 关联数组列表(索引从0开始的数组),绑定多条记录(使用别名)
|
||||
type List = []Map
|
||||
|
||||
var (
|
||||
// 支持的数据库类型map
|
||||
driverMap = make(map[string]interface{})
|
||||
// 数据库查询缓存对象map,使用数据库连接名称作为键名,键值为查询缓存对象
|
||||
dbCaches = gmap.NewStringInterfaceMap()
|
||||
const (
|
||||
OPTION_INSERT = 0
|
||||
OPTION_REPLACE = 1
|
||||
OPTION_SAVE = 2
|
||||
OPTION_IGNORE = 3
|
||||
// 默认的连接池连接存活时间(秒)
|
||||
gDEFAULT_CONN_MAX_LIFE_TIME = 30
|
||||
)
|
||||
func init() {
|
||||
driverMap["mysql"] = linkMysql
|
||||
driverMap["oracle"] = linkOracle
|
||||
driverMap["sqlite"] = linkSqlite
|
||||
driverMap["pgsql"] = linkPgsql
|
||||
}
|
||||
|
||||
// 使用默认/指定分组配置进行连接,数据库集群配置项:default
|
||||
func New(groupName ...string) (*Db, error) {
|
||||
func New(groupName ...string) (db DB, err error) {
|
||||
group := config.d
|
||||
if len(groupName) > 0 {
|
||||
group = groupName[0]
|
||||
@ -149,24 +162,30 @@ func New(groupName ...string) (*Db, error) {
|
||||
}
|
||||
if _, ok := config.c[group]; ok {
|
||||
if node, err := getConfigNodeByGroup(group, true); err == nil {
|
||||
link, err := getLinkByType(node.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db := &Db {
|
||||
link : link,
|
||||
base := &dbBase {
|
||||
group : group,
|
||||
charl : link.getQuoteCharLeft(),
|
||||
charr : link.getQuoteCharRight(),
|
||||
debug : gtype.NewBool(),
|
||||
cache : gcache.New(),
|
||||
schema : gtype.NewString(),
|
||||
maxIdleConnCount : gtype.NewInt(),
|
||||
maxOpenConnCount : gtype.NewInt(),
|
||||
maxConnLifetime : gtype.NewInt(),
|
||||
maxConnLifetime : gtype.NewInt(gDEFAULT_CONN_MAX_LIFE_TIME),
|
||||
}
|
||||
db.cache = dbCaches.GetOrSetFuncLock(group, func() interface{} {
|
||||
return gcache.New()
|
||||
}).(*gcache.Cache)
|
||||
return db, nil
|
||||
switch node.Type {
|
||||
case "mysql":
|
||||
base.db = &dbMysql{dbBase : base}
|
||||
case "pgsql":
|
||||
base.db = &dbPgsql{dbBase : base}
|
||||
case "mssql":
|
||||
base.db = &dbMssql{dbBase : base}
|
||||
case "sqlite":
|
||||
base.db = &dbSqlite{dbBase : base}
|
||||
case "oracle":
|
||||
base.db = &dbOracle{dbBase : base}
|
||||
default:
|
||||
return nil, errors.New(fmt.Sprintf(`unsupported database type "%s"`, node.Type))
|
||||
}
|
||||
return base.db, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
@ -218,6 +237,13 @@ func getConfigNodeByPriority(cg ConfigGroup) *ConfigNode {
|
||||
for i := 0; i < len(cg); i++ {
|
||||
total += cg[i].Priority * 100
|
||||
}
|
||||
// 如果total为0表示所有连接都没有配置priority属性,那么默认都是1
|
||||
if total == 0 {
|
||||
for i := 0; i < len(cg); i++ {
|
||||
cg[i].Priority = 1
|
||||
total += cg[i].Priority * 100
|
||||
}
|
||||
}
|
||||
// 不能取到末尾的边界点
|
||||
r := grand.Rand(0, total)
|
||||
if r > 0 {
|
||||
@ -237,58 +263,63 @@ func getConfigNodeByPriority(cg ConfigGroup) *ConfigNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 根据配置的数据库;类型获得Link接口对象
|
||||
func getLinkByType(dbType string) (Link, error) {
|
||||
if dblink, ok := driverMap[dbType]; ok == false {
|
||||
return nil, errors.New(fmt.Sprintf("unsupported db type '%s'", dbType))
|
||||
} else {
|
||||
return dblink.(Link), nil
|
||||
}
|
||||
// 获得底层数据库链接对象
|
||||
func (bs *dbBase) getSqlDb(master bool) (sqlDb *sql.DB, err error) {
|
||||
// 负载均衡
|
||||
node, err := getConfigNodeByGroup(bs.group, master)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 默认值设定
|
||||
if node.Charset == "" {
|
||||
node.Charset = "utf8"
|
||||
}
|
||||
v := bs.cache.GetOrSetFuncLock(node.String(), func() interface{} {
|
||||
sqlDb, err = bs.db.Open(node)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if n := bs.maxIdleConnCount.Val(); n > 0 {
|
||||
sqlDb.SetMaxIdleConns(n)
|
||||
} else if node.MaxIdleConnCount > 0 {
|
||||
sqlDb.SetMaxIdleConns(node.MaxIdleConnCount)
|
||||
}
|
||||
|
||||
if n := bs.maxOpenConnCount.Val(); n > 0 {
|
||||
sqlDb.SetMaxOpenConns(n)
|
||||
} else if node.MaxOpenConnCount > 0 {
|
||||
sqlDb.SetMaxOpenConns(node.MaxOpenConnCount)
|
||||
}
|
||||
|
||||
if n := bs.maxConnLifetime.Val(); n > 0 {
|
||||
sqlDb.SetConnMaxLifetime(time.Duration(n) * time.Second)
|
||||
} else if node.MaxConnLifetime > 0 {
|
||||
sqlDb.SetConnMaxLifetime(time.Duration(node.MaxConnLifetime) * time.Second)
|
||||
}
|
||||
return sqlDb
|
||||
}, 0)
|
||||
if v != nil && sqlDb == nil {
|
||||
sqlDb = v.(*sql.DB)
|
||||
}
|
||||
// 是否手动选择数据库
|
||||
if v := bs.schema.Val(); v != "" {
|
||||
sqlDb.Exec("USE " + v)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 获得底层数据库链接对象
|
||||
func (db *Db) getSqlDb(master bool) (*sql.DB, error) {
|
||||
node, err := getConfigNodeByGroup(db.group, master)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
link, err := getLinkByType(node.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sqlDb, err := link.Open(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if node.MaxIdleConnCount > 0 {
|
||||
sqlDb.SetMaxIdleConns(node.MaxIdleConnCount)
|
||||
}
|
||||
if n := db.maxIdleConnCount.Val(); n > 0 {
|
||||
sqlDb.SetMaxIdleConns(n)
|
||||
}
|
||||
|
||||
if node.MaxOpenConnCount > 0 {
|
||||
sqlDb.SetMaxOpenConns(node.MaxOpenConnCount)
|
||||
}
|
||||
if n := db.maxOpenConnCount.Val(); n > 0 {
|
||||
sqlDb.SetMaxOpenConns(n)
|
||||
}
|
||||
|
||||
if node.MaxConnLifetime > 0 {
|
||||
sqlDb.SetConnMaxLifetime(time.Duration(node.MaxConnLifetime) * time.Second)
|
||||
}
|
||||
if n := db.maxConnLifetime.Val(); n > 0 {
|
||||
sqlDb.SetConnMaxLifetime(time.Duration(n) * time.Second)
|
||||
}
|
||||
return sqlDb, nil
|
||||
// 切换操作的数据库(注意该切换是全局的)
|
||||
func (bs *dbBase) SetSchema(schema string) {
|
||||
bs.schema.Set(schema)
|
||||
}
|
||||
|
||||
// 创建底层数据库master链接对象
|
||||
func (db *Db) Master() (*sql.DB, error) {
|
||||
return db.getSqlDb(true)
|
||||
func (bs *dbBase) Master() (*sql.DB, error) {
|
||||
return bs.getSqlDb(true)
|
||||
}
|
||||
|
||||
// 创建底层数据库slave链接对象
|
||||
func (db *Db) Slave() (*sql.DB, error) {
|
||||
return db.getSqlDb(false)
|
||||
func (bs *dbBase) Slave() (*sql.DB, error) {
|
||||
return bs.getSqlDb(false)
|
||||
}
|
||||
|
||||
@ -8,39 +8,29 @@
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"strings"
|
||||
"reflect"
|
||||
"database/sql"
|
||||
"gitee.com/johng/gf/g/util/gstr"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/container/gring"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/os/gcache"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/container/gvar"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
gDEFAULT_DEBUG_SQL_LENGTH = 1000 // 默认调试模式下记录的SQL条数
|
||||
)
|
||||
|
||||
// 是否开启调试服务
|
||||
func (db *Db) SetDebug(debug bool) {
|
||||
db.debug.Set(debug)
|
||||
if debug && db.sqls == nil {
|
||||
db.sqls = gring.New(gDEFAULT_DEBUG_SQL_LENGTH)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取已经执行的SQL列表(仅在debug=true时有效)
|
||||
func (db *Db) GetQueriedSqls() []*Sql {
|
||||
if db.sqls == nil {
|
||||
func (bs *dbBase) GetQueriedSqls() []*Sql {
|
||||
if bs.sqls == nil {
|
||||
return nil
|
||||
}
|
||||
sqls := make([]*Sql, 0)
|
||||
db.sqls.Prev()
|
||||
db.sqls.RLockIteratorPrev(func(value interface{}) bool {
|
||||
bs.sqls.Prev()
|
||||
bs.sqls.RLockIteratorPrev(func(value interface{}) bool {
|
||||
if value == nil {
|
||||
return false
|
||||
}
|
||||
@ -51,8 +41,8 @@ func (db *Db) GetQueriedSqls() []*Sql {
|
||||
}
|
||||
|
||||
// 打印已经执行的SQL列表(仅在debug=true时有效)
|
||||
func (db *Db) PrintQueriedSqls() {
|
||||
sqls := db.GetQueriedSqls()
|
||||
func (bs *dbBase) PrintQueriedSqls() {
|
||||
sqls := bs.GetQueriedSqls()
|
||||
for k, v := range sqls {
|
||||
fmt.Println(len(sqls) - k, ":")
|
||||
fmt.Println(" Sql :", v.Sql)
|
||||
@ -61,145 +51,110 @@ func (db *Db) PrintQueriedSqls() {
|
||||
fmt.Println(" Start:", gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u"))
|
||||
fmt.Println(" End :", gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u"))
|
||||
fmt.Println(" Cost :", v.End - v.Start, "ms")
|
||||
fmt.Println(" Func :", v.Func)
|
||||
}
|
||||
}
|
||||
|
||||
// 打印SQL对象(仅在debug=true时有效)
|
||||
func (db *Db) printSql(v *Sql) {
|
||||
s := fmt.Sprintf("%s, %v, %s, %s, %d ms, %s", v.Sql, v.Args,
|
||||
gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u"),
|
||||
gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u"),
|
||||
v.End - v.Start, v.Func,
|
||||
)
|
||||
if v.Error != nil {
|
||||
s += "\nError: " + v.Error.Error()
|
||||
glog.Backtrace(true, 2).Error(s)
|
||||
} else {
|
||||
glog.Debug(s)
|
||||
}
|
||||
}
|
||||
|
||||
// 数据库sql查询操作,主要执行查询
|
||||
func (db *Db) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
var err error
|
||||
var rows *sql.Rows
|
||||
var slave *sql.DB
|
||||
slave, err = db.Slave();
|
||||
func (bs *dbBase) Query(query string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
link, err := bs.db.Slave()
|
||||
if err != nil {
|
||||
return nil,err
|
||||
}
|
||||
defer slave.Close()
|
||||
p := db.link.handleSqlBeforeExec(&query)
|
||||
if db.debug.Val() {
|
||||
militime1 := gtime.Millisecond()
|
||||
rows, err = slave.Query(*p, args ...)
|
||||
militime2 := gtime.Millisecond()
|
||||
s := &Sql{
|
||||
Sql : *p,
|
||||
return bs.db.doQuery(link, query, args...)
|
||||
}
|
||||
|
||||
// 数据库sql查询操作,主要执行查询
|
||||
func (bs *dbBase) doQuery(link dbLink, query string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
query = bs.db.handleSqlBeforeExec(query)
|
||||
if bs.db.getDebug() {
|
||||
mTime1 := gtime.Millisecond()
|
||||
rows, err = link.Query(query, args...)
|
||||
mTime2 := gtime.Millisecond()
|
||||
s := &Sql {
|
||||
Sql : query,
|
||||
Args : args,
|
||||
Error : err,
|
||||
Start : militime1,
|
||||
End : militime2,
|
||||
Func : "DB:Query",
|
||||
Start : mTime1,
|
||||
End : mTime2,
|
||||
}
|
||||
db.sqls.Put(s)
|
||||
db.printSql(s)
|
||||
bs.sqls.Put(s)
|
||||
printSql(s)
|
||||
} else {
|
||||
rows, err = slave.Query(*p, args ...)
|
||||
rows, err = link.Query(query, args ...)
|
||||
}
|
||||
if err == nil {
|
||||
return rows, nil
|
||||
} else {
|
||||
err = db.formatError(err, p, args...)
|
||||
err = formatError(err, query, args...)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 执行一条sql,并返回执行情况,主要用于非查询操作
|
||||
func (db *Db) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
var err error
|
||||
var result sql.Result
|
||||
var master *sql.DB
|
||||
master, err = db.Master();
|
||||
func (bs *dbBase) Exec(query string, args ...interface{}) (result sql.Result, err error) {
|
||||
link, err := bs.db.Master()
|
||||
if err != nil {
|
||||
return nil,err
|
||||
}
|
||||
defer master.Close()
|
||||
p := db.link.handleSqlBeforeExec(&query)
|
||||
if db.debug.Val() {
|
||||
militime1 := gtime.Millisecond()
|
||||
result, err = master.Exec(*p, args ...)
|
||||
militime2 := gtime.Millisecond()
|
||||
s := &Sql{
|
||||
Sql : *p,
|
||||
return bs.db.doExec(link, query, args...)
|
||||
}
|
||||
|
||||
// 执行一条sql,并返回执行情况,主要用于非查询操作
|
||||
func (bs *dbBase) doExec(link dbLink, query string, args ...interface{}) (result sql.Result, err error) {
|
||||
query = bs.db.handleSqlBeforeExec(query)
|
||||
if bs.db.getDebug() {
|
||||
mTime1 := gtime.Millisecond()
|
||||
result, err = link.Exec(query, args ...)
|
||||
mTime2 := gtime.Millisecond()
|
||||
s := &Sql{
|
||||
Sql : query,
|
||||
Args : args,
|
||||
Error : err,
|
||||
Start : militime1,
|
||||
End : militime2,
|
||||
Func : "DB:Exec",
|
||||
Start : mTime1,
|
||||
End : mTime2,
|
||||
}
|
||||
db.sqls.Put(s)
|
||||
db.printSql(s)
|
||||
bs.sqls.Put(s)
|
||||
printSql(s)
|
||||
} else {
|
||||
result, err = master.Exec(*p, args ...)
|
||||
result, err = link.Exec(query, args ...)
|
||||
}
|
||||
return result, db.formatError(err, p, args...)
|
||||
return result, formatError(err, query, args...)
|
||||
}
|
||||
|
||||
// 格式化错误信息
|
||||
func (db *Db) formatError(err error, query *string, args ...interface{}) error {
|
||||
if err != nil {
|
||||
errstr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
|
||||
errstr += fmt.Sprintf("DB QUERY: %s\n", *query)
|
||||
if len(args) > 0 {
|
||||
errstr += fmt.Sprintf("DB PARAM: %v\n", args)
|
||||
// SQL预处理,执行完成后调用返回值sql.Stmt.Exec完成sql操作; 默认执行在Slave上, 通过第二个参数指定执行在Master上
|
||||
func (bs *dbBase) Prepare(query string, execOnMaster...bool) (*sql.Stmt, error) {
|
||||
err := (error)(nil)
|
||||
link := (dbLink)(nil)
|
||||
if len(execOnMaster) > 0 && execOnMaster[0] {
|
||||
if link, err = bs.db.Master(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if link, err = bs.db.Slave(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = errors.New(errstr)
|
||||
}
|
||||
return err
|
||||
return bs.db.doPrepare(link, query)
|
||||
}
|
||||
|
||||
// SQL预处理,执行完成后调用返回值sql.Stmt.Exec完成sql操作
|
||||
func (bs *dbBase) doPrepare(link dbLink, query string) (*sql.Stmt, error) {
|
||||
return link.Prepare(query)
|
||||
}
|
||||
|
||||
// 数据库查询,获取查询结果集,以列表结构返回
|
||||
func (db *Db) GetAll(query string, args ...interface{}) (Result, error) {
|
||||
// 执行sql
|
||||
rows, err := db.Query(query, args ...)
|
||||
func (bs *dbBase) GetAll(query string, args ...interface{}) (Result, error) {
|
||||
rows, err := bs.Query(query, args ...)
|
||||
if err != nil || rows == nil {
|
||||
return nil, err
|
||||
}
|
||||
// 列名称列表
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 返回结构组装
|
||||
values := make([]sql.RawBytes, len(columns))
|
||||
scanArgs := make([]interface{}, len(values))
|
||||
records := make(Result, 0)
|
||||
for i := range values {
|
||||
scanArgs[i] = &values[i]
|
||||
}
|
||||
for rows.Next() {
|
||||
err = rows.Scan(scanArgs...)
|
||||
if err != nil {
|
||||
return records, err
|
||||
}
|
||||
row := make(Record)
|
||||
// 注意col字段是一个[]byte类型(slice类型本身是一个指针),多个记录循环时该变量指向的是同一个内存地址
|
||||
for i, col := range values {
|
||||
v := make([]byte, len(col))
|
||||
copy(v, col)
|
||||
row[columns[i]] = gvar.New(v)
|
||||
}
|
||||
records = append(records, row)
|
||||
}
|
||||
return records, nil
|
||||
defer rows.Close()
|
||||
return rowsToResult(rows)
|
||||
}
|
||||
|
||||
// 数据库查询,获取查询结果记录,以关联数组结构返回
|
||||
func (db *Db) GetOne(query string, args ...interface{}) (Record, error) {
|
||||
list, err := db.GetAll(query, args ...)
|
||||
func (bs *dbBase) GetOne(query string, args ...interface{}) (Record, error) {
|
||||
list, err := bs.GetAll(query, args ...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -210,18 +165,17 @@ func (db *Db) GetOne(query string, args ...interface{}) (Record, error) {
|
||||
}
|
||||
|
||||
// 数据库查询,获取查询结果记录,自动映射数据到给定的struct对象中
|
||||
func (db *Db) GetStruct(obj interface{}, query string, args ...interface{}) error {
|
||||
one, err := db.GetOne(query, args...)
|
||||
func (bs *dbBase) GetStruct(obj interface{}, query string, args ...interface{}) error {
|
||||
one, err := bs.GetOne(query, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return one.ToStruct(obj)
|
||||
}
|
||||
|
||||
|
||||
// 数据库查询,获取查询字段值
|
||||
func (db *Db) GetValue(query string, args ...interface{}) (Value, error) {
|
||||
one, err := db.GetOne(query, args ...)
|
||||
func (bs *dbBase) GetValue(query string, args ...interface{}) (Value, error) {
|
||||
one, err := bs.GetOne(query, args ...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -232,72 +186,44 @@ func (db *Db) GetValue(query string, args ...interface{}) (Value, error) {
|
||||
}
|
||||
|
||||
// 数据库查询,获取查询数量
|
||||
func (db *Db) GetCount(query string, args ...interface{}) (int, error) {
|
||||
val, err := db.GetValue(query, args ...)
|
||||
func (bs *dbBase) GetCount(query string, args ...interface{}) (int, error) {
|
||||
if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, query) {
|
||||
query, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, query)
|
||||
}
|
||||
value, err := bs.GetValue(query, args ...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return gconv.Int(val), nil
|
||||
}
|
||||
|
||||
// 数据表查询,其中tables可以是多个联表查询语句,这种查询方式较复杂,建议使用链式操作
|
||||
func (db *Db) Select(tables, fields string, condition interface{}, groupBy, orderBy string, first, limit int, args ... interface{}) (Result, error) {
|
||||
s := fmt.Sprintf("SELECT %s FROM %s ", fields, tables)
|
||||
if condition != nil {
|
||||
s += fmt.Sprintf("WHERE %s ", db.formatCondition(condition))
|
||||
}
|
||||
if len(groupBy) > 0 {
|
||||
s += fmt.Sprintf("GROUP BY %s ", groupBy)
|
||||
}
|
||||
if len(orderBy) > 0 {
|
||||
s += fmt.Sprintf("ORDER BY %s ", orderBy)
|
||||
}
|
||||
if limit > 0 {
|
||||
s += fmt.Sprintf("LIMIT %d,%d ", first, limit)
|
||||
}
|
||||
return db.GetAll(s, args ... )
|
||||
}
|
||||
|
||||
// sql预处理,执行完成后调用返回值sql.Stmt.Exec完成sql操作
|
||||
// 记得调用sql.Stmt.Close关闭操作对象
|
||||
func (db *Db) Prepare(query string) (*sql.Stmt, error) {
|
||||
if master, err := db.Master(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
defer master.Close()
|
||||
return master.Prepare(query)
|
||||
}
|
||||
return value.Int(), nil
|
||||
}
|
||||
|
||||
// ping一下,判断或保持数据库链接(master)
|
||||
func (db *Db) PingMaster() error {
|
||||
if master, err := db.Master(); err != nil {
|
||||
func (bs *dbBase) PingMaster() error {
|
||||
if master, err := bs.db.Master(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer master.Close()
|
||||
return master.Ping()
|
||||
}
|
||||
}
|
||||
|
||||
// ping一下,判断或保持数据库链接(slave)
|
||||
func (db *Db) PingSlave() error {
|
||||
if slave, err := db.Slave(); err != nil {
|
||||
func (bs *dbBase) PingSlave() error {
|
||||
if slave, err := bs.db.Slave(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
defer slave.Close()
|
||||
return slave.Ping()
|
||||
}
|
||||
}
|
||||
|
||||
// 事务操作,开启,会返回一个底层的事务操作对象链接如需要嵌套事务,那么可以使用该对象,否则请忽略
|
||||
// 只有在tx.Commit/tx.Rollback时,链接会自动Close
|
||||
func (db *Db) Begin() (*Tx, error) {
|
||||
if master, err := db.Master(); err != nil {
|
||||
func (bs *dbBase) Begin() (*TX, error) {
|
||||
if master, err := bs.db.Master(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
if tx, err := master.Begin(); err == nil {
|
||||
return &Tx {
|
||||
db : db,
|
||||
return &TX {
|
||||
db : bs.db,
|
||||
tx : tx,
|
||||
master : master,
|
||||
}, nil
|
||||
@ -307,17 +233,19 @@ func (db *Db) Begin() (*Tx, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 根据insert选项获得操作名称
|
||||
func (db *Db) getInsertOperationByOption(option uint8) string {
|
||||
oper := "INSERT"
|
||||
switch option {
|
||||
case OPTION_REPLACE:
|
||||
oper = "REPLACE"
|
||||
case OPTION_SAVE:
|
||||
case OPTION_IGNORE:
|
||||
oper = "INSERT IGNORE"
|
||||
}
|
||||
return oper
|
||||
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
|
||||
func (bs *dbBase) Insert(table string, data Map) (sql.Result, error) {
|
||||
return bs.db.doInsert(nil, table, data, OPTION_INSERT)
|
||||
}
|
||||
|
||||
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
|
||||
func (bs *dbBase) Replace(table string, data Map) (sql.Result, error) {
|
||||
return bs.db.doInsert(nil, table, data, OPTION_REPLACE)
|
||||
}
|
||||
|
||||
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
|
||||
func (bs *dbBase) Save(table string, data Map) (sql.Result, error) {
|
||||
return bs.db.doInsert(nil, table, data, OPTION_SAVE)
|
||||
}
|
||||
|
||||
// insert、replace, save, ignore操作
|
||||
@ -325,95 +253,102 @@ func (db *Db) getInsertOperationByOption(option uint8) string {
|
||||
// 1: replace: 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
|
||||
// 2: save: 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
|
||||
// 3: ignore: 如果数据存在(主键或者唯一索引),那么什么也不做
|
||||
func (db *Db) insert(table string, data Map, option uint8) (sql.Result, error) {
|
||||
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, db.charl + k + db.charr)
|
||||
fields = append(fields, charl + k + charr)
|
||||
values = append(values, "?")
|
||||
params = append(params, v)
|
||||
}
|
||||
operation := db.getInsertOperationByOption(option)
|
||||
operation := getInsertOperationByOption(option)
|
||||
updatestr := ""
|
||||
if option == OPTION_SAVE {
|
||||
var updates []string
|
||||
for k, _ := range data {
|
||||
updates = append(updates,
|
||||
fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
|
||||
db.charl, k, db.charr,
|
||||
db.charl, k, db.charr,
|
||||
charl, k, charr,
|
||||
charl, k, charr,
|
||||
),
|
||||
)
|
||||
}
|
||||
updatestr = fmt.Sprintf("ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
|
||||
}
|
||||
return db.Exec(
|
||||
fmt.Sprintf("%s INTO %s(%s) VALUES(%s) %s",
|
||||
operation, table, strings.Join(fields, ","),
|
||||
strings.Join(values, ","),
|
||||
updatestr),
|
||||
params...
|
||||
)
|
||||
if link == nil {
|
||||
if link, err = bs.db.Master(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES(%s) %s",
|
||||
operation, table, strings.Join(fields, ","),
|
||||
strings.Join(values, ","), updatestr),
|
||||
params...)
|
||||
}
|
||||
|
||||
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
|
||||
func (db *Db) Insert(table string, data Map) (sql.Result, error) {
|
||||
return db.insert(table, data, OPTION_INSERT)
|
||||
// CURD操作:批量数据指定批次量写入
|
||||
func (bs *dbBase) BatchInsert(table string, list List, batch int) (sql.Result, error) {
|
||||
return bs.db.doBatchInsert(nil, table, list, batch, OPTION_INSERT)
|
||||
}
|
||||
|
||||
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
|
||||
func (db *Db) Replace(table string, data Map) (sql.Result, error) {
|
||||
return db.insert(table, data, OPTION_REPLACE)
|
||||
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
|
||||
func (bs *dbBase) BatchReplace(table string, list List, batch int) (sql.Result, error) {
|
||||
return bs.db.doBatchInsert(nil, table, list, batch, OPTION_REPLACE)
|
||||
}
|
||||
|
||||
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
|
||||
func (db *Db) Save(table string, data Map) (sql.Result, error) {
|
||||
return db.insert(table, data, OPTION_SAVE)
|
||||
// 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 (db *Db) batchInsert(table string, list List, batch int, option uint8) (sql.Result, error) {
|
||||
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{}
|
||||
var result sql.Result
|
||||
var size = len(list)
|
||||
// 判断长度
|
||||
if size < 1 {
|
||||
if len(list) < 1 {
|
||||
return result, errors.New("empty data list")
|
||||
}
|
||||
if link == nil {
|
||||
if link, err = bs.db.Master(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 首先获取字段名称及记录长度
|
||||
for k, _ := range list[0] {
|
||||
keys = append(keys, k)
|
||||
values = append(values, "?")
|
||||
}
|
||||
keyStr := db.charl + strings.Join(keys, db.charl + "," + db.charr) + db.charr
|
||||
charl, charr := bs.db.getChars()
|
||||
keyStr := charl + strings.Join(keys, charl + "," + charr) + charr
|
||||
valueHolderStr := "(" + strings.Join(values, ",") + ")"
|
||||
// 操作判断
|
||||
operation := db.getInsertOperationByOption(option)
|
||||
operation := getInsertOperationByOption(option)
|
||||
updatestr := ""
|
||||
if option == OPTION_SAVE {
|
||||
var updates []string
|
||||
for _, k := range keys {
|
||||
updates = append(updates,
|
||||
fmt.Sprintf("%s%s%s=VALUES(%s%s%s)",
|
||||
db.charl, k, db.charr,
|
||||
db.charl, k, db.charr,
|
||||
charl, k, charr,
|
||||
charl, k, charr,
|
||||
),
|
||||
)
|
||||
}
|
||||
updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
|
||||
}
|
||||
// 构造批量写入数据格式(注意map的遍历是无序的)
|
||||
for i := 0; i < size; i++ {
|
||||
for i := 0; i < len(list); i++ {
|
||||
for _, k := range keys {
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
bvalues = append(bvalues, valueHolderStr)
|
||||
if len(bvalues) == batch {
|
||||
r, err := db.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
|
||||
r, err := bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
|
||||
operation, table, keyStr, strings.Join(bvalues, ","),
|
||||
updatestr),
|
||||
params...)
|
||||
@ -427,7 +362,7 @@ func (db *Db) batchInsert(table string, list List, batch int, option uint8) (sql
|
||||
}
|
||||
// 处理最后不构成指定批量的数据
|
||||
if len(bvalues) > 0 {
|
||||
r, err := db.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
|
||||
r, err := bs.db.doExec(link, fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
|
||||
operation, table, keyStr, strings.Join(bvalues, ","),
|
||||
updatestr),
|
||||
params...)
|
||||
@ -439,32 +374,28 @@ func (db *Db) batchInsert(table string, list List, batch int, option uint8) (sql
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CURD操作:批量数据指定批次量写入
|
||||
func (db *Db) BatchInsert(table string, list List, batch int) (sql.Result, error) {
|
||||
return db.batchInsert(table, list, batch, OPTION_INSERT)
|
||||
}
|
||||
|
||||
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
|
||||
func (db *Db) BatchReplace(table string, list List, batch int) (sql.Result, error) {
|
||||
return db.batchInsert(table, list, batch, OPTION_REPLACE)
|
||||
}
|
||||
|
||||
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
|
||||
func (db *Db) BatchSave(table string, list List, batch int) (sql.Result, error) {
|
||||
return db.batchInsert(table, list, batch, OPTION_SAVE)
|
||||
// CURD操作:数据更新,统一采用sql预处理
|
||||
// data参数支持字符串或者关联数组类型,内部会自行做判断处理
|
||||
func (bs *dbBase) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
link, err := bs.db.Master()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bs.db.doUpdate(link, table, data, condition, args ...)
|
||||
}
|
||||
|
||||
// CURD操作:数据更新,统一采用sql预处理
|
||||
// data参数支持字符串或者关联数组类型,内部会自行做判断处理
|
||||
func (db *Db) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
var params []interface{}
|
||||
var updates string
|
||||
refValue := reflect.ValueOf(data)
|
||||
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=?", db.charl, k, db.charr))
|
||||
fields = append(fields, fmt.Sprintf("%s%s%s=?", charl, k, charr))
|
||||
params = append(params, gconv.String(refValue.MapIndex(k).Interface()))
|
||||
}
|
||||
updates = strings.Join(fields, ",")
|
||||
@ -474,34 +405,65 @@ func (db *Db) Update(table string, data interface{}, condition interface{}, args
|
||||
for _, v := range args {
|
||||
params = append(params, gconv.String(v))
|
||||
}
|
||||
return db.Exec(fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, db.formatCondition(condition)), params...)
|
||||
if link == nil {
|
||||
if link, err = bs.db.Master(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
newWhere, newArgs := formatCondition(condition, params)
|
||||
return bs.db.doExec(link, fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, newWhere), newArgs...)
|
||||
}
|
||||
|
||||
// CURD操作:删除数据
|
||||
func (db *Db) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
return db.Exec(fmt.Sprintf("DELETE FROM %s WHERE %s", table, db.formatCondition(condition)), args...)
|
||||
func (bs *dbBase) Delete(table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
|
||||
link, err := bs.db.Master()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bs.db.doDelete(link, table, condition, args ...)
|
||||
}
|
||||
|
||||
// 格式化SQL查询条件
|
||||
func (db *Db) formatCondition(condition interface{}) (where string) {
|
||||
if reflect.ValueOf(condition).Kind() == reflect.Map {
|
||||
ks := reflect.ValueOf(condition).MapKeys()
|
||||
vs := reflect.ValueOf(condition)
|
||||
for _, k := range ks {
|
||||
key := gconv.String(k.Interface())
|
||||
value := gconv.String(vs.MapIndex(k).Interface())
|
||||
isNum := gstr.IsNumeric(value)
|
||||
if len(where) > 0 {
|
||||
where += " AND "
|
||||
}
|
||||
if isNum || value == "?" {
|
||||
where += key + "=" + value
|
||||
} else {
|
||||
where += key + "='" + value + "'"
|
||||
// CURD操作:删除数据
|
||||
func (bs *dbBase) doDelete(link dbLink, table string, condition interface{}, args ...interface{}) (result sql.Result, err error) {
|
||||
newWhere, newArgs := formatCondition(condition, args)
|
||||
return bs.db.doExec(link, fmt.Sprintf("DELETE FROM %s WHERE %s", table, newWhere), newArgs...)
|
||||
}
|
||||
|
||||
// 获得缓存对象
|
||||
func (bs *dbBase) getCache() *gcache.Cache {
|
||||
return bs.cache
|
||||
}
|
||||
|
||||
// 将map的数据按照fields进行过滤,只保留与表字段同名的数据
|
||||
func (bs *dbBase) filterFields(table string, data map[string]interface{}) map[string]interface{} {
|
||||
if fields, err := bs.db.getTableFields(table); err == nil {
|
||||
for k, _ := range data {
|
||||
if _, ok := fields[k]; !ok {
|
||||
delete(data, k)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
where += gconv.String(condition)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// 获得指定表表的数据结构,构造成map哈希表返回,其中键名为表字段名称,键值暂无用途(默认为字段数据类型).
|
||||
func (bs *dbBase) getTableFields(table string) (fields map[string]string, err error) {
|
||||
// 缓存不存在时会查询数据表结构,缓存后不过期,直至程序重启(重新部署)
|
||||
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))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
fields = make(map[string]string)
|
||||
for _, m := range result {
|
||||
fields[m["Field"].String()] = m["Type"].String()
|
||||
}
|
||||
return fields
|
||||
}, 0)
|
||||
if err == nil {
|
||||
fields = v.(map[string]string)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -8,6 +8,8 @@
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/gring"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@ -113,6 +115,13 @@ func AddDefaultConfigGroup (nodes ConfigGroup) {
|
||||
AddConfigGroup(DEFAULT_GROUP_NAME, nodes)
|
||||
}
|
||||
|
||||
// 添加一台数据库服务器配置
|
||||
func GetConfig (group string) ConfigGroup {
|
||||
config.RLock()
|
||||
defer config.RUnlock()
|
||||
return config.c[group]
|
||||
}
|
||||
|
||||
// 设置默认链接的数据库链接配置项(默认是 default)
|
||||
func SetDefaultGroup (groupName string) {
|
||||
config.Lock()
|
||||
@ -121,17 +130,41 @@ func SetDefaultGroup (groupName string) {
|
||||
}
|
||||
|
||||
// 设置数据库连接池中空闲链接的大小
|
||||
func (db *Db) SetMaxIdleConns(n int) {
|
||||
db.maxIdleConnCount.Set(n)
|
||||
func (bs *dbBase) SetMaxIdleConns(n int) {
|
||||
bs.maxIdleConnCount.Set(n)
|
||||
}
|
||||
|
||||
// 设置数据库连接池最大打开的链接数量
|
||||
func (db *Db) SetMaxOpenConns(n int) {
|
||||
db.maxOpenConnCount.Set(n)
|
||||
func (bs *dbBase) SetMaxOpenConns(n int) {
|
||||
bs.maxOpenConnCount.Set(n)
|
||||
}
|
||||
|
||||
// 设置数据库连接可重复利用的时间,超过该时间则被关闭废弃
|
||||
// 如果 d <= 0 表示该链接会一直重复利用
|
||||
func (db *Db) SetConnMaxLifetime(n int) {
|
||||
db.maxConnLifetime.Set(n)
|
||||
func (bs *dbBase) SetConnMaxLifetime(n int) {
|
||||
bs.maxConnLifetime.Set(n)
|
||||
}
|
||||
|
||||
// 节点配置转换为字符串
|
||||
func (node *ConfigNode) String() string {
|
||||
if node.Linkinfo != "" {
|
||||
return node.Linkinfo
|
||||
}
|
||||
return fmt.Sprintf(`%s@%s:%s,%s,%s,%s,%s,%d-%d-%d`, node.User, node.Host, node.Port,
|
||||
node.Name, node.Type, node.Role, node.Charset,
|
||||
node.MaxIdleConnCount, node.MaxOpenConnCount, node.MaxConnLifetime,
|
||||
)
|
||||
}
|
||||
|
||||
// 是否开启调试服务
|
||||
func (bs *dbBase) SetDebug(debug bool) {
|
||||
bs.debug.Set(debug)
|
||||
if debug && bs.sqls == nil {
|
||||
bs.sqls = gring.New(gDEFAULT_DEBUG_SQL_LENGTH)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取是否开启调试服务
|
||||
func (bs *dbBase) getDebug() bool {
|
||||
return bs.debug.Val()
|
||||
}
|
||||
158
g/database/gdb/gdb_func.go
Normal file
158
g/database/gdb/gdb_func.go
Normal file
@ -0,0 +1,158 @@
|
||||
// Copyright 2017-2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
|
||||
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/gvar"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
"gitee.com/johng/gf/g/util/gstr"
|
||||
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 将数据查询的列表数据*sql.Rows转换为Result类型
|
||||
func rowsToResult(rows *sql.Rows) (Result, error) {
|
||||
// 列名称列表
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 返回结构组装
|
||||
values := make([]sql.RawBytes, len(columns))
|
||||
scanArgs := make([]interface{}, len(values))
|
||||
records := make(Result, 0)
|
||||
for i := range values {
|
||||
scanArgs[i] = &values[i]
|
||||
}
|
||||
for rows.Next() {
|
||||
err = rows.Scan(scanArgs...)
|
||||
if err != nil {
|
||||
return records, err
|
||||
}
|
||||
row := make(Record)
|
||||
// 注意col字段是一个[]byte类型(slice类型本身是一个指针),多个记录循环时该变量指向的是同一个内存地址
|
||||
for i, col := range values {
|
||||
if col == nil {
|
||||
row[columns[i]] = gvar.New(nil, false)
|
||||
} else {
|
||||
v := make([]byte, len(col))
|
||||
copy(v, col)
|
||||
row[columns[i]] = gvar.New(v, false)
|
||||
}
|
||||
}
|
||||
records = append(records, row)
|
||||
}
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// 格式化SQL查询条件
|
||||
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 ")
|
||||
}
|
||||
if gstr.IsNumeric(value) || value == "?" {
|
||||
buffer.WriteString(key + "=" + value)
|
||||
} else {
|
||||
buffer.WriteString(key + "='" + value + "'")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buffer.Write(gconv.Bytes(where))
|
||||
}
|
||||
if buffer.Len() == 0 {
|
||||
buffer.WriteString("1")
|
||||
}
|
||||
// 查询条件处理
|
||||
newWhere := buffer.String()
|
||||
newArgs := make([]interface{}, 0)
|
||||
if len(args) > 0 {
|
||||
for index, arg := range args {
|
||||
rv := reflect.ValueOf(arg)
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Slice: fallthrough
|
||||
case reflect.Array:
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
newArgs = append(newArgs, rv.Index(i).Interface())
|
||||
}
|
||||
counter := 0
|
||||
newWhere, _ = gregex.ReplaceStringFunc(`\?`, newWhere, func(s string) string {
|
||||
counter++
|
||||
if counter == index + 1 {
|
||||
return "?" + strings.Repeat(",?", rv.Len() - 1)
|
||||
}
|
||||
return s
|
||||
})
|
||||
default:
|
||||
newArgs = append(newArgs, arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
return newWhere, newArgs
|
||||
}
|
||||
|
||||
// 打印SQL对象(仅在debug=true时有效)
|
||||
func printSql(v *Sql) {
|
||||
s := fmt.Sprintf("%s, %v, %s, %s, %d ms, %s", v.Sql, v.Args,
|
||||
gtime.NewFromTimeStamp(v.Start).Format("Y-m-d H:i:s.u"),
|
||||
gtime.NewFromTimeStamp(v.End).Format("Y-m-d H:i:s.u"),
|
||||
v.End - v.Start,
|
||||
v.Func,
|
||||
)
|
||||
if v.Error != nil {
|
||||
s += "\nError: " + v.Error.Error()
|
||||
glog.Backtrace(true, 2).Error(s)
|
||||
} else {
|
||||
glog.Debug(s)
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化错误信息
|
||||
func formatError(err error, query string, args ...interface{}) error {
|
||||
if err != nil {
|
||||
errstr := fmt.Sprintf("DB ERROR: %s\n", err.Error())
|
||||
errstr += fmt.Sprintf("DB QUERY: %s\n", query)
|
||||
if len(args) > 0 {
|
||||
errstr += fmt.Sprintf("DB PARAM: %v\n", args)
|
||||
}
|
||||
err = errors.New(errstr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 根据insert选项获得操作名称
|
||||
func getInsertOperationByOption(option int) string {
|
||||
oper := "INSERT"
|
||||
switch option {
|
||||
case OPTION_REPLACE:
|
||||
oper = "REPLACE"
|
||||
case OPTION_SAVE:
|
||||
case OPTION_IGNORE:
|
||||
oper = "INSERT IGNORE"
|
||||
}
|
||||
return oper
|
||||
}
|
||||
@ -12,12 +12,15 @@ import (
|
||||
"database/sql"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 数据库链式操作模型对象
|
||||
type Model struct {
|
||||
tx *Tx // 数据库事务对象
|
||||
db *Db // 数据库操作对象
|
||||
db DB // 数据库操作对象
|
||||
tx *TX // 数据库事务对象
|
||||
tablesInit string // 初始化Model时的表名称(可以是多个)
|
||||
tables string // 数据库操作表
|
||||
fields string // 操作字段
|
||||
where string // 操作条件
|
||||
@ -28,123 +31,213 @@ type Model struct {
|
||||
limit int // 分页条数
|
||||
data interface{} // 操作记录(支持Map/List/string类型)
|
||||
batch int // 批量操作条数
|
||||
filter bool // 是否按照表字段过滤data参数
|
||||
cacheEnabled bool // 当前SQL操作是否开启查询缓存功能
|
||||
cacheTime int // 查询缓存时间
|
||||
cacheName string // 查询缓存名称
|
||||
}
|
||||
|
||||
// 链式操作,数据表字段,可支持多个表,以半角逗号连接
|
||||
func (db *Db) Table(tables string) (*Model) {
|
||||
return &Model{
|
||||
db: db,
|
||||
tables: tables,
|
||||
fields: "*",
|
||||
func (bs *dbBase) Table(tables string) (*Model) {
|
||||
return &Model {
|
||||
db : bs.db,
|
||||
tablesInit : tables,
|
||||
tables : tables,
|
||||
fields : "*",
|
||||
}
|
||||
}
|
||||
|
||||
// 链式操作,数据表字段,可支持多个表,以半角逗号连接
|
||||
func (db *Db) From(tables string) (*Model) {
|
||||
return db.Table(tables)
|
||||
func (bs *dbBase) From(tables string) (*Model) {
|
||||
return bs.db.Table(tables)
|
||||
}
|
||||
|
||||
// (事务)链式操作,数据表字段,可支持多个表,以半角逗号连接
|
||||
func (tx *Tx) Table(tables string) (*Model) {
|
||||
func (tx *TX) Table(tables string) (*Model) {
|
||||
return &Model{
|
||||
db: tx.db,
|
||||
tx: tx,
|
||||
tables: tables,
|
||||
db : tx.db,
|
||||
tx : tx,
|
||||
tablesInit : tables,
|
||||
tables : tables,
|
||||
}
|
||||
}
|
||||
|
||||
// (事务)链式操作,数据表字段,可支持多个表,以半角逗号连接
|
||||
func (tx *Tx) From(tables string) (*Model) {
|
||||
func (tx *TX) From(tables string) (*Model) {
|
||||
return tx.Table(tables)
|
||||
}
|
||||
|
||||
// 克隆一个当前对象
|
||||
func (md *Model) Clone() *Model {
|
||||
newModel := (*Model)(nil)
|
||||
if md.tx != nil {
|
||||
newModel = md.tx.Table(md.tablesInit)
|
||||
} else {
|
||||
newModel = md.db.Table(md.tablesInit)
|
||||
}
|
||||
*newModel = *md
|
||||
return newModel
|
||||
}
|
||||
|
||||
// 链式操作,左联表
|
||||
func (md *Model) LeftJoin(joinTable string, on string) (*Model) {
|
||||
md.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", joinTable, on)
|
||||
return md
|
||||
model := md.Clone()
|
||||
model.tables += fmt.Sprintf(" LEFT JOIN %s ON (%s)", joinTable, on)
|
||||
return model
|
||||
}
|
||||
|
||||
// 链式操作,右联表
|
||||
func (md *Model) RightJoin(joinTable string, on string) (*Model) {
|
||||
md.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", joinTable, on)
|
||||
return md
|
||||
model := md.Clone()
|
||||
model.tables += fmt.Sprintf(" RIGHT JOIN %s ON (%s)", joinTable, on)
|
||||
return model
|
||||
}
|
||||
|
||||
// 链式操作,内联表
|
||||
func (md *Model) InnerJoin(joinTable string, on string) (*Model) {
|
||||
md.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", joinTable, on)
|
||||
return md
|
||||
model := md.Clone()
|
||||
model.tables += fmt.Sprintf(" INNER JOIN %s ON (%s)", joinTable, on)
|
||||
return model
|
||||
}
|
||||
|
||||
// 链式操作,查询字段
|
||||
func (md *Model) Fields(fields string) (*Model) {
|
||||
md.fields = fields
|
||||
return md
|
||||
model := md.Clone()
|
||||
model.fields = fields
|
||||
return model
|
||||
}
|
||||
|
||||
// 链式操作,过滤字段
|
||||
func (md *Model) Filter() (*Model) {
|
||||
model := md.Clone()
|
||||
model.filter = true
|
||||
return model
|
||||
}
|
||||
|
||||
// 链式操作,condition,支持string & gdb.Map
|
||||
func (md *Model) Where(where interface{}, args ...interface{}) (*Model) {
|
||||
md.where = md.db.formatCondition(where)
|
||||
md.whereArgs = append(md.whereArgs, args...)
|
||||
return md
|
||||
model := md.Clone()
|
||||
newWhere, newArgs := formatCondition(where, args)
|
||||
model.where = newWhere
|
||||
model.whereArgs = append(model.whereArgs, newArgs...)
|
||||
// 支持 Where("uid", 1)这种格式
|
||||
if len(args) == 1 && strings.Index(model.where , "?") < 0 {
|
||||
model.where += "=?"
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// 链式操作,添加AND条件到Where中
|
||||
func (md *Model) And(where interface{}, args ...interface{}) (*Model) {
|
||||
md.where += " AND " + md.db.formatCondition(where)
|
||||
md.whereArgs = append(md.whereArgs, args...)
|
||||
return md
|
||||
model := md.Clone()
|
||||
newWhere, newArgs := formatCondition(where, args)
|
||||
model.where += " AND " + newWhere
|
||||
model.whereArgs = append(model.whereArgs, newArgs...)
|
||||
return model
|
||||
}
|
||||
|
||||
// 链式操作,添加OR条件到Where中
|
||||
func (md *Model) Or(where interface{}, args ...interface{}) (*Model) {
|
||||
md.where += " OR " + md.db.formatCondition(where)
|
||||
md.whereArgs = append(md.whereArgs, args...)
|
||||
return md
|
||||
model := md.Clone()
|
||||
newWhere, newArgs := formatCondition(where, args)
|
||||
model.where += " OR " + newWhere
|
||||
model.whereArgs = append(model.whereArgs, newArgs...)
|
||||
return model
|
||||
}
|
||||
|
||||
// 链式操作,group by
|
||||
func (md *Model) GroupBy(groupBy string) (*Model) {
|
||||
md.groupBy = groupBy
|
||||
return md
|
||||
model := md.Clone()
|
||||
model.groupBy = groupBy
|
||||
return model
|
||||
}
|
||||
|
||||
// 链式操作,order by
|
||||
func (md *Model) OrderBy(orderBy string) (*Model) {
|
||||
md.orderBy = orderBy
|
||||
return md
|
||||
model := md.Clone()
|
||||
model.orderBy = orderBy
|
||||
return model
|
||||
}
|
||||
|
||||
// 链式操作,limit
|
||||
func (md *Model) Limit(start int, limit int) (*Model) {
|
||||
md.start = start
|
||||
md.limit = limit
|
||||
return md
|
||||
model := md.Clone()
|
||||
model.start = start
|
||||
model.limit = limit
|
||||
return model
|
||||
}
|
||||
|
||||
// 链式操作,翻页
|
||||
// @author ymrjqyy
|
||||
func (md *Model) ForPage(page, limit int) (*Model) {
|
||||
md.start = (page - 1) * limit
|
||||
md.limit = limit
|
||||
return md
|
||||
model := md.Clone()
|
||||
model.start = (page - 1) * limit
|
||||
model.limit = limit
|
||||
return model
|
||||
}
|
||||
|
||||
// 设置批处理的大小
|
||||
func (md *Model) Batch(batch int) *Model {
|
||||
model := md.Clone()
|
||||
model.batch = batch
|
||||
return model
|
||||
}
|
||||
|
||||
// 查询缓存/清除缓存操作,需要注意的是,事务查询不支持缓存。
|
||||
// 当time < 0时表示清除缓存, time=0时表示不过期, time > 0时表示过期时间,time过期时间单位:秒;
|
||||
// name表示自定义的缓存名称,便于业务层精准定位缓存项(如果业务层需要手动清理时,必须指定缓存名称),
|
||||
// 例如:查询缓存时设置名称,清理缓存时可以给定清理的缓存名称进行精准清理。
|
||||
func (md *Model) Cache(time int, name ... string) *Model {
|
||||
model := md.Clone()
|
||||
model.cacheTime = time
|
||||
if len(name) > 0 {
|
||||
model.cacheName = name[0]
|
||||
}
|
||||
// 查询缓存特性不支持事务操作
|
||||
if model.tx == nil {
|
||||
model.cacheEnabled = true
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// 链式操作,操作数据记录项,可以是string/Map, 也可以是: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]
|
||||
}
|
||||
md.data = m
|
||||
model.data = m
|
||||
} else {
|
||||
md.data = data[0]
|
||||
switch data[0].(type) {
|
||||
case List:
|
||||
model.data = data[0]
|
||||
case Map:
|
||||
model.data = data[0]
|
||||
default:
|
||||
rv := reflect.ValueOf(data[0])
|
||||
kind := rv.Kind()
|
||||
if kind == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
kind = rv.Kind()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Slice: fallthrough
|
||||
case reflect.Array:
|
||||
list := make(List, rv.Len())
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
list[i] = gconv.Map(rv.Index(i).Interface())
|
||||
}
|
||||
model.data = list
|
||||
case reflect.Map:
|
||||
model.data = gconv.Map(data[0])
|
||||
default:
|
||||
model.data = data[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
return md
|
||||
return model
|
||||
}
|
||||
|
||||
// 链式操作, CURD - Insert/BatchInsert
|
||||
@ -163,16 +256,24 @@ func (md *Model) Insert() (result sql.Result, err error) {
|
||||
if md.batch > 0 {
|
||||
batch = md.batch
|
||||
}
|
||||
if md.filter {
|
||||
for k, m := range list {
|
||||
list[k] = md.db.filterFields(md.tables, m)
|
||||
}
|
||||
}
|
||||
if md.tx == nil {
|
||||
return md.db.BatchInsert(md.tables, list, batch)
|
||||
} else {
|
||||
return md.tx.BatchInsert(md.tables, list, batch)
|
||||
}
|
||||
} else if dataMap, ok := md.data.(Map); ok {
|
||||
} else if data, ok := md.data.(Map); ok {
|
||||
if md.filter {
|
||||
data = md.db.filterFields(md.tables, data)
|
||||
}
|
||||
if md.tx == nil {
|
||||
return md.db.Insert(md.tables, dataMap)
|
||||
return md.db.Insert(md.tables, data)
|
||||
} else {
|
||||
return md.tx.Insert(md.tables, dataMap)
|
||||
return md.tx.Insert(md.tables, data)
|
||||
}
|
||||
}
|
||||
return nil, errors.New("inserting into table with invalid data type")
|
||||
@ -194,16 +295,24 @@ func (md *Model) Replace() (result sql.Result, err error) {
|
||||
if md.batch > 0 {
|
||||
batch = md.batch
|
||||
}
|
||||
if md.filter {
|
||||
for k, m := range list {
|
||||
list[k] = md.db.filterFields(md.tables, m)
|
||||
}
|
||||
}
|
||||
if md.tx == nil {
|
||||
return md.db.BatchReplace(md.tables, list, batch)
|
||||
} else {
|
||||
return md.tx.BatchReplace(md.tables, list, batch)
|
||||
}
|
||||
} else if dataMap, ok := md.data.(Map); ok {
|
||||
} else if data, ok := md.data.(Map); ok {
|
||||
if md.filter {
|
||||
data = md.db.filterFields(md.tables, data)
|
||||
}
|
||||
if md.tx == nil {
|
||||
return md.db.Insert(md.tables, dataMap)
|
||||
return md.db.Replace(md.tables, data)
|
||||
} else {
|
||||
return md.tx.Insert(md.tables, dataMap)
|
||||
return md.tx.Replace(md.tables, data)
|
||||
}
|
||||
}
|
||||
return nil, errors.New("replacing into table with invalid data type")
|
||||
@ -225,16 +334,24 @@ func (md *Model) Save() (result sql.Result, err error) {
|
||||
if md.batch > 0 {
|
||||
batch = md.batch
|
||||
}
|
||||
if md.filter {
|
||||
for k, m := range list {
|
||||
list[k] = md.db.filterFields(md.tables, m)
|
||||
}
|
||||
}
|
||||
if md.tx == nil {
|
||||
return md.db.BatchSave(md.tables, list, batch)
|
||||
} else {
|
||||
return md.tx.BatchSave(md.tables, list, batch)
|
||||
}
|
||||
} else if dataMap, ok := md.data.(Map); ok {
|
||||
} else if data, ok := md.data.(Map); ok {
|
||||
if md.filter {
|
||||
data = md.db.filterFields(md.tables, data)
|
||||
}
|
||||
if md.tx == nil {
|
||||
return md.db.Save(md.tables, dataMap)
|
||||
return md.db.Save(md.tables, data)
|
||||
} else {
|
||||
return md.tx.Save(md.tables, dataMap)
|
||||
return md.tx.Save(md.tables, data)
|
||||
}
|
||||
}
|
||||
return nil, errors.New("saving into table with invalid data type")
|
||||
@ -250,6 +367,13 @@ func (md *Model) Update() (result sql.Result, err error) {
|
||||
if md.data == nil {
|
||||
return nil, errors.New("updating table with empty data")
|
||||
}
|
||||
if md.filter {
|
||||
if data, ok := md.data.(Map); ok {
|
||||
if md.filter {
|
||||
md.data = md.db.filterFields(md.tables, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
if md.tx == nil {
|
||||
return md.db.Update(md.tables, md.data, md.where, md.whereArgs ...)
|
||||
} else {
|
||||
@ -264,9 +388,6 @@ func (md *Model) Delete() (result sql.Result, err error) {
|
||||
md.checkAndRemoveCache()
|
||||
}
|
||||
}()
|
||||
if md.where == "" {
|
||||
return nil, errors.New("where is required while deleting")
|
||||
}
|
||||
if md.tx == nil {
|
||||
return md.db.Delete(md.tables, md.where, md.whereArgs...)
|
||||
} else {
|
||||
@ -274,36 +395,14 @@ func (md *Model) Delete() (result sql.Result, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置批处理的大小
|
||||
func (md *Model) Batch(batch int) *Model {
|
||||
md.batch = batch
|
||||
return md
|
||||
}
|
||||
|
||||
// 查询缓存/清除缓存操作,需要注意的是,事务查询不支持缓存。
|
||||
// 当time < 0时表示清除缓存, time=0时表示不过期, time > 0时表示过期时间,time过期时间单位:秒;
|
||||
// name表示自定义的缓存名称,便于业务层精准定位缓存项(如果业务层需要手动清理时,必须指定缓存名称),
|
||||
// 例如:查询缓存时设置名称,清理缓存时可以给定清理的缓存名称进行精准清理。
|
||||
func (md *Model) Cache(time int, name ... string) *Model {
|
||||
md.cacheTime = time
|
||||
if len(name) > 0 {
|
||||
md.cacheName = name[0]
|
||||
}
|
||||
// 查询缓存特性不支持事务操作
|
||||
if md.tx == nil {
|
||||
md.cacheEnabled = true
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
// 链式操作,select
|
||||
func (md *Model) Select() (Result, error) {
|
||||
return md.getAll(md.getFormattedSql(), md.whereArgs...)
|
||||
return md.All()
|
||||
}
|
||||
|
||||
// 链式操作,查询所有记录
|
||||
func (md *Model) All() (Result, error) {
|
||||
return md.Select()
|
||||
return md.getAll(md.getFormattedSql(), md.whereArgs...)
|
||||
}
|
||||
|
||||
// 链式操作,查询单条记录
|
||||
@ -342,8 +441,13 @@ func (md *Model) Struct(obj interface{}) error {
|
||||
// 链式操作,查询数量,fields可以为空,也可以自定义查询字段,
|
||||
// 当给定自定义查询字段时,该字段必须为数量结果,否则会引起歧义,使用如:md.Fields("COUNT(id)")
|
||||
func (md *Model) Count() (int, error) {
|
||||
defer func(fields string) {
|
||||
md.fields = fields
|
||||
}(md.fields)
|
||||
if md.fields == "" || md.fields == "*" {
|
||||
md.fields = "COUNT(1)"
|
||||
} else {
|
||||
md.fields = fmt.Sprintf(`COUNT(%s)`, md.fields)
|
||||
}
|
||||
s := md.getFormattedSql()
|
||||
if len(md.groupBy) > 0 {
|
||||
@ -362,29 +466,30 @@ func (md *Model) Count() (int, error) {
|
||||
}
|
||||
|
||||
// 查询操作,对底层SQL操作的封装
|
||||
func (md *Model) getAll(sql string, args ...interface{}) (result Result, err error) {
|
||||
var cacheKey string
|
||||
func (md *Model) getAll(query string, args ...interface{}) (result Result, err error) {
|
||||
cacheKey := ""
|
||||
// 查询缓存查询处理
|
||||
if md.cacheEnabled {
|
||||
cacheKey = md.cacheName
|
||||
if len(cacheKey) == 0 {
|
||||
cacheKey = sql + "/" + gconv.String(args)
|
||||
cacheKey = query + "/" + gconv.String(args)
|
||||
}
|
||||
if v := md.db.cache.Get(cacheKey); v != nil {
|
||||
if v := md.db.getCache().Get(cacheKey); v != nil {
|
||||
return v.(Result), nil
|
||||
}
|
||||
}
|
||||
|
||||
if md.tx == nil {
|
||||
result, err = md.db.GetAll(sql, args...)
|
||||
result, err = md.db.GetAll(query, args...)
|
||||
} else {
|
||||
result, err = md.tx.GetAll(sql, args...)
|
||||
result, err = md.tx.GetAll(query, args...)
|
||||
}
|
||||
// 查询缓存保存处理
|
||||
if len(cacheKey) > 0 && err == nil {
|
||||
if md.cacheTime < 0 {
|
||||
md.db.cache.Remove(cacheKey)
|
||||
md.db.getCache().Remove(cacheKey)
|
||||
} else {
|
||||
md.db.cache.Set(cacheKey, result, md.cacheTime*1000)
|
||||
md.db.getCache().Set(cacheKey, result, md.cacheTime*1000)
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
@ -393,7 +498,7 @@ func (md *Model) getAll(sql string, args ...interface{}) (result Result, err err
|
||||
// 检查是否需要查询查询缓存
|
||||
func (md *Model) checkAndRemoveCache() {
|
||||
if md.cacheEnabled && md.cacheTime < 0 && len(md.cacheName) > 0 {
|
||||
md.db.cache.Remove(md.cacheName)
|
||||
md.db.getCache().Remove(md.cacheName)
|
||||
}
|
||||
}
|
||||
|
||||
@ -422,11 +527,10 @@ func (md *Model) getFormattedSql() string {
|
||||
// @author ymrjqyy
|
||||
// @author 2018-08-15
|
||||
func (md *Model) Chunk(limit int, callback func(result Result, err error) bool) {
|
||||
var page = 1
|
||||
page := 1
|
||||
for {
|
||||
md.ForPage(page, limit)
|
||||
sqls := md.getFormattedSql()
|
||||
data, err := md.getAll(sqls, md.whereArgs...)
|
||||
data, err := md.getAll(md.getFormattedSql(), md.whereArgs...)
|
||||
if err != nil {
|
||||
callback(nil, err)
|
||||
break
|
||||
|
||||
153
g/database/gdb/gdb_mssql.go
Normal file
153
g/database/gdb/gdb_mssql.go
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
|
||||
/*
|
||||
@author wenzi1<liyz23@qq.com>
|
||||
@date 20181109
|
||||
说明:
|
||||
1.需要导入sqlserver驱动: github.com/denisenkom/go-mssqldb
|
||||
2.不支持save/replace方法
|
||||
3.不支持LastInsertId方法
|
||||
*/
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
// 数据库链接对象
|
||||
type dbMssql struct {
|
||||
*dbBase
|
||||
}
|
||||
|
||||
// 创建SQL操作对象
|
||||
func (db *dbMssql) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
source := ""
|
||||
if config.Linkinfo != "" {
|
||||
source = config.Linkinfo
|
||||
} else {
|
||||
source = fmt.Sprintf("user id=%s;password=%s;server=%s;port=%s;database=%s;encrypt=disable",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name)
|
||||
}
|
||||
if db, err := sql.Open("sqlserver", source); err == nil {
|
||||
return db, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 获得关键字操作符
|
||||
func (db *dbMssql) getChars () (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
// 在执行sql之前对sql进行进一步处理
|
||||
func (db *dbMssql) handleSqlBeforeExec(query string) string {
|
||||
index := 0
|
||||
str, _ := gregex.ReplaceStringFunc("\\?", query, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf("@p%d", index)
|
||||
})
|
||||
|
||||
str, _ = gregex.ReplaceString("\"", "", str)
|
||||
|
||||
return db.parseSql(str)
|
||||
}
|
||||
|
||||
//将MYSQL的SQL语法转换为MSSQL的语法
|
||||
//1.由于mssql不支持limit写法所以需要对mysql中的limit用法做转换
|
||||
func (db *dbMssql) parseSql(sql string) string {
|
||||
//下面的正则表达式匹配出SELECT和INSERT的关键字后分别做不同的处理,如有LIMIT则将LIMIT的关键字也匹配出
|
||||
patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
|
||||
if gregex.IsMatchString(patten, sql) == false {
|
||||
fmt.Println("not matched..")
|
||||
return sql
|
||||
}
|
||||
|
||||
res, err := gregex.MatchAllString(patten, sql)
|
||||
if err != nil {
|
||||
fmt.Println("MatchString error.", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
index := 0
|
||||
keyword := strings.TrimSpace(res[index][0])
|
||||
keyword = strings.ToUpper(keyword)
|
||||
|
||||
index++
|
||||
switch keyword {
|
||||
case "SELECT":
|
||||
//不含LIMIT关键字则不处理
|
||||
if len(res) < 2 || (strings.HasPrefix(res[index][0], "LIMIT") == false && strings.HasPrefix(res[index][0], "limit") == false) {
|
||||
break
|
||||
}
|
||||
|
||||
//不含LIMIT则不处理
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
|
||||
break
|
||||
}
|
||||
|
||||
//判断SQL中是否含有order by
|
||||
selectStr := ""
|
||||
orderbyStr := ""
|
||||
haveOrderby := gregex.IsMatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
|
||||
if haveOrderby {
|
||||
//取order by 前面的字符串
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
|
||||
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "ORDER BY") == false{
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
|
||||
//取order by表达式的值
|
||||
orderbyExpr, _ := gregex.MatchString("((?i)ORDER BY)(.+)((?i)LIMIT)", sql)
|
||||
if len(orderbyExpr) != 4 || strings.EqualFold(orderbyExpr[1], "ORDER BY") == false || strings.EqualFold(orderbyExpr[3], "LIMIT") == false{
|
||||
break
|
||||
}
|
||||
orderbyStr = orderbyExpr[2]
|
||||
} else {
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false{
|
||||
break
|
||||
}
|
||||
selectStr = queryExpr[2]
|
||||
}
|
||||
|
||||
|
||||
|
||||
//取limit后面的取值范围
|
||||
first, limit := 0, 0
|
||||
for i := 1; i < len(res[index]); i++ {
|
||||
if len(strings.TrimSpace(res[index][i])) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(res[index][i], "LIMIT") || strings.HasPrefix(res[index][i], "limit") {
|
||||
first, _ = strconv.Atoi(res[index][i+1])
|
||||
limit, _ = strconv.Atoi(res[index][i+2])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if haveOrderby {
|
||||
sql = fmt.Sprintf("SELECT * FROM (SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROWNUMBER_, %s ) as TMP_ WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d", orderbyStr, selectStr, first, limit)
|
||||
} else {
|
||||
if first == 0 {
|
||||
first = limit
|
||||
} else {
|
||||
first = limit - first
|
||||
}
|
||||
sql = fmt.Sprintf("SELECT * FROM (SELECT TOP %d * FROM (SELECT TOP %d %s) as TMP1_ ) as TMP2_ ", first, limit, selectStr)
|
||||
}
|
||||
default:
|
||||
}
|
||||
return sql
|
||||
}
|
||||
@ -12,22 +12,19 @@ import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// MySQL接口对象
|
||||
var linkMysql = &dbmysql{}
|
||||
|
||||
|
||||
// 数据库链接对象
|
||||
type dbmysql struct {
|
||||
Db
|
||||
type dbMysql struct {
|
||||
*dbBase
|
||||
}
|
||||
|
||||
// 创建SQL操作对象,内部采用了lazy link处理
|
||||
func (db *dbmysql) Open (c *ConfigNode) (*sql.DB, error) {
|
||||
func (db *dbMysql) Open (config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if c.Linkinfo != "" {
|
||||
source = c.Linkinfo
|
||||
if config.Linkinfo != "" {
|
||||
source = config.Linkinfo
|
||||
} else {
|
||||
source = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", c.User, c.Pass, c.Host, c.Port, c.Name)
|
||||
source = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&multiStatements=true",
|
||||
config.User, config.Pass, config.Host, config.Port, config.Name, config.Charset)
|
||||
}
|
||||
if db, err := sql.Open("mysql", source); err == nil {
|
||||
return db, nil
|
||||
@ -36,17 +33,12 @@ func (db *dbmysql) Open (c *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获得关键字操作符 - 左
|
||||
func (db *dbmysql) getQuoteCharLeft () string {
|
||||
return "`"
|
||||
}
|
||||
|
||||
// 获得关键字操作符 - 右
|
||||
func (db *dbmysql) getQuoteCharRight () string {
|
||||
return "`"
|
||||
// 获得关键字操作符
|
||||
func (db *dbMysql) getChars () (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
}
|
||||
|
||||
// 在执行sql之前对sql进行进一步处理
|
||||
func (db *dbmysql) handleSqlBeforeExec(q *string) *string {
|
||||
return q
|
||||
func (db *dbMysql) handleSqlBeforeExec(query string) string {
|
||||
return query
|
||||
}
|
||||
@ -21,20 +21,18 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var linkOracle = &dboracle{}
|
||||
|
||||
// 数据库链接对象
|
||||
type dboracle struct {
|
||||
Db
|
||||
type dbOracle struct {
|
||||
*dbBase
|
||||
}
|
||||
|
||||
// 创建SQL操作对象
|
||||
func (db *dboracle) Open(c *ConfigNode) (*sql.DB, error) {
|
||||
func (db *dbOracle) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if c.Linkinfo != "" {
|
||||
source = c.Linkinfo
|
||||
if config.Linkinfo != "" {
|
||||
source = config.Linkinfo
|
||||
} else {
|
||||
source = fmt.Sprintf("%s/%s@%s", c.User, c.Pass, c.Name)
|
||||
source = fmt.Sprintf("%s/%s@%s", config.User, config.Pass, config.Name)
|
||||
}
|
||||
if db, err := sql.Open("oci8", source); err == nil {
|
||||
return db, nil
|
||||
@ -43,42 +41,37 @@ func (db *dboracle) Open(c *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获得关键字操作符 - 左
|
||||
func (db *dboracle) getQuoteCharLeft() string {
|
||||
return "\""
|
||||
}
|
||||
|
||||
// 获得关键字操作符 - 右
|
||||
func (db *dboracle) getQuoteCharRight() string {
|
||||
return "\""
|
||||
// 获得关键字操作符
|
||||
func (db *dbOracle) getChars () (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
// 在执行sql之前对sql进行进一步处理
|
||||
func (db *dboracle) handleSqlBeforeExec(q *string) *string {
|
||||
func (db *dbOracle) handleSqlBeforeExec(query string) string {
|
||||
index := 0
|
||||
str, _ := gregex.ReplaceStringFunc("\\?", *q, func(s string) string {
|
||||
str, _ := gregex.ReplaceStringFunc("\\?", query, func(s string) string {
|
||||
index++
|
||||
return fmt.Sprintf(":%d", index)
|
||||
})
|
||||
|
||||
str, _ = gregex.ReplaceString("\"", "", str)
|
||||
|
||||
return db.parseSql(&str)
|
||||
return db.parseSql(str)
|
||||
}
|
||||
|
||||
//由于ORACLE中对LIMIT和批量插入的语法与MYSQL不一致,所以这里需要对LIMIT和批量插入做语法上的转换
|
||||
func (db *dboracle) parseSql(sql *string) *string {
|
||||
func (db *dbOracle) parseSql(sql string) string {
|
||||
//下面的正则表达式匹配出SELECT和INSERT的关键字后分别做不同的处理,如有LIMIT则将LIMIT的关键字也匹配出
|
||||
patten := `^\s*(?i)(SELECT)|(INSERT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
|
||||
if gregex.IsMatchString(patten, *sql) == false {
|
||||
if gregex.IsMatchString(patten, sql) == false {
|
||||
fmt.Println("not matched..")
|
||||
return sql
|
||||
}
|
||||
|
||||
res, err := gregex.MatchAllString(patten, *sql)
|
||||
res, err := gregex.MatchAllString(patten, sql)
|
||||
if err != nil {
|
||||
fmt.Println("MatchString error.", err)
|
||||
return nil
|
||||
return ""
|
||||
}
|
||||
|
||||
index := 0
|
||||
@ -94,13 +87,14 @@ func (db *dboracle) parseSql(sql *string) *string {
|
||||
}
|
||||
|
||||
//取limit前面的字符串
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", *sql) == false {
|
||||
if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
|
||||
break
|
||||
}
|
||||
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", *sql)
|
||||
queryExpr[0] = strings.TrimRight(queryExpr[0], "LIMIT")
|
||||
queryExpr[0] = strings.TrimRight(queryExpr[0], "limit")
|
||||
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
|
||||
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false{
|
||||
break
|
||||
}
|
||||
|
||||
//取limit后面的取值范围
|
||||
first, limit := 0, 0
|
||||
@ -117,10 +111,10 @@ func (db *dboracle) parseSql(sql *string) *string {
|
||||
}
|
||||
|
||||
//也可以使用between,据说这种写法的性能会比between好点,里层SQL中的ROWNUM_ >= limit可以缩小查询后的数据集规模
|
||||
*sql = fmt.Sprintf("SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d", queryExpr[0], limit, first)
|
||||
sql = fmt.Sprintf("SELECT * FROM (SELECT GFORM.*, ROWNUM ROWNUM_ FROM (%s %s) GFORM WHERE ROWNUM <= %d) WHERE ROWNUM_ >= %d", queryExpr[1], queryExpr[2], limit, first)
|
||||
case "INSERT":
|
||||
//获取VALUE的值,匹配所有带括号的值,会将INSERT INTO后的值匹配到,所以下面的判断语句会判断数组长度是否小于3
|
||||
valueExpr, err := gregex.MatchAllString(`(\s*\(([^\(\)]*)\))`, *sql)
|
||||
valueExpr, err := gregex.MatchAllString(`(\s*\(([^\(\)]*)\))`, sql)
|
||||
if err != nil {
|
||||
return sql
|
||||
}
|
||||
@ -131,17 +125,17 @@ func (db *dboracle) parseSql(sql *string) *string {
|
||||
}
|
||||
|
||||
//获取INTO后面的值
|
||||
tableExpr, err := gregex.MatchString(`(?i)\s*(INTO\s+\w+\(([^\(\)]*)\))`, *sql)
|
||||
tableExpr, err := gregex.MatchString(`(?i)\s*(INTO\s+\w+\(([^\(\)]*)\))`, sql)
|
||||
if err != nil {
|
||||
return sql
|
||||
}
|
||||
tableExpr[0] = strings.TrimSpace(tableExpr[0])
|
||||
|
||||
*sql = "INSERT ALL"
|
||||
sql = "INSERT ALL"
|
||||
for i := 1; i < len(valueExpr); i++ {
|
||||
*sql += fmt.Sprintf(" %s VALUES%s", tableExpr[0], strings.TrimSpace(valueExpr[i][0]))
|
||||
sql += fmt.Sprintf(" %s VALUES%s", tableExpr[0], strings.TrimSpace(valueExpr[i][0]))
|
||||
}
|
||||
*sql += " SELECT 1 FROM DUAL"
|
||||
sql += " SELECT 1 FROM DUAL"
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
@ -18,22 +18,18 @@ import (
|
||||
// _ "gitee.com/johng/gf/third/github.com/lib/pq"
|
||||
// @todo 需要完善replace和save的操作覆盖
|
||||
|
||||
// PostgreSQL接口对象
|
||||
var linkPgsql = &dbpgsql{}
|
||||
|
||||
|
||||
// 数据库链接对象
|
||||
type dbpgsql struct {
|
||||
Db
|
||||
type dbPgsql struct {
|
||||
*dbBase
|
||||
}
|
||||
|
||||
// 创建SQL操作对象,内部采用了lazy link处理
|
||||
func (db *dbpgsql) Open (c *ConfigNode) (*sql.DB, error) {
|
||||
func (db *dbPgsql) Open (config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if c.Linkinfo != "" {
|
||||
source = c.Linkinfo
|
||||
if config.Linkinfo != "" {
|
||||
source = config.Linkinfo
|
||||
} else {
|
||||
source = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s", c.User, c.Pass, c.Host, c.Port, c.Name)
|
||||
source = fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s", config.User, config.Pass, config.Host, config.Port, config.Name)
|
||||
}
|
||||
if db, err := sql.Open("postgres", source); err == nil {
|
||||
return db, nil
|
||||
@ -42,23 +38,18 @@ func (db *dbpgsql) Open (c *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获得关键字操作符 - 左
|
||||
func (db *dbpgsql) getQuoteCharLeft () string {
|
||||
return "\""
|
||||
}
|
||||
|
||||
// 获得关键字操作符 - 右
|
||||
func (db *dbpgsql) getQuoteCharRight () string {
|
||||
return "\""
|
||||
// 获得关键字操作符
|
||||
func (db *dbPgsql) getChars () (charLeft string, charRight string) {
|
||||
return "\"", "\""
|
||||
}
|
||||
|
||||
// 在执行sql之前对sql进行进一步处理
|
||||
func (db *dbpgsql) handleSqlBeforeExec(q *string) *string {
|
||||
func (db *dbPgsql) handleSqlBeforeExec(query string) string {
|
||||
reg := regexp.MustCompile("\\?")
|
||||
index := 0
|
||||
str := reg.ReplaceAllStringFunc(*q, func (s string) string {
|
||||
str := reg.ReplaceAllStringFunc(query, func (s string) string {
|
||||
index ++
|
||||
return fmt.Sprintf("$%d", index)
|
||||
})
|
||||
return &str
|
||||
return str
|
||||
}
|
||||
@ -16,20 +16,18 @@ import (
|
||||
|
||||
// Sqlite接口对象
|
||||
// @author wxkj<wxscz@qq.com>
|
||||
var linkSqlite = &dbsqlite{}
|
||||
|
||||
|
||||
// 数据库链接对象
|
||||
type dbsqlite struct {
|
||||
Db
|
||||
type dbSqlite struct {
|
||||
*dbBase
|
||||
}
|
||||
|
||||
func (db *dbsqlite) Open(c *ConfigNode) (*sql.DB, error) {
|
||||
func (db *dbSqlite) Open(config *ConfigNode) (*sql.DB, error) {
|
||||
var source string
|
||||
if c.Linkinfo != "" {
|
||||
source = c.Linkinfo
|
||||
if config.Linkinfo != "" {
|
||||
source = config.Linkinfo
|
||||
} else {
|
||||
source = c.Name
|
||||
source = config.Name
|
||||
}
|
||||
if db, err := sql.Open("sqlite3", source); err == nil {
|
||||
return db, nil
|
||||
@ -38,20 +36,14 @@ func (db *dbsqlite) Open(c *ConfigNode) (*sql.DB, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获得关键字操作符 - 左
|
||||
func (db *dbsqlite) getQuoteCharLeft() string {
|
||||
return "`"
|
||||
}
|
||||
|
||||
// 获得关键字操作符 - 右
|
||||
func (db *dbsqlite) getQuoteCharRight() string {
|
||||
return "`"
|
||||
// 获得关键字操作符
|
||||
func (db *dbSqlite) getChars () (charLeft string, charRight string) {
|
||||
return "`", "`"
|
||||
}
|
||||
|
||||
// 在执行sql之前对sql进行进一步处理
|
||||
// @todo 需要增加对Save方法的支持,可使用正则来实现替换,
|
||||
// @todo 将ON DUPLICATE KEY UPDATE触发器修改为两条SQL语句(INSERT OR IGNORE & UPDATE)
|
||||
func (db *dbsqlite) handleSqlBeforeExec(q *string) *string {
|
||||
|
||||
return q
|
||||
func (db *dbSqlite) handleSqlBeforeExec(query string) string {
|
||||
return query
|
||||
}
|
||||
@ -7,132 +7,55 @@
|
||||
package gdb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"strings"
|
||||
"reflect"
|
||||
"database/sql"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
|
||||
"gitee.com/johng/gf/g/container/gvar"
|
||||
)
|
||||
|
||||
// 数据库事务对象
|
||||
type Tx struct {
|
||||
db *Db
|
||||
type TX struct {
|
||||
db DB
|
||||
tx *sql.Tx
|
||||
master *sql.DB
|
||||
}
|
||||
|
||||
// 事务操作,提交
|
||||
func (tx *Tx) Commit() error {
|
||||
err := tx.tx.Commit()
|
||||
tx.master.Close()
|
||||
return err
|
||||
func (tx *TX) Commit() error {
|
||||
return tx.tx.Commit()
|
||||
}
|
||||
|
||||
// 事务操作,回滚
|
||||
func (tx *Tx) Rollback() error {
|
||||
err := tx.tx.Rollback()
|
||||
tx.master.Close()
|
||||
return err
|
||||
func (tx *TX) Rollback() error {
|
||||
return tx.tx.Rollback()
|
||||
}
|
||||
|
||||
// (事务)数据库sql查询操作,主要执行查询
|
||||
func (tx *Tx) Query(query string, args ...interface{}) (*sql.Rows, error) {
|
||||
var err error
|
||||
var rows *sql.Rows
|
||||
p := tx.db.link.handleSqlBeforeExec(&query)
|
||||
if tx.db.debug.Val() {
|
||||
militime1 := gtime.Millisecond()
|
||||
rows, err = tx.tx.Query(*p, args ...)
|
||||
militime2 := gtime.Millisecond()
|
||||
s := &Sql{
|
||||
Sql : *p,
|
||||
Args : args,
|
||||
Error : err,
|
||||
Start : militime1,
|
||||
End : militime2,
|
||||
Func : "TX:Query",
|
||||
}
|
||||
tx.db.sqls.Put(s)
|
||||
tx.db.printSql(s)
|
||||
} else {
|
||||
rows, err = tx.tx.Query(*p, args ...)
|
||||
}
|
||||
if err == nil {
|
||||
return rows, nil
|
||||
} else {
|
||||
err = tx.db.formatError(err, p, args...)
|
||||
}
|
||||
return nil, err
|
||||
func (tx *TX) Query(query string, args ...interface{}) (rows *sql.Rows, err error) {
|
||||
return tx.db.doQuery(tx.tx, query, args...)
|
||||
}
|
||||
|
||||
// (事务)执行一条sql,并返回执行情况,主要用于非查询操作
|
||||
func (tx *Tx) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
var err error
|
||||
var result sql.Result
|
||||
p := tx.db.link.handleSqlBeforeExec(&query)
|
||||
if tx.db.debug.Val() {
|
||||
militime1 := gtime.Millisecond()
|
||||
result, err = tx.tx.Exec(*p, args ...)
|
||||
militime2 := gtime.Millisecond()
|
||||
s := &Sql{
|
||||
Sql : *p,
|
||||
Args : args,
|
||||
Error : err,
|
||||
Start : militime1,
|
||||
End : militime2,
|
||||
Func : "TX:Exec",
|
||||
}
|
||||
tx.db.sqls.Put(s)
|
||||
tx.db.printSql(s)
|
||||
} else {
|
||||
result, err = tx.tx.Exec(*p, args ...)
|
||||
}
|
||||
return result, tx.db.formatError(err, p, args...)
|
||||
func (tx *TX) Exec(query string, args ...interface{}) (sql.Result, error) {
|
||||
return tx.db.doExec(tx.tx, query, args...)
|
||||
}
|
||||
|
||||
// sql预处理,执行完成后调用返回值sql.Stmt.Exec完成sql操作
|
||||
func (tx *TX) Prepare(query string) (*sql.Stmt, error) {
|
||||
return tx.db.doPrepare(tx.tx, query)
|
||||
}
|
||||
|
||||
// 数据库查询,获取查询结果集,以列表结构返回
|
||||
func (tx *Tx) GetAll(query string, args ...interface{}) (Result, error) {
|
||||
// 执行sql
|
||||
func (tx *TX) GetAll(query string, args ...interface{}) (Result, error) {
|
||||
rows, err := tx.Query(query, args ...)
|
||||
if err != nil || rows == nil {
|
||||
return nil, err
|
||||
}
|
||||
// 列名称列表
|
||||
columns, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 返回结构组装
|
||||
values := make([]sql.RawBytes, len(columns))
|
||||
scanArgs := make([]interface{}, len(values))
|
||||
records := make(Result, 0)
|
||||
for i := range values {
|
||||
scanArgs[i] = &values[i]
|
||||
}
|
||||
for rows.Next() {
|
||||
err = rows.Scan(scanArgs...)
|
||||
if err != nil {
|
||||
return records, err
|
||||
}
|
||||
row := make(Record)
|
||||
// 注意col字段是一个[]byte类型(slice类型本身是一个指针),多个记录循环时该变量指向的是同一个内存地址
|
||||
for i, col := range values {
|
||||
v := make([]byte, len(col))
|
||||
copy(v, col)
|
||||
row[columns[i]] = gvar.New(v)
|
||||
}
|
||||
//fmt.Printf("%p\n", row["typeid"])
|
||||
records = append(records, row)
|
||||
}
|
||||
return records, nil
|
||||
defer rows.Close()
|
||||
return rowsToResult(rows)
|
||||
}
|
||||
|
||||
// 数据库查询,获取查询结果记录,以关联数组结构返回
|
||||
func (tx *Tx) GetOne(query string, args ...interface{}) (Record, error) {
|
||||
func (tx *TX) GetOne(query string, args ...interface{}) (Record, error) {
|
||||
list, err := tx.GetAll(query, args ...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -144,7 +67,7 @@ func (tx *Tx) GetOne(query string, args ...interface{}) (Record, error) {
|
||||
}
|
||||
|
||||
// 数据库查询,获取查询结果记录,自动映射数据到给定的struct对象中
|
||||
func (tx *Tx) GetStruct(obj interface{}, query string, args ...interface{}) error {
|
||||
func (tx *TX) GetStruct(obj interface{}, query string, args ...interface{}) error {
|
||||
one, err := tx.GetOne(query, args...)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -152,9 +75,8 @@ func (tx *Tx) GetStruct(obj interface{}, query string, args ...interface{}) erro
|
||||
return one.ToStruct(obj)
|
||||
}
|
||||
|
||||
|
||||
// 数据库查询,获取查询字段值
|
||||
func (tx *Tx) GetValue(query string, args ...interface{}) (Value, error) {
|
||||
func (tx *TX) GetValue(query string, args ...interface{}) (Value, error) {
|
||||
one, err := tx.GetOne(query, args ...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -166,187 +88,55 @@ func (tx *Tx) GetValue(query string, args ...interface{}) (Value, error) {
|
||||
}
|
||||
|
||||
// 数据库查询,获取查询数量
|
||||
func (tx *Tx) GetCount(query string, args ...interface{}) (int, error) {
|
||||
val, err := tx.GetValue(query, args ...)
|
||||
func (tx *TX) GetCount(query string, args ...interface{}) (int, error) {
|
||||
if !gregex.IsMatchString(`(?i)SELECT\s+COUNT\(.+\)\s+FROM`, query) {
|
||||
query, _ = gregex.ReplaceString(`(?i)(SELECT)\s+(.+)\s+(FROM)`, `$1 COUNT($2) $3`, query)
|
||||
}
|
||||
value, err := tx.GetValue(query, args ...)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return gconv.Int(val), nil
|
||||
}
|
||||
|
||||
// 数据表查询,其中tables可以是多个联表查询语句,这种查询方式较复杂,建议使用链式操作
|
||||
func (tx *Tx) Select(tables, fields string, condition interface{}, groupBy, orderBy string, first, limit int, args ... interface{}) (Result, error) {
|
||||
s := fmt.Sprintf("SELECT %s FROM %s ", fields, tables)
|
||||
if condition != nil {
|
||||
s += fmt.Sprintf("WHERE %s ", tx.db.formatCondition(condition))
|
||||
}
|
||||
if len(groupBy) > 0 {
|
||||
s += fmt.Sprintf("GROUP BY %s ", groupBy)
|
||||
}
|
||||
if len(orderBy) > 0 {
|
||||
s += fmt.Sprintf("ORDER BY %s ", orderBy)
|
||||
}
|
||||
if limit > 0 {
|
||||
s += fmt.Sprintf("LIMIT %d,%d ", first, limit)
|
||||
}
|
||||
return tx.GetAll(s, args ... )
|
||||
}
|
||||
|
||||
// sql预处理,执行完成后调用返回值sql.Stmt.Exec完成sql操作
|
||||
// 记得调用sql.Stmt.Close关闭操作对象
|
||||
func (tx *Tx) Prepare(query string) (*sql.Stmt, error) {
|
||||
return tx.tx.Prepare(query)
|
||||
}
|
||||
|
||||
// insert、replace, save, ignore操作
|
||||
// 0: insert: 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
|
||||
// 1: replace: 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
|
||||
// 2: save: 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
|
||||
// 3: ignore: 如果数据存在(主键或者唯一索引),那么什么也不做
|
||||
func (tx *Tx) insert(table string, data Map, option uint8) (sql.Result, error) {
|
||||
var keys []string
|
||||
var values []string
|
||||
var params []interface{}
|
||||
for k, v := range data {
|
||||
keys = append(keys, tx.db.charl + k + tx.db.charr)
|
||||
values = append(values, "?")
|
||||
params = append(params, v)
|
||||
}
|
||||
operation := tx.db.getInsertOperationByOption(option)
|
||||
updatestr := ""
|
||||
if option == OPTION_SAVE {
|
||||
var updates []string
|
||||
for k, _ := range data {
|
||||
updates = append(updates, fmt.Sprintf("%s%s%s=VALUES(%s)", tx.db.charl, k, tx.db.charr, k))
|
||||
}
|
||||
updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
|
||||
}
|
||||
return tx.Exec(
|
||||
fmt.Sprintf("%s INTO %s(%s) VALUES(%s) %s",
|
||||
operation, table, strings.Join(keys, ","),
|
||||
strings.Join(values, ","),
|
||||
updatestr),
|
||||
params...
|
||||
)
|
||||
return value.Int(), nil
|
||||
}
|
||||
|
||||
// CURD操作:单条数据写入, 仅仅执行写入操作,如果存在冲突的主键或者唯一索引,那么报错返回
|
||||
func (tx *Tx) Insert(table string, data Map) (sql.Result, error) {
|
||||
return tx.insert(table, data, OPTION_INSERT)
|
||||
func (tx *TX) Insert(table string, data Map) (sql.Result, error) {
|
||||
return tx.db.doInsert(tx.tx, table, data, OPTION_INSERT)
|
||||
}
|
||||
|
||||
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
|
||||
func (tx *Tx) Replace(table string, data Map) (sql.Result, error) {
|
||||
return tx.insert(table, data, OPTION_REPLACE)
|
||||
func (tx *TX) Replace(table string, data Map) (sql.Result, error) {
|
||||
return tx.db.doInsert(tx.tx, table, data, OPTION_REPLACE)
|
||||
}
|
||||
|
||||
// CURD操作:单条数据写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
|
||||
func (tx *Tx) Save(table string, data Map) (sql.Result, error) {
|
||||
return tx.insert(table, data, OPTION_SAVE)
|
||||
}
|
||||
|
||||
// 批量写入数据
|
||||
func (tx *Tx) batchInsert(table string, list List, batch int, option uint8) (sql.Result, error) {
|
||||
var keys []string
|
||||
var values []string
|
||||
var bvalues []string
|
||||
var params []interface{}
|
||||
var result sql.Result
|
||||
var size = len(list)
|
||||
// 判断长度
|
||||
if size < 1 {
|
||||
return result, errors.New("empty data list")
|
||||
}
|
||||
// 首先获取字段名称及记录长度
|
||||
for k, _ := range list[0] {
|
||||
keys = append(keys, k)
|
||||
values = append(values, "?")
|
||||
}
|
||||
keyStr := tx.db.charl + strings.Join(keys, tx.db.charl + "," + tx.db.charr) + tx.db.charr
|
||||
valueHolderStr := "(" + strings.Join(values, ",") + ")"
|
||||
// 操作判断
|
||||
operation := tx.db.getInsertOperationByOption(option)
|
||||
updatestr := ""
|
||||
if option == OPTION_SAVE {
|
||||
var updates []string
|
||||
for _, k := range keys {
|
||||
updates = append(updates, fmt.Sprintf("%s%s%s=VALUES(%s)", tx.db.charl, k, tx.db.charr, k))
|
||||
}
|
||||
updatestr = fmt.Sprintf(" ON DUPLICATE KEY UPDATE %s", strings.Join(updates, ","))
|
||||
}
|
||||
// 构造批量写入数据格式(注意map的遍历是无序的)
|
||||
for i := 0; i < size; i++ {
|
||||
for _, k := range keys {
|
||||
params = append(params, list[i][k])
|
||||
}
|
||||
bvalues = append(bvalues, valueHolderStr)
|
||||
if len(bvalues) == batch {
|
||||
r, err := tx.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
|
||||
operation, table, keyStr, strings.Join(bvalues, ","),
|
||||
updatestr),
|
||||
params...)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result = r
|
||||
params = params[:0]
|
||||
bvalues = bvalues[:0]
|
||||
}
|
||||
}
|
||||
// 处理最后不构成指定批量的数据
|
||||
if len(bvalues) > 0 {
|
||||
r, err := tx.Exec(fmt.Sprintf("%s INTO %s(%s) VALUES%s %s",
|
||||
operation, table, keyStr, strings.Join(bvalues, ","),
|
||||
updatestr),
|
||||
params...)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result = r
|
||||
}
|
||||
return result, nil
|
||||
func (tx *TX) Save(table string, data Map) (sql.Result, error) {
|
||||
return tx.db.doInsert(tx.tx, table, data, OPTION_SAVE)
|
||||
}
|
||||
|
||||
// CURD操作:批量数据指定批次量写入
|
||||
func (tx *Tx) BatchInsert(table string, list List, batch int) (sql.Result, error) {
|
||||
return tx.batchInsert(table, list, batch, OPTION_INSERT)
|
||||
func (tx *TX) BatchInsert(table string, list List, batch int) (sql.Result, error) {
|
||||
return tx.db.doBatchInsert(tx.tx, table, list, batch, OPTION_INSERT)
|
||||
}
|
||||
|
||||
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么删除后重新写入一条
|
||||
func (tx *Tx) BatchReplace(table string, list List, batch int) (sql.Result, error) {
|
||||
return tx.batchInsert(table, list, batch, OPTION_REPLACE)
|
||||
func (tx *TX) BatchReplace(table string, list List, batch int) (sql.Result, error) {
|
||||
return tx.db.doBatchInsert(tx.tx, table, list, batch, OPTION_REPLACE)
|
||||
}
|
||||
|
||||
// CURD操作:批量数据指定批次量写入, 如果数据存在(主键或者唯一索引),那么更新,否则写入一条新数据
|
||||
func (tx *Tx) BatchSave(table string, list List, batch int) (sql.Result, error) {
|
||||
return tx.batchInsert(table, list, batch, OPTION_SAVE)
|
||||
func (tx *TX) BatchSave(table string, list List, batch int) (sql.Result, error) {
|
||||
return tx.db.doBatchInsert(tx.tx, table, list, batch, OPTION_SAVE)
|
||||
}
|
||||
|
||||
// CURD操作:数据更新,统一采用sql预处理
|
||||
// data参数支持字符串或者关联数组类型,内部会自行做判断处理
|
||||
func (tx *Tx) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
var params []interface{}
|
||||
var updates string
|
||||
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=?", tx.db.charl, k, tx.db.charr))
|
||||
params = append(params, gconv.String(refValue.MapIndex(k).Interface()))
|
||||
updates = strings.Join(fields, ",")
|
||||
}
|
||||
} else {
|
||||
updates = gconv.String(data)
|
||||
}
|
||||
for _, v := range args {
|
||||
params = append(params, gconv.String(v))
|
||||
}
|
||||
return tx.Exec(fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, updates, tx.db.formatCondition(condition)), params...)
|
||||
func (tx *TX) Update(table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
return tx.db.doUpdate(tx.tx, table, data, condition, args ...)
|
||||
}
|
||||
|
||||
// CURD操作:删除数据
|
||||
func (tx *Tx) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
return tx.Exec(fmt.Sprintf("DELETE FROM %s WHERE %s", table, tx.db.formatCondition(condition)), args...)
|
||||
func (tx *TX) Delete(table string, condition interface{}, args ...interface{}) (sql.Result, error) {
|
||||
return tx.db.doDelete(tx.tx, table, condition, args ...)
|
||||
}
|
||||
|
||||
|
||||
52
g/database/gdb/gdb_unit_0_test.go
Normal file
52
g/database/gdb/gdb_unit_0_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
package gdb_test
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/database/gdb"
|
||||
"gitee.com/johng/gf/g/util/gtest"
|
||||
)
|
||||
|
||||
var (
|
||||
// 数据库对象/接口
|
||||
db gdb.DB
|
||||
)
|
||||
|
||||
// 初始化连接参数。
|
||||
// 测试前需要修改连接参数。
|
||||
func init() {
|
||||
gdb.AddDefaultConfigNode(gdb.ConfigNode{
|
||||
Host: "127.0.0.1",
|
||||
Port: "3306",
|
||||
User: "root",
|
||||
Pass: "",
|
||||
Name: "",
|
||||
Type: "mysql",
|
||||
Role: "master",
|
||||
Charset: "utf8",
|
||||
Priority: 1,
|
||||
})
|
||||
if r, err := gdb.New(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
db = r
|
||||
}
|
||||
// 准备测试数据结构
|
||||
if _, err := db.Exec("CREATE DATABASE IF NOT EXISTS `test` CHARACTER SET UTF8"); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
db.SetSchema("test")
|
||||
if _, err := db.Exec("DROP TABLE IF EXISTS `user`"); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := db.Exec(`
|
||||
CREATE TABLE user (
|
||||
id int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID',
|
||||
passport varchar(45) NOT NULL COMMENT '账号',
|
||||
password char(32) NOT NULL COMMENT '密码',
|
||||
nickname varchar(45) NOT NULL COMMENT '昵称',
|
||||
create_time timestamp NOT NULL COMMENT '创建时间/注册时间',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
`); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
172
g/database/gdb/gdb_unit_1_test.go
Normal file
172
g/database/gdb/gdb_unit_1_test.go
Normal file
@ -0,0 +1,172 @@
|
||||
package gdb_test
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDbBase_Query(t *testing.T) {
|
||||
if _, err := db.Query("SELECT ?", 1); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := db.Query("ERROR"); err == nil {
|
||||
gtest.Fatal("FAIL")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_Exec(t *testing.T) {
|
||||
if _, err := db.Exec("SELECT ?", 1); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := db.Exec("ERROR"); err == nil {
|
||||
gtest.Fatal("FAIL")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_Prepare(t *testing.T) {
|
||||
st, err := db.Prepare("SELECT 100")
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
rows, err := st.Query()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
array, err := rows.Columns()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
gtest.Assert(array[0], "100")
|
||||
if err := rows.Close(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_Insert(t *testing.T) {
|
||||
if _, err := db.Insert("user", g.Map{
|
||||
"id" : 1,
|
||||
"passport" : "t1",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T1",
|
||||
"create_time" : gtime.Now().String(),
|
||||
}); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_BatchInsert(t *testing.T) {
|
||||
if _, err := db.BatchInsert("user", g.List {
|
||||
{
|
||||
"id" : 2,
|
||||
"passport" : "t2",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T2",
|
||||
"create_time" : gtime.Now().String(),
|
||||
},
|
||||
{
|
||||
"id" : 3,
|
||||
"passport" : "t3",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T3",
|
||||
"create_time" : gtime.Now().String(),
|
||||
},
|
||||
}, 10); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_Save(t *testing.T) {
|
||||
if _, err := db.Save("user", g.Map{
|
||||
"id" : 1,
|
||||
"passport" : "t1",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T11",
|
||||
"create_time" : gtime.Now().String(),
|
||||
}); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_Replace(t *testing.T) {
|
||||
if _, err := db.Save("user", g.Map{
|
||||
"id" : 1,
|
||||
"passport" : "t1",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T111",
|
||||
"create_time" : gtime.Now().String(),
|
||||
}); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_Update(t *testing.T) {
|
||||
if result, err := db.Update("user", "create_time='2010-10-10 00:00:01'", "id=3"); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_GetAll(t *testing.T) {
|
||||
if result, err := db.GetAll("SELECT * FROM user WHERE id=?", 1); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(len(result), 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_GetOne(t *testing.T) {
|
||||
if record, err := db.GetOne("SELECT * FROM user WHERE passport=?", "t1"); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
if record == nil {
|
||||
gtest.Fatal("FAIL")
|
||||
}
|
||||
gtest.Assert(record["nickname"].String(), "T111")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_GetValue(t *testing.T) {
|
||||
if value, err := db.GetValue("SELECT id FROM user WHERE passport=?", "t3"); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(value.Int(), 3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_GetCount(t *testing.T) {
|
||||
if count, err := db.GetCount("SELECT * FROM user"); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(count, 3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_GetStruct(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
NickName string
|
||||
CreateTime gtime.Time
|
||||
}
|
||||
user := new(User)
|
||||
if err := db.GetStruct(user, "SELECT * FROM user WHERE id=?", 3); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(user.CreateTime.String(), "2010-10-10 00:00:01")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDbBase_Delete(t *testing.T) {
|
||||
if result, err := db.Delete("user", nil); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 3)
|
||||
}
|
||||
}
|
||||
|
||||
220
g/database/gdb/gdb_unit_2_test.go
Normal file
220
g/database/gdb/gdb_unit_2_test.go
Normal file
@ -0,0 +1,220 @@
|
||||
package gdb_test
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestModel_Insert(t *testing.T) {
|
||||
result, err := db.Table("user").Filter().Data(g.Map{
|
||||
"id" : 1,
|
||||
"uid" : 1,
|
||||
"passport" : "t1",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T1",
|
||||
"create_time" : gtime.Now().String(),
|
||||
}).Insert()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
n, _ := result.LastInsertId()
|
||||
gtest.Assert(n, 1)
|
||||
}
|
||||
|
||||
func TestModel_Batch(t *testing.T) {
|
||||
result, err := db.Table("user").Filter().Data(g.List{
|
||||
{
|
||||
"id" : 2,
|
||||
"uid" : 2,
|
||||
"passport" : "t2",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T2",
|
||||
"create_time" : gtime.Now().String(),
|
||||
},
|
||||
{
|
||||
"id" : 3,
|
||||
"uid" : 3,
|
||||
"passport" : "t3",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T3",
|
||||
"create_time" : gtime.Now().String(),
|
||||
},
|
||||
}).Batch(10).Insert()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 2)
|
||||
}
|
||||
|
||||
func TestModel_Replace(t *testing.T) {
|
||||
result, err := db.Table("user").Data(g.Map{
|
||||
"id" : 1,
|
||||
"passport" : "t11",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T11",
|
||||
"create_time" : gtime.Now().String(),
|
||||
}).Replace()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 2)
|
||||
}
|
||||
|
||||
func TestModel_Save(t *testing.T) {
|
||||
result, err := db.Table("user").Data(g.Map{
|
||||
"id" : 1,
|
||||
"passport" : "t111",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T111",
|
||||
"create_time" : gtime.Now().String(),
|
||||
}).Save()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 2)
|
||||
}
|
||||
|
||||
func TestModel_Update(t *testing.T) {
|
||||
result, err := db.Table("user").Data("passport", "t22").Where("passport=?", "t2").Update()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 1)
|
||||
}
|
||||
|
||||
func TestModel_Clone(t *testing.T) {
|
||||
md := db.Table("user").Where("id IN(?)", g.Slice{1,3})
|
||||
count, err := md.Count()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
record, err := md.OrderBy("id DESC").One()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
result, err := md.OrderBy("id ASC").All()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
gtest.Assert(count, 2)
|
||||
gtest.Assert(record["id"].Int(), 3)
|
||||
gtest.Assert(len(result), 2)
|
||||
gtest.Assert(result[0]["id"].Int(), 1)
|
||||
gtest.Assert(result[1]["id"].Int(), 3)
|
||||
}
|
||||
|
||||
func TestModel_All(t *testing.T) {
|
||||
result, err := db.Table("user").All()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
gtest.Assert(len(result), 3)
|
||||
}
|
||||
|
||||
func TestModel_One(t *testing.T) {
|
||||
record, err := db.Table("user").Where("id", 1).One()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if record == nil {
|
||||
gtest.Fatal("FAIL")
|
||||
}
|
||||
gtest.Assert(record["nickname"].String(), "T111")
|
||||
}
|
||||
|
||||
func TestModel_Value(t *testing.T) {
|
||||
value, err := db.Table("user").Fields("nickname").Where("id", 1).Value()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if value == nil {
|
||||
gtest.Fatal("FAIL")
|
||||
}
|
||||
gtest.Assert(value.String(), "T111")
|
||||
}
|
||||
|
||||
func TestModel_Count(t *testing.T) {
|
||||
count, err := db.Table("user").Count()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
gtest.Assert(count, 3)
|
||||
}
|
||||
|
||||
func TestModel_Select(t *testing.T) {
|
||||
result, err := db.Table("user").Select()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
gtest.Assert(len(result), 3)
|
||||
}
|
||||
|
||||
func TestModel_Struct(t *testing.T) {
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
NickName string
|
||||
CreateTime gtime.Time
|
||||
}
|
||||
user := new(User)
|
||||
err := db.Table("user").Where("id=1").Struct(user)
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
gtest.Assert(user.NickName, "T111")
|
||||
}
|
||||
|
||||
func TestModel_OrderBy(t *testing.T) {
|
||||
result, err := db.Table("user").OrderBy("id DESC").Select()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
gtest.Assert(len(result), 3)
|
||||
gtest.Assert(result[0]["nickname"].String(), "T3")
|
||||
}
|
||||
|
||||
func TestModel_GroupBy(t *testing.T) {
|
||||
result, err := db.Table("user").GroupBy("id").Select()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
gtest.Assert(len(result), 3)
|
||||
gtest.Assert(result[0]["nickname"].String(), "T111")
|
||||
}
|
||||
|
||||
func TestModel_Where1(t *testing.T) {
|
||||
result, err := db.Table("user").Where("id IN(?)", g.Slice{1,3}).OrderBy("id ASC").All()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
gtest.Assert(len(result), 2)
|
||||
gtest.Assert(result[0]["id"].Int(), 1)
|
||||
gtest.Assert(result[1]["id"].Int(), 3)
|
||||
}
|
||||
|
||||
func TestModel_Where2(t *testing.T) {
|
||||
result, err := db.Table("user").Where("nickname=? AND id IN(?)", "T3", g.Slice{1,3}).OrderBy("id ASC").All()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
gtest.Assert(len(result), 1)
|
||||
gtest.Assert(result[0]["id"].Int(), 3)
|
||||
}
|
||||
|
||||
func TestModel_Delete(t *testing.T) {
|
||||
result, err := db.Table("user").Delete()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
n, _ := result.RowsAffected()
|
||||
gtest.Assert(n, 3)
|
||||
}
|
||||
|
||||
|
||||
372
g/database/gdb/gdb_unit_3_test.go
Normal file
372
g/database/gdb/gdb_unit_3_test.go
Normal file
@ -0,0 +1,372 @@
|
||||
package gdb_test
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gtest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTX_Query(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if rows, err := tx.Query("SELECT ?", 1); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
rows.Close()
|
||||
}
|
||||
if _, err := tx.Query("ERROR"); err == nil {
|
||||
gtest.Fatal("FAIL")
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_Exec(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := tx.Exec("SELECT ?", 1); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := tx.Exec("ERROR"); err == nil {
|
||||
gtest.Fatal("FAIL")
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_Commit(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_Rollback(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if err := tx.Rollback(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_Prepare(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
st, err := tx.Prepare("SELECT 100")
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
rows, err := st.Query()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
array, err := rows.Columns()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
gtest.Assert(array[0], "100")
|
||||
if err := rows.Close(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_Insert(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := tx.Insert("user", g.Map {
|
||||
"id" : 1,
|
||||
"passport" : "t1",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T1",
|
||||
"create_time" : gtime.Now().String(),
|
||||
}); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if n, err := db.Table("user").Count(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(n, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_BatchInsert(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := tx.BatchInsert("user", g.List {
|
||||
{
|
||||
"id" : 2,
|
||||
"passport" : "t",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T2",
|
||||
"create_time" : gtime.Now().String(),
|
||||
},
|
||||
{
|
||||
"id" : 3,
|
||||
"passport" : "t3",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T3",
|
||||
"create_time" : gtime.Now().String(),
|
||||
},
|
||||
}, 10); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if n, err := db.Table("user").Count(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(n, 3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_BatchReplace(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := tx.BatchReplace("user", g.List {
|
||||
{
|
||||
"id" : 2,
|
||||
"passport" : "t2",
|
||||
"password" : "p2",
|
||||
"nickname" : "T2",
|
||||
"create_time" : gtime.Now().String(),
|
||||
},
|
||||
{
|
||||
"id" : 4,
|
||||
"passport" : "t4",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T4",
|
||||
"create_time" : gtime.Now().String(),
|
||||
},
|
||||
}, 10); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
// 数据数量
|
||||
if n, err := db.Table("user").Count(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(n, 4)
|
||||
}
|
||||
// 检查replace后的数值
|
||||
if value, err := db.Table("user").Fields("password").Where("id", 2).Value(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(value.String(), "p2")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_BatchSave(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := tx.BatchSave("user", g.List {
|
||||
{
|
||||
"id" : 4,
|
||||
"passport" : "t4",
|
||||
"password" : "p4",
|
||||
"nickname" : "T4",
|
||||
"create_time" : gtime.Now().String(),
|
||||
},
|
||||
}, 10); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
// 数据数量
|
||||
if n, err := db.Table("user").Count(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(n, 4)
|
||||
}
|
||||
// 检查replace后的数值
|
||||
if value, err := db.Table("user").Fields("password").Where("id", 4).Value(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(value.String(), "p4")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_Replace(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := tx.Replace("user", g.Map {
|
||||
"id" : 1,
|
||||
"passport" : "t11",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T11",
|
||||
"create_time" : gtime.Now().String(),
|
||||
}); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if err := tx.Rollback(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if value, err := db.Table("user").Fields("nickname").Where("id", 1).Value(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(value.String(), "T1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_Save(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := tx.Save("user", g.Map {
|
||||
"id" : 1,
|
||||
"passport" : "t11",
|
||||
"password" : "25d55ad283aa400af464c76d713c07ad",
|
||||
"nickname" : "T11",
|
||||
"create_time" : gtime.Now().String(),
|
||||
}); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if value, err := db.Table("user").Fields("nickname").Where("id", 1).Value(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(value.String(), "T11")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_GetAll(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if result, err := tx.GetAll("SELECT * FROM user WHERE id=?", 1); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(len(result), 1)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_GetOne(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if record, err := tx.GetOne("SELECT * FROM user WHERE passport=?", "t2"); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
if record == nil {
|
||||
gtest.Fatal("FAIL")
|
||||
}
|
||||
gtest.Assert(record["nickname"].String(), "T2")
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_GetValue(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if value, err := tx.GetValue("SELECT id FROM user WHERE passport=?", "t3"); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(value.Int(), 3)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_GetCount(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if count, err := tx.GetCount("SELECT * FROM user"); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(count, 4)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_GetStruct(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
type User struct {
|
||||
Id int
|
||||
Passport string
|
||||
Password string
|
||||
NickName string
|
||||
CreateTime gtime.Time
|
||||
}
|
||||
user := new(User)
|
||||
if err := tx.GetStruct(user, "SELECT * FROM user WHERE id=?", 1); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(user.NickName, "T11")
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTX_Delete(t *testing.T) {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if _, err := tx.Delete("user", nil); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
}
|
||||
if n, err := db.Table("user").Count(); err != nil {
|
||||
gtest.Fatal(err)
|
||||
} else {
|
||||
gtest.Assert(n, 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,16 +4,15 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Kafka Client.
|
||||
// Package gkafka provides producer and consumer client for kafka server/Kafka客户端.
|
||||
package gkafka
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"time"
|
||||
"strings"
|
||||
"gitee.com/johng/gf/third/github.com/Shopify/sarama"
|
||||
"gitee.com/johng/gf/third/github.com/johng-cn/sarama-cluster"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -177,8 +176,6 @@ func (client *Client) Receive() (*Message, error) {
|
||||
case <-notifyChan:
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New("unknown error")
|
||||
}
|
||||
|
||||
// Send data to kafka in synchronized way.
|
||||
|
||||
@ -87,6 +87,12 @@ func (r *Redis) Close() error {
|
||||
return r.pool.Close()
|
||||
}
|
||||
|
||||
// 获得一个原生的redis连接对象,用于自定义连接操作,
|
||||
// 但是需要注意的是如果不再使用该连接对象时,需要手动Close连接,否则会造成连接数超限。
|
||||
func (r *Redis) GetConn() redis.Conn {
|
||||
return r.pool.Get()
|
||||
}
|
||||
|
||||
// 设置属性 - MaxIdle
|
||||
func (r *Redis) SetMaxIdle(value int) {
|
||||
r.pool.MaxIdle = value
|
||||
|
||||
@ -174,24 +174,28 @@ func EncodeFloat64(f float64) []byte {
|
||||
|
||||
// 当b位数不够时,进行高位补0
|
||||
func fillUpSize(b []byte, l int) []byte {
|
||||
if len(b) >= l {
|
||||
return b
|
||||
}
|
||||
c := make([]byte, 0)
|
||||
c = append(c, b...)
|
||||
for i := 0; i <= l - len(b); i++ {
|
||||
for i := 0; i < l - len(b); i++ {
|
||||
c = append(c, 0x00)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// 将二进制解析为int类型,根据[]byte的长度进行自动转换
|
||||
// 将二进制解析为int类型,根据[]byte的长度进行自动转换.
|
||||
// 注意内部使用的是uint*,使用int会造成位丢失。
|
||||
func DecodeToInt(b []byte) int {
|
||||
if len(b) < 2 {
|
||||
return int(DecodeToInt8(b))
|
||||
return int(DecodeToUint8(b))
|
||||
} else if len(b) < 3 {
|
||||
return int(DecodeToInt16(b))
|
||||
return int(DecodeToUint16(b))
|
||||
} else if len(b) < 5 {
|
||||
return int(DecodeToInt32(b))
|
||||
return int(DecodeToUint32(b))
|
||||
} else {
|
||||
return int(DecodeToInt64(b))
|
||||
return int(DecodeToUint64(b))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,103 +9,106 @@
|
||||
package ghash_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"gitee.com/johng/gf/g/encoding/ghash"
|
||||
"gitee.com/johng/gf/g/encoding/gbinary"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
str = []byte("This is the test string for hash.")
|
||||
)
|
||||
|
||||
func BenchmarkBKDRHash(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.BKDRHash(gbinary.EncodeInt(i))
|
||||
ghash.BKDRHash(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBKDRHash64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.BKDRHash64(gbinary.EncodeInt(i))
|
||||
ghash.BKDRHash64(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSDBMHash(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.SDBMHash(gbinary.EncodeInt(i))
|
||||
ghash.SDBMHash(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSDBMHash64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.SDBMHash64(gbinary.EncodeInt(i))
|
||||
ghash.SDBMHash64(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkRSHash(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.RSHash(gbinary.EncodeInt(i))
|
||||
ghash.RSHash(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSRSHash64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.RSHash64(gbinary.EncodeInt(i))
|
||||
ghash.RSHash64(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkJSHash(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.JSHash(gbinary.EncodeInt(i))
|
||||
ghash.JSHash(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkJSHash64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.JSHash64(gbinary.EncodeInt(i))
|
||||
ghash.JSHash64(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPJWHash(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.PJWHash(gbinary.EncodeInt(i))
|
||||
ghash.PJWHash(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPJWHash64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.PJWHash64(gbinary.EncodeInt(i))
|
||||
ghash.PJWHash64(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkELFHash(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.ELFHash(gbinary.EncodeInt(i))
|
||||
ghash.ELFHash(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkELFHash64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.ELFHash64(gbinary.EncodeInt(i))
|
||||
ghash.ELFHash64(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDJBHash(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.DJBHash(gbinary.EncodeInt(i))
|
||||
ghash.DJBHash(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDJBHash64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.DJBHash64(gbinary.EncodeInt(i))
|
||||
ghash.DJBHash64(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAPHash(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.APHash(gbinary.EncodeInt(i))
|
||||
ghash.APHash(str)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAPHash64(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ghash.APHash64(gbinary.EncodeInt(i))
|
||||
ghash.APHash64(str)
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,21 +41,29 @@ type Json struct {
|
||||
func New(value interface{}, safe...bool) *Json {
|
||||
j := (*Json)(nil)
|
||||
switch value.(type) {
|
||||
case map[string]interface{}, []interface{}, nil:
|
||||
j = &Json{
|
||||
p : &value,
|
||||
c : byte(gDEFAULT_SPLIT_CHAR),
|
||||
vc : false ,
|
||||
}
|
||||
default:
|
||||
// 这里效率会比较低
|
||||
b, _ := Encode(value)
|
||||
v, _ := Decode(b)
|
||||
j = &Json{
|
||||
case map[string]interface{}, []interface{}, nil:
|
||||
j = &Json{
|
||||
p : &value,
|
||||
c : byte(gDEFAULT_SPLIT_CHAR),
|
||||
vc : false ,
|
||||
}
|
||||
default:
|
||||
v := (interface{})(nil)
|
||||
if m := gconv.Map(value); m != nil {
|
||||
v = m
|
||||
j = &Json {
|
||||
p : &v,
|
||||
c : byte(gDEFAULT_SPLIT_CHAR),
|
||||
vc : false,
|
||||
}
|
||||
} else {
|
||||
v = gconv.Interfaces(value)
|
||||
j = &Json {
|
||||
p : &v,
|
||||
c : byte(gDEFAULT_SPLIT_CHAR),
|
||||
vc : false,
|
||||
}
|
||||
}
|
||||
}
|
||||
j.mu = rwmutex.New(safe...)
|
||||
return j
|
||||
@ -116,27 +124,27 @@ func LoadContent (data []byte, dataType...string) (*Json, error) {
|
||||
t = dataType[0]
|
||||
}
|
||||
switch t {
|
||||
case "xml": fallthrough
|
||||
case ".xml":
|
||||
data, err = gxml.ToJson(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "yml": fallthrough
|
||||
case "yaml": fallthrough
|
||||
case ".yml": fallthrough
|
||||
case ".yaml":
|
||||
data, err = gyaml.ToJson(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "xml": fallthrough
|
||||
case ".xml":
|
||||
data, err = gxml.ToJson(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "yml": fallthrough
|
||||
case "yaml": fallthrough
|
||||
case ".yml": fallthrough
|
||||
case ".yaml":
|
||||
data, err = gyaml.ToJson(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case "toml": fallthrough
|
||||
case ".toml":
|
||||
data, err = gtoml.ToJson(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "toml": fallthrough
|
||||
case ".toml":
|
||||
data, err = gtoml.ToJson(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return nil, err
|
||||
@ -328,136 +336,136 @@ func (j *Json) setValue(pattern string, value interface{}, removed bool) error {
|
||||
defer j.mu.Unlock()
|
||||
for i:= 0; i < length; i++ {
|
||||
switch (*pointer).(type) {
|
||||
case map[string]interface{}:
|
||||
if i == length - 1 {
|
||||
case map[string]interface{}:
|
||||
if i == length - 1 {
|
||||
if removed && value == nil {
|
||||
// 删除map元素
|
||||
delete((*pointer).(map[string]interface{}), array[i])
|
||||
} else {
|
||||
(*pointer).(map[string]interface{})[array[i]] = value
|
||||
}
|
||||
} else {
|
||||
// 当键名不存在的情况这里会进行处理
|
||||
if v, ok := (*pointer).(map[string]interface{})[array[i]]; !ok {
|
||||
if removed && value == nil {
|
||||
// 删除map元素
|
||||
delete((*pointer).(map[string]interface{}), array[i])
|
||||
} else {
|
||||
(*pointer).(map[string]interface{})[array[i]] = value
|
||||
goto done
|
||||
}
|
||||
} else {
|
||||
// 当键名不存在的情况这里会进行处理
|
||||
if v, ok := (*pointer).(map[string]interface{})[array[i]]; !ok {
|
||||
if removed && value == nil {
|
||||
goto done
|
||||
}
|
||||
// 创建新节点
|
||||
if gstr.IsNumeric(array[i + 1]) {
|
||||
// 创建array节点
|
||||
n, _ := strconv.Atoi(array[i + 1])
|
||||
var v interface{} = make([]interface{}, n + 1)
|
||||
pparent = j.setPointerWithValue(pointer, array[i], v)
|
||||
pointer = &v
|
||||
} else {
|
||||
// 创建map节点
|
||||
var v interface{} = make(map[string]interface{})
|
||||
pparent = j.setPointerWithValue(pointer, array[i], v)
|
||||
pointer = &v
|
||||
}
|
||||
} else {
|
||||
pparent = pointer
|
||||
pointer = &v
|
||||
}
|
||||
}
|
||||
|
||||
case []interface{}:
|
||||
// 键名与当前指针类型不符合,需要执行**覆盖操作**
|
||||
if !gstr.IsNumeric(array[i]) {
|
||||
if i == length - 1 {
|
||||
*pointer = map[string]interface{}{ array[i] : value }
|
||||
} else {
|
||||
var v interface{} = make(map[string]interface{})
|
||||
*pointer = v
|
||||
pparent = pointer
|
||||
pointer = &v
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
valn, err := strconv.Atoi(array[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 叶子节点
|
||||
if i == length - 1 {
|
||||
if len((*pointer).([]interface{})) > valn {
|
||||
if removed && value == nil {
|
||||
// 删除数据元素
|
||||
j.setPointerWithValue(pparent, array[i - 1], append((*pointer).([]interface{})[ : valn], (*pointer).([]interface{})[valn + 1 : ]...))
|
||||
} else {
|
||||
(*pointer).([]interface{})[valn] = value
|
||||
}
|
||||
} else {
|
||||
if removed && value == nil {
|
||||
goto done
|
||||
}
|
||||
if pparent == nil {
|
||||
// 表示根节点
|
||||
j.setPointerWithValue(pointer, array[i], value)
|
||||
} else {
|
||||
// 非根节点
|
||||
s := make([]interface{}, valn + 1)
|
||||
copy(s, (*pointer).([]interface{}))
|
||||
s[valn] = value
|
||||
j.setPointerWithValue(pparent, array[i - 1], s)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 创建新节点
|
||||
if gstr.IsNumeric(array[i + 1]) {
|
||||
// 创建array节点
|
||||
n, _ := strconv.Atoi(array[i + 1])
|
||||
if len((*pointer).([]interface{})) > valn {
|
||||
(*pointer).([]interface{})[valn] = make([]interface{}, n + 1)
|
||||
pparent = pointer
|
||||
pointer = &(*pointer).([]interface{})[valn]
|
||||
} else {
|
||||
if removed && value == nil {
|
||||
goto done
|
||||
}
|
||||
var v interface{} = make([]interface{}, n + 1)
|
||||
pparent = j.setPointerWithValue(pointer, array[i], v)
|
||||
pointer = &v
|
||||
}
|
||||
var v interface{} = make([]interface{}, n + 1)
|
||||
pparent = j.setPointerWithValue(pointer, array[i], v)
|
||||
pointer = &v
|
||||
} else {
|
||||
// 创建map节点
|
||||
var v interface{} = make(map[string]interface{})
|
||||
pparent = j.setPointerWithValue(pointer, array[i], v)
|
||||
pointer = &v
|
||||
}
|
||||
} else {
|
||||
pparent = pointer
|
||||
pointer = &v
|
||||
}
|
||||
}
|
||||
|
||||
// 如果当前指针指向的变量不是引用类型的,
|
||||
// 那么修改变量必须通过父级进行修改,即 pparent
|
||||
default:
|
||||
if removed && value == nil {
|
||||
goto done
|
||||
case []interface{}:
|
||||
// 键名与当前指针类型不符合,需要执行**覆盖操作**
|
||||
if !gstr.IsNumeric(array[i]) {
|
||||
if i == length - 1 {
|
||||
*pointer = map[string]interface{}{ array[i] : value }
|
||||
} else {
|
||||
var v interface{} = make(map[string]interface{})
|
||||
*pointer = v
|
||||
pparent = pointer
|
||||
pointer = &v
|
||||
}
|
||||
if gstr.IsNumeric(array[i]) {
|
||||
n, _ := strconv.Atoi(array[i])
|
||||
s := make([]interface{}, n + 1)
|
||||
if i == length - 1 {
|
||||
s[n] = value
|
||||
}
|
||||
if pparent != nil {
|
||||
pparent = j.setPointerWithValue(pparent, array[i - 1], s)
|
||||
continue
|
||||
}
|
||||
|
||||
valn, err := strconv.Atoi(array[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 叶子节点
|
||||
if i == length - 1 {
|
||||
if len((*pointer).([]interface{})) > valn {
|
||||
if removed && value == nil {
|
||||
// 删除数据元素
|
||||
j.setPointerWithValue(pparent, array[i - 1], append((*pointer).([]interface{})[ : valn], (*pointer).([]interface{})[valn + 1 : ]...))
|
||||
} else {
|
||||
*pointer = s
|
||||
pparent = pointer
|
||||
(*pointer).([]interface{})[valn] = value
|
||||
}
|
||||
} else {
|
||||
if removed && value == nil {
|
||||
goto done
|
||||
}
|
||||
if pparent == nil {
|
||||
// 表示根节点
|
||||
j.setPointerWithValue(pointer, array[i], value)
|
||||
} else {
|
||||
// 非根节点
|
||||
s := make([]interface{}, valn + 1)
|
||||
copy(s, (*pointer).([]interface{}))
|
||||
s[valn] = value
|
||||
j.setPointerWithValue(pparent, array[i - 1], s)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if gstr.IsNumeric(array[i + 1]) {
|
||||
n, _ := strconv.Atoi(array[i + 1])
|
||||
if len((*pointer).([]interface{})) > valn {
|
||||
(*pointer).([]interface{})[valn] = make([]interface{}, n + 1)
|
||||
pparent = pointer
|
||||
pointer = &(*pointer).([]interface{})[valn]
|
||||
} else {
|
||||
if removed && value == nil {
|
||||
goto done
|
||||
}
|
||||
var v interface{} = make([]interface{}, n + 1)
|
||||
pparent = j.setPointerWithValue(pointer, array[i], v)
|
||||
pointer = &v
|
||||
}
|
||||
} else {
|
||||
var v interface{} = make(map[string]interface{})
|
||||
if i == length - 1 {
|
||||
v = map[string]interface{}{
|
||||
array[i] : value,
|
||||
}
|
||||
}
|
||||
if pparent != nil {
|
||||
pparent = j.setPointerWithValue(pparent, array[i - 1], v)
|
||||
} else {
|
||||
*pointer = v
|
||||
pparent = pointer
|
||||
}
|
||||
pparent = j.setPointerWithValue(pointer, array[i], v)
|
||||
pointer = &v
|
||||
}
|
||||
}
|
||||
|
||||
// 如果当前指针指向的变量不是引用类型的,
|
||||
// 那么修改变量必须通过父级进行修改,即 pparent
|
||||
default:
|
||||
if removed && value == nil {
|
||||
goto done
|
||||
}
|
||||
if gstr.IsNumeric(array[i]) {
|
||||
n, _ := strconv.Atoi(array[i])
|
||||
s := make([]interface{}, n + 1)
|
||||
if i == length - 1 {
|
||||
s[n] = value
|
||||
}
|
||||
if pparent != nil {
|
||||
pparent = j.setPointerWithValue(pparent, array[i - 1], s)
|
||||
} else {
|
||||
*pointer = s
|
||||
pparent = pointer
|
||||
}
|
||||
} else {
|
||||
var v interface{} = make(map[string]interface{})
|
||||
if i == length - 1 {
|
||||
v = map[string]interface{}{
|
||||
array[i] : value,
|
||||
}
|
||||
}
|
||||
if pparent != nil {
|
||||
pparent = j.setPointerWithValue(pparent, array[i - 1], v)
|
||||
} else {
|
||||
*pointer = v
|
||||
pparent = pointer
|
||||
}
|
||||
pointer = &v
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -468,41 +476,40 @@ done:
|
||||
// 数据结构转换,map参数必须转换为map[string]interface{},数组参数必须转换为[]interface{}
|
||||
func (j *Json) convertValue(value interface{}) interface{} {
|
||||
switch value.(type) {
|
||||
case map[string]interface{}:
|
||||
return value
|
||||
case []interface{}:
|
||||
return value
|
||||
default:
|
||||
// 这里效率会比较低,当然比直接用反射也不会差到哪儿去
|
||||
// 为了操作的灵活性,牺牲了一定的效率
|
||||
b, _ := Encode(value)
|
||||
v, _ := Decode(b)
|
||||
return v
|
||||
case map[string]interface{}:
|
||||
return value
|
||||
case []interface{}:
|
||||
return value
|
||||
default:
|
||||
// 这里效率会比较低,当然比直接用反射也不会差到哪儿去
|
||||
// 为了操作的灵活性,牺牲了一定的效率
|
||||
b, _ := Encode(value)
|
||||
v, _ := Decode(b)
|
||||
return v
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// 用于Set方法中,对指针指向的内存地址进行赋值
|
||||
// 返回修改后的父级指针
|
||||
func (j *Json) setPointerWithValue(pointer *interface{}, key string, value interface{}) *interface{} {
|
||||
switch (*pointer).(type) {
|
||||
case map[string]interface{}:
|
||||
(*pointer).(map[string]interface{})[key] = value
|
||||
return &value
|
||||
case []interface{}:
|
||||
n, _ := strconv.Atoi(key)
|
||||
if len((*pointer).([]interface{})) > n {
|
||||
(*pointer).([]interface{})[n] = value
|
||||
return &(*pointer).([]interface{})[n]
|
||||
} else {
|
||||
s := make([]interface{}, n + 1)
|
||||
copy(s, (*pointer).([]interface{}))
|
||||
s[n] = value
|
||||
*pointer = s
|
||||
return &s[n]
|
||||
}
|
||||
default:
|
||||
*pointer = value
|
||||
case map[string]interface{}:
|
||||
(*pointer).(map[string]interface{})[key] = value
|
||||
return &value
|
||||
case []interface{}:
|
||||
n, _ := strconv.Atoi(key)
|
||||
if len((*pointer).([]interface{})) > n {
|
||||
(*pointer).([]interface{})[n] = value
|
||||
return &(*pointer).([]interface{})[n]
|
||||
} else {
|
||||
s := make([]interface{}, n + 1)
|
||||
copy(s, (*pointer).([]interface{}))
|
||||
s[n] = value
|
||||
*pointer = s
|
||||
return &s[n]
|
||||
}
|
||||
default:
|
||||
*pointer = value
|
||||
}
|
||||
return pointer
|
||||
}
|
||||
@ -535,12 +542,12 @@ func (j *Json) Len(pattern string) int {
|
||||
p := j.getPointerByPattern(pattern)
|
||||
if p != nil {
|
||||
switch (*p).(type) {
|
||||
case map[string]interface{}:
|
||||
return len((*p).(map[string]interface{}))
|
||||
case []interface{}:
|
||||
return len((*p).([]interface{}))
|
||||
default:
|
||||
return -1
|
||||
case map[string]interface{}:
|
||||
return len((*p).(map[string]interface{}))
|
||||
case []interface{}:
|
||||
return len((*p).([]interface{}))
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return -1
|
||||
@ -627,17 +634,17 @@ func (j *Json) getPointerByPatternWithoutSplitCharViolenceCheck(pattern string)
|
||||
// 注意这里返回的指针都是临时变量的内存地址
|
||||
func (j *Json) checkPatternByPointer(key string, pointer *interface{}) *interface{} {
|
||||
switch (*pointer).(type) {
|
||||
case map[string]interface{}:
|
||||
if v, ok := (*pointer).(map[string]interface{})[key]; ok {
|
||||
return &v
|
||||
}
|
||||
case []interface{}:
|
||||
if gstr.IsNumeric(key) {
|
||||
n, err := strconv.Atoi(key)
|
||||
if err == nil && len((*pointer).([]interface{})) > n {
|
||||
return &(*pointer).([]interface{})[n]
|
||||
}
|
||||
case map[string]interface{}:
|
||||
if v, ok := (*pointer).(map[string]interface{})[key]; ok {
|
||||
return &v
|
||||
}
|
||||
case []interface{}:
|
||||
if gstr.IsNumeric(key) {
|
||||
n, err := strconv.Atoi(key)
|
||||
if err == nil && len((*pointer).([]interface{})) > n {
|
||||
return &(*pointer).([]interface{})[n]
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -647,10 +654,10 @@ func (j *Json) ToMap() map[string]interface{} {
|
||||
j.mu.RLock()
|
||||
defer j.mu.RUnlock()
|
||||
switch (*(j.p)).(type) {
|
||||
case map[string]interface{}:
|
||||
return (*(j.p)).(map[string]interface{})
|
||||
default:
|
||||
return nil
|
||||
case map[string]interface{}:
|
||||
return (*(j.p)).(map[string]interface{})
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -659,10 +666,10 @@ func (j *Json) ToArray() []interface{} {
|
||||
j.mu.RLock()
|
||||
defer j.mu.RUnlock()
|
||||
switch (*(j.p)).(type) {
|
||||
case []interface{}:
|
||||
return (*(j.p)).([]interface{})
|
||||
default:
|
||||
return nil
|
||||
case []interface{}:
|
||||
return (*(j.p)).([]interface{})
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,18 +9,18 @@
|
||||
package gins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"gitee.com/johng/gf/g/database/gdb"
|
||||
"gitee.com/johng/gf/g/database/gredis"
|
||||
"gitee.com/johng/gf/g/os/gcfg"
|
||||
"gitee.com/johng/gf/g/os/gcmd"
|
||||
"gitee.com/johng/gf/g/os/genv"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/os/gfsnotify"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/os/gview"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/database/gdb"
|
||||
"gitee.com/johng/gf/g/os/gfsnotify"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/database/gredis"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
)
|
||||
|
||||
@ -79,7 +79,7 @@ func View(name...string) *gview.View {
|
||||
path = gfile.SelfDir()
|
||||
}
|
||||
}
|
||||
view := gview.Get(path)
|
||||
view := gview.New(path)
|
||||
// 添加基于源码的搜索目录检索地址,常用于开发环境调试,只添加入口文件目录
|
||||
if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) {
|
||||
view.AddPath(p)
|
||||
@ -116,7 +116,7 @@ func Config(file...string) *gcfg.Config {
|
||||
}
|
||||
|
||||
// 数据库操作对象,使用了连接池
|
||||
func Database(name...string) *gdb.Db {
|
||||
func Database(name...string) gdb.DB {
|
||||
config := Config()
|
||||
group := gdb.DEFAULT_GROUP_NAME
|
||||
if len(name) > 0 {
|
||||
@ -124,64 +124,67 @@ func Database(name...string) *gdb.Db {
|
||||
}
|
||||
key := fmt.Sprintf("%s.%s", gFRAME_CORE_COMPONENT_NAME_DATABASE, group)
|
||||
db := instances.GetOrSetFuncLock(key, func() interface{} {
|
||||
m := config.GetMap("database")
|
||||
if m == nil {
|
||||
glog.Errorfln(`incomplete configuration for database: "database" node not found in config file "%s"`, config.GetFilePath())
|
||||
}
|
||||
for group, v := range m {
|
||||
cg := gdb.ConfigGroup{}
|
||||
if list, ok := v.([]interface{}); ok {
|
||||
for _, nodev := range list {
|
||||
node := gdb.ConfigNode{}
|
||||
nodem := nodev.(map[string]interface{})
|
||||
if value, ok := nodem["host"]; ok {
|
||||
node.Host = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["port"]; ok {
|
||||
node.Port = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["user"]; ok {
|
||||
node.User = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["pass"]; ok {
|
||||
node.Pass = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["name"]; ok {
|
||||
node.Name = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["type"]; ok {
|
||||
node.Type = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["role"]; ok {
|
||||
node.Role = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["charset"]; ok {
|
||||
node.Charset = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["priority"]; ok {
|
||||
node.Priority = gconv.Int(value)
|
||||
}
|
||||
if value, ok := nodem["linkinfo"]; ok {
|
||||
node.Linkinfo = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["max-idle"]; ok {
|
||||
node.MaxIdleConnCount = gconv.Int(value)
|
||||
}
|
||||
if value, ok := nodem["max-open"]; ok {
|
||||
node.MaxOpenConnCount = gconv.Int(value)
|
||||
}
|
||||
if value, ok := nodem["max-lifetime"]; ok {
|
||||
node.MaxConnLifetime = gconv.Int(value)
|
||||
}
|
||||
cg = append(cg, node)
|
||||
}
|
||||
if gdb.GetConfig(group) == nil {
|
||||
m := config.GetMap("database")
|
||||
if m == nil {
|
||||
glog.Error(`database init failed: "database" node not found, is config file or configuration missing?`)
|
||||
return nil
|
||||
}
|
||||
gdb.AddConfigGroup(group, cg)
|
||||
for group, v := range m {
|
||||
cg := gdb.ConfigGroup{}
|
||||
if list, ok := v.([]interface{}); ok {
|
||||
for _, nodev := range list {
|
||||
node := gdb.ConfigNode{}
|
||||
nodem := nodev.(map[string]interface{})
|
||||
if value, ok := nodem["host"]; ok {
|
||||
node.Host = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["port"]; ok {
|
||||
node.Port = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["user"]; ok {
|
||||
node.User = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["pass"]; ok {
|
||||
node.Pass = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["name"]; ok {
|
||||
node.Name = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["type"]; ok {
|
||||
node.Type = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["role"]; ok {
|
||||
node.Role = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["charset"]; ok {
|
||||
node.Charset = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["priority"]; ok {
|
||||
node.Priority = gconv.Int(value)
|
||||
}
|
||||
if value, ok := nodem["linkinfo"]; ok {
|
||||
node.Linkinfo = gconv.String(value)
|
||||
}
|
||||
if value, ok := nodem["max-idle"]; ok {
|
||||
node.MaxIdleConnCount = gconv.Int(value)
|
||||
}
|
||||
if value, ok := nodem["max-open"]; ok {
|
||||
node.MaxOpenConnCount = gconv.Int(value)
|
||||
}
|
||||
if value, ok := nodem["max-lifetime"]; ok {
|
||||
node.MaxConnLifetime = gconv.Int(value)
|
||||
}
|
||||
cg = append(cg, node)
|
||||
}
|
||||
}
|
||||
gdb.AddConfigGroup(group, cg)
|
||||
}
|
||||
// 使用gfsnotify进行文件监控,当配置文件有任何变化时,清空数据库配置缓存
|
||||
gfsnotify.Add(config.GetFilePath(), func(event *gfsnotify.Event) {
|
||||
instances.Remove(key)
|
||||
})
|
||||
}
|
||||
// 使用gfsnotify进行文件监控,当配置文件有任何变化时,清空数据库配置缓存
|
||||
gfsnotify.Add(config.GetFilePath(), func(event *gfsnotify.Event) {
|
||||
instances.Remove(key)
|
||||
})
|
||||
if db, err := gdb.New(name...); err == nil {
|
||||
return db
|
||||
} else {
|
||||
@ -190,7 +193,7 @@ func Database(name...string) *gdb.Db {
|
||||
return nil
|
||||
})
|
||||
if db != nil {
|
||||
return db.(*gdb.Db)
|
||||
return db.(gdb.DB)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ func (c *Controller) Init(r *ghttp.Request) {
|
||||
}
|
||||
|
||||
// 控制器结束请求接口方法
|
||||
func (c *Controller) Shut(r *ghttp.Request) {
|
||||
func (c *Controller) Shut() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
23
g/g.go
23
g/g.go
@ -10,14 +10,27 @@ package g
|
||||
import "gitee.com/johng/gf/g/container/gvar"
|
||||
|
||||
// 框架动态变量,可以用该类型替代interface{}类型
|
||||
type Var = gvar.Var
|
||||
type Var = gvar.Var
|
||||
|
||||
// 常用map数据结构(使用别名)
|
||||
type Map = map[string]interface{}
|
||||
type Map = map[string]interface{}
|
||||
type MapStrStr = map[string]string
|
||||
type MapStrInt = map[string]int
|
||||
type MapIntStr = map[int]string
|
||||
type MapIntInt = map[int]int
|
||||
|
||||
// 常用list数据结构(使用别名)
|
||||
type List = []Map
|
||||
type List = []Map
|
||||
type ListStrStr = []map[string]string
|
||||
type ListStrInt = []map[string]int
|
||||
type ListIntStr = []map[int]string
|
||||
type ListIntInt = []map[int]int
|
||||
|
||||
|
||||
// 常用slice数据结构(使用别名)
|
||||
type Slice = []interface{}
|
||||
type Array = Slice
|
||||
type Slice = []interface{}
|
||||
type SliceStr = []string
|
||||
type SliceInt = []int
|
||||
type Array = Slice
|
||||
type ArrayStr = SliceStr
|
||||
type ArrayInt = SliceInt
|
||||
|
||||
@ -23,12 +23,12 @@ func Server(name...interface{}) *ghttp.Server {
|
||||
}
|
||||
|
||||
// TCPServer单例对象
|
||||
func TcpServer(name...interface{}) *gtcp.Server {
|
||||
func TCPServer(name...interface{}) *gtcp.Server {
|
||||
return gtcp.GetServer(name...)
|
||||
}
|
||||
|
||||
// UDPServer单例对象
|
||||
func UdpServer(name...interface{}) *gudp.Server {
|
||||
func UDPServer(name...interface{}) *gudp.Server {
|
||||
return gudp.GetServer(name...)
|
||||
}
|
||||
|
||||
@ -44,12 +44,12 @@ func Config(file...string) *gcfg.Config {
|
||||
}
|
||||
|
||||
// 数据库操作对象,使用了连接池
|
||||
func Database(name...string) *gdb.Db {
|
||||
func Database(name...string) gdb.DB {
|
||||
return gins.Database(name...)
|
||||
}
|
||||
|
||||
// (别名)Database
|
||||
func DB(name...string) *gdb.Db {
|
||||
func DB(name...string) gdb.DB {
|
||||
return gins.Database(name...)
|
||||
}
|
||||
|
||||
|
||||
@ -10,5 +10,5 @@ package ghttp
|
||||
// 控制器接口
|
||||
type Controller interface {
|
||||
Init(*Request)
|
||||
Shut(*Request)
|
||||
Shut()
|
||||
}
|
||||
|
||||
@ -20,24 +20,23 @@ import (
|
||||
// 请求对象
|
||||
type Request struct {
|
||||
http.Request
|
||||
parsedGet bool // GET参数是否已经解析
|
||||
parsedPost bool // POST参数是否已经解析
|
||||
queryVars map[string][]string // GET参数
|
||||
routerVars map[string][]string // 路由解析参数
|
||||
exit bool // 是否退出当前请求流程执行
|
||||
Id int // 请求id(唯一)
|
||||
Server *Server // 请求关联的服务器对象
|
||||
Cookie *Cookie // 与当前请求绑定的Cookie对象(并发安全)
|
||||
Session *Session // 与当前请求绑定的Session对象(并发安全)
|
||||
Response *Response // 对应请求的返回数据操作对象
|
||||
Router *Router // 匹配到的路由对象
|
||||
EnterTime int64 // 请求进入时间(微秒)
|
||||
LeaveTime int64 // 请求完成时间(微秒)
|
||||
Param interface{} // 开发者自定义参数
|
||||
parsedHost string // 解析过后不带端口号的服务器域名名称
|
||||
clientIp string // 解析过后的客户端IP地址
|
||||
isFileRequest bool // 是否为静态文件请求(非服务请求,当静态文件存在时,优先级会被服务请求高,被识别为文件请求)
|
||||
isFileServe bool // 是否为文件处理(调用Server.serveFile时设置为true), isFileRequest为true时isFileServe也为true
|
||||
parsedGet bool // GET参数是否已经解析
|
||||
parsedPost bool // POST参数是否已经解析
|
||||
queryVars map[string][]string // GET参数
|
||||
routerVars map[string][]string // 路由解析参数
|
||||
exit bool // 是否退出当前请求流程执行
|
||||
Id int // 请求id(唯一)
|
||||
Server *Server // 请求关联的服务器对象
|
||||
Cookie *Cookie // 与当前请求绑定的Cookie对象(并发安全)
|
||||
Session *Session // 与当前请求绑定的Session对象(并发安全)
|
||||
Response *Response // 对应请求的返回数据操作对象
|
||||
Router *Router // 匹配到的路由对象
|
||||
EnterTime int64 // 请求进入时间(微秒)
|
||||
LeaveTime int64 // 请求完成时间(微秒)
|
||||
params map[string]interface{} // 开发者自定义参数(请求流程中有效)
|
||||
parsedHost string // 解析过后不带端口号的服务器域名名称
|
||||
clientIp string // 解析过后的客户端IP地址
|
||||
isFileRequest bool // 是否为静态文件请求(非服务请求,当静态文件存在时,优先级会被服务请求高,被识别为文件请求)
|
||||
}
|
||||
|
||||
// 创建一个Request对象
|
||||
@ -74,7 +73,8 @@ func (r *Request) Get(key string, def ... string) string {
|
||||
return r.GetRequestString(key, def...)
|
||||
}
|
||||
|
||||
func (r *Request) GetVar(key string, def ... interface{}) *gvar.Var {
|
||||
// 建议都用该参数替代参数获取
|
||||
func (r *Request) GetVar(key string, def ... interface{}) gvar.VarRead {
|
||||
return r.GetRequestVar(key, def...)
|
||||
}
|
||||
|
||||
@ -173,11 +173,6 @@ func (r *Request) IsFileRequest() bool {
|
||||
return r.isFileRequest
|
||||
}
|
||||
|
||||
// 判断请求是否为文件处理
|
||||
func (r *Request) IsFileServe() bool {
|
||||
return r.isFileServe
|
||||
}
|
||||
|
||||
// 判断是否为AJAX请求
|
||||
func (r *Request) IsAjaxRequest() bool {
|
||||
return strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest")
|
||||
@ -203,7 +198,7 @@ func (r *Request) GetReferer() string {
|
||||
return r.Header.Get("Referer")
|
||||
}
|
||||
|
||||
// 获得结构体顶替的参数名称标签,构成map返回
|
||||
// 获得结构体对象的参数名称标签,构成map返回
|
||||
func (r *Request) getStructParamsTagMap(object interface{}) map[string]string {
|
||||
tagmap := make(map[string]string)
|
||||
fields := structs.Fields(object)
|
||||
|
||||
28
g/net/ghttp/ghttp_request_params.go
Normal file
28
g/net/ghttp/ghttp_request_params.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
|
||||
|
||||
package ghttp
|
||||
|
||||
import "gitee.com/johng/gf/g/container/gvar"
|
||||
|
||||
// 设置请求流程共享变量
|
||||
func (r *Request) SetParam(key string, value interface{}) {
|
||||
if r.params == nil {
|
||||
r.params = make(map[string]interface{})
|
||||
}
|
||||
r.params[key] = value
|
||||
}
|
||||
|
||||
// 获取请求流程共享变量
|
||||
func (r *Request) GetParam(key string) gvar.VarRead {
|
||||
if r.params != nil {
|
||||
if v, ok := r.params[key]; ok {
|
||||
return gvar.New(v, false)
|
||||
}
|
||||
}
|
||||
return gvar.New(nil, false)
|
||||
}
|
||||
|
||||
@ -26,15 +26,15 @@ func (r *Request) GetRequest(key string, def ... []string) []string {
|
||||
return v
|
||||
}
|
||||
|
||||
func (r *Request) GetRequestVar(key string, def ... interface{}) *gvar.Var {
|
||||
func (r *Request) GetRequestVar(key string, def ... interface{}) gvar.VarRead {
|
||||
value := r.GetRequest(key)
|
||||
if value != nil {
|
||||
return gvar.New(value)
|
||||
return gvar.New(value[0], false)
|
||||
}
|
||||
if len(def) > 0 {
|
||||
return gvar.New(def[0])
|
||||
return gvar.New(def[0], false)
|
||||
}
|
||||
return nil
|
||||
return gvar.New(nil, false)
|
||||
}
|
||||
|
||||
func (r *Request) GetRequestString(key string, def ... string) string {
|
||||
|
||||
@ -9,12 +9,12 @@ package ghttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"net/http"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/encoding/gparser"
|
||||
"strconv"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/encoding/gparser"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// 服务端请求返回对象。
|
||||
@ -158,11 +158,8 @@ func (r *Response) WriteStatus(status int, content...string) {
|
||||
|
||||
// 静态文件处理
|
||||
func (r *Response) ServeFile(path string) {
|
||||
r.request.isFileServe = true
|
||||
// 首先判断是否给定的path已经是一个绝对路径
|
||||
if !gfile.Exists(path) {
|
||||
path, _ = r.Server.paths.Search(path)
|
||||
}
|
||||
path = gfile.RealPath(path)
|
||||
if path == "" {
|
||||
r.WriteStatus(http.StatusNotFound)
|
||||
return
|
||||
@ -170,10 +167,31 @@ func (r *Response) ServeFile(path string) {
|
||||
r.Server.serveFile(r.request, path)
|
||||
}
|
||||
|
||||
// 静态文件下载处理
|
||||
func (r *Response) ServeFileDownload(path string, name...string) {
|
||||
// 首先判断是否给定的path已经是一个绝对路径
|
||||
path = gfile.RealPath(path)
|
||||
if path == "" {
|
||||
r.WriteStatus(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
downloadName := ""
|
||||
if len(name) > 0 {
|
||||
downloadName = name[0]
|
||||
} else {
|
||||
downloadName = gfile.Basename(path)
|
||||
}
|
||||
r.Header().Set("Content-Type", "application/force-download")
|
||||
r.Header().Set("Accept-Ranges", "bytes")
|
||||
r.Header().Set("Content-Disposition", fmt.Sprintf(`attachment;filename="%s"`, downloadName))
|
||||
r.Server.serveFile(r.request, path)
|
||||
}
|
||||
|
||||
// 返回location标识,引导客户端跳转
|
||||
func (r *Response) RedirectTo(location string) {
|
||||
r.Header().Set("Location", location)
|
||||
r.WriteHeader(http.StatusFound)
|
||||
r.request.Exit()
|
||||
}
|
||||
|
||||
// 返回location标识,引导客户端跳转到来源页面
|
||||
|
||||
@ -18,7 +18,6 @@ import (
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/os/gproc"
|
||||
"gitee.com/johng/gf/g/os/gspath"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
@ -33,6 +32,74 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// Server结构体
|
||||
Server struct {
|
||||
// 基本属性变量
|
||||
name string // 服务名称,方便识别
|
||||
config ServerConfig // 配置对象
|
||||
servers []*gracefulServer // 底层http.Server列表
|
||||
methodsMap map[string]struct{} // 所有支持的HTTP Method(初始化时自动填充)
|
||||
servedCount *gtype.Int // 已经服务的请求数(4-8字节,不考虑溢出情况),同时作为请求ID
|
||||
// 服务注册相关
|
||||
serveTree map[string]interface{} // 所有注册的服务回调函数(路由表,树型结构,哈希表+链表优先级匹配)
|
||||
hooksTree map[string]interface{} // 所有注册的事件回调函数(路由表,树型结构,哈希表+链表优先级匹配)
|
||||
serveCache *gcache.Cache // 服务注册路由内存缓存
|
||||
hooksCache *gcache.Cache // 事件回调路由内存缓存
|
||||
routesMap map[string][]registeredRouteItem // 已经注册的路由及对应的注册方法文件地址(用以路由重复注册判断)
|
||||
// 自定义状态码回调
|
||||
hsmu sync.RWMutex // status handler互斥锁
|
||||
statusHandlerMap map[string]HandlerFunc // 不同状态码下的注册处理方法(例如404状态时的处理方法)
|
||||
// SESSION
|
||||
sessions *gcache.Cache // Session内存缓存
|
||||
// Logger
|
||||
logger *glog.Logger // 日志管理对象
|
||||
}
|
||||
|
||||
// 路由对象
|
||||
Router struct {
|
||||
Uri string // 注册时的pattern - uri
|
||||
Method string // 注册时的pattern - method
|
||||
Domain string // 注册时的pattern - domain
|
||||
RegRule string // 路由规则解析后对应的正则表达式
|
||||
RegNames []string // 路由规则解析后对应的变量名称数组
|
||||
Priority int // 优先级,用于链表排序,值越大优先级越高
|
||||
}
|
||||
|
||||
// http回调函数注册信息
|
||||
handlerItem struct {
|
||||
name string // 注册的方法名称信息
|
||||
rtype int // 注册方式(执行对象/回调函数/控制器)
|
||||
ctype reflect.Type // 控制器类型(反射类型)
|
||||
fname string // 回调方法名称
|
||||
faddr HandlerFunc // 准确的执行方法内存地址(与以上两个参数二选一)
|
||||
finit HandlerFunc // 初始化请求回调方法(执行对象注册方式下有效)
|
||||
fshut HandlerFunc // 完成请求回调方法(执行对象注册方式下有效)
|
||||
router *Router // 注册时绑定的路由对象
|
||||
}
|
||||
|
||||
// 根据特定URL.Path解析后的路由检索结果项
|
||||
handlerParsedItem struct {
|
||||
handler *handlerItem // 路由注册项
|
||||
values map[string][]string // 特定URL.Path的Router解析参数
|
||||
}
|
||||
|
||||
// 已注册的路由项
|
||||
registeredRouteItem struct {
|
||||
file string // 文件路径及行数地址
|
||||
handler *handlerItem // 路由注册项
|
||||
}
|
||||
|
||||
// pattern与回调函数的绑定map
|
||||
handlerMap = map[string]*handlerItem
|
||||
|
||||
// HTTP注册函数
|
||||
HandlerFunc = func(r *Request)
|
||||
|
||||
// 文件描述符map
|
||||
listenerFdMap = map[string]string
|
||||
)
|
||||
|
||||
const (
|
||||
SERVER_STATUS_STOPPED = 0 // Server状态:停止
|
||||
SERVER_STATUS_RUNNING = 1 // Server状态:运行
|
||||
@ -42,8 +109,7 @@ const (
|
||||
HOOK_AFTER_OUTPUT = "AfterOutput"
|
||||
HOOK_BEFORE_CLOSE = "BeforeClose"
|
||||
HOOK_AFTER_CLOSE = "AfterClose"
|
||||
)
|
||||
const (
|
||||
|
||||
gHTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
|
||||
gDEFAULT_SERVER = "default"
|
||||
gDEFAULT_DOMAIN = "default"
|
||||
@ -54,93 +120,27 @@ const (
|
||||
gEXCEPTION_EXIT = "exit"
|
||||
)
|
||||
|
||||
// ghttp.Server结构体
|
||||
type Server struct {
|
||||
// 基本属性变量
|
||||
name string // 服务名称,方便识别
|
||||
paths *gspath.SPath // 静态文件检索对象(类似nginx tryfile功能)
|
||||
config ServerConfig // 配置对象
|
||||
servers []*gracefulServer // 底层http.Server列表
|
||||
methodsMap map[string]struct{} // 所有支持的HTTP Method(初始化时自动填充)
|
||||
servedCount *gtype.Int // 已经服务的请求数(4-8字节,不考虑溢出情况),同时作为请求ID
|
||||
// 服务注册相关
|
||||
serveTree map[string]interface{} // 所有注册的服务回调函数(路由表,树型结构,哈希表+链表优先级匹配)
|
||||
hooksTree map[string]interface{} // 所有注册的事件回调函数(路由表,树型结构,哈希表+链表优先级匹配)
|
||||
serveCache *gmap.StringInterfaceMap // 服务注册路由内存缓存
|
||||
hooksCache *gmap.StringInterfaceMap // 事件回调路由内存缓存
|
||||
routesMap map[string]registeredRouteItem // 已经注册的路由及对应的注册方法文件地址(用以路由重复注册判断)
|
||||
// 自定义状态码回调
|
||||
hsmu sync.RWMutex // status handler互斥锁
|
||||
statusHandlerMap map[string]HandlerFunc // 不同状态码下的注册处理方法(例如404状态时的处理方法)
|
||||
// SESSION
|
||||
sessions *gcache.Cache // Session内存缓存
|
||||
// Logger
|
||||
logger *glog.Logger // 日志管理对象
|
||||
}
|
||||
var (
|
||||
// Server表,用以存储和检索名称与Server对象之间的关联关系
|
||||
serverMapping = gmap.NewStringInterfaceMap()
|
||||
|
||||
// 路由对象
|
||||
type Router struct {
|
||||
Uri string // 注册时的pattern - uri
|
||||
Method string // 注册时的pattern - method
|
||||
Domain string // 注册时的pattern - domain
|
||||
RegRule string // 路由规则解析后对应的正则表达式
|
||||
RegNames []string // 路由规则解析后对应的变量名称数组
|
||||
Priority int // 优先级,用于链表排序,值越大优先级越高
|
||||
}
|
||||
// 正常运行的Server数量,如果没有运行、失败或者全部退出,那么该值为0
|
||||
serverRunning = gtype.NewInt()
|
||||
|
||||
// pattern与回调函数的绑定map
|
||||
type handlerMap map[string]*handlerItem
|
||||
// Web Socket默认配置
|
||||
wsUpgrader = websocket.Upgrader {
|
||||
// 默认允许WebSocket请求跨域,权限控制可以由业务层自己负责,灵活度更高
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
// Web Server已完成服务事件通道,当有事件时表示服务完成,当前进程退出
|
||||
doneChan = make(chan struct{}, 1000)
|
||||
|
||||
// http回调函数注册信息
|
||||
type handlerItem struct {
|
||||
name string // 注册的方法名称信息
|
||||
rtype int // 注册方式(执行对象/回调函数/控制器)
|
||||
ctype reflect.Type // 控制器类型(反射类型)
|
||||
fname string // 回调方法名称
|
||||
faddr HandlerFunc // 准确的执行方法内存地址(与以上两个参数二选一)
|
||||
finit HandlerFunc // 初始化请求回调方法(执行对象注册方式下有效)
|
||||
fshut HandlerFunc // 完成请求回调方法(执行对象注册方式下有效)
|
||||
router *Router // 注册时绑定的路由对象
|
||||
}
|
||||
// 用于服务进程初始化,只能初始化一次,采用“懒初始化”(在server运行时才初始化)
|
||||
serverProcInited = gtype.NewBool()
|
||||
)
|
||||
|
||||
// 根据特定URL.Path解析后的路由检索结果项
|
||||
type handlerParsedItem struct {
|
||||
handler *handlerItem // 路由注册项
|
||||
values map[string][]string // 特定URL.Path的Router解析参数
|
||||
}
|
||||
|
||||
// 已注册的路由项
|
||||
type registeredRouteItem struct {
|
||||
file string // 文件路径及行数地址
|
||||
handler *handlerItem // 路由注册项
|
||||
}
|
||||
|
||||
// HTTP注册函数
|
||||
type HandlerFunc func(r *Request)
|
||||
|
||||
// 文件描述符map
|
||||
type listenerFdMap map[string]string
|
||||
|
||||
|
||||
// Server表,用以存储和检索名称与Server对象之间的关联关系
|
||||
var serverMapping = gmap.NewStringInterfaceMap()
|
||||
|
||||
// 正常运行的Server数量,如果没有运行、失败或者全部退出,那么该值为0
|
||||
var serverRunning = gtype.NewInt()
|
||||
|
||||
// Web Socket默认配置
|
||||
var wsUpgrader = websocket.Upgrader {
|
||||
// 默认允许WebSocket请求跨域,权限控制可以由业务层自己负责,灵活度更高
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// Web Server已完成服务事件通道,当有事件时表示服务完成,当前进程退出
|
||||
var doneChan = make(chan struct{}, 1000)
|
||||
|
||||
// 用于服务进程初始化,只能初始化一次,采用“懒初始化”(在server运行时才初始化)
|
||||
var serverProcInited = gtype.NewBool()
|
||||
|
||||
// Web Server进程初始化.
|
||||
// 注意该方法不能放置于包初始化方法init中,不使用ghttp.Server的功能便不能初始化对应的协程goroutine逻辑.
|
||||
@ -177,15 +177,14 @@ func GetServer(name...interface{}) (*Server) {
|
||||
}
|
||||
s := &Server {
|
||||
name : sname,
|
||||
paths : gspath.New(),
|
||||
servers : make([]*gracefulServer, 0),
|
||||
methodsMap : make(map[string]struct{}),
|
||||
statusHandlerMap : make(map[string]HandlerFunc),
|
||||
serveTree : make(map[string]interface{}),
|
||||
hooksTree : make(map[string]interface{}),
|
||||
serveCache : gmap.NewStringInterfaceMap(),
|
||||
hooksCache : gmap.NewStringInterfaceMap(),
|
||||
routesMap : make(map[string]registeredRouteItem),
|
||||
serveCache : gcache.New(),
|
||||
hooksCache : gcache.New(),
|
||||
routesMap : make(map[string][]registeredRouteItem),
|
||||
sessions : gcache.New(),
|
||||
servedCount : gtype.NewInt(),
|
||||
logger : glog.New(),
|
||||
@ -213,30 +212,11 @@ func (s *Server) Start() error {
|
||||
return errors.New("server is already running")
|
||||
}
|
||||
|
||||
// 如果设置了静态文件目录,那么优先按照静态文件目录进行检索,其次是当前可执行文件工作目录;
|
||||
// 并且如果是开发环境,默认也会添加main包的源码目录路径做为二级检索。
|
||||
if s.config.ServerRoot != "" {
|
||||
if rp, err := s.paths.Set(s.config.ServerRoot); err != nil {
|
||||
glog.Error("ghttp.SetServerRoot failed:", err.Error())
|
||||
return err
|
||||
} else {
|
||||
glog.Debug("ghttp.SetServerRoot:", rp)
|
||||
}
|
||||
}
|
||||
// 添加当前可执行文件运行目录到搜索目录
|
||||
s.paths.Add(gfile.SelfDir())
|
||||
// (开发环境)添加main源码包到搜索目录
|
||||
if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) {
|
||||
s.paths.Add(p)
|
||||
}
|
||||
// (安全控制)不能访问当前执行文件
|
||||
s.paths.Remove(gfile.SelfPath())
|
||||
|
||||
// 底层http server配置
|
||||
if s.config.Handler == nil {
|
||||
s.config.Handler = http.HandlerFunc(s.defaultHttpHandle)
|
||||
}
|
||||
// 不允许访问的路由注册(通过HOOK实现)
|
||||
// 不允许访问的路由注册(使用HOOK实现)
|
||||
if s.config.DenyRoutes != nil {
|
||||
for _, v := range s.config.DenyRoutes {
|
||||
s.BindHookHandler(v, HOOK_BEFORE_SERVE, func(r *Request) {
|
||||
@ -246,18 +226,6 @@ func (s *Server) Start() error {
|
||||
}
|
||||
}
|
||||
|
||||
// 配置相关相对路径处理
|
||||
if s.config.HTTPSCertPath != "" && !gfile.Exists(s.config.HTTPSCertPath) {
|
||||
if t, _ := s.paths.Search(s.config.HTTPSCertPath); t != "" {
|
||||
s.config.HTTPSCertPath = t
|
||||
}
|
||||
}
|
||||
if s.config.HTTPSKeyPath != "" && !gfile.Exists(s.config.HTTPSKeyPath) {
|
||||
if t, _ := s.paths.Search(s.config.HTTPSKeyPath); t != "" {
|
||||
s.config.HTTPSKeyPath = t
|
||||
}
|
||||
}
|
||||
|
||||
// gzip压缩文件类型
|
||||
//if s.config.GzipContentTypes != nil {
|
||||
// for _, v := range s.config.GzipContentTypes {
|
||||
@ -282,9 +250,15 @@ func (s *Server) Start() error {
|
||||
// 如果是子进程,那么服务开启后通知父进程销毁
|
||||
if gproc.IsChild() {
|
||||
gtime.SetTimeout(2*time.Second, func() {
|
||||
gproc.Send(gproc.PPid(), []byte("exit"), gADMIN_GPROC_COMM_GROUP)
|
||||
if err := gproc.Send(gproc.PPid(), []byte("exit"), gADMIN_GPROC_COMM_GROUP); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
// 是否处于开发环境
|
||||
if gfile.MainPkgPath() != "" {
|
||||
glog.Debug("GF notices that you're in develop environment, so error logs are auto enabled to stdout.")
|
||||
}
|
||||
|
||||
// 打印展示路由表
|
||||
s.DumpRoutesMap()
|
||||
@ -304,11 +278,12 @@ func (s *Server) DumpRoutesMap() {
|
||||
// 获得路由表(格式化字符串)
|
||||
func (s *Server) GetRouteMap() string {
|
||||
type tableItem struct {
|
||||
hook string
|
||||
domain string
|
||||
method string
|
||||
route string
|
||||
handler string
|
||||
hook string
|
||||
domain string
|
||||
method string
|
||||
route string
|
||||
handler string
|
||||
priority int
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
@ -319,31 +294,37 @@ func (s *Server) GetRouteMap() string {
|
||||
table.SetCenterSeparator("|")
|
||||
|
||||
m := make(map[string]*garray.SortedArray)
|
||||
for k, v := range s.routesMap {
|
||||
for k, registeredItems := range s.routesMap {
|
||||
array, _ := gregex.MatchString(`(.*?)%([A-Z]+):(.+)@(.+)`, k)
|
||||
item := &tableItem{
|
||||
hook : array[1],
|
||||
domain : array[4],
|
||||
method : array[2],
|
||||
route : array[3],
|
||||
handler : v.handler.name,
|
||||
}
|
||||
if _, ok := m[item.domain]; !ok {
|
||||
m[item.domain] = garray.NewSortedArray(100, func(v1, v2 interface{}) int {
|
||||
item1 := v1.(*tableItem)
|
||||
item2 := v2.(*tableItem)
|
||||
r := 0
|
||||
if r = strings.Compare(item1.domain, item2.domain); r == 0 {
|
||||
if r = strings.Compare(item1.route, item2.route); r == 0 {
|
||||
if r = strings.Compare(item1.method, item2.method); r == 0 {
|
||||
r = strings.Compare(item1.hook, item2.hook)
|
||||
for index, registeredItem := range registeredItems {
|
||||
item := &tableItem {
|
||||
hook : array[1],
|
||||
domain : array[4],
|
||||
method : array[2],
|
||||
route : array[3],
|
||||
handler : registeredItem.handler.name,
|
||||
priority : len(registeredItems) - index - 1,
|
||||
}
|
||||
if _, ok := m[item.domain]; !ok {
|
||||
// 注意排序函数的逻辑
|
||||
m[item.domain] = garray.NewSortedArray(100, func(v1, v2 interface{}) int {
|
||||
item1 := v1.(*tableItem)
|
||||
item2 := v2.(*tableItem)
|
||||
r := 0
|
||||
if r = strings.Compare(item1.domain, item2.domain); r == 0 {
|
||||
if r = strings.Compare(item1.route, item2.route); r == 0 {
|
||||
if r = strings.Compare(item1.method, item2.method); r == 0 {
|
||||
if r = strings.Compare(item1.hook, item2.hook); r == 0 {
|
||||
r = item2.priority - item1.priority
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return r
|
||||
}, false)
|
||||
return r
|
||||
}, false)
|
||||
}
|
||||
m[item.domain].Add(item)
|
||||
}
|
||||
m[item.domain].Add(item)
|
||||
}
|
||||
addr := s.config.Addr
|
||||
if s.config.HTTPSAddr != "" {
|
||||
@ -357,7 +338,7 @@ func (s *Server) GetRouteMap() string {
|
||||
data[1] = addr
|
||||
data[2] = item.domain
|
||||
data[3] = item.method
|
||||
data[4] = gconv.String(len(strings.Split(item.route, "/")) - 1)
|
||||
data[4] = gconv.String(len(strings.Split(item.route, "/")) - 1 + item.priority)
|
||||
data[5] = item.route
|
||||
data[6] = item.handler
|
||||
data[7] = item.hook
|
||||
@ -482,7 +463,7 @@ func (s *Server) startServer(fdMap listenerFdMap) {
|
||||
serverRunning.Add(-1)
|
||||
// 如果非关闭错误,那么提示报错,否则认为是正常的服务关闭操作
|
||||
if err != nil && !strings.EqualFold(http.ErrServerClosed.Error(), err.Error()) {
|
||||
glog.Error(err)
|
||||
glog.Fatal(err)
|
||||
}
|
||||
// 如果所有异步的Server都已经停止,并且没有在管理操作(重启/关闭)进行中,那么主Server就可以退出了
|
||||
if serverRunning.Val() < 1 && serverProcessStatus.Val() == 0 {
|
||||
|
||||
@ -50,16 +50,18 @@ var serverProcessStatus = gtype.NewInt()
|
||||
// 服务管理首页
|
||||
func (p *utilAdmin) Index(r *Request) {
|
||||
data := map[string]interface{}{
|
||||
"pid" : gproc.Pid(),
|
||||
"uri" : strings.TrimRight(r.URL.Path, "/"),
|
||||
}
|
||||
buffer, _ := gview.ParseContent(`
|
||||
<html>
|
||||
<head>
|
||||
<title>gf ghttp admin</title>
|
||||
<title>GoFrame Web Server Admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<p><a href="{{$.uri}}/restart">restart</a></p>
|
||||
<p><a href="{{$.uri}}/shutdown">shutdown</a></p>
|
||||
<p>PID: {{.pid}}</p>
|
||||
<p><a href="{{$.uri}}/restart">Restart</a></p>
|
||||
<p><a href="{{$.uri}}/shutdown">Shutdown</a></p>
|
||||
</body>
|
||||
</html>
|
||||
`, data)
|
||||
|
||||
@ -25,7 +25,6 @@ func handleProcessSignal() {
|
||||
syscall.SIGINT,
|
||||
syscall.SIGQUIT,
|
||||
syscall.SIGKILL,
|
||||
syscall.SIGHUP,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGUSR1,
|
||||
syscall.SIGUSR2,
|
||||
@ -34,7 +33,7 @@ func handleProcessSignal() {
|
||||
sig = <- procSignalChan
|
||||
switch sig {
|
||||
// 进程终止,停止所有子进程运行
|
||||
case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGTERM:
|
||||
case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM:
|
||||
shutdownWebServers(sig.String())
|
||||
return
|
||||
|
||||
|
||||
@ -3,17 +3,16 @@
|
||||
// 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://gitee.com/johng/gf.
|
||||
// 配置管理数据结构定义.
|
||||
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"time"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -36,75 +35,87 @@ type LogHandler func(r *Request, error ... interface{})
|
||||
// HTTP Server 设置结构体,静态配置
|
||||
type ServerConfig struct {
|
||||
// 底层http对象配置
|
||||
Addr string // 监听IP和端口,监听本地所有IP使用":端口"(支持多个地址,使用","号分隔)
|
||||
HTTPSAddr string // HTTPS服务监听地址(支持多个地址,使用","号分隔)
|
||||
HTTPSCertPath string // HTTPS证书文件路径
|
||||
HTTPSKeyPath string // HTTPS签名文件路径
|
||||
Handler http.Handler // 默认的处理函数
|
||||
ReadTimeout time.Duration // 读取超时
|
||||
WriteTimeout time.Duration // 写入超时
|
||||
IdleTimeout time.Duration // 等待超时
|
||||
MaxHeaderBytes int // 最大的header长度
|
||||
Addr string // 监听IP和端口,监听本地所有IP使用":端口"(支持多个地址,使用","号分隔)
|
||||
HTTPSAddr string // HTTPS服务监听地址(支持多个地址,使用","号分隔)
|
||||
HTTPSCertPath string // HTTPS证书文件路径
|
||||
HTTPSKeyPath string // HTTPS签名文件路径
|
||||
Handler http.Handler // 默认的处理函数
|
||||
ReadTimeout time.Duration // 读取超时
|
||||
WriteTimeout time.Duration // 写入超时
|
||||
IdleTimeout time.Duration // 等待超时
|
||||
MaxHeaderBytes int // 最大的header长度
|
||||
|
||||
// 静态文件配置
|
||||
IndexFiles []string // 默认访问的文件列表
|
||||
IndexFolder bool // 如果访问目录是否显示目录列表
|
||||
ServerAgent string // server agent
|
||||
ServerRoot string // 服务器服务的本地目录根路径
|
||||
IndexFiles []string // 默认访问的文件列表
|
||||
IndexFolder bool // 如果访问目录是否显示目录列表
|
||||
ServerAgent string // Server Agent
|
||||
ServerRoot string // 服务器服务的本地目录根路径(检索优先级比StaticPaths低)
|
||||
SearchPaths []string // 静态文件搜索目录(包含ServerRoot,按照优先级进行排序)
|
||||
StaticPaths []staticPathItem // 静态文件目录映射(按照优先级进行排序)
|
||||
FileServerEnabled bool // 是否允许静态文件服务(总开关,默认开启)
|
||||
|
||||
// COOKIE
|
||||
CookieMaxAge int // Cookie有效期
|
||||
CookiePath string // Cookie有效Path(注意同时也会影响SessionID)
|
||||
CookieDomain string // Cookie有效Domain(注意同时也会影响SessionID)
|
||||
CookieMaxAge int // Cookie有效期
|
||||
CookiePath string // Cookie有效Path(注意同时也会影响SessionID)
|
||||
CookieDomain string // Cookie有效Domain(注意同时也会影响SessionID)
|
||||
|
||||
// SESSION
|
||||
SessionMaxAge int // Session有效期
|
||||
SessionIdName string // SessionId名称
|
||||
SessionMaxAge int // Session有效期
|
||||
SessionIdName string // SessionId名称
|
||||
|
||||
// IP访问控制
|
||||
DenyIps []string // 不允许访问的ip列表,支持ip前缀过滤,如: 10 将不允许10开头的ip访问
|
||||
AllowIps []string // 仅允许访问的ip列表,支持ip前缀过滤,如: 10 将仅允许10开头的ip访问
|
||||
|
||||
// ip访问控制
|
||||
DenyIps []string // 不允许访问的ip列表,支持ip前缀过滤,如: 10 将不允许10开头的ip访问
|
||||
AllowIps []string // 仅允许访问的ip列表,支持ip前缀过滤,如: 10 将仅允许10开头的ip访问
|
||||
// 路由访问控制
|
||||
DenyRoutes []string // 不允许访问的路由规则列表
|
||||
DenyRoutes []string // 不允许访问的路由规则列表
|
||||
Rewrites map[string]string // URI Rewrite重写配置
|
||||
|
||||
// 日志配置
|
||||
LogPath string // 存放日志的目录路径
|
||||
LogHandler LogHandler // 自定义日志处理回调方法
|
||||
ErrorLogEnabled bool // 是否开启error log
|
||||
AccessLogEnabled bool // 是否开启access log
|
||||
LogPath string // 存放日志的目录路径
|
||||
LogHandler LogHandler // 自定义日志处理回调方法
|
||||
ErrorLogEnabled bool // 是否开启error log
|
||||
AccessLogEnabled bool // 是否开启access log
|
||||
|
||||
// 其他设置
|
||||
NameToUriType int // 服务注册时对象和方法名称转换为URI时的规则
|
||||
GzipContentTypes []string // 允许进行gzip压缩的文件类型
|
||||
DumpRouteMap bool // 是否在程序启动时默认打印路由表信息
|
||||
NameToUriType int // 服务注册时对象和方法名称转换为URI时的规则
|
||||
GzipContentTypes []string // 允许进行gzip压缩的文件类型
|
||||
DumpRouteMap bool // 是否在程序启动时默认打印路由表信息
|
||||
RouterCacheExpire int // 路由检索缓存过期时间(秒)
|
||||
}
|
||||
|
||||
// 默认HTTP Server
|
||||
// 默认HTTP Server配置
|
||||
var defaultServerConfig = ServerConfig {
|
||||
Addr : "",
|
||||
HTTPSAddr : "",
|
||||
Handler : nil,
|
||||
ReadTimeout : 60 * time.Second,
|
||||
WriteTimeout : 60 * time.Second,
|
||||
IdleTimeout : 60 * time.Second,
|
||||
MaxHeaderBytes : 1024,
|
||||
IndexFiles : []string{"index.html", "index.htm"},
|
||||
IndexFolder : false,
|
||||
ServerAgent : "gf",
|
||||
ServerRoot : "",
|
||||
Addr : "",
|
||||
HTTPSAddr : "",
|
||||
Handler : nil,
|
||||
ReadTimeout : 60 * time.Second,
|
||||
WriteTimeout : 60 * time.Second,
|
||||
IdleTimeout : 60 * time.Second,
|
||||
MaxHeaderBytes : 1024,
|
||||
|
||||
CookieMaxAge : gDEFAULT_COOKIE_MAX_AGE,
|
||||
CookiePath : gDEFAULT_COOKIE_PATH,
|
||||
CookieDomain : "",
|
||||
IndexFiles : []string{"index.html", "index.htm"},
|
||||
IndexFolder : false,
|
||||
ServerAgent : "gf",
|
||||
ServerRoot : "",
|
||||
StaticPaths : make([]staticPathItem, 0),
|
||||
FileServerEnabled : true,
|
||||
|
||||
SessionMaxAge : gDEFAULT_SESSION_MAX_AGE,
|
||||
SessionIdName : gDEFAULT_SESSION_ID_NAME,
|
||||
CookieMaxAge : gDEFAULT_COOKIE_MAX_AGE,
|
||||
CookiePath : gDEFAULT_COOKIE_PATH,
|
||||
CookieDomain : "",
|
||||
|
||||
ErrorLogEnabled : true,
|
||||
SessionMaxAge : gDEFAULT_SESSION_MAX_AGE,
|
||||
SessionIdName : gDEFAULT_SESSION_ID_NAME,
|
||||
|
||||
GzipContentTypes : defaultGzipContentTypes,
|
||||
ErrorLogEnabled : true,
|
||||
|
||||
DumpRouteMap : true,
|
||||
GzipContentTypes : defaultGzipContentTypes,
|
||||
|
||||
DumpRouteMap : true,
|
||||
|
||||
RouterCacheExpire : 60,
|
||||
Rewrites : make(map[string]string),
|
||||
}
|
||||
|
||||
// 获取默认的http server设置
|
||||
@ -117,6 +128,7 @@ func Config() ServerConfig {
|
||||
func (s *Server)SetConfig(c ServerConfig) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
if c.Handler == nil {
|
||||
c.Handler = http.HandlerFunc(s.defaultHttpHandle)
|
||||
@ -132,6 +144,7 @@ func (s *Server)SetConfig(c ServerConfig) {
|
||||
func (s *Server)SetAddr(addr string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.Addr = addr
|
||||
}
|
||||
@ -156,6 +169,7 @@ func (s *Server)SetPort(port...int) {
|
||||
func (s *Server)SetHTTPSAddr(addr string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.HTTPSAddr = addr
|
||||
}
|
||||
@ -164,6 +178,7 @@ func (s *Server)SetHTTPSAddr(addr string) {
|
||||
func (s *Server)SetHTTPSPort(port...int) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
if len(port) > 0 {
|
||||
s.config.HTTPSAddr = ""
|
||||
@ -180,15 +195,31 @@ func (s *Server)SetHTTPSPort(port...int) {
|
||||
func (s *Server)EnableHTTPS(certFile, keyFile string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.HTTPSCertPath = certFile
|
||||
s.config.HTTPSKeyPath = keyFile
|
||||
certFileRealPath := gfile.RealPath(certFile)
|
||||
if certFileRealPath == "" {
|
||||
certFileRealPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + certFileRealPath)
|
||||
}
|
||||
if certFileRealPath == "" {
|
||||
glog.Fatal(fmt.Sprintf(`[ghttp] EnableHTTPS failed: certFile "%s" does not exist`, certFile))
|
||||
}
|
||||
keyFileRealPath := gfile.RealPath(keyFile)
|
||||
if keyFileRealPath == "" {
|
||||
keyFileRealPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + keyFileRealPath)
|
||||
}
|
||||
if keyFileRealPath == "" {
|
||||
glog.Fatal(fmt.Sprintf(`[ghttp] EnableHTTPS failed: keyFile "%s" does not exist`, keyFile))
|
||||
}
|
||||
s.config.HTTPSCertPath = certFileRealPath
|
||||
s.config.HTTPSKeyPath = keyFileRealPath
|
||||
}
|
||||
|
||||
// 设置http server参数 - ReadTimeout
|
||||
func (s *Server)SetReadTimeout(t time.Duration) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.ReadTimeout = t
|
||||
}
|
||||
@ -197,6 +228,7 @@ func (s *Server)SetReadTimeout(t time.Duration) {
|
||||
func (s *Server)SetWriteTimeout(t time.Duration) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.WriteTimeout = t
|
||||
}
|
||||
@ -205,6 +237,7 @@ func (s *Server)SetWriteTimeout(t time.Duration) {
|
||||
func (s *Server)SetIdleTimeout(t time.Duration) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.IdleTimeout = t
|
||||
}
|
||||
@ -213,74 +246,25 @@ func (s *Server)SetIdleTimeout(t time.Duration) {
|
||||
func (s *Server)SetMaxHeaderBytes(b int) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.MaxHeaderBytes = b
|
||||
|
||||
}
|
||||
|
||||
// 设置http server参数 - IndexFiles,默认展示文件,如:index.html, index.htm
|
||||
func (s *Server)SetIndexFiles(index []string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
}
|
||||
s.config.IndexFiles = index
|
||||
}
|
||||
|
||||
// 允许展示访问目录的文件列表
|
||||
func (s *Server)SetIndexFolder(index bool) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
}
|
||||
s.config.IndexFolder = index
|
||||
|
||||
}
|
||||
|
||||
// 设置http server参数 - ServerAgent
|
||||
func (s *Server)SetServerAgent(agent string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.ServerAgent = agent
|
||||
|
||||
}
|
||||
|
||||
// 设置http server参数 - ServerRoot
|
||||
func (s *Server)SetServerRoot(root string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
}
|
||||
// RealPath的作用除了校验地址正确性以外,还转换分隔符号为当前系统正确的文件分隔符号
|
||||
path := gfile.RealPath(root)
|
||||
if path == "" {
|
||||
glog.Error("invalid root path \"" + root + "\"")
|
||||
}
|
||||
s.config.ServerRoot = strings.TrimRight(path, gfile.Separator)
|
||||
}
|
||||
|
||||
func (s *Server) SetDenyIps(ips []string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
}
|
||||
s.config.DenyIps = ips
|
||||
}
|
||||
|
||||
func (s *Server) SetAllowIps(ips []string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
}
|
||||
s.config.AllowIps = ips
|
||||
}
|
||||
|
||||
func (s *Server) SetDenyRoutes(routes []string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
}
|
||||
s.config.DenyRoutes = routes
|
||||
}
|
||||
|
||||
func (s *Server) SetGzipContentTypes(types []string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.GzipContentTypes = types
|
||||
}
|
||||
@ -289,6 +273,7 @@ func (s *Server) SetGzipContentTypes(types []string) {
|
||||
func (s *Server) SetNameToUriType(t int) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.NameToUriType = t
|
||||
}
|
||||
@ -297,22 +282,21 @@ func (s *Server) SetNameToUriType(t int) {
|
||||
func (s *Server) SetDumpRouteMap(enabled bool) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.DumpRouteMap = enabled
|
||||
}
|
||||
|
||||
// 添加静态文件搜索目录,必须给定目录的绝对路径
|
||||
func (s *Server) AddSearchPath(path string) error {
|
||||
if rp, err := s.paths.Add(path); err != nil {
|
||||
glog.Error("ghttp.AddSearchPath failed:", err.Error())
|
||||
return err
|
||||
} else {
|
||||
glog.Debug("ghttp.AddSearchPath:", rp)
|
||||
// 设置路由缓存过期时间(秒)
|
||||
func (s *Server) SetRouterCacheExpire(expire int) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
return nil
|
||||
s.config.RouterCacheExpire = expire
|
||||
}
|
||||
|
||||
// 获取
|
||||
// 获取WebServer名称
|
||||
func (s *Server) GetName() string {
|
||||
return s.name
|
||||
}
|
||||
@ -3,7 +3,6 @@
|
||||
// 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://gitee.com/johng/gf.
|
||||
// 配置管理数据结构定义.
|
||||
|
||||
package ghttp
|
||||
|
||||
@ -16,6 +15,7 @@ import (
|
||||
func (s *Server)SetCookieMaxAge(age int) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.CookieMaxAge = age
|
||||
}
|
||||
@ -24,6 +24,7 @@ func (s *Server)SetCookieMaxAge(age int) {
|
||||
func (s *Server)SetCookiePath(path string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.CookiePath = path
|
||||
}
|
||||
@ -32,6 +33,7 @@ func (s *Server)SetCookiePath(path string) {
|
||||
func (s *Server)SetCookieDomain(domain string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.CookieDomain = domain
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
// 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://gitee.com/johng/gf.
|
||||
// 配置管理数据结构定义.
|
||||
|
||||
package ghttp
|
||||
|
||||
@ -15,6 +14,7 @@ import (
|
||||
func (s *Server)SetLogPath(path string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
if len(path) == 0 {
|
||||
return
|
||||
@ -27,6 +27,7 @@ func (s *Server)SetLogPath(path string) {
|
||||
func (s *Server)SetAccessLogEnabled(enabled bool) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.AccessLogEnabled = enabled
|
||||
}
|
||||
@ -35,6 +36,7 @@ func (s *Server)SetAccessLogEnabled(enabled bool) {
|
||||
func (s *Server)SetErrorLogEnabled(enabled bool) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.ErrorLogEnabled = enabled
|
||||
}
|
||||
@ -43,6 +45,7 @@ func (s *Server)SetErrorLogEnabled(enabled bool) {
|
||||
func (s *Server) SetLogHandler(handler LogHandler) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.LogHandler = handler
|
||||
}
|
||||
|
||||
54
g/net/ghttp/ghttp_server_config_route.go
Normal file
54
g/net/ghttp/ghttp_server_config_route.go
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
|
||||
|
||||
|
||||
package ghttp
|
||||
|
||||
import "gitee.com/johng/gf/g/os/glog"
|
||||
|
||||
func (s *Server) SetDenyIps(ips []string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.DenyIps = ips
|
||||
}
|
||||
|
||||
func (s *Server) SetAllowIps(ips []string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.AllowIps = ips
|
||||
}
|
||||
|
||||
func (s *Server) SetDenyRoutes(routes []string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.DenyRoutes = routes
|
||||
}
|
||||
|
||||
// 设置URI重写规则
|
||||
func (s *Server) SetRewrite(uri string, rewrite string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.Rewrites[uri] = rewrite
|
||||
}
|
||||
|
||||
// 设置URI重写规则(批量)
|
||||
func (s *Server) SetRewriteMap(rewrites map[string]string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
for k, v := range rewrites {
|
||||
s.config.Rewrites[k] = v
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,6 @@
|
||||
// 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://gitee.com/johng/gf.
|
||||
// 配置管理数据结构定义.
|
||||
|
||||
package ghttp
|
||||
|
||||
@ -13,6 +12,7 @@ import "gitee.com/johng/gf/g/os/glog"
|
||||
func (s *Server) SetSessionMaxAge(age int) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.SessionMaxAge = age
|
||||
}
|
||||
@ -21,6 +21,7 @@ func (s *Server) SetSessionMaxAge(age int) {
|
||||
func (s *Server) SetSessionIdName(name string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.SessionIdName = name
|
||||
}
|
||||
|
||||
136
g/net/ghttp/ghttp_server_config_static.go
Normal file
136
g/net/ghttp/ghttp_server_config_static.go
Normal file
@ -0,0 +1,136 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
|
||||
|
||||
// 静态文件搜索优先级: ServerPaths > ServerRoot > SearchPath
|
||||
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/garray"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 静态文件目录映射关系对象
|
||||
type staticPathItem struct {
|
||||
prefix string // 映射的URI前缀
|
||||
path string // 静态文件目录绝对路径
|
||||
}
|
||||
|
||||
// 设置http server参数 - IndexFiles,默认展示文件,如:index.html, index.htm
|
||||
func (s *Server)SetIndexFiles(index []string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.IndexFiles = index
|
||||
}
|
||||
|
||||
// 允许展示访问目录的文件列表
|
||||
func (s *Server)SetIndexFolder(enabled bool) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.IndexFolder = enabled
|
||||
}
|
||||
|
||||
// 是否开启/关闭静态文件服务,当关闭时仅提供动态接口服务,路由性能会得到一定提升
|
||||
func (s *Server) SetFileServerEnabled(enabled bool) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.FileServerEnabled = enabled
|
||||
}
|
||||
|
||||
// 设置http server参数 - ServerRoot
|
||||
func (s *Server)SetServerRoot(root string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
// RealPath的作用除了校验地址正确性以外,还转换分隔符号为当前系统正确的文件分隔符号
|
||||
path := gfile.RealPath(root)
|
||||
if path == "" {
|
||||
path = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + root)
|
||||
}
|
||||
if path == "" {
|
||||
glog.Fatal(fmt.Sprintf(`[ghttp] SetServerRoot failed: path "%s" does not exist`, root))
|
||||
}
|
||||
s.config.SearchPaths = []string{strings.TrimRight(path, gfile.Separator)}
|
||||
}
|
||||
|
||||
// 添加静态文件搜索**目录**,必须给定目录的绝对路径
|
||||
func (s *Server) AddSearchPath(path string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
// RealPath的作用除了校验地址正确性以外,还转换分隔符号为当前系统正确的文件分隔符号
|
||||
realPath := gfile.RealPath(path)
|
||||
if realPath == "" {
|
||||
realPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + path)
|
||||
}
|
||||
if realPath == "" {
|
||||
glog.Fatal(fmt.Sprintf(`[ghttp] AddSearchPath failed: path "%s" does not exist`, path))
|
||||
}
|
||||
s.config.SearchPaths = append(s.config.SearchPaths, realPath)
|
||||
}
|
||||
|
||||
// 添加URI与静态**目录**的映射
|
||||
func (s *Server) AddStaticPath(prefix string, path string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
// RealPath的作用除了校验地址正确性以外,还转换分隔符号为当前系统正确的文件分隔符号
|
||||
realPath := gfile.RealPath(path)
|
||||
if realPath == "" {
|
||||
realPath = gfile.RealPath(gfile.MainPkgPath() + gfile.Separator + path)
|
||||
}
|
||||
if realPath == "" {
|
||||
glog.Fatal(fmt.Sprintf(`[ghttp] AddStaticPath failed: path "%s" does not exist`, path))
|
||||
}
|
||||
addItem := staticPathItem {
|
||||
prefix : prefix,
|
||||
path : realPath,
|
||||
}
|
||||
if len(s.config.StaticPaths) > 0 {
|
||||
// 先添加item
|
||||
s.config.StaticPaths = append(s.config.StaticPaths, addItem)
|
||||
// 按照prefix从长到短进行排序
|
||||
array := garray.NewSortedArray(0, func(v1, v2 interface{}) int {
|
||||
s1 := gconv.String(v1)
|
||||
s2 := gconv.String(v2)
|
||||
r := len(s2) - len(s1)
|
||||
if r == 0 {
|
||||
r = strings.Compare(s1, s2)
|
||||
}
|
||||
return r
|
||||
}, false)
|
||||
for _, v := range s.config.StaticPaths {
|
||||
array.Add(v.prefix)
|
||||
}
|
||||
// 按照重新排序的顺序重新添加item
|
||||
paths := make([]staticPathItem, 0)
|
||||
for _, v := range array.Slice() {
|
||||
for _, item := range s.config.StaticPaths {
|
||||
if strings.EqualFold(gconv.String(v), item.prefix) {
|
||||
paths = append(paths, item)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
s.config.StaticPaths = paths
|
||||
} else {
|
||||
s.config.StaticPaths = []staticPathItem { addItem }
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,39 +35,40 @@ type CookieItem struct {
|
||||
httpOnly bool
|
||||
}
|
||||
|
||||
// 获取或者创建一个cookie对象,与传入的请求对应
|
||||
// 获取或者创建一个COOKIE对象,与传入的请求对应(延迟初始化)
|
||||
func GetCookie(r *Request) *Cookie {
|
||||
if r.Cookie != nil {
|
||||
return r.Cookie
|
||||
}
|
||||
r.Cookie = &Cookie {
|
||||
data : make(map[string]CookieItem),
|
||||
path : r.Server.GetCookiePath(),
|
||||
domain : r.Server.GetCookieDomain(),
|
||||
maxage : r.Server.GetCookieMaxAge(),
|
||||
server : r.Server,
|
||||
request : r,
|
||||
response : r.Response,
|
||||
return &Cookie {
|
||||
request : r,
|
||||
}
|
||||
// 默认有效域名
|
||||
if r.Cookie.domain == "" {
|
||||
r.Cookie.domain = r.GetHost()
|
||||
}
|
||||
r.Cookie.init()
|
||||
return r.Cookie
|
||||
}
|
||||
|
||||
// 从请求流中初始化,无锁
|
||||
// 从请求流中初始化,无锁,延迟初始化
|
||||
func (c *Cookie) init() {
|
||||
for _, v := range c.request.Cookies() {
|
||||
c.data[v.Name] = CookieItem {
|
||||
v.Value, v.Domain, v.Path, v.Expires.Second(), v.HttpOnly,
|
||||
if c.data == nil {
|
||||
c.data = make(map[string]CookieItem)
|
||||
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 == "" {
|
||||
c.domain = c.request.GetHost()
|
||||
}
|
||||
for _, v := range c.request.Cookies() {
|
||||
c.data[v.Name] = CookieItem {
|
||||
v.Value, v.Domain, v.Path, v.Expires.Second(), v.HttpOnly,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有的Cookie并构造成map返回
|
||||
// 获取所有的Cookie并构造成map[string]string返回.
|
||||
func (c *Cookie) Map() map[string]string {
|
||||
c.init()
|
||||
m := make(map[string]string)
|
||||
for k, v := range c.data {
|
||||
m[k] = v.value
|
||||
@ -77,6 +78,7 @@ func (c *Cookie) Map() map[string]string {
|
||||
|
||||
// 获取SessionId,不存在时则创建
|
||||
func (c *Cookie) SessionId() string {
|
||||
c.init()
|
||||
id := c.Get(c.server.GetSessionIdName())
|
||||
if id == "" {
|
||||
id = makeSessionId()
|
||||
@ -87,6 +89,7 @@ func (c *Cookie) SessionId() string {
|
||||
|
||||
// 判断Cookie中是否存在制定键名(并且没有过期)
|
||||
func (c *Cookie) Contains(key string) bool {
|
||||
c.init()
|
||||
if r, ok := c.data[key]; ok {
|
||||
if r.expire >= 0 {
|
||||
return true
|
||||
@ -95,11 +98,6 @@ func (c *Cookie) Contains(key string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// 设置SessionId
|
||||
func (c *Cookie) SetSessionId(id string) {
|
||||
c.Set(c.server.GetSessionIdName(), id)
|
||||
}
|
||||
|
||||
// 设置cookie,使用默认参数
|
||||
func (c *Cookie) Set(key, value string) {
|
||||
c.SetCookie(key, value, c.domain, c.path, c.server.GetCookieMaxAge())
|
||||
@ -107,6 +105,7 @@ func (c *Cookie) Set(key, value string) {
|
||||
|
||||
// 设置cookie,带详细cookie参数
|
||||
func (c *Cookie) SetCookie(key, value, domain, path string, maxAge int, httpOnly ... bool) {
|
||||
c.init()
|
||||
isHttpOnly := false
|
||||
if len(httpOnly) > 0 {
|
||||
isHttpOnly = httpOnly[0]
|
||||
@ -116,8 +115,14 @@ func (c *Cookie) SetCookie(key, value, domain, path string, maxAge int, httpOnly
|
||||
}
|
||||
}
|
||||
|
||||
// 设置SessionId
|
||||
func (c *Cookie) SetSessionId(id string) {
|
||||
c.Set(c.server.GetSessionIdName(), id)
|
||||
}
|
||||
|
||||
// 查询cookie
|
||||
func (c *Cookie) Get(key string) string {
|
||||
c.init()
|
||||
if r, ok := c.data[key]; ok {
|
||||
if r.expire >= 0 {
|
||||
return r.value
|
||||
|
||||
@ -10,6 +10,7 @@ package ghttp
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/encoding/ghtml"
|
||||
"gitee.com/johng/gf/g/os/gspath"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -28,9 +29,17 @@ func (s *Server)defaultHttpHandle(w http.ResponseWriter, r *http.Request) {
|
||||
// 其次,如果没有对应的自定义处理接口配置,那么走默认的域名处理接口配置;
|
||||
// 最后,如果以上都没有找到处理接口,那么进行文件处理;
|
||||
func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// 重写规则判断
|
||||
if len(s.config.Rewrites) > 0 {
|
||||
if rewrite, ok := s.config.Rewrites[r.URL.Path]; ok {
|
||||
r.URL.Path = rewrite
|
||||
}
|
||||
}
|
||||
// 去掉末尾的"/"号
|
||||
if r.URL.Path != "/" {
|
||||
r.URL.Path = strings.TrimRight(r.URL.Path, "/")
|
||||
for r.URL.Path[len(r.URL.Path) - 1] == '/' {
|
||||
r.URL.Path = r.URL.Path[:len(r.URL.Path) - 1]
|
||||
}
|
||||
}
|
||||
|
||||
// 创建请求处理对象
|
||||
@ -57,10 +66,14 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// 静态文件 > 动态服务 > 静态目录
|
||||
// ============================================================
|
||||
|
||||
staticFile := ""
|
||||
isStaticDir := false
|
||||
// 优先执行静态文件检索(检测是否存在对应的静态文件,包括index files处理)
|
||||
staticFile, isStaticDir := s.paths.Search(r.URL.Path, s.config.IndexFiles...)
|
||||
if staticFile != "" {
|
||||
request.isFileRequest = true
|
||||
if s.config.FileServerEnabled {
|
||||
staticFile, isStaticDir = s.searchStaticFile(r.URL.Path)
|
||||
if staticFile != "" {
|
||||
request.isFileRequest = true
|
||||
}
|
||||
}
|
||||
|
||||
// 动态服务检索
|
||||
@ -75,13 +88,18 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// 判断最终对该请求提供的服务方式
|
||||
if isStaticDir && handler != nil {
|
||||
request.isFileRequest = false
|
||||
}
|
||||
|
||||
// 事件 - BeforeServe
|
||||
s.callHookHandler(HOOK_BEFORE_SERVE, request)
|
||||
|
||||
// 执行静态文件服务/回调控制器/执行对象/方法
|
||||
if !request.IsExited() {
|
||||
// 需要再次判断文件是否真实存在,因为文件检索可能使用了缓存,从健壮性考虑这里需要二次判断
|
||||
if request.IsFileRequest() && !isStaticDir /* && gfile.Exists(staticFile) */{
|
||||
if request.isFileRequest /* && gfile.Exists(staticFile) */{
|
||||
// 静态文件
|
||||
s.serveFile(request, staticFile)
|
||||
} else {
|
||||
@ -115,37 +133,73 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
s.callHookHandler(HOOK_AFTER_OUTPUT, request)
|
||||
}
|
||||
|
||||
// 查找静态文件的绝对路径
|
||||
func (s *Server) searchStaticFile(uri string) (filePath string, isDir bool) {
|
||||
// 优先查找URI映射
|
||||
if len(s.config.StaticPaths) > 0 {
|
||||
for _, item := range s.config.StaticPaths {
|
||||
if len(uri) >= len(item.prefix) && strings.EqualFold(item.prefix, uri[0 : len(item.prefix)]) {
|
||||
// 防止类似 /static/style 映射到 /static/style.css 的情况
|
||||
if len(uri) > len(item.prefix) && uri[len(item.prefix)] != '/' {
|
||||
continue
|
||||
}
|
||||
return gspath.Search(item.path, uri[len(item.prefix):], s.config.IndexFiles...)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 其次查找root和search path
|
||||
if len(s.config.SearchPaths) > 0 {
|
||||
for _, path := range s.config.SearchPaths {
|
||||
if filePath, isDir = gspath.Search(path, uri, s.config.IndexFiles...); filePath != "" {
|
||||
return filePath, isDir
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// 初始化控制器
|
||||
func (s *Server) callServeHandler(h *handlerItem, r *Request) {
|
||||
if h.faddr == nil {
|
||||
c := reflect.New(h.ctype)
|
||||
s.niceCall(func() {
|
||||
c.MethodByName("Init").Call([]reflect.Value{reflect.ValueOf(r)})
|
||||
})
|
||||
s.niceCall(func() {
|
||||
c.MethodByName(h.fname).Call(nil)
|
||||
})
|
||||
s.niceCall(func() {
|
||||
c.MethodByName("Shut").Call(nil)
|
||||
})
|
||||
} else {
|
||||
if h.finit != nil {
|
||||
s.niceCall(func() {
|
||||
h.finit(r)
|
||||
})
|
||||
}
|
||||
s.niceCall(func() {
|
||||
h.faddr(r)
|
||||
})
|
||||
if h.fshut != nil {
|
||||
s.niceCall(func() {
|
||||
h.fshut(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 友好地调用方法
|
||||
func (s *Server) niceCall(f func()) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil && e != gEXCEPTION_EXIT {
|
||||
panic(e)
|
||||
}
|
||||
}()
|
||||
if h.faddr == nil {
|
||||
// 新建一个控制器对象处理请求
|
||||
c := reflect.New(h.ctype)
|
||||
c.MethodByName("Init").Call([]reflect.Value{reflect.ValueOf(r)})
|
||||
if !r.IsExited() {
|
||||
c.MethodByName(h.fname).Call(nil)
|
||||
c.MethodByName("Shut").Call([]reflect.Value{reflect.ValueOf(r)})
|
||||
}
|
||||
} else {
|
||||
// 是否有初始化及完成回调方法
|
||||
if h.finit != nil {
|
||||
h.finit(r)
|
||||
}
|
||||
if !r.IsExited() {
|
||||
h.faddr(r)
|
||||
if h.fshut != nil {
|
||||
h.fshut(r)
|
||||
}
|
||||
}
|
||||
}
|
||||
f()
|
||||
}
|
||||
|
||||
// http server静态文件处理,path可以为相对路径也可以为绝对路径
|
||||
func (s *Server)serveFile(r *Request, path string) {
|
||||
func (s *Server) serveFile(r *Request, path string) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
r.Response.WriteStatus(http.StatusForbidden)
|
||||
@ -176,9 +230,15 @@ func (s *Server)listDir(r *Request, f http.File) {
|
||||
|
||||
r.Response.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
r.Response.Write("<pre>\n")
|
||||
if r.URL.Path != "/" {
|
||||
r.Response.Write(fmt.Sprint("<a href=\"..\">..</a>\n"))
|
||||
}
|
||||
for _, file := range files {
|
||||
name := file.Name()
|
||||
r.Response.Write(fmt.Sprintf("<a href=\"%s/%s\">%s</a>\n", r.URL.Path, name, ghtml.SpecialChars(name)))
|
||||
if file.IsDir() {
|
||||
name += "/"
|
||||
}
|
||||
r.Response.Write(fmt.Sprintf("<a href=\"%s\">%s</a>\n", name, ghtml.SpecialChars(name)))
|
||||
}
|
||||
r.Response.Write("</pre>\n")
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ package ghttp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@ -36,7 +37,7 @@ func (s *Server) handleErrorLog(error interface{}, r *Request) {
|
||||
r.Response.WriteStatus(http.StatusInternalServerError)
|
||||
|
||||
// 错误输出默认是开启的
|
||||
if !s.IsErrorLogEnabled() {
|
||||
if !s.IsErrorLogEnabled() && gfile.MainPkgPath() == "" {
|
||||
return
|
||||
}
|
||||
|
||||
@ -56,5 +57,9 @@ func (s *Server) handleErrorLog(error interface{}, r *Request) {
|
||||
s.logger.Cat("error").Backtrace(true, 2).StdPrint(true).Error(content)
|
||||
} else {
|
||||
s.logger.Cat("error").Backtrace(true, 2).Error(content)
|
||||
// 开发环境下(MainPkgPath)自动输出错误信息到标准输出
|
||||
if gfile.MainPkgPath() != "" {
|
||||
s.logger.Cat("error").Backtrace(true, 2).StdPrint(true).Error(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,36 +8,40 @@
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
"gitee.com/johng/gf/g/util/gstr"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
|
||||
// 解析pattern
|
||||
func (s *Server)parsePattern(pattern string) (domain, method, uri string, err error) {
|
||||
uri = pattern
|
||||
func (s *Server)parsePattern(pattern string) (domain, method, path string, err error) {
|
||||
path = strings.TrimSpace(pattern)
|
||||
domain = gDEFAULT_DOMAIN
|
||||
method = gDEFAULT_METHOD
|
||||
if array, err := gregex.MatchString(`([a-zA-Z]+):(.+)`, pattern); len(array) > 1 && err == nil {
|
||||
method = array[1]
|
||||
uri = array[2]
|
||||
path = strings.TrimSpace(array[2])
|
||||
if v := strings.TrimSpace(array[1]); v != "" {
|
||||
method = v
|
||||
}
|
||||
}
|
||||
if array, err := gregex.MatchString(`(.+)@([\w\.\-]+)`, uri); len(array) > 1 && err == nil {
|
||||
uri = array[1]
|
||||
domain = array[2]
|
||||
if array, err := gregex.MatchString(`(.+)@([\w\.\-]+)`, path); len(array) > 1 && err == nil {
|
||||
path = strings.TrimSpace(array[1])
|
||||
if v := strings.TrimSpace(array[2]); v != "" {
|
||||
domain = v
|
||||
}
|
||||
}
|
||||
if uri == "" {
|
||||
if path == "" {
|
||||
err = errors.New("invalid pattern")
|
||||
}
|
||||
// 去掉末尾的"/"符号,与路由匹配时处理一致
|
||||
if uri != "/" {
|
||||
uri = strings.TrimRight(uri, "/")
|
||||
if path != "/" {
|
||||
path = strings.TrimRight(path, "/")
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -55,9 +59,9 @@ func (s *Server) getHandlerRegisterCallerLine(handler *handlerItem) string {
|
||||
}
|
||||
|
||||
// 路由注册处理方法。
|
||||
// 如果带有hook参数,表示是回调注册方法,否则为普通路由执行方法。
|
||||
// 如果带有hook参数,表示是回调注册方法; 否则为普通路由执行方法。
|
||||
func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... string) (resultErr error) {
|
||||
// Web Server正字运行时无法动态注册路由方法
|
||||
// Web Server正常运行时无法动态注册路由方法
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
return errors.New("cannot bind handler while server running")
|
||||
}
|
||||
@ -69,22 +73,27 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin
|
||||
if err != nil {
|
||||
return errors.New("invalid pattern")
|
||||
}
|
||||
regkey := s.hookHandlerKey(hookName, method, uri, domain)
|
||||
// 注册地址记录及重复注册判断
|
||||
regkey := s.handlerKey(hookName, method, uri, domain)
|
||||
caller := s.getHandlerRegisterCallerLine(handler)
|
||||
if item, ok := s.routesMap[regkey]; ok {
|
||||
s := fmt.Sprintf(`duplicated route registry "%s", already registered in %s`, pattern, item.file)
|
||||
glog.Errorfln(s)
|
||||
return errors.New(s)
|
||||
} else {
|
||||
defer func() {
|
||||
if resultErr == nil {
|
||||
s.routesMap[regkey] = registeredRouteItem{
|
||||
file : caller,
|
||||
handler : handler,
|
||||
}
|
||||
}
|
||||
}()
|
||||
if len(hook) == 0 {
|
||||
if item, ok := s.routesMap[regkey]; ok {
|
||||
s := fmt.Sprintf(`duplicated route registry "%s", already registered in %s`, pattern, item[0].file)
|
||||
glog.Errorfln(s)
|
||||
return errors.New(s)
|
||||
}
|
||||
}
|
||||
defer func() {
|
||||
if resultErr == nil {
|
||||
if _, ok := s.routesMap[regkey]; !ok {
|
||||
s.routesMap[regkey] = make([]registeredRouteItem, 0)
|
||||
}
|
||||
s.routesMap[regkey] = append(s.routesMap[regkey], registeredRouteItem {
|
||||
file : caller,
|
||||
handler : handler,
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
// 路由对象
|
||||
handler.router = &Router {
|
||||
@ -156,14 +165,14 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin
|
||||
}
|
||||
}
|
||||
}
|
||||
// 得到的lists是该路由规则一路匹配下来相关的模糊匹配链表(注意不是这棵树所有的链表),
|
||||
// 从头开始遍历每个节点的模糊匹配链表,将该路由项插入进去(按照优先级高的放在前面)
|
||||
// 上面循环后得到的lists是该路由规则一路匹配下来相关的模糊匹配链表(注意不是这棵树所有的链表)。
|
||||
// 下面从头开始遍历每个节点的模糊匹配链表,将该路由项插入进去(按照优先级高的放在lists链表的前面)
|
||||
item := (*handlerItem)(nil)
|
||||
for _, l := range lists {
|
||||
pushed := false
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
item = e.Value.(*handlerItem)
|
||||
// 判断是否已存在相同的路由注册项,是则进行替换
|
||||
// 判断是否已存在相同的路由注册项,(如果不是hook注册)是则进行替换
|
||||
if len(hookName) == 0 {
|
||||
if strings.EqualFold(handler.router.Domain, item.router.Domain) &&
|
||||
strings.EqualFold(handler.router.Method, item.router.Method) &&
|
||||
@ -173,6 +182,7 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin
|
||||
break
|
||||
}
|
||||
}
|
||||
// 如果路由注册项不相等,那么判断优先级,决定插入顺序
|
||||
if s.compareRouterPriority(handler.router, item.router) {
|
||||
l.InsertBefore(handler, e)
|
||||
pushed = true
|
||||
@ -189,10 +199,12 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin
|
||||
}
|
||||
|
||||
// 对比两个handlerItem的优先级,需要非常注意的是,注意新老对比项的参数先后顺序。
|
||||
// 返回值true表示newRouter优先级比oldRouter高,会被添加链表中oldRouter的前面;否则后面。
|
||||
// 优先级比较规则:
|
||||
// 1、层级越深优先级越高(对比/数量);
|
||||
// 2、模糊规则优先级:{xxx} > :xxx > *xxx;
|
||||
func (s *Server) compareRouterPriority(newRouter, oldRouter *Router) bool {
|
||||
// 优先比较层级,层级越深优先级越高
|
||||
if newRouter.Priority > oldRouter.Priority {
|
||||
return true
|
||||
}
|
||||
@ -249,6 +261,9 @@ func (s *Server) compareRouterPriority(newRouter, oldRouter *Router) bool {
|
||||
if fuzzyCountNameNew < fuzzyCountNameOld {
|
||||
return false
|
||||
}
|
||||
|
||||
/* 模糊规则数量相等,后续不用再判断*规则的数量比较了 */
|
||||
|
||||
// 比较HTTP METHOD,更精准的优先级更高
|
||||
if newRouter.Method != gDEFAULT_METHOD {
|
||||
return true
|
||||
@ -256,9 +271,9 @@ func (s *Server) compareRouterPriority(newRouter, oldRouter *Router) bool {
|
||||
if oldRouter.Method != gDEFAULT_METHOD {
|
||||
return true
|
||||
}
|
||||
// 模糊规则数量相等,后续不用再判断*规则的数量比较了,
|
||||
// 这种情况下新的规则比旧的规则优先级更高
|
||||
return true
|
||||
|
||||
// 最后新的规则比旧的规则优先级低
|
||||
return false
|
||||
}
|
||||
|
||||
// 将pattern(不带method和domain)解析成正则表达式匹配以及对应的query字符串
|
||||
@ -282,7 +297,6 @@ func (s *Server) patternToRegRule(rule string) (regrule string, names []string)
|
||||
regrule += `/[^/]+`
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case '*':
|
||||
if len(v) > 1 {
|
||||
regrule += `/{0,1}(.*)`
|
||||
@ -292,7 +306,6 @@ func (s *Server) patternToRegRule(rule string) (regrule string, names []string)
|
||||
regrule += `/{0,1}.*`
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
// 特殊字符替换
|
||||
v = gstr.ReplaceByMap(v, map[string]string{
|
||||
|
||||
214
g/net/ghttp/ghttp_server_router_group.go
Normal file
214
g/net/ghttp/ghttp_server_router_group.go
Normal file
@ -0,0 +1,214 @@
|
||||
// Copyright 2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
|
||||
// 分组路由管理.
|
||||
|
||||
package ghttp
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 分组路由对象
|
||||
type RouterGroup struct {
|
||||
server *Server // Server
|
||||
domain *Domain // Domain
|
||||
prefix string // URI前缀
|
||||
}
|
||||
|
||||
// 分组路由批量绑定项
|
||||
type GroupItem = []interface{}
|
||||
|
||||
// 获取分组路由对象
|
||||
func (s *Server) Group(prefix...string) *RouterGroup {
|
||||
if len(prefix) > 0 {
|
||||
return &RouterGroup{
|
||||
server : s,
|
||||
prefix : prefix[0],
|
||||
}
|
||||
}
|
||||
return &RouterGroup{}
|
||||
}
|
||||
|
||||
// 获取分组路由对象
|
||||
func (d *Domain) Group(prefix...string) *RouterGroup {
|
||||
if len(prefix) > 0 {
|
||||
return &RouterGroup{
|
||||
domain : d,
|
||||
prefix : prefix[0],
|
||||
}
|
||||
}
|
||||
return &RouterGroup{}
|
||||
}
|
||||
|
||||
// 执行分组路由批量绑定
|
||||
func (g *RouterGroup) Bind(group string, items []GroupItem) {
|
||||
for _, item := range items {
|
||||
if len(item) < 3 {
|
||||
glog.Fatalfln("invalid router item: %s", item)
|
||||
}
|
||||
if strings.EqualFold(gconv.String(item[0]), "REST") {
|
||||
g.bind("REST", gconv.String(item[0]) + ":" + gconv.String(item[1]), item[2])
|
||||
} else {
|
||||
if len(item) > 3 {
|
||||
g.bind("HANDLER", gconv.String(item[0]) + ":" + gconv.String(item[1]), item[2], item[3])
|
||||
} else {
|
||||
g.bind("HANDLER", gconv.String(item[0]) + ":" + gconv.String(item[1]), item[2])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定所有的HTTP Method请求方式
|
||||
func (g *RouterGroup) ALL(pattern string, object interface{}, params...interface{}) {
|
||||
g.bind("HANDLER", gDEFAULT_METHOD + ":" + pattern, object, params...)
|
||||
}
|
||||
|
||||
func (g *RouterGroup) GET(pattern string, object interface{}, params...interface{}) {
|
||||
g.bind("HANDLER", "GET:" + pattern, object, params...)
|
||||
}
|
||||
|
||||
func (g *RouterGroup) PUT(pattern string, object interface{}, params...interface{}) {
|
||||
g.bind("HANDLER", "PUT:" + pattern, object, params...)
|
||||
}
|
||||
|
||||
func (g *RouterGroup) POST(pattern string, object interface{}, params...interface{}) {
|
||||
g.bind("HANDLER", "POST:" + pattern, object, params...)
|
||||
}
|
||||
|
||||
func (g *RouterGroup) DELETE(pattern string, object interface{}, params...interface{}) {
|
||||
g.bind("HANDLER", "DELETE:" + pattern, object, params...)
|
||||
}
|
||||
|
||||
func (g *RouterGroup) PATCH(pattern string, object interface{}, params...interface{}) {
|
||||
g.bind("HANDLER", "PATCH:" + pattern, object, params...)
|
||||
}
|
||||
|
||||
func (g *RouterGroup) HEAD(pattern string, object interface{}, params...interface{}) {
|
||||
g.bind("HANDLER", "HEAD:" + pattern, object, params...)
|
||||
}
|
||||
|
||||
func (g *RouterGroup) CONNECT(pattern string, object interface{}, params...interface{}) {
|
||||
g.bind("HANDLER", "CONNECT:" + pattern, object, params...)
|
||||
}
|
||||
|
||||
func (g *RouterGroup) OPTIONS(pattern string, object interface{}, params...interface{}) {
|
||||
g.bind("HANDLER", "OPTIONS:" + pattern, object, params...)
|
||||
}
|
||||
|
||||
func (g *RouterGroup) TRACE(pattern string, object interface{}, params...interface{}) {
|
||||
g.bind("HANDLER", "TRACE:" + pattern, object, params...)
|
||||
}
|
||||
|
||||
// REST路由注册
|
||||
func (g *RouterGroup) REST(pattern string, object interface{}) {
|
||||
g.bind("REST", pattern, object)
|
||||
}
|
||||
|
||||
// 执行路由绑定
|
||||
func (g *RouterGroup) bind(bindType string, pattern string, object interface{}, params...interface{}) {
|
||||
// 注册路由处理
|
||||
if len(g.prefix) > 0 {
|
||||
domain, method, path, err := g.server.parsePattern(pattern)
|
||||
if err != nil {
|
||||
glog.Fatalfln("invalid pattern: %s", pattern)
|
||||
}
|
||||
if bindType == "HANDLER" {
|
||||
pattern = g.server.serveHandlerKey(method, g.prefix + "/" + strings.TrimLeft(path, "/"), domain)
|
||||
} else {
|
||||
pattern = g.prefix + "/" + strings.TrimLeft(path, "/")
|
||||
}
|
||||
}
|
||||
methods := gconv.Strings(params)
|
||||
// 判断是否事件回调注册
|
||||
if _, ok := object.(HandlerFunc); ok && len(methods) > 0 {
|
||||
bindType = "HOOK"
|
||||
}
|
||||
switch bindType {
|
||||
case "HANDLER":
|
||||
if h, ok := object.(HandlerFunc); ok {
|
||||
if g.server != nil {
|
||||
g.server.BindHandler(pattern, h)
|
||||
} else {
|
||||
g.domain.BindHandler(pattern, h)
|
||||
}
|
||||
} else if g.isController(object) {
|
||||
if len(methods) > 0 {
|
||||
if g.server != nil {
|
||||
g.server.BindControllerMethod(pattern, object.(Controller), methods[0])
|
||||
} else {
|
||||
g.domain.BindControllerMethod(pattern, object.(Controller), methods[0])
|
||||
}
|
||||
} else {
|
||||
if g.server != nil {
|
||||
g.server.BindController(pattern, object.(Controller))
|
||||
} else {
|
||||
g.domain.BindController(pattern, object.(Controller))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if len(methods) > 0 {
|
||||
if g.server != nil {
|
||||
g.server.BindObjectMethod(pattern, object, methods[0])
|
||||
} else {
|
||||
g.domain.BindObjectMethod(pattern, object, methods[0])
|
||||
}
|
||||
} else {
|
||||
if g.server != nil {
|
||||
g.server.BindObject(pattern, object)
|
||||
} else {
|
||||
g.domain.BindObject(pattern, object)
|
||||
}
|
||||
}
|
||||
}
|
||||
case "REST":
|
||||
if g.isController(object) {
|
||||
if g.server != nil {
|
||||
g.server.BindControllerRest(pattern, object.(Controller))
|
||||
} else {
|
||||
g.domain.BindControllerRest(pattern, object.(Controller))
|
||||
}
|
||||
} else {
|
||||
if g.server != nil {
|
||||
g.server.BindObjectRest(pattern, object)
|
||||
} else {
|
||||
g.domain.BindObjectRest(pattern, object)
|
||||
}
|
||||
}
|
||||
case "HOOK":
|
||||
if h, ok := object.(HandlerFunc); ok {
|
||||
if g.server != nil {
|
||||
g.server.BindHookHandler(pattern, methods[0], h)
|
||||
} else {
|
||||
g.domain.BindHookHandler(pattern, methods[0], h)
|
||||
}
|
||||
} else {
|
||||
glog.Fatalfln("invalid hook handler for pattern:%s", pattern)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判断给定对象是否控制器对象:
|
||||
// 控制器必须包含以下公开的属性对象:Request/Response/Server/Cookie/Session/View.
|
||||
func (g *RouterGroup) isController(value interface{}) bool {
|
||||
// 首先判断是否满足控制器接口定义
|
||||
if _, ok := value.(Controller); !ok {
|
||||
return false
|
||||
}
|
||||
// 其次检查控制器的必需属性
|
||||
v := reflect.ValueOf(value)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
if v.FieldByName("Request").IsValid() && v.FieldByName("Response").IsValid() &&
|
||||
v.FieldByName("Server").IsValid() && v.FieldByName("Cookie").IsValid() &&
|
||||
v.FieldByName("Session").IsValid() && v.FieldByName("View").IsValid() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@ -25,7 +25,6 @@ func (s *Server)BindHookHandler(pattern string, hook string, handler HandlerFunc
|
||||
fname : "",
|
||||
faddr : handler,
|
||||
}, hook)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 通过map批量绑定回调函数
|
||||
@ -81,11 +80,11 @@ func (s *Server) callHookHandler(hook string, r *Request) {
|
||||
// 查询请求处理方法, 带缓存机制,按照Host、Method、Path进行缓存.
|
||||
func (s *Server) getHookHandlerWithCache(hook string, r *Request) []*handlerParsedItem {
|
||||
cacheItems := ([]*handlerParsedItem)(nil)
|
||||
cacheKey := s.hookHandlerKey(hook, r.Method, r.URL.Path, r.GetHost())
|
||||
cacheKey := s.handlerKey(hook, r.Method, r.URL.Path, r.GetHost())
|
||||
if v := s.hooksCache.Get(cacheKey); v == nil {
|
||||
cacheItems = s.searchHookHandler(r.Method, r.URL.Path, r.GetHost(), hook)
|
||||
if cacheItems != nil {
|
||||
s.hooksCache.Set(cacheKey, cacheItems)
|
||||
s.hooksCache.Set(cacheKey, cacheItems, s.config.RouterCacheExpire*1000)
|
||||
}
|
||||
} else {
|
||||
cacheItems = v.([]*handlerParsedItem)
|
||||
@ -189,7 +188,7 @@ func (s *Server) searchHookHandler(method, path, domain, hook string) []*handler
|
||||
}
|
||||
|
||||
// 生成hook key,如果是hook key,那么使用'%'符号分隔
|
||||
func (s *Server) hookHandlerKey(hook, method, path, domain string) string {
|
||||
func (s *Server) handlerKey(hook, method, path, domain string) string {
|
||||
return hook + "%" + s.serveHandlerKey(method, path, domain)
|
||||
}
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ func (s *Server) getServeHandlerWithCache(r *Request) *handlerParsedItem {
|
||||
if v := s.serveCache.Get(cacheKey); v == nil {
|
||||
cacheItem = s.searchServeHandler(r.Method, r.URL.Path, r.GetHost())
|
||||
if cacheItem != nil {
|
||||
s.serveCache.Set(cacheKey, cacheItem)
|
||||
s.serveCache.Set(cacheKey, cacheItem, s.config.RouterCacheExpire*1000)
|
||||
}
|
||||
} else {
|
||||
cacheItem = v.(*handlerParsedItem)
|
||||
|
||||
@ -9,6 +9,7 @@ package ghttp
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"gitee.com/johng/gf/g/container/gvar"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/util/grand"
|
||||
@ -30,11 +31,12 @@ func makeSessionId() string {
|
||||
return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 32) + grand.RandStr(3))
|
||||
}
|
||||
|
||||
// 获取或者生成一个session对象
|
||||
// 获取或者生成一个session对象(延迟初始化)
|
||||
func GetSession(r *Request) *Session {
|
||||
if r.Session != nil {
|
||||
return r.Session
|
||||
}
|
||||
return &Session {
|
||||
data : gmap.NewStringInterfaceMap(),
|
||||
server : r.Server,
|
||||
request : r,
|
||||
}
|
||||
}
|
||||
@ -42,8 +44,9 @@ func GetSession(r *Request) *Session {
|
||||
// 执行初始化(用于延迟初始化)
|
||||
func (s *Session) init() {
|
||||
if len(s.id) == 0 {
|
||||
s.id = s.request.Cookie.SessionId()
|
||||
s.data = s.server.sessions.GetOrSetFuncLock(s.id, func() interface{} {
|
||||
s.id = s.request.Cookie.SessionId()
|
||||
s.server = s.request.Server
|
||||
s.data = s.server.sessions.GetOrSetFuncLock(s.id, func() interface{} {
|
||||
return gmap.NewStringInterfaceMap()
|
||||
}, s.server.GetSessionMaxAge()).(*gmap.StringInterfaceMap)
|
||||
}
|
||||
@ -68,13 +71,13 @@ func (s *Session) Set(key string, value interface{}) {
|
||||
}
|
||||
|
||||
// 批量设置(BatchSet别名)
|
||||
func (s *Session) Sets (m map[string]interface{}) {
|
||||
func (s *Session) Sets(m map[string]interface{}) {
|
||||
s.init()
|
||||
s.BatchSet(m)
|
||||
}
|
||||
|
||||
// 批量设置
|
||||
func (s *Session) BatchSet (m map[string]interface{}) {
|
||||
func (s *Session) BatchSet(m map[string]interface{}) {
|
||||
s.init()
|
||||
s.data.BatchSet(m)
|
||||
}
|
||||
@ -85,13 +88,21 @@ func (s *Session) Contains (key string) bool {
|
||||
return s.data.Contains(key)
|
||||
}
|
||||
|
||||
// 获取session
|
||||
// 获取SESSION
|
||||
func (s *Session) Get (key string) interface{} {
|
||||
s.init()
|
||||
return s.data.Get(key)
|
||||
}
|
||||
func (s *Session) GetString (key string) string { return gconv.String(s.Get(key)) }
|
||||
func (s *Session) GetBool(key string) bool { return gconv.Bool(s.Get(key)) }
|
||||
|
||||
// 获取SESSION,建议都用该方法获取参数
|
||||
func (s *Session) GetVar(key string) gvar.VarRead {
|
||||
s.init()
|
||||
return gvar.NewRead(s.data.Get(key), false)
|
||||
}
|
||||
|
||||
|
||||
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)) }
|
||||
@ -105,8 +116,8 @@ func (s *Session) GetUint16(key string) uint16 { return gconv.U
|
||||
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) 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)) }
|
||||
@ -123,13 +134,13 @@ func (s *Session) GetStruct(key string, objPointer interface{}, attrMapping...ma
|
||||
}
|
||||
|
||||
// 删除session
|
||||
func (s *Session) Remove (key string) {
|
||||
func (s *Session) Remove(key string) {
|
||||
s.init()
|
||||
s.data.Remove(key)
|
||||
}
|
||||
|
||||
// 清空session
|
||||
func (s *Session) Clear () {
|
||||
func (s *Session) Clear() {
|
||||
s.init()
|
||||
s.data.Clear()
|
||||
}
|
||||
|
||||
@ -82,5 +82,4 @@ func (s *Server) Run() error {
|
||||
go s.handler(NewConnByNetConn(conn))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -85,7 +85,6 @@ func (c *Conn) Send(data []byte, retry...Retry) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 接收数据
|
||||
|
||||
@ -77,5 +77,4 @@ func (s *Server) Run() error {
|
||||
for {
|
||||
s.handler(NewConnByNetConn(conn))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,17 +1,12 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
// Copyright 2017-2018 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
|
||||
|
||||
// 单进程高速缓存.
|
||||
// 并发安全的单进程高速缓存.
|
||||
package gcache
|
||||
|
||||
const (
|
||||
// 当数据不过期时,默认设置的过期属性值,相当于:math.MaxInt64/1000000
|
||||
gDEFAULT_MAX_EXPIRE = 9223372036854
|
||||
)
|
||||
|
||||
// 全局缓存管理对象
|
||||
var cache = New()
|
||||
|
||||
@ -66,6 +61,11 @@ func BatchRemove(keys []interface{}) {
|
||||
cache.BatchRemove(keys)
|
||||
}
|
||||
|
||||
// 返回缓存的所有数据键值对(不包含已过期数据)
|
||||
func Data() map[interface{}]interface{} {
|
||||
return cache.Data()
|
||||
}
|
||||
|
||||
// 获得所有的键名,组成数组返回
|
||||
func Keys() []interface{} {
|
||||
return cache.Keys()
|
||||
|
||||
@ -22,8 +22,7 @@ func New(lruCap...int) *Cache {
|
||||
c := &Cache {
|
||||
memCache : newMemCache(lruCap...),
|
||||
}
|
||||
go c.autoSyncLoop()
|
||||
go c.autoClearLoop()
|
||||
go c.autoLoop()
|
||||
return c
|
||||
}
|
||||
|
||||
|
||||
@ -7,33 +7,37 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"math"
|
||||
"gitee.com/johng/gf/g/container/gset"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"sync"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/container/glist"
|
||||
"gitee.com/johng/gf/g/container/gset"
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
// 缓存对象
|
||||
type memCache struct {
|
||||
dmu sync.RWMutex // data锁(自定义锁的目的是除去键值的断言转换造成的性能损耗)
|
||||
emu sync.RWMutex // ekmap锁(expire key map)
|
||||
smu sync.RWMutex // eksets锁(expire key sets)
|
||||
lru *memCacheLru // LRU缓存限制(只有限定池大小时才启用)
|
||||
cap int // 控制缓存池大小,超过大小则按照LRU算法进行缓存过期处理(默认为0表示不进行限制)
|
||||
data map[interface{}]memCacheItem // 缓存数据(所有的缓存数据存放哈希表)
|
||||
ekmap map[interface{}]int64 // 键名对应的分组过期时间(用于相同键名过期时间快速更新),键值为10秒级时间戳
|
||||
eksets map[int64]*gset.Set // 分组过期时间对应的键名列表(用于自动过期快速删除),键值为10秒级时间戳
|
||||
eventList *glist.List // 异步处理队列
|
||||
lruGetList *glist.List // 获取方法的LRU列表
|
||||
stopChan chan struct{} // 关闭时间通知
|
||||
dataMu sync.RWMutex
|
||||
expireTimeMu sync.RWMutex
|
||||
expireSetMu sync.RWMutex
|
||||
|
||||
cap int // 控制缓存池大小,超过大小则按照LRU算法进行缓存过期处理(默认为0表示不进行限制)
|
||||
data map[interface{}]memCacheItem // 缓存数据(所有的缓存数据存放哈希表)
|
||||
expireTimes map[interface{}]int64 // 键名对应的分组过期时间(用于相同键名过期时间快速更新),键值为10秒级时间戳
|
||||
expireSets map[int64]*gset.Set // 分组过期时间对应的键名列表(用于自动过期快速删除),键值为10秒级时间戳
|
||||
|
||||
lru *memCacheLru // LRU缓存限制(只有限定cap池大小时才启用)
|
||||
lruGetList *glist.List // Get操作的LRU记录
|
||||
eventList *glist.List // 异步处理队列
|
||||
closed *gtype.Bool // 关闭事件通知
|
||||
}
|
||||
|
||||
// 缓存数据项
|
||||
type memCacheItem struct {
|
||||
v interface{} // 缓存键值
|
||||
v interface{} // 键值
|
||||
e int64 // 过期时间
|
||||
}
|
||||
|
||||
@ -43,81 +47,81 @@ type memCacheEvent struct {
|
||||
e int64 // 过期时间
|
||||
}
|
||||
|
||||
const (
|
||||
// 当数据不过期时,默认设置的过期属性值,相当于:math.MaxInt64/1000000
|
||||
gDEFAULT_MAX_EXPIRE = 9223372036854
|
||||
)
|
||||
|
||||
// 创建底层的缓存对象
|
||||
func newMemCache(lruCap...int) *memCache {
|
||||
c := &memCache {
|
||||
lru : newMemCacheLru(),
|
||||
data : make(map[interface{}]memCacheItem),
|
||||
ekmap : make(map[interface{}]int64),
|
||||
eksets : make(map[int64]*gset.Set),
|
||||
stopChan : make(chan struct{}),
|
||||
eventList : glist.New(),
|
||||
lruGetList : glist.New(),
|
||||
lruGetList : glist.New(),
|
||||
data : make(map[interface{}]memCacheItem),
|
||||
expireTimes : make(map[interface{}]int64),
|
||||
expireSets : make(map[int64]*gset.Set),
|
||||
eventList : glist.New(),
|
||||
closed : gtype.NewBool(),
|
||||
}
|
||||
if len(lruCap) > 0 {
|
||||
c.cap = lruCap[0]
|
||||
c.cap = lruCap[0]
|
||||
c.lru = newMemCacheLru(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// 计算过期缓存的键名(将毫秒换算成秒的整数毫秒)
|
||||
// 计算过期缓存的键名(将毫秒换算成秒的整数毫秒,按照10秒进行分组)
|
||||
func (c *memCache) makeExpireKey(expire int64) int64 {
|
||||
return int64(math.Ceil(float64(expire/10000) + 1)*10000)
|
||||
}
|
||||
|
||||
// 获取一个过期键名存放Set,如果没有则返回nil
|
||||
func (c *memCache) getExpireSet(expire int64) *gset.Set {
|
||||
c.smu.RLock()
|
||||
if ekset, ok := c.eksets[expire]; ok {
|
||||
c.smu.RUnlock()
|
||||
return ekset
|
||||
}
|
||||
c.smu.RUnlock()
|
||||
func (c *memCache) getExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
c.expireSetMu.RLock()
|
||||
expireSet, _ = c.expireSets[expire]
|
||||
c.expireSetMu.RUnlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取或者创建一个过期键名存放Set(由于是异步单线程执行,因此不会出现创建set时的覆盖问题)
|
||||
func (c *memCache) getOrNewExpireSet(expire int64) *gset.Set {
|
||||
if ekset := c.getExpireSet(expire); ekset == nil {
|
||||
set := gset.New()
|
||||
c.smu.Lock()
|
||||
// 二次检索确认
|
||||
if ekset, ok := c.eksets[expire]; !ok {
|
||||
c.eksets[expire] = set
|
||||
func (c *memCache) getOrNewExpireSet(expire int64) (expireSet *gset.Set) {
|
||||
if expireSet = c.getExpireSet(expire); expireSet == nil {
|
||||
expireSet = gset.New()
|
||||
c.expireSetMu.Lock()
|
||||
// 写锁二次检索确认
|
||||
if es, ok := c.expireSets[expire]; ok {
|
||||
expireSet = es
|
||||
} else {
|
||||
set = ekset
|
||||
c.expireSets[expire] = expireSet
|
||||
}
|
||||
c.smu.Unlock()
|
||||
return set
|
||||
} else {
|
||||
return ekset
|
||||
c.expireSetMu.Unlock()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 设置kv缓存键值对,过期时间单位为毫秒,expire<=0表示不过期
|
||||
func (c *memCache) Set(key interface{}, value interface{}, expire int) {
|
||||
expireTimestamp := c.getInternalExpire(expire)
|
||||
c.dmu.Lock()
|
||||
c.data[key] = memCacheItem{v : value, e : expireTimestamp}
|
||||
c.dmu.Unlock()
|
||||
c.eventList.PushBack(memCacheEvent{k : key, e : expireTimestamp})
|
||||
expireTime := c.getInternalExpire(expire)
|
||||
c.dataMu.Lock()
|
||||
c.data[key] = memCacheItem{v : value, e : expireTime}
|
||||
c.dataMu.Unlock()
|
||||
c.eventList.PushBack(&memCacheEvent{k : key, e : expireTime})
|
||||
}
|
||||
|
||||
// 设置kv缓存键值对,内部会对键名的存在性使用写锁进行二次检索确认,如果存在则不再写入;返回键名对应的键值。
|
||||
// 在高并发下有用,防止数据写入的并发逻辑错误。
|
||||
func (c *memCache) doSetWithLockCheck(key interface{}, value interface{}, expire int) interface{} {
|
||||
expireTimestamp := c.getInternalExpire(expire)
|
||||
c.dmu.Lock()
|
||||
c.dataMu.Lock()
|
||||
if v, ok := c.data[key]; ok && !v.IsExpired() {
|
||||
c.dmu.Unlock()
|
||||
return v
|
||||
c.dataMu.Unlock()
|
||||
return v.v
|
||||
}
|
||||
if f, ok := value.(func() interface {}); ok {
|
||||
value = f()
|
||||
}
|
||||
c.data[key] = memCacheItem{v : value, e : expireTimestamp}
|
||||
c.dmu.Unlock()
|
||||
c.eventList.PushBack(memCacheEvent{k : key, e : expireTimestamp})
|
||||
c.dataMu.Unlock()
|
||||
c.eventList.PushBack(&memCacheEvent{k : key, e : expireTimestamp})
|
||||
return value
|
||||
}
|
||||
|
||||
@ -140,23 +144,23 @@ func (c *memCache) SetIfNotExist(key interface{}, value interface{}, expire int)
|
||||
}
|
||||
|
||||
// 批量设置
|
||||
func (c *memCache) BatchSet(data map[interface{}]interface{}, expire int) {
|
||||
expireTimestamp := c.getInternalExpire(expire)
|
||||
func (c *memCache) BatchSet(data map[interface{}]interface{}, expire int) {
|
||||
expireTime := c.getInternalExpire(expire)
|
||||
for k, v := range data {
|
||||
c.dmu.Lock()
|
||||
c.data[k] = memCacheItem{v: v, e: expireTimestamp}
|
||||
c.dmu.Unlock()
|
||||
c.eventList.PushBack(memCacheEvent{k: k, e: expireTimestamp})
|
||||
c.dataMu.Lock()
|
||||
c.data[k] = memCacheItem{v: v, e: expireTime}
|
||||
c.dataMu.Unlock()
|
||||
c.eventList.PushBack(&memCacheEvent{k: k, e: expireTime})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取指定键名的值
|
||||
func (c *memCache) Get(key interface{}) interface{} {
|
||||
c.dmu.RLock()
|
||||
c.dataMu.RLock()
|
||||
item, ok := c.data[key]
|
||||
c.dmu.RUnlock()
|
||||
c.dataMu.RUnlock()
|
||||
if ok && !item.IsExpired() {
|
||||
// LRU(Least Recently Used)操作记录
|
||||
// 增加LRU(Least Recently Used)操作记录
|
||||
if c.cap > 0 {
|
||||
c.lruGetList.PushBack(key)
|
||||
}
|
||||
@ -199,35 +203,50 @@ func (c *memCache) Contains(key interface{}) bool {
|
||||
}
|
||||
|
||||
// 删除指定键值对,并返回被删除的键值
|
||||
func (c *memCache) Remove(key interface{}) interface{} {
|
||||
c.dmu.Lock()
|
||||
func (c *memCache) Remove(key interface{}) (value interface{}) {
|
||||
c.dataMu.RLock()
|
||||
item, ok := c.data[key]
|
||||
c.dataMu.RUnlock()
|
||||
if ok {
|
||||
value = item.v
|
||||
c.dataMu.Lock()
|
||||
delete(c.data, key)
|
||||
c.dataMu.Unlock()
|
||||
c.eventList.PushBack(&memCacheEvent{k: key, e: gtime.Millisecond() - 1000})
|
||||
}
|
||||
c.dmu.Unlock()
|
||||
return item.v
|
||||
return
|
||||
}
|
||||
|
||||
// 批量删除键值对,并返回被删除的键值对数据
|
||||
func (c *memCache) BatchRemove(keys []interface{}) {
|
||||
c.dmu.Lock()
|
||||
for _, key := range keys {
|
||||
delete(c.data, key)
|
||||
c.Remove(key)
|
||||
}
|
||||
c.dmu.Unlock()
|
||||
}
|
||||
|
||||
// 返回缓存的所有数据键值对(不包含已过期数据)
|
||||
func (c *memCache) Data() map[interface{}]interface{} {
|
||||
m := make(map[interface{}]interface{})
|
||||
c.dataMu.RLock()
|
||||
for k, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
m[k] = v.v
|
||||
}
|
||||
}
|
||||
c.dataMu.RUnlock()
|
||||
return m
|
||||
}
|
||||
|
||||
// 获得所有的键名,组成数组返回
|
||||
func (c *memCache) Keys() []interface{} {
|
||||
keys := make([]interface{}, 0)
|
||||
c.dmu.RLock()
|
||||
c.dataMu.RLock()
|
||||
for k, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
c.dmu.RUnlock()
|
||||
c.dataMu.RUnlock()
|
||||
return keys
|
||||
}
|
||||
|
||||
@ -239,129 +258,120 @@ func (c *memCache) KeyStrings() []string {
|
||||
// 获得所有的值,组成数组返回
|
||||
func (c *memCache) Values() []interface{} {
|
||||
values := make([]interface{}, 0)
|
||||
c.dmu.RLock()
|
||||
c.dataMu.RLock()
|
||||
for _, v := range c.data {
|
||||
if !v.IsExpired() {
|
||||
values = append(values, v.v)
|
||||
}
|
||||
}
|
||||
c.dmu.RUnlock()
|
||||
c.dataMu.RUnlock()
|
||||
return values
|
||||
}
|
||||
|
||||
// 获得缓存对象的键值对数量
|
||||
func (c *memCache) Size() int {
|
||||
c.dmu.RLock()
|
||||
length := len(c.data)
|
||||
c.dmu.RUnlock()
|
||||
return length
|
||||
func (c *memCache) Size() (size int) {
|
||||
c.dataMu.RLock()
|
||||
size = len(c.data)
|
||||
c.dataMu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// 删除缓存对象
|
||||
func (c *memCache) Close() {
|
||||
close(c.stopChan)
|
||||
c.lru.Close()
|
||||
}
|
||||
|
||||
// 数据自动同步循环
|
||||
func (c *memCache) autoSyncLoop() {
|
||||
newe := int64(0)
|
||||
for {
|
||||
select {
|
||||
case <-c.stopChan:
|
||||
return
|
||||
default:
|
||||
for {
|
||||
v := c.eventList.PopFront()
|
||||
if v == nil {
|
||||
break
|
||||
}
|
||||
item := v.(memCacheEvent)
|
||||
nowm := gtime.Millisecond()
|
||||
// 如果用户设置的时间比当前时间还小,那么表示要自动清除了,
|
||||
// 这里赋值一个当前时间-10秒的时间,在自动清理的goroutine中会自动检测删除该key
|
||||
if item.e < nowm {
|
||||
newe = c.makeExpireKey(nowm) - 10000
|
||||
} else {
|
||||
newe = c.makeExpireKey(item.e)
|
||||
}
|
||||
// 添加该key到对应的过期集合中
|
||||
// 注意:这里不需要检查存在性,
|
||||
// 因为在key过期的时候,会和原始的键值对中的过期时间做核对。
|
||||
c.getOrNewExpireSet(newe).Add(item.k)
|
||||
// 重新设置对应键名的过期时间
|
||||
c.emu.Lock()
|
||||
c.ekmap[item.k] = newe
|
||||
c.emu.Unlock()
|
||||
// LRU(Least Recently Used)操作记录
|
||||
if c.cap > 0 {
|
||||
c.lru.Push(item.k)
|
||||
}
|
||||
}
|
||||
if c.cap > 0 {
|
||||
// 优先级高的lru key放后面,读取列表
|
||||
for {
|
||||
if v := c.lruGetList.PopFront(); v != nil {
|
||||
c.lru.Push(v)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
if c.cap > 0 {
|
||||
c.lru.Close()
|
||||
}
|
||||
c.closed.Set(true)
|
||||
}
|
||||
|
||||
// LRU缓存淘汰处理+自动清理过期键值对
|
||||
// 每隔10秒清除过去60秒的键值对数据
|
||||
func (c *memCache) autoClearLoop() {
|
||||
for {
|
||||
select {
|
||||
case <- c.stopChan:
|
||||
return
|
||||
default:
|
||||
// 缓存过期处理
|
||||
ek := c.makeExpireKey(gtime.Millisecond())
|
||||
eks := []int64{ek - 10000, ek - 20000, ek - 30000, ek - 40000, ek - 50000, ek - 60000}
|
||||
for _, v := range eks {
|
||||
if ekset := c.getExpireSet(v); ekset != nil {
|
||||
ekset.Iterator(func(v interface{}) bool {
|
||||
return c.clearByKey(v)
|
||||
})
|
||||
}
|
||||
// 数据处理完之后从集合中删除该时间段
|
||||
c.smu.Lock()
|
||||
delete(c.eksets, v)
|
||||
c.smu.Unlock()
|
||||
}
|
||||
// LRU缓存淘汰清理
|
||||
if c.cap > 0 {
|
||||
for i := c.Size() - c.cap; i > 0; i-- {
|
||||
if s := c.lru.Pop(); s != nil {
|
||||
c.clearByKey(s, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
time.Sleep(10*time.Second)
|
||||
}
|
||||
}
|
||||
// 数据异步任务循环:
|
||||
// 1、将事件列表中的数据异步处理,并同步结果到expireTimes和expireSets属性中;
|
||||
// 2、清理过期键值对数据;
|
||||
func (c *memCache) autoLoop() {
|
||||
event := (*memCacheEvent)(nil)
|
||||
oldExpireTime := int64(0)
|
||||
newExpireTime := int64(0)
|
||||
for {
|
||||
if c.closed.Val() {
|
||||
return
|
||||
}
|
||||
// ========================
|
||||
// 数据同步处理
|
||||
// ========================
|
||||
for {
|
||||
v := c.eventList.PopFront()
|
||||
if v == nil {
|
||||
break
|
||||
}
|
||||
event = v.(*memCacheEvent)
|
||||
// 获得旧的过期时间分组
|
||||
c.expireTimeMu.RLock()
|
||||
oldExpireTime = c.expireTimes[event.k]
|
||||
c.expireTimeMu.RUnlock()
|
||||
// 计算新的过期时间分组
|
||||
newExpireTime = c.makeExpireKey(event.e)
|
||||
if newExpireTime != oldExpireTime {
|
||||
c.getOrNewExpireSet(newExpireTime).Add(event.k)
|
||||
if oldExpireTime != 0 {
|
||||
c.getOrNewExpireSet(oldExpireTime).Remove(event.k)
|
||||
}
|
||||
// 重新设置对应键名的过期时间
|
||||
c.expireTimeMu.Lock()
|
||||
c.expireTimes[event.k] = newExpireTime
|
||||
c.expireTimeMu.Unlock()
|
||||
}
|
||||
// 写入操作也会增加到LRU(Least Recently Used)操作记录
|
||||
if c.cap > 0 {
|
||||
c.lru.Push(event.k)
|
||||
}
|
||||
}
|
||||
// 异步处理读取操作的LRU列表
|
||||
if c.cap > 0 && c.lruGetList.Len() > 0 {
|
||||
for {
|
||||
if v := c.lruGetList.PopFront(); v != nil {
|
||||
c.lru.Push(v)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// ========================
|
||||
// 缓存过期处理
|
||||
// ========================
|
||||
ek := c.makeExpireKey(gtime.Millisecond())
|
||||
eks := []int64{ek - 10000, ek - 20000, ek - 30000, ek - 40000, ek - 50000}
|
||||
for _, expireTime := range eks {
|
||||
if expireSet := c.getExpireSet(expireTime); expireSet != nil {
|
||||
// 遍历Set,执行数据过期删除
|
||||
expireSet.Iterator(func(key interface{}) bool {
|
||||
return c.clearByKey(key)
|
||||
})
|
||||
// Set数据处理完之后删除该Set
|
||||
c.expireSetMu.Lock()
|
||||
delete(c.expireSets, expireTime)
|
||||
c.expireSetMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// 每间隔1秒批量处理一次
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// 删除对应键名的缓存数据
|
||||
func (c *memCache) clearByKey(key interface{}, force...bool) bool {
|
||||
// 删除缓存数据
|
||||
c.dmu.Lock()
|
||||
c.dataMu.Lock()
|
||||
// 删除核对,真正的过期才删除
|
||||
if item, ok := c.data[key]; (ok && item.IsExpired()) || (len(force) > 0 && force[0]) {
|
||||
delete(c.data, key)
|
||||
}
|
||||
c.dmu.Unlock()
|
||||
c.dataMu.Unlock()
|
||||
|
||||
// 删除异步处理数据项
|
||||
c.emu.Lock()
|
||||
delete(c.ekmap, key)
|
||||
c.emu.Unlock()
|
||||
c.expireTimeMu.Lock()
|
||||
delete(c.expireTimes, key)
|
||||
c.expireTimeMu.Unlock()
|
||||
|
||||
// 删除LRU管理对象中指定键名
|
||||
c.lru.Remove(key)
|
||||
|
||||
@ -7,27 +7,31 @@
|
||||
package gcache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/glist"
|
||||
"gitee.com/johng/gf/g/container/gqueue"
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LRU算法实现对象,底层双向链表使用了标准库的list.List
|
||||
type memCacheLru struct {
|
||||
data *gmap.Map // 记录键名与链表中的位置项指针
|
||||
list *glist.List // 键名历史记录链表
|
||||
queue *gqueue.Queue // 事件队列
|
||||
cache *memCache // 所属Cache对象
|
||||
data *gmap.Map // 记录键名与链表中的位置项指针
|
||||
list *glist.List // 键名历史记录链表
|
||||
rawList *glist.List // 事件列表
|
||||
closed *gtype.Bool // 是否关闭
|
||||
}
|
||||
|
||||
// 创建LRU管理对象
|
||||
func newMemCacheLru() *memCacheLru {
|
||||
func newMemCacheLru(cache *memCache) *memCacheLru {
|
||||
lru := &memCacheLru {
|
||||
list : glist.New(),
|
||||
data : gmap.New(),
|
||||
queue : gqueue.New(),
|
||||
cache : cache,
|
||||
data : gmap.New(),
|
||||
list : glist.New(),
|
||||
rawList : glist.New(),
|
||||
closed : gtype.NewBool(),
|
||||
}
|
||||
go lru.StartAutoLoop()
|
||||
return lru
|
||||
@ -35,7 +39,7 @@ func newMemCacheLru() *memCacheLru {
|
||||
|
||||
// 关闭LRU对象
|
||||
func (lru *memCacheLru) Close() {
|
||||
lru.queue.Close()
|
||||
lru.closed.Set(true)
|
||||
}
|
||||
|
||||
// 删除指定数据项
|
||||
@ -53,7 +57,7 @@ func (lru *memCacheLru) Size() int {
|
||||
|
||||
// 添加LRU数据项
|
||||
func (lru *memCacheLru) Push(key interface{}) {
|
||||
lru.queue.Push(key)
|
||||
lru.rawList.PushBack(key)
|
||||
}
|
||||
|
||||
// 从链表尾删除LRU数据项,并返回对应数据
|
||||
@ -70,20 +74,34 @@ func (lru *memCacheLru) Print() {
|
||||
for _, v := range lru.list.FrontAll() {
|
||||
fmt.Printf("%v ", v)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// 异步执行协程,将queue中的数据同步到list中
|
||||
func (lru *memCacheLru) StartAutoLoop() {
|
||||
for {
|
||||
if v := lru.queue.Pop(); v != nil {
|
||||
// 删除对应链表项
|
||||
if v := lru.data.Get(v); v != nil {
|
||||
lru.list.Remove(v.(*list.Element))
|
||||
}
|
||||
// 将数据插入到链表头,并记录对应的链表项到哈希表中,便于检索
|
||||
lru.data.Set(v, lru.list.PushFront(v))
|
||||
} else {
|
||||
break
|
||||
if lru.closed.Val() {
|
||||
return
|
||||
}
|
||||
// 数据同步
|
||||
for {
|
||||
if v := lru.rawList.PopFront(); v != nil {
|
||||
// 删除对应链表项
|
||||
if v := lru.data.Get(v); v != nil {
|
||||
lru.list.Remove(v.(*list.Element))
|
||||
}
|
||||
// 将数据插入到链表头,并记录对应的链表项到哈希表中,便于检索
|
||||
lru.data.Set(v, lru.list.PushFront(v))
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
// 数据清理
|
||||
for i := lru.Size() - lru.cache.cap; i > 0; i-- {
|
||||
if s := lru.Pop(); s != nil {
|
||||
lru.cache.clearByKey(s, true)
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
@ -9,11 +9,15 @@
|
||||
package gcfg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/garray"
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"gitee.com/johng/gf/g/container/gvar"
|
||||
"gitee.com/johng/gf/g/encoding/gjson"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/os/gfsnotify"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/os/gspath"
|
||||
@ -26,7 +30,7 @@ const (
|
||||
// 配置管理对象
|
||||
type Config struct {
|
||||
name *gtype.String // 默认配置文件名称
|
||||
paths *gspath.SPath // 搜索目录路径
|
||||
paths *garray.StringArray // 搜索目录路径
|
||||
jsons *gmap.StringInterfaceMap // 配置文件对象
|
||||
vc *gtype.Bool // 层级检索是否执行分隔符冲突检测(默认为false,检测会比较影响检索效率)
|
||||
}
|
||||
@ -39,33 +43,54 @@ func New(path string, file...string) *Config {
|
||||
}
|
||||
c := &Config {
|
||||
name : gtype.NewString(name),
|
||||
paths : gspath.New(),
|
||||
paths : garray.NewStringArray(0, 1),
|
||||
jsons : gmap.NewStringInterfaceMap(),
|
||||
vc : gtype.NewBool(),
|
||||
}
|
||||
c.SetPath(path)
|
||||
if len(path) > 0 {
|
||||
c.SetPath(path)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// 判断从哪个配置文件中获取内容,返回配置文件的绝对路径
|
||||
func (c *Config) filePath(file...string) string {
|
||||
func (c *Config) filePath(file...string) (path string) {
|
||||
name := c.name.Val()
|
||||
if len(file) > 0 {
|
||||
name = file[0]
|
||||
}
|
||||
path, _ := c.paths.Search(name)
|
||||
c.paths.RLockFunc(func(array []string) {
|
||||
for _, v := range array {
|
||||
if path, _ = gspath.Search(v, name); path != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
if path == "" {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" in following paths:", name))
|
||||
c.paths.RLockFunc(func(array []string) {
|
||||
for k, v := range array {
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
|
||||
}
|
||||
})
|
||||
glog.Error(buffer.String())
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// 设置配置管理器的配置文件存放目录绝对路径
|
||||
func (c *Config) SetPath(path string) error {
|
||||
if rp, err := c.paths.Set(path); err != nil {
|
||||
glog.Error("gcfg.SetPath failed:", err.Error())
|
||||
realPath := gfile.RealPath(path)
|
||||
if realPath == "" {
|
||||
err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
|
||||
glog.Error(fmt.Sprintf(`[gcfg] SetPath failed: %s`, err.Error()))
|
||||
return err
|
||||
} else {
|
||||
c.jsons.Clear()
|
||||
glog.Debug("gcfg.SetPath:", rp)
|
||||
}
|
||||
c.jsons.Clear()
|
||||
c.paths.Clear()
|
||||
c.paths.Append(realPath)
|
||||
glog.Debug("[gcfg] SetPath:", realPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -78,44 +103,44 @@ func (c *Config) SetViolenceCheck(check bool) {
|
||||
|
||||
// 添加配置管理器的配置文件搜索路径
|
||||
func (c *Config) AddPath(path string) error {
|
||||
if rp, err := c.paths.Add(path); err != nil {
|
||||
glog.Debug("gcfg.AddPath failed:", err.Error())
|
||||
realPath := gfile.RealPath(path)
|
||||
if realPath == "" {
|
||||
err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
|
||||
glog.Error(fmt.Sprintf(`[gcfg] AddPath failed: %s`, err.Error()))
|
||||
return err
|
||||
} else {
|
||||
glog.Debug("gcfg.AddPath:", rp)
|
||||
}
|
||||
c.paths.Append(realPath)
|
||||
glog.Debug("[gcfg] AddPath:", realPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取指定文件的绝对路径,默认获取默认的配置文件路径
|
||||
func (c *Config) GetFilePath(file...string) string {
|
||||
name := c.name.Val()
|
||||
if len(file) > 0 {
|
||||
name = file[0]
|
||||
}
|
||||
path, _ := c.paths.Search(name)
|
||||
return path
|
||||
return c.filePath(file...)
|
||||
}
|
||||
|
||||
// 设置配置管理对象的默认文件名称
|
||||
func (c *Config) SetFileName(name string) {
|
||||
glog.Debug("gcfg.SetFileName:", name)
|
||||
glog.Debug("[gcfg] SetFileName:", name)
|
||||
c.name.Set(name)
|
||||
}
|
||||
|
||||
// 添加配置文件到配置管理器中,第二个参数为非必须,如果不输入表示添加进入默认的配置名称中
|
||||
func (c *Config) getJson(file...string) *gjson.Json {
|
||||
fpath := c.filePath(file...)
|
||||
if r := c.jsons.Get(fpath); r != nil {
|
||||
filePath := c.filePath(file...)
|
||||
if filePath == "" {
|
||||
return nil
|
||||
}
|
||||
if r := c.jsons.Get(filePath); r != nil {
|
||||
return r.(*gjson.Json)
|
||||
}
|
||||
if j, err := gjson.Load(fpath); err == nil {
|
||||
if j, err := gjson.Load(filePath); err == nil {
|
||||
j.SetViolenceCheck(c.vc.Val())
|
||||
c.addMonitor(fpath)
|
||||
c.jsons.Set(fpath, j)
|
||||
c.addMonitor(filePath)
|
||||
c.jsons.Set(filePath, j)
|
||||
return j
|
||||
} else {
|
||||
glog.Errorfln(`gcfg.Load config file "%s" failed: %s`, fpath, err.Error())
|
||||
glog.Errorfln(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -129,11 +154,11 @@ func (c *Config) Get(pattern string, file...string) interface{} {
|
||||
}
|
||||
|
||||
// 获得配置项,返回动态变量
|
||||
func (c *Config) GetVar(pattern string, file...string) *gvar.Var {
|
||||
func (c *Config) GetVar(pattern string, file...string) gvar.VarRead {
|
||||
if j := c.getJson(file...); j != nil {
|
||||
return gvar.New(j.Get(pattern))
|
||||
return gvar.New(j.Get(pattern), false)
|
||||
}
|
||||
return nil
|
||||
return gvar.New(nil, false)
|
||||
}
|
||||
|
||||
// 获得一个键值对关联数组/哈希表,方便操作,不需要自己做类型转换
|
||||
|
||||
@ -108,10 +108,9 @@ func IsDir(path string) bool {
|
||||
return s.IsDir()
|
||||
}
|
||||
|
||||
// 获取当前工作目录
|
||||
// 获取当前工作目录(SelfDir()方法的别名)
|
||||
func Pwd() string {
|
||||
pwd, _ := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
return pwd
|
||||
return SelfDir()
|
||||
}
|
||||
|
||||
// 判断所给路径是否为文件
|
||||
@ -371,7 +370,7 @@ func homeWindows() (string, error) {
|
||||
return home, nil
|
||||
}
|
||||
|
||||
// 获取入口函数文件所在目录(main包文件目录),
|
||||
// 获取入口函数文件所在目录(main包文件目录),
|
||||
// **仅对源码开发环境有效(即仅对生成该可执行文件的系统下有效)**
|
||||
func MainPkgPath() string {
|
||||
path := mainPkgPath.Val()
|
||||
@ -402,6 +401,7 @@ func MainPkgPath() string {
|
||||
if p == f {
|
||||
break
|
||||
}
|
||||
// 会自动扫描源码,寻找main包
|
||||
if paths, err := ScanDir(p, "*.go"); err == nil && len(paths) > 0 {
|
||||
for _, path := range paths {
|
||||
if gregex.IsMatchString(`package\s+main`, GetContents(path)) {
|
||||
|
||||
@ -105,8 +105,6 @@ func GetNextCharOffsetByPath(path string, char byte, start int64) int64 {
|
||||
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, gDEFAULT_PERM); err == nil {
|
||||
defer f.Close()
|
||||
return GetNextCharOffset(f, char, start)
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
return -1
|
||||
}
|
||||
@ -124,8 +122,6 @@ func GetBinContentsTilCharByPath(path string, char byte, start int64) ([]byte, i
|
||||
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, gDEFAULT_PERM); err == nil {
|
||||
defer f.Close()
|
||||
return GetBinContentsTilChar(f, char, start)
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
@ -144,8 +140,6 @@ func GetBinContentsByTwoOffsetsByPath(path string, start int64, end int64) []byt
|
||||
if f, err := OpenWithFlagPerm(path, os.O_RDONLY, gDEFAULT_PERM); err == nil {
|
||||
defer f.Close()
|
||||
return GetBinContentsByTwoOffsets(f, start, end)
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -7,7 +7,6 @@
|
||||
package gfsnotify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/glist"
|
||||
)
|
||||
|
||||
@ -32,8 +31,8 @@ func (w *Watcher) startWatchLoop() {
|
||||
return struct {}{}
|
||||
}, REPEAT_EVENT_FILTER_INTERVAL)
|
||||
|
||||
case err := <- w.watcher.Errors:
|
||||
fmt.Errorf("error: %s\n" + err.Error());
|
||||
case <- w.watcher.Errors:
|
||||
//fmt.Fprintf(os.Stderr, "[gfsnotify] error: %s\n", err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@ -151,10 +151,6 @@ func Fatalf(format string, v ...interface{}) {
|
||||
logger.Fatalf(format, v ...)
|
||||
}
|
||||
|
||||
func Fatalln(v ...interface{}) {
|
||||
logger.Fatalln(v ...)
|
||||
}
|
||||
|
||||
func Fatalfln(format string, v ...interface{}) {
|
||||
logger.Fatalfln(format, v ...)
|
||||
}
|
||||
@ -167,10 +163,6 @@ func Panicf(format string, v ...interface{}) {
|
||||
logger.Panicf(format, v ...)
|
||||
}
|
||||
|
||||
func Panicln(v ...interface{}) {
|
||||
logger.Panicln(v ...)
|
||||
}
|
||||
|
||||
func Panicfln(format string, v ...interface{}) {
|
||||
logger.Panicfln(format, v ...)
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ type Logger struct {
|
||||
file *gtype.String // 日志文件名称格式
|
||||
level *gtype.Int // 日志输出等级
|
||||
btSkip *gtype.Int // 错误产生时的backtrace回调信息skip条数
|
||||
btEnabled *gtype.Bool // 是否当打印错误时同时开启backtrace打印
|
||||
btStatus *gtype.Int // 是否当打印错误时同时开启backtrace打印(默认-1,表示默认打印逻辑 - 错误才打印)
|
||||
printHeader *gtype.Bool // 是否不打印前缀信息(时间,级别等)
|
||||
alsoStdPrint *gtype.Bool // 控制台打印开关,当输出到文件/自定义输出时也同时打印到终端
|
||||
}
|
||||
@ -65,7 +65,7 @@ func New() *Logger {
|
||||
file : gtype.NewString(gDEFAULT_FILE_FORMAT),
|
||||
level : gtype.NewInt(defaultLevel.Val()),
|
||||
btSkip : gtype.NewInt(),
|
||||
btEnabled : gtype.NewBool(true),
|
||||
btStatus : gtype.NewInt(-1),
|
||||
printHeader : gtype.NewBool(true),
|
||||
alsoStdPrint : gtype.NewBool(true),
|
||||
}
|
||||
@ -80,7 +80,7 @@ func (l *Logger) Clone() *Logger {
|
||||
file : l.file.Clone(),
|
||||
level : l.level.Clone(),
|
||||
btSkip : l.btSkip.Clone(),
|
||||
btEnabled : l.btEnabled.Clone(),
|
||||
btStatus : l.btStatus.Clone(),
|
||||
printHeader : l.printHeader.Clone(),
|
||||
alsoStdPrint : l.alsoStdPrint.Clone(),
|
||||
}
|
||||
@ -106,7 +106,12 @@ func (l *Logger) SetDebug(debug bool) {
|
||||
}
|
||||
|
||||
func (l *Logger) SetBacktrace(enabled bool) {
|
||||
l.btEnabled.Set(enabled)
|
||||
if enabled {
|
||||
l.btStatus.Set(1)
|
||||
} else {
|
||||
l.btStatus.Set(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 设置BacktraceSkip
|
||||
@ -136,6 +141,13 @@ func (l *Logger) getFilePointer() *gfpool.File {
|
||||
file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.file.Val(), func(s string) string {
|
||||
return gtime.Now().Format(strings.Trim(s, "{}"))
|
||||
})
|
||||
// 如果日志目录不存在则创建目录路径
|
||||
if !gfile.Exists(path) {
|
||||
if err := gfile.Mkdir(path); err != nil {
|
||||
fmt.Fprintln(os.Stderr, fmt.Sprintf(`[glog] mkdir "%s" failed: %s`, path, err.Error()))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
fpath := path + gfile.Separator + file
|
||||
if fp, err := gfpool.Open(fpath, gDEFAULT_FILE_POOL_FLAGS, gDEFAULT_FPOOL_PERM, gDEFAULT_FPOOL_EXPIRE); err == nil {
|
||||
return fp
|
||||
@ -151,7 +163,7 @@ func (l *Logger) SetPath(path string) error {
|
||||
// 如果目录不存在,则递归创建
|
||||
if !gfile.Exists(path) {
|
||||
if err := gfile.Mkdir(path); err != nil {
|
||||
fmt.Fprintln(os.Stderr, fmt.Sprintf(`glog mkdir "%s" failed: %s`, path, err.Error()))
|
||||
fmt.Fprintln(os.Stderr, fmt.Sprintf(`[glog] mkdir "%s" failed: %s`, path, err.Error()))
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -220,24 +232,35 @@ func (l *Logger) stdPrint(s string) {
|
||||
// 核心打印数据方法(标准错误)
|
||||
func (l *Logger) errPrint(s string) {
|
||||
// 记录调用回溯信息
|
||||
if l.btEnabled.Val() {
|
||||
tracestr := l.GetBacktrace()
|
||||
if tracestr != "" {
|
||||
backtrace := "Backtrace:" + ln + tracestr
|
||||
if s[len(s) - 1] == byte('\n') {
|
||||
s = s + backtrace + ln
|
||||
} else {
|
||||
s = s + ln + backtrace + ln
|
||||
}
|
||||
}
|
||||
status := l.btStatus.Val()
|
||||
if status == -1 || status == 1 {
|
||||
s = l.appendBacktrace(s)
|
||||
}
|
||||
// 防止串日志情况,这里不使用stderr,而是使用stdout
|
||||
l.print(os.Stdout, s)
|
||||
}
|
||||
|
||||
// 输出内容中添加回溯信息
|
||||
func (l *Logger) appendBacktrace(s string, skip...int) string {
|
||||
trace := l.GetBacktrace(skip...)
|
||||
if trace != "" {
|
||||
backtrace := "Backtrace:" + ln + trace
|
||||
if len(s) > 0 {
|
||||
if s[len(s)-1] == byte('\n') {
|
||||
s = s + backtrace + ln
|
||||
} else {
|
||||
s = s + ln + backtrace + ln
|
||||
}
|
||||
} else {
|
||||
s = backtrace
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// 直接打印回溯信息,参数skip表示调用端往上多少级开始回溯
|
||||
func (l *Logger) PrintBacktrace(skip...int) {
|
||||
l.Println(l.GetBacktrace(skip...))
|
||||
l.Println(l.appendBacktrace("", skip...))
|
||||
}
|
||||
|
||||
// 获取文件调用回溯字符串,参数skip表示调用端往上多少级开始回溯
|
||||
@ -295,46 +318,35 @@ func (l *Logger) Printfln(format string, v ...interface{}) {
|
||||
}
|
||||
|
||||
func (l *Logger) Fatal(v ...interface{}) {
|
||||
l.errPrint(fmt.Sprintln(v...))
|
||||
l.errPrint("[FATA] " + fmt.Sprintln(v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatalf(format string, v ...interface{}) {
|
||||
l.errPrint(fmt.Sprintf(format, v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatalln(v ...interface{}) {
|
||||
l.errPrint(fmt.Sprintln(v...))
|
||||
l.errPrint("[FATA] " + fmt.Sprintf(format, v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *Logger) Fatalfln(format string, v ...interface{}) {
|
||||
l.errPrint(fmt.Sprintf(format + ln, v...))
|
||||
l.errPrint("[FATA] " + fmt.Sprintf(format + ln, v...))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *Logger) Panic(v ...interface{}) {
|
||||
s := fmt.Sprintln(v...)
|
||||
l.errPrint(s)
|
||||
l.errPrint("[PANI] " + s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
func (l *Logger) Panicf(format string, v ...interface{}) {
|
||||
s := fmt.Sprintf(format, v...)
|
||||
l.errPrint(s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
func (l *Logger) Panicln(v ...interface{}) {
|
||||
s := fmt.Sprintln(v...)
|
||||
l.errPrint(s)
|
||||
l.errPrint("[PANI] " + s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
func (l *Logger) Panicfln(format string, v ...interface{}) {
|
||||
s := fmt.Sprintf(format + ln, v...)
|
||||
l.errPrint(s)
|
||||
l.errPrint("[PANI] " + s)
|
||||
panic(s)
|
||||
}
|
||||
|
||||
|
||||
@ -121,7 +121,6 @@ func getShell() string {
|
||||
}
|
||||
return path
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 获取当前系统默认shell执行指令的option参数
|
||||
@ -132,7 +131,6 @@ func getShellOption() string {
|
||||
default:
|
||||
return "-c"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 从环境变量PATH中搜索可执行文件
|
||||
|
||||
@ -62,7 +62,10 @@ func startTcpListening() {
|
||||
|
||||
// TCP数据通信处理回调函数
|
||||
func tcpServiceHandler(conn *gtcp.Conn) {
|
||||
var retry = gtcp.Retry{3, 10}
|
||||
retry := gtcp.Retry {
|
||||
Count : 3,
|
||||
Interval: 10,
|
||||
}
|
||||
for {
|
||||
var result []byte
|
||||
buffer, err := conn.Recv(-1, retry)
|
||||
@ -97,32 +100,32 @@ func tcpServiceHandler(conn *gtcp.Conn) {
|
||||
}
|
||||
|
||||
// 数据解包,防止黏包
|
||||
// 数据格式:总长度(24bit)|发送进程PID(16bit)|接收进程PID(16bit)|分组长度(8bit)|分组名称(变长)|校验(32bit)|参数(变长)
|
||||
// 数据格式:总长度(24bit)|发送进程PID(24bit)|接收进程PID(24bit)|分组长度(8bit)|分组名称(变长)|校验(32bit)|参数(变长)
|
||||
func bufferToMsgs(buffer []byte) []*Msg {
|
||||
s := 0
|
||||
msgs := make([]*Msg, 0)
|
||||
for s < len(buffer) {
|
||||
// 长度解析及校验
|
||||
length := gbinary.DecodeToInt(buffer[s : s + 3])
|
||||
if length < 12 || length > len(buffer) {
|
||||
if length < 14 || length > len(buffer) {
|
||||
s++
|
||||
continue
|
||||
}
|
||||
// 分组信息解析
|
||||
groupLen := gbinary.DecodeToInt(buffer[s + 7 : s + 8])
|
||||
groupLen := gbinary.DecodeToInt(buffer[s + 9 : s + 10])
|
||||
// checksum校验(仅对参数做校验,提高校验效率)
|
||||
checksum1 := gbinary.DecodeToUint32(buffer[s + 8 + groupLen : s + 8 + groupLen + 4])
|
||||
checksum2 := gtcp.Checksum(buffer[s + 8 + groupLen + 4 : s + length])
|
||||
checksum1 := gbinary.DecodeToUint32(buffer[s + 10 + groupLen : s + 10 + groupLen + 4])
|
||||
checksum2 := gtcp.Checksum(buffer[s + 10 + groupLen + 4 : s + length])
|
||||
if checksum1 != checksum2 {
|
||||
s++
|
||||
continue
|
||||
}
|
||||
// 接收进程PID校验
|
||||
if Pid() == gbinary.DecodeToInt(buffer[s + 5 : s + 7]) {
|
||||
if Pid() == gbinary.DecodeToInt(buffer[s + 6 : s + 9]) {
|
||||
msgs = append(msgs, &Msg {
|
||||
Pid : gbinary.DecodeToInt(buffer[s + 3 : s + 5]),
|
||||
Data : buffer[s + 8 + groupLen + 4 : s + length],
|
||||
Group : string(buffer[s + 8 : s + 8 + groupLen]),
|
||||
Pid : gbinary.DecodeToInt(buffer[s + 3 : s + 6]),
|
||||
Data : buffer[s + 10 + groupLen + 4 : s + length],
|
||||
Group : string(buffer[s + 10 : s + 10 + groupLen]),
|
||||
})
|
||||
}
|
||||
s += length
|
||||
|
||||
@ -27,16 +27,16 @@ const (
|
||||
)
|
||||
|
||||
// 向指定gproc进程发送数据
|
||||
// 数据格式:总长度(24bit)|发送进程PID(16bit)|接收进程PID(16bit)|分组长度(8bit)|分组名称(变长)|校验(32bit)|参数(变长)
|
||||
// 数据格式:总长度(24bit)|发送进程PID(24bit)|接收进程PID(24bit)|分组长度(8bit)|分组名称(变长)|校验(32bit)|参数(变长)
|
||||
func Send(pid int, data []byte, group...string) error {
|
||||
groupName := gPROC_COMM_DEAFULT_GRUOP_NAME
|
||||
if len(group) > 0 {
|
||||
groupName = group[0]
|
||||
}
|
||||
buffer := make([]byte, 0)
|
||||
buffer = append(buffer, gbinary.EncodeByLength(3, len(groupName) + len(data) + 12)...)
|
||||
buffer = append(buffer, gbinary.EncodeByLength(2, Pid())...)
|
||||
buffer = append(buffer, gbinary.EncodeByLength(2, pid)...)
|
||||
buffer = append(buffer, gbinary.EncodeByLength(3, len(groupName) + len(data) + 14)...)
|
||||
buffer = append(buffer, gbinary.EncodeByLength(3, Pid())...)
|
||||
buffer = append(buffer, gbinary.EncodeByLength(3, pid)...)
|
||||
buffer = append(buffer, gbinary.EncodeByLength(1, len(groupName))...)
|
||||
buffer = append(buffer, []byte(groupName)...)
|
||||
buffer = append(buffer, gbinary.EncodeUint32(gtcp.Checksum(data))...)
|
||||
|
||||
@ -13,18 +13,18 @@ import (
|
||||
"gitee.com/johng/gf/g/os/grpool"
|
||||
)
|
||||
|
||||
func increment1() {
|
||||
func increment() {
|
||||
for i := 0; i < 1000000; i++ {}
|
||||
}
|
||||
|
||||
func BenchmarkGrpool_1(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
grpool.Add(increment1)
|
||||
grpool.Add(increment)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGoroutine_1(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
go increment1()
|
||||
go increment()
|
||||
}
|
||||
}
|
||||
@ -15,10 +15,6 @@ import (
|
||||
|
||||
var n = 500000
|
||||
|
||||
func increment2() {
|
||||
for i := 0; i < 1000000; i++ {}
|
||||
}
|
||||
|
||||
func BenchmarkGrpool2(b *testing.B) {
|
||||
b.N = n
|
||||
for i := 0; i < b.N; i++ {
|
||||
@ -1,35 +0,0 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/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://gitee.com/johng/gf.
|
||||
|
||||
package grpool_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"runtime"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func increment() {
|
||||
for i := 0; i < 100000; i++ {}
|
||||
}
|
||||
|
||||
//func Test_GrpoolMemUsage(t *testing.T) {
|
||||
// for i := 0; i < n; i++ {
|
||||
// grpool.Add(increment)
|
||||
// }
|
||||
// mem := runtime.MemStats{}
|
||||
// runtime.ReadMemStats(&mem)
|
||||
// fmt.Println("mem usage:", mem.TotalAlloc/1024)
|
||||
//}
|
||||
|
||||
func Test_GroroutineMemUsage(t *testing.T) {
|
||||
for i := 0; i < n; i++ {
|
||||
go increment()
|
||||
}
|
||||
mem := runtime.MemStats{}
|
||||
runtime.ReadMemStats(&mem)
|
||||
fmt.Println("mem usage:", mem.TotalAlloc/1024)
|
||||
}
|
||||
@ -23,24 +23,46 @@ import (
|
||||
|
||||
// 文件目录搜索管理对象
|
||||
type SPath struct {
|
||||
paths *garray.StringArray // 搜索路径,按照优先级进行排序
|
||||
cache *gmap.StringInterfaceMap // 搜索结果缓存map
|
||||
paths *garray.StringArray // 搜索路径,按照优先级进行排序
|
||||
cache *gmap.StringStringMap // 搜索结果缓存map
|
||||
}
|
||||
|
||||
// 文件搜索缓存项
|
||||
type SPathCacheItem struct {
|
||||
path string // 文件/目录绝对路径
|
||||
isDir bool // 是否目录
|
||||
path string // 文件/目录绝对路径
|
||||
isDir bool // 是否目录
|
||||
}
|
||||
|
||||
var (
|
||||
// 单个目录路径对应的SPath对象指针,用于路径检索对象复用
|
||||
pathsMap = gmap.NewStringInterfaceMap()
|
||||
)
|
||||
|
||||
// 创建一个搜索对象
|
||||
func New () *SPath {
|
||||
return &SPath {
|
||||
paths : garray.NewStringArray(0, 2),
|
||||
cache : gmap.NewStringInterfaceMap(),
|
||||
func New(path...string) *SPath {
|
||||
sp := &SPath {
|
||||
paths : garray.NewStringArray(0, 1),
|
||||
cache : gmap.NewStringStringMap(),
|
||||
}
|
||||
if len(path) > 0 {
|
||||
sp.Add(path[0])
|
||||
}
|
||||
return sp
|
||||
}
|
||||
|
||||
// 创建/获取一个单例的搜索对象, root必须为目录的绝对路径
|
||||
func Get(root string) *SPath {
|
||||
return pathsMap.GetOrSetFuncLock(root, func() interface{} {
|
||||
return New(root)
|
||||
}).(*SPath)
|
||||
}
|
||||
|
||||
// 检索root目录(必须为绝对路径)下面的name文件的绝对路径,indexFiles用于指定当检索到的结果为目录时,同时检索是否存在这些indexFiles文件
|
||||
func Search(root string, name string, indexFiles...string) (filePath string, isDir bool) {
|
||||
return Get(root).Search(name, indexFiles...)
|
||||
}
|
||||
|
||||
|
||||
// 设置搜索路径,只保留当前设置项,其他搜索路径被清空
|
||||
func (sp *SPath) Set(path string) (realPath string, err error) {
|
||||
realPath = gfile.RealPath(path)
|
||||
@ -53,9 +75,6 @@ func (sp *SPath) Set(path string) (realPath string, err error) {
|
||||
if realPath == "" {
|
||||
return realPath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
|
||||
}
|
||||
if realPath == "" {
|
||||
return realPath, errors.New("invalid path:" + path)
|
||||
}
|
||||
// 设置的搜索路径必须为目录
|
||||
if gfile.IsDir(realPath) {
|
||||
realPath = strings.TrimRight(realPath, gfile.Separator)
|
||||
@ -66,6 +85,7 @@ func (sp *SPath) Set(path string) (realPath string, err error) {
|
||||
}
|
||||
sp.paths.Clear()
|
||||
sp.cache.Clear()
|
||||
|
||||
sp.paths.Append(realPath)
|
||||
sp.updateCacheByPath(realPath)
|
||||
sp.addMonitorByPath(realPath)
|
||||
@ -87,9 +107,6 @@ func (sp *SPath) Add(path string) (realPath string, err error) {
|
||||
if realPath == "" {
|
||||
return realPath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
|
||||
}
|
||||
if realPath == "" {
|
||||
return realPath, errors.New("invalid path:" + path)
|
||||
}
|
||||
// 添加的搜索路径必须为目录
|
||||
if gfile.IsDir(realPath) {
|
||||
// 如果已经添加则不再添加
|
||||
@ -108,24 +125,22 @@ func (sp *SPath) Add(path string) (realPath string, err error) {
|
||||
// 给定的name只是相对文件路径,找不到该文件时,返回空字符串;
|
||||
// 当给定indexFiles时,如果name时一个目录,那么会进一步检索其下对应的indexFiles文件是否存在,存在则返回indexFile绝对路径;
|
||||
// 否则返回name目录绝对路径。
|
||||
func (sp *SPath) Search(name string, indexFiles...string) (path string, isDir bool) {
|
||||
func (sp *SPath) Search(name string, indexFiles...string) (filePath string, isDir bool) {
|
||||
name = sp.formatCacheName(name)
|
||||
if v := sp.cache.Get(name); v != nil {
|
||||
item := v.(*SPathCacheItem)
|
||||
if len(indexFiles) > 0 && item.isDir {
|
||||
if v := sp.cache.Get(name); v != "" {
|
||||
filePath, isDir = sp.parseCacheValue(v)
|
||||
if len(indexFiles) > 0 && isDir {
|
||||
if name == "/" {
|
||||
name = ""
|
||||
}
|
||||
for _, file := range indexFiles {
|
||||
if v := sp.cache.Get(name + "/" + file); v != nil {
|
||||
item := v.(*SPathCacheItem)
|
||||
return item.path, item.isDir
|
||||
if v := sp.cache.Get(name + "/" + file); v != "" {
|
||||
return sp.parseCacheValue(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return item.path, item.isDir
|
||||
}
|
||||
return "", false
|
||||
return
|
||||
}
|
||||
|
||||
// 从搜索路径中移除指定的文件,这样该文件无法给搜索。
|
||||
@ -143,6 +158,11 @@ func (sp *SPath) Remove(path string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 返回当前对象搜索目录路径列表
|
||||
func (sp *SPath) Paths() []string {
|
||||
return sp.paths.Slice()
|
||||
}
|
||||
|
||||
// 返回当前对象缓存的所有路径列表
|
||||
func (sp *SPath) AllPaths() []string {
|
||||
paths := sp.cache.Keys()
|
||||
@ -164,35 +184,45 @@ func (sp *SPath) updateCacheByPath(path string) {
|
||||
|
||||
// 格式化name返回符合规范的缓存名称,分隔符号统一为'/',且前缀必须以'/'开头(类似HTTP URI).
|
||||
func (sp *SPath) formatCacheName(name string) string {
|
||||
name = strings.Trim(name, "./")
|
||||
if runtime.GOOS != "linux" {
|
||||
name = gstr.Replace(name, "\\", "/")
|
||||
}
|
||||
return "/" + name
|
||||
return "/" + strings.Trim(name, "./")
|
||||
}
|
||||
|
||||
// 根据path计算出对应的缓存name
|
||||
func (sp *SPath) nameFromPath(filePath, dirPath string) string {
|
||||
name := gstr.Replace(filePath, dirPath, "")
|
||||
// 根据path计算出对应的缓存name, dirPath为检索根目录路径
|
||||
func (sp *SPath) nameFromPath(filePath, rootPath string) string {
|
||||
name := gstr.Replace(filePath, rootPath, "")
|
||||
name = sp.formatCacheName(name)
|
||||
return name
|
||||
}
|
||||
|
||||
// 按照一定数据结构生成缓存的数据项字符串
|
||||
func (sp *SPath) makeCacheValue(filePath string, isDir bool) string {
|
||||
if isDir {
|
||||
return filePath + "_D_"
|
||||
}
|
||||
return filePath + "_F_"
|
||||
}
|
||||
|
||||
// 按照一定数据结构解析数据项字符串
|
||||
func (sp *SPath) parseCacheValue(value string) (filePath string, isDir bool) {
|
||||
if value[len(value) - 2 : len(value) - 1][0] == 'F' {
|
||||
return value[: len(value) - 3], false
|
||||
}
|
||||
return value[: len(value) - 3], true
|
||||
}
|
||||
|
||||
// 添加path到缓存中(递归)
|
||||
func (sp *SPath) addToCache(filePath, dirPath string) {
|
||||
func (sp *SPath) addToCache(filePath, rootPath string) {
|
||||
// 首先添加自身
|
||||
idDir := gfile.IsDir(filePath)
|
||||
sp.cache.SetIfNotExist(sp.nameFromPath(filePath, dirPath), func() interface{} {
|
||||
return &SPathCacheItem {
|
||||
path : filePath,
|
||||
isDir : idDir,
|
||||
}
|
||||
})
|
||||
// 如果添加的是目录,那么需要递归
|
||||
sp.cache.SetIfNotExist(sp.nameFromPath(filePath, rootPath), sp.makeCacheValue(filePath, idDir))
|
||||
// 如果添加的是目录,那么需要递归添加
|
||||
if idDir {
|
||||
if files, err := gfile.ScanDir(filePath, "*", true); err == nil {
|
||||
for _, path := range files {
|
||||
sp.addToCache(path, dirPath)
|
||||
sp.cache.SetIfNotExist(sp.nameFromPath(path, rootPath), sp.makeCacheValue(path, gfile.IsDir(path)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,32 +10,44 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkSecond(b *testing.B) {
|
||||
func Benchmark_Second(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Second()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMillisecond(b *testing.B) {
|
||||
func Benchmark_Millisecond(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Millisecond()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMicrosecond(b *testing.B) {
|
||||
func Benchmark_Microsecond(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Microsecond()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNanosecond(b *testing.B) {
|
||||
func Benchmark_Nanosecond(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Nanosecond()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStrToTime(b *testing.B) {
|
||||
func Benchmark_StrToTime(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
StrToTime("2018-02-09T20:46:17.897Z")
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_ParseTimeFromContent(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ParseTimeFromContent("2018-02-09T20:46:17.897Z")
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_NewFromTimeStamp(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
NewFromTimeStamp(1542674930)
|
||||
}
|
||||
}
|
||||
@ -123,7 +123,7 @@ func (t *Time) ToZone(zone string) *Time {
|
||||
t.Time = t.Time.In(l)
|
||||
return t
|
||||
} else {
|
||||
panic(err)
|
||||
//panic(err)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,28 +8,30 @@
|
||||
package gview
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf"
|
||||
"gitee.com/johng/gf/g/container/garray"
|
||||
"gitee.com/johng/gf/g/encoding/ghash"
|
||||
"gitee.com/johng/gf/g/encoding/ghtml"
|
||||
"gitee.com/johng/gf/g/encoding/gurl"
|
||||
"gitee.com/johng/gf/g/os/gfcache"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/os/gspath"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/os/gview/internal/text/template"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/util/gstr"
|
||||
"strings"
|
||||
"sync"
|
||||
"bytes"
|
||||
"errors"
|
||||
"text/template"
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"gitee.com/johng/gf/g/encoding/ghash"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/os/gspath"
|
||||
"gitee.com/johng/gf/g/os/gfcache"
|
||||
"gitee.com/johng/gf/g/encoding/ghtml"
|
||||
)
|
||||
|
||||
// 视图对象
|
||||
type View struct {
|
||||
mu sync.RWMutex
|
||||
paths *gspath.SPath // 模板查找目录(绝对路径)
|
||||
paths *garray.StringArray // 模板查找目录(绝对路径)
|
||||
data map[string]interface{} // 模板变量
|
||||
funcmap map[string]interface{} // FuncMap
|
||||
delimiters []string // 模板变量分隔符号
|
||||
@ -41,16 +43,19 @@ type Params = map[string]interface{}
|
||||
// 函数映射表
|
||||
type FuncMap = map[string]interface{}
|
||||
|
||||
// 视图表
|
||||
var viewMap = gmap.NewStringInterfaceMap()
|
||||
|
||||
// 默认的视图对象
|
||||
var viewObj *View
|
||||
|
||||
// 初始化默认的视图对象
|
||||
func checkAndInitDefaultView() {
|
||||
if viewObj == nil {
|
||||
viewObj = Get(".")
|
||||
// gfile.MainPkgPath() 用以判断是否开发环境
|
||||
mainPkgPath := gfile.MainPkgPath()
|
||||
if gfile.MainPkgPath() == "" {
|
||||
viewObj = New(gfile.SelfDir())
|
||||
} else {
|
||||
viewObj = New(mainPkgPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,26 +65,22 @@ func ParseContent(content string, params Params) ([]byte, error) {
|
||||
return viewObj.ParseContent(content, params)
|
||||
}
|
||||
|
||||
// 获取或者创建一个视图对象
|
||||
func Get(path string) *View {
|
||||
if r := viewMap.Get(path); r != nil {
|
||||
return r.(*View)
|
||||
}
|
||||
v := New(path)
|
||||
viewMap.Set(path, v)
|
||||
return v
|
||||
}
|
||||
|
||||
// 生成一个视图对象
|
||||
func New(path string) *View {
|
||||
func New(path...string) *View {
|
||||
view := &View {
|
||||
paths : gspath.New(),
|
||||
paths : garray.NewStringArray(0, 1),
|
||||
data : make(map[string]interface{}),
|
||||
funcmap : make(map[string]interface{}),
|
||||
delimiters : make([]string, 2),
|
||||
}
|
||||
view.SetPath(path)
|
||||
if len(path) > 0 && len(path[0]) > 0 {
|
||||
view.SetPath(path[0])
|
||||
}
|
||||
view.SetDelimiters("{{", "}}")
|
||||
// 内置变量
|
||||
view.data["GF"] = map[string]interface{} {
|
||||
"version" : gf.VERSION,
|
||||
}
|
||||
// 内置方法
|
||||
view.BindFunc("text", view.funcText)
|
||||
view.BindFunc("html", view.funcHtmlEncode)
|
||||
@ -103,23 +104,28 @@ func New(path string) *View {
|
||||
|
||||
// 设置模板目录绝对路径
|
||||
func (view *View) SetPath(path string) error {
|
||||
if rp, err := view.paths.Set(path); err != nil {
|
||||
glog.Error("gview.SetPath failed:", err.Error())
|
||||
realPath := gfile.RealPath(path)
|
||||
if realPath == "" {
|
||||
err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
|
||||
glog.Error(fmt.Sprintf(`[gview] SetPath failed: %s`, err.Error()))
|
||||
return err
|
||||
} else {
|
||||
glog.Debug("gview.SetPath:", rp)
|
||||
}
|
||||
view.paths.Clear()
|
||||
view.paths.Append(realPath)
|
||||
glog.Debug("[gview] SetPath:", realPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 添加模板目录搜索路径
|
||||
func (view *View) AddPath(path string) error {
|
||||
if rp, err := view.paths.Add(path); err != nil {
|
||||
glog.Error("gview.AddPath failed:", err.Error())
|
||||
realPath := gfile.RealPath(path)
|
||||
if realPath == "" {
|
||||
err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
|
||||
glog.Error(fmt.Sprintf(`[gview] AddPath failed: %s`, err.Error()))
|
||||
return err
|
||||
} else {
|
||||
glog.Debug("gview.AddPath:", rp)
|
||||
}
|
||||
view.paths.Append(realPath)
|
||||
glog.Debug("[gview] AddPath:", realPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -141,9 +147,24 @@ func (view *View) Assign(key string, value interface{}) {
|
||||
|
||||
// 解析模板,返回解析后的内容
|
||||
func (view *View) Parse(file string, params Params, funcmap...map[string]interface{}) ([]byte, error) {
|
||||
path, _ := view.paths.Search(file)
|
||||
path := ""
|
||||
view.paths.RLockFunc(func(array []string) {
|
||||
for _, v := range array {
|
||||
if path, _ = gspath.Search(v, file); path != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
if path == "" {
|
||||
return nil, errors.New("tpl \"" + file + "\" not found")
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString(fmt.Sprintf("[gview] cannot find template file \"%s\" in following paths:", file))
|
||||
view.paths.RLockFunc(func(array []string) {
|
||||
for k, v := range array {
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
|
||||
}
|
||||
})
|
||||
glog.Error(buffer.String())
|
||||
return nil, errors.New(fmt.Sprintf(`tpl "%s" not found`, file))
|
||||
}
|
||||
content := gfcache.GetContents(path)
|
||||
// 执行模板解析,互斥锁主要是用于funcmap
|
||||
@ -276,8 +297,15 @@ func (view *View) funcUrlDecode(url interface{}) string {
|
||||
}
|
||||
|
||||
// 模板内置方法:date
|
||||
func (view *View) funcDate(format string, timestamp interface{}) string {
|
||||
return gtime.NewFromTimeStamp(gconv.Int64(timestamp)).Format(format)
|
||||
func (view *View) funcDate(format string, timestamp...interface{}) string {
|
||||
t := int64(0)
|
||||
if len(timestamp) > 0 {
|
||||
t = gconv.Int64(timestamp[0])
|
||||
}
|
||||
if t == 0 {
|
||||
t = gtime.Millisecond()
|
||||
}
|
||||
return gtime.NewFromTimeStamp(t).Format(format)
|
||||
}
|
||||
|
||||
// 模板内置方法:compare
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user