Compare commits

...

125 Commits

Author SHA1 Message Date
d604d198ab hot fix 2019-01-04 15:43:56 +08:00
36791d2f48 README updates 2019-01-02 21:43:17 +08:00
08f9cffed9 TODO++ 2019-01-02 21:38:26 +08:00
783c0ba846 updates kafka dependences 2019-01-02 21:35:48 +08:00
7ad4f61564 revert hot fix codes, waiting for next release to fix 2019-01-02 11:41:22 +08:00
adf06a2b0d thirdparty package kafka updated to date 2019-01-02 11:02:03 +08:00
d6aa2b2512 hot fix for gcache 2019-01-02 10:30:27 +08:00
0a8af94610 !15 打开文件没关闭
Merge pull request !15 from hello/master
2019-01-01 19:45:46 +08:00
wgb
2c27c0f58a close file 2019-01-01 15:35:25 +08:00
4172eae87e update default ConnMaxLifeTime to 30 seconds in gdb package 2018-12-28 22:02:21 +08:00
26f2c61068 update default ConnMaxLifeTime to 30 seconds in gdb package 2018-12-28 22:00:49 +08:00
f97bed2607 update default ConnMaxLifeTime to 10 seconds in gdb package 2018-12-28 21:56:27 +08:00
8ef7155c70 hot fix 2018-12-28 21:46:01 +08:00
2c6e8f88fb README updates, TODO++ 2018-12-27 20:47:13 +08:00
25068b1e83 README updates 2018-12-27 13:27:57 +08:00
1f36eb3a9a README updates 2018-12-27 13:27:04 +08:00
a9ed577d05 README updates 2018-12-27 09:59:56 +08:00
782d614082 README updates 2018-12-27 09:57:54 +08:00
0629c00b07 README updates 2018-12-27 09:49:50 +08:00
b90d5bb205 README updates 2018-12-27 09:46:53 +08:00
cbc824c80a README updates 2018-12-27 09:46:35 +08:00
0c9be40b86 README updates 2018-12-27 09:46:18 +08:00
c96abd706d README updates 2018-12-27 09:45:04 +08:00
0ae5872783 README updates 2018-12-27 09:22:07 +08:00
2cff10e0d2 fix issue in controller interface definition 2018-12-26 10:17:24 +08:00
cab78f557d fix issue in controller detection for object parameter, in router group of web server 2018-12-25 23:20:43 +08:00
04353aa1a5 RELEASE updates 2018-12-25 13:54:36 +08:00
35121a66e9 README updates 2018-12-22 21:50:47 +08:00
e726ed2c19 gdb.Model updates 2018-12-22 21:03:03 +08:00
503446afc7 fix issue of ghttp.Request.GetVar 2018-12-22 11:52:12 +08:00
2063f662d3 fix silly issue in binary search of garray package, and add unit-test file for garray 2018-12-20 21:55:05 +08:00
d7381399aa fix issue of grand.intn in x86 arch; add router group feature for WebServer 2018-12-20 21:04:43 +08:00
d05b497cdb Merge branch 'master' into qiangg_router_group 2018-12-19 18:58:47 +08:00
ef919be587 g.DB can use gdb's configurations, not to force using config.toml 2018-12-19 18:35:44 +08:00
fff31e0f4f add Charset support for mysql of gdb package; fix issue for glog for log writing failed when the folder path wa deleted 2018-12-19 18:15:22 +08:00
cdd6fc7c1e extend pid length from 16bit to 24bit in process communication of gproc package 2018-12-19 16:17:54 +08:00
74bc36a2dc remove gfile.MainPkgPath check in gcfg/gview default path 2018-12-19 14:51:09 +08:00
48328ae52c router group developing 2018-12-19 14:45:39 +08:00
a86f4f8e23 disable auto adding temp directory to gview/gcfg search path; disable backtrace feature in normal log print with glog; fix issue caused by fmt.Fprintf in gfsnotify 2018-12-18 20:03:23 +08:00
0a1e048268 add Model.Clone support for gdb package 2018-12-18 10:10:14 +08:00
6fc5efd6ba README updates 2018-12-17 20:51:49 +08:00
2d795b593d README updates 2018-12-17 20:44:38 +08:00
20628ec75c README updates 2018-12-17 19:50:35 +08:00
10d1ccb009 README updates 2018-12-17 19:41:08 +08:00
fcc37c9581 CI updates 2018-12-17 19:36:34 +08:00
43cd391543 CI updates 2018-12-17 19:29:03 +08:00
18d2df33f7 CI updates 2018-12-17 19:26:59 +08:00
a85daa5617 CI updates 2018-12-17 18:35:29 +08:00
48dc4ce3e2 travis updates 2018-12-17 14:01:43 +08:00
d07bac89a0 travis updates 2018-12-17 13:59:00 +08:00
5d32ad6bc4 travis updates 2018-12-17 13:57:15 +08:00
397b0a3e7e travis updates 2018-12-17 13:50:17 +08:00
259961632d travis updates 2018-12-17 13:46:06 +08:00
cb1d6382ec travis updates 2018-12-17 13:38:35 +08:00
8714a69a13 travis updates 2018-12-17 13:36:38 +08:00
3ae0ea2de7 travis updates 2018-12-17 13:32:35 +08:00
1879a9f4c7 README updates 2018-12-17 13:28:19 +08:00
3938717b04 travis updates 2018-12-17 13:24:53 +08:00
1208b688f1 add code helper 2018-12-17 13:08:32 +08:00
0ad7ee5a32 add code helper 2018-12-17 13:07:01 +08:00
7a4e68e6b9 add code helper 2018-12-17 13:06:58 +08:00
71222b247f add travis/goreport/golint/govet 2018-12-17 13:02:55 +08:00
95db811943 add travis/goreport/golint/govet 2018-12-17 13:02:18 +08:00
2dbc817132 VERSION updates 2018-12-17 11:24:58 +08:00
7a8bd96edc gdb: add support for slice argument in where statement 2018-12-17 10:52:44 +08:00
c5e9686a95 gdb updates, make priority=1 when no priority set 2018-12-16 23:11:15 +08:00
c914edf616 gdb comment updates 2018-12-16 22:27:04 +08:00
656bfcb6bd Merge branch 'qiangg_db2' into develop 2018-12-16 22:22:33 +08:00
7434dfe6fa done refacting gdb package 2018-12-16 22:22:07 +08:00
e67aa63a50 refract gdb package, add complete unit test cases, almost there 2018-12-15 15:50:39 +08:00
06fc786416 fix issue of gcache 2018-12-14 18:38:29 +08:00
d5e46f2b42 refracting gdb package 2018-12-14 18:35:51 +08:00
c003a92408 remove HANGUP signal handle of Web Server admin 2018-12-14 10:11:05 +08:00
09e6f10b60 new version of gdb developing 2018-12-14 10:09:45 +08:00
c961c22cd7 update gview: show empty string when the variable does not exist 2018-12-13 18:24:22 +08:00
105a821069 fix issue: database connection pool does not work expectly 2018-12-12 20:01:10 +08:00
670993f769 remove auto-adding main package path to search path of WebServer in develop environment 2018-12-11 19:30:38 +08:00
60a571f291 Fix issue: add mutex lock for method Remove of garray. 2018-12-11 09:56:58 +08:00
20a0cb2cd9 Merge branch 'master' of https://gitee.com/johng/gf 2018-12-10 20:24:24 +08:00
51e70be04d Add rewrite feature to WebServer; Add Unique for un sorted array of garray; Fix map conversion issue of gjson. 2018-12-10 20:24:20 +08:00
ac65b808c6 !14 修复mssql的OPEN方法BUG
Merge pull request !14 from 蚊子/master
2018-12-10 18:43:56 +08:00
eb9ddf3c47 修改mssql的open方法连接串 2018-12-10 18:14:51 +08:00
80993e9f77 Fix issue of gjson package in map conversion 2018-12-09 22:30:10 +08:00
b7a6d257d5 Fix binary decoding issue of gbinary package. 2018-12-08 18:18:05 +08:00
7022486e93 Package gview: build-in function 'date' supports printing current datetime. 2018-12-05 15:52:38 +08:00
83f5a9d34e Package gview: build-in function 'date' supports printing current datetime. 2018-12-05 15:48:15 +08:00
431e1051b8 Fix gdb.Model.Count method: Count with no fields checking 2018-12-05 13:52:34 +08:00
f8ab5c3842 VERSION updates 2018-12-04 20:29:32 +08:00
101d095f45 GF greets you. 2018-12-04 20:28:06 +08:00
8481de2b47 Add method for gcache package, to show all map datas of the cache. 2018-12-04 20:23:48 +08:00
c973f133de hot fix 2018-12-04 19:50:24 +08:00
d23cdcbe57 改进gcache,完善功能及基准测试;改进gconv.Map对struct转换时默认使用json tag作为键名 2018-12-04 19:26:46 +08:00
9a52175bd6 gredis增加GetConn方法获取原生redis连接对象 2018-12-01 11:28:47 +08:00
4275218841 README updates 2018-11-30 20:43:08 +08:00
663a2c2a16 README updates 2018-11-30 20:40:23 +08:00
da58a60ad5 README updates 2018-11-30 20:38:53 +08:00
2c26063f4b README updates 2018-11-30 20:37:28 +08:00
b19e47783b ghttp增加静态文件目录映射功能;改进gspath目录管理功能;改进gconv的slice转换功能,并增加gconv.Map方法 2018-11-30 09:48:57 +08:00
aee266eea0 WebServer改进 2018-11-28 20:19:28 +08:00
8304769953 代码修正 2018-11-27 20:37:57 +08:00
914a74bca9 完善代码示例 2018-11-26 09:33:45 +08:00
b965dbff70 gvar调用端改进,去掉不必要的并发安全参数;错误提示细节改进 2018-11-25 22:18:36 +08:00
9f9bcd2467 RELEASE updates 2018-11-24 17:21:30 +08:00
b3353afe3c WebServer添加RouterCacheExpire配置参数 2018-11-24 11:55:57 +08:00
4e3081afee ORM增加mysql datetime参数写入示例 2018-11-24 09:42:21 +08:00
578a6a2df3 改进随机数生成缓冲区 2018-11-24 08:43:39 +08:00
aa42ddd3f1 改进随机数生成缓冲区 2018-11-23 21:39:05 +08:00
69738c337f VERSION updates 2018-11-23 16:50:58 +08:00
12f099fd54 VERSION updates, TODO++ 2018-11-23 16:47:03 +08:00
38932f306d 改进gspath缓存数据结构 2018-11-23 16:45:30 +08:00
5e7e1077a1 性能改进 2018-11-23 09:20:45 +08:00
8f85311332 完善获取数据库配置失败时的错误提示 2018-11-21 00:02:29 +08:00
6eb2887a5a README updates 2018-11-20 23:26:58 +08:00
54f4fd3101 README updates 2018-11-20 10:15:23 +08:00
64a22acf84 WebServer允许同一HOOK事件被多次绑定注册,先注册的回调函数优先级更高 2018-11-19 23:13:12 +08:00
4e5877923d dev 2018-11-19 21:49:43 +08:00
ceaa1a4dd1 Merge branch 'develop' of https://gitee.com/johng/gf into develop 2018-11-19 21:12:21 +08:00
9e1ad46c90 改进ghttp.Request,增加SetParam/GetParam请求流程自定义变量方法;gvar模块增加VarRead只读接口 2018-11-19 21:11:43 +08:00
d5a3fefd8b !13 ORM新增对MSSQL的支持
Merge pull request !13 from 蚊子/master
2018-11-19 11:48:18 +08:00
d85332aca1 ORM新增对MSSQL的支持 2018-11-19 11:38:57 +08:00
10c3f6d85a 完善程序细节和测试 2018-11-18 22:22:44 +08:00
ea4764f1f9 当前工作目录为系统临时目录时,gcfg/gview/ghttp模块默认不添加工作目录到搜索路径 2018-11-18 19:45:04 +08:00
fe753b0bc8 当前工作目录为系统临时目录时,gcfg/gview/ghttp模块默认不添加工作目录到搜索路径 2018-11-18 19:37:42 +08:00
04608269fe 修复gspath模块是windows下搜索失效问题 2018-11-18 19:14:17 +08:00
6addd64cf0 glog模块日志前缀输出改进 2018-11-17 22:12:41 +08:00
354 changed files with 69677 additions and 4465 deletions

1
.gitignore vendored
View File

@ -16,3 +16,4 @@ cbuild
**/.DS_Store
.vscode/
go.sum

33
.travis.yml Normal file
View 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)

View File

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

@ -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">
[![Go Doc](https://godoc.org/github.com/johng-cn/gf?status.svg)](https://godoc.org/github.com/johng-cn/gf)
[![Build Status](https://travis-ci.org/johng-cn/gf.svg?branch=master)](https://travis-ci.org/johng-cn/gf)
[![Go Report](https://goreportcard.com/badge/github.com/johng-cn/gf)](https://goreportcard.com/report/github.com/johng-cn/gf)
[![Documents](https://img.shields.io/badge/docs-100%25-green.svg)](https://gfer.me)
[![License](https://img.shields.io/github/license/johng-cn/gf.svg?style=flat)](https://github.com/johng-cn/gf)
[![Language](https://img.shields.io/badge/language-go-blue.svg)](https://github.com/johng-cn/gf)
[![Release](https://img.shields.io/github/release/johng-cn/gf.svg?style=flat)](https://github.com/johng-cn/gf/releases)
GF(Go Frame)是一款模块化、松耦合、轻量级、高性能的Go语言Web开发框架。支持热重启、热更新、多域名、多端口、多服务、HTTP/HTTPS、动态路由等特性
并提供了Web服务开发的系列核心组件Router、Cookie、Session、服务注册、配置管理、模板引擎、数据校验、分页管理、数据库ORM等等等等
并且提供了数十个实用开发模块集缓存、日志、时间、命令行、二进制、文件锁、对象池、连接池、数据编码、进程管理、进程通信、TCP/UDP组件、
并发安全容器、Goroutine池等等等等等等。
<!--
[![Code Coverage](https://codecov.io/gh/johng-cn/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/johng-cn/gf)
[![Code Helper](https://www.codetriage.com/johng-cn/gf/badges/users.svg)](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
View File

@ -0,0 +1,104 @@
# GoFrame
<img align="right" height="150px" src="https://gfer.me/cover.png">
[![Go Doc](https://godoc.org/github.com/johng-cn/gf?status.svg)](https://godoc.org/github.com/johng-cn/gf)
[![Build Status](https://travis-ci.org/johng-cn/gf.svg?branch=master)](https://travis-ci.org/johng-cn/gf)
[![Go Report](https://goreportcard.com/badge/github.com/johng-cn/gf)](https://goreportcard.com/report/github.com/johng-cn/gf)
[![Documents](https://img.shields.io/badge/docs-100%25-green.svg)](https://gfer.me)
[![License](https://img.shields.io/github/license/johng-cn/gf.svg?style=flat)](https://github.com/johng-cn/gf)
[![Language](https://img.shields.io/badge/language-go-blue.svg)](https://github.com/johng-cn/gf)
[![Release](https://img.shields.io/github/release/johng-cn/gf.svg?style=flat)](https://github.com/johng-cn/gf/releases)
<!--
[![Code Coverage](https://codecov.io/gh/johng-cn/gf/branch/master/graph/badge.svg)](https://codecov.io/gh/johng-cn/gf)
[![Code Helper](https://www.codetriage.com/johng-cn/gf/badges/users.svg)](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>

View File

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

@ -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类型的时区问题分析

View File

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

View File

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

View File

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

View File

@ -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 < v20: v1 == v21: 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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View 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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

@ -10,5 +10,5 @@ package ghttp
// 控制器接口
type Controller interface {
Init(*Request)
Shut(*Request)
Shut()
}

View File

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

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

View File

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

View File

@ -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标识引导客户端跳转到来源页面

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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
}
}

View File

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

View 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 }
}
}

View File

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

View File

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

View File

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

View File

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

View 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
}

View File

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

View File

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

View File

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

View File

@ -82,5 +82,4 @@ func (s *Server) Run() error {
go s.handler(NewConnByNetConn(conn))
}
}
return nil
}

View File

@ -85,7 +85,6 @@ func (c *Conn) Send(data []byte, retry...Retry) error {
}
}
}
return nil
}
// 接收数据

View File

@ -77,5 +77,4 @@ func (s *Server) Run() error {
for {
s.handler(NewConnByNetConn(conn))
}
return nil
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
}
// 获得一个键值对关联数组/哈希表,方便操作,不需要自己做类型转换

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -121,7 +121,6 @@ func getShell() string {
}
return path
}
return ""
}
// 获取当前系统默认shell执行指令的option参数
@ -132,7 +131,6 @@ func getShellOption() string {
default:
return "-c"
}
return ""
}
// 从环境变量PATH中搜索可执行文件

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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