Compare commits

...

33 Commits

Author SHA1 Message Date
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
90 changed files with 2480 additions and 871 deletions

169
README.MD
View File

@ -1,49 +1,29 @@
<div align=center>
<img src="http://cover.kancloud.cn/johng/gf" width="150"/>
<img src="https://gfer.me/cover.png" width="150"/>
</div>
`GF(GoFrame)` is a modular, lightweight, loosely coupled, high performance application development framework written in Go. Supporting graceful server, hot updates, multi-domain, multi-port, multi-service, HTTP/HTTPS, dynamic/hook routing and many more features. Providing a series of core components and dozens of practical modules.
GF(Go Frame)是一款模块化、松耦合、轻量级、高性能的Go语言Web开发框架。支持热重启、热更新、多域名、多端口、多服务、HTTP/HTTPS、动态路由等特性
并提供了Web服务开发的系列核心组件Router、Cookie、Session、服务注册、配置管理、模板引擎、数据校验、分页管理、数据库ORM等等等等
并且提供了数十个实用开发模块集缓存、日志、时间、命令行、二进制、文件锁、对象池、连接池、数据编码、进程管理、进程通信、TCP/UDP组件、
并发安全容器、Goroutine池等等等等等等。
开源项目地址(仓库保持实时同步)
[Gitee](https://gitee.com/johng/gf)[Github](https://github.com/johng-cn/gf)。
使用中有任何问题/建议欢迎加入技术QQ群交流**116707870**。
如有优秀的框架使用案例,欢迎联系作者将地址展示到项目库中,您的牛逼将被世人所瞻仰。
# 安装
```html
# Installation
```
go get -u gitee.com/johng/gf
```
# 限制
```shell
golang版本 >= 1.9.2
# Limitation
```
golang version >= 1.9.2
```
# 特点
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)
# Documentation
# 文档
GoFrame开发文档[gfer.me](https://gfer.me)
* [中文文档](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 +40,7 @@ func main() {
s.Run()
}
```
## 多域名支持
```go
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
)
# License
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)
}
```
...
更多特性及示例请查看官方开发文档:[gfer.me](https://gfer.me)
GF is licensed under the [MIT License](LICENSE), 100% free and open-source.

47
README_ZH.MD Normal file
View File

@ -0,0 +1,47 @@
<div align=center>
<img src="https://gfer.me/cover.png" width="150"/>
</div>
`GF(Go Frame)`是一款模块化、松耦合、轻量级、高性能的Go应用开发框架。支持热重启、热更新、多域名、多端口、多服务、HTTP/HTTPS、动态路由等特性
并提供了Web服务开发的系列核心组件Router、Cookie、Session、服务注册、配置管理、模板引擎、数据校验、分页管理、数据库ORM等等等等
并且提供了数十个内置核心开发模块集缓存、日志、时间、命令行、二进制、文件锁、内存锁、对象池、连接池、数据编码、进程管理、进程通信、文件监控、定时任务、TCP/UDP组件、
并发安全容器等等等等等等。
# 安装
```html
go get -u gitee.com/johng/gf
```
# 限制
```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()
}
```

View File

@ -1,136 +1,48 @@
# `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)
# `v0.98.503 beta` (2018-05-21)
# `v1.2.11` (2018-11-26)
## 新特性
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. `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、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. 改进`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、改进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. 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、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.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. 修复`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下的执行问题;
## 问题修复
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的问题
# `v1.0.898 stable` (2018-10-24)
@ -227,3 +139,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)

11
TODO.MD
View File

@ -34,16 +34,15 @@
- 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. 路由增加不区分大小写得匹配方式;
# DONE
@ -96,4 +95,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

@ -66,13 +66,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()

View File

@ -161,7 +161,7 @@ func (a *SortedIntArray) binSearch(value int, lock bool) (index int, result int)
case 0 :
case 1 : min = mid + 1
}
if cmp == 0 || min > max {
if cmp == 0 || min >= max {
break
}
}

View File

@ -154,7 +154,7 @@ func (a *SortedArray) binSearch(value interface{}, lock bool)(index int, result
case 0 :
case 1 : min = mid + 1
}
if cmp == 0 || min > max {
if cmp == 0 || min >= max {
break
}
}

View File

@ -155,7 +155,7 @@ func (a *SortedStringArray) binSearch(value string, lock bool) (index int, resul
case 0 :
case 1 : min = mid + 1
}
if cmp == 0 || min > max {
if cmp == 0 || min >= max {
break
}
}

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

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

@ -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,14 @@
// 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/util/gconv"
"time"
)
type Var struct {
@ -30,6 +31,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 +59,7 @@ func (v *Var) Val() interface{} {
}
}
// Val() 别名
func (v *Var) Interface() interface{} {
return v.Val()
}

View File

@ -0,0 +1,38 @@
// 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 "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
Struct(objPointer interface{}, attrMapping ...map[string]string) error
}

View File

@ -108,7 +108,7 @@ type Sql struct {
}
// 返回数据表记录值
type Value = *gvar.Var
type Value = gvar.VarRead
// 返回数据表记录Map
type Record map[string]Value
@ -133,6 +133,7 @@ func init() {
driverMap["oracle"] = linkOracle
driverMap["sqlite"] = linkSqlite
driverMap["pgsql"] = linkPgsql
driverMap["mssql"] = linkMssql
}
// 使用默认/指定分组配置进行连接数据库集群配置项default

View File

@ -190,7 +190,7 @@ func (db *Db) GetAll(query string, args ...interface{}) (Result, error) {
for i, col := range values {
v := make([]byte, len(col))
copy(v, col)
row[columns[i]] = gvar.New(v)
row[columns[i]] = gvar.New(v, false)
}
records = append(records, row)
}

159
g/database/gdb/gdb_mssql.go Normal file
View File

@ -0,0 +1,159 @@
// 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"
)
var linkMssql = &dbmssql{}
// 数据库链接对象
type dbmssql struct {
Db
}
// 创建SQL操作对象
func (db *dbmssql) Open(c *ConfigNode) (*sql.DB, error) {
var source string
if c.Linkinfo != "" {
source = c.Linkinfo
} else {
source = fmt.Sprintf("uid=%s;pwd=%s;server=%s;port=%s;database=%s;encrypt=disable", c.User, c.Pass, c.Host, c.Port, c.Name)
}
if db, err := sql.Open("sqlserver", source); err == nil {
return db, nil
} else {
return nil, err
}
}
// 获得关键字操作符 - 左
func (db *dbmssql) getQuoteCharLeft() string {
return "\""
}
// 获得关键字操作符 - 右
func (db *dbmssql) getQuoteCharRight() string {
return "\""
}
// 在执行sql之前对sql进行进一步处理
func (db *dbmssql) handleSqlBeforeExec(q *string) *string {
index := 0
str, _ := gregex.ReplaceStringFunc("\\?", *q, 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 nil
}
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

@ -99,8 +99,9 @@ func (db *dboracle) parseSql(sql *string) *string {
}
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", *sql)
queryExpr[0] = strings.TrimRight(queryExpr[0], "LIMIT")
queryExpr[0] = strings.TrimRight(queryExpr[0], "limit")
if len(queryExpr) != 4 || strings.EqualFold(queryExpr[1], "SELECT") == false || strings.EqualFold(queryExpr[3], "LIMIT") == false{
break
}
//取limit后面的取值范围
first, limit := 0, 0
@ -117,7 +118,7 @@ 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)

View File

@ -123,7 +123,7 @@ func (tx *Tx) GetAll(query string, args ...interface{}) (Result, error) {
for i, col := range values {
v := make([]byte, len(col))
copy(v, col)
row[columns[i]] = gvar.New(v)
row[columns[i]] = gvar.New(v, false)
}
//fmt.Printf("%p\n", row["typeid"])
records = append(records, row)

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

@ -48,13 +48,20 @@ func New(value interface{}, safe...bool) *Json {
vc : false ,
}
default:
// 这里效率会比较低
b, _ := Encode(value)
v, _ := Decode(b)
j = &Json{
p : &v,
c : byte(gDEFAULT_SPLIT_CHAR),
vc : false,
v := (interface{})(nil)
if v = gconv.Map(value); v != nil {
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...)

View File

@ -76,10 +76,12 @@ func View(name...string) *gview.View {
if path == "" {
path = genv.Get("GF_VIEWPATH")
if path == "" {
path = gfile.SelfDir()
if gfile.SelfDir() != gfile.TempDir() {
path = gfile.SelfDir()
}
}
}
view := gview.Get(path)
view := gview.New(path)
// 添加基于源码的搜索目录检索地址,常用于开发环境调试,只添加入口文件目录
if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) {
view.AddPath(p)
@ -103,7 +105,9 @@ func Config(file...string) *gcfg.Config {
if path == "" {
path = genv.Get("GF_CFGPATH")
if path == "" {
path = gfile.SelfDir()
if gfile.SelfDir() != gfile.TempDir() {
path = gfile.SelfDir()
}
}
}
config := gcfg.New(path, configFile)
@ -126,7 +130,8 @@ func Database(name...string) *gdb.Db {
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())
glog.Error(`database init failed: "database" node not found, is config file or configuration missing?`)
return nil
}
for group, v := range m {
cg := gdb.ConfigGroup{}

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, 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,19 @@ 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)
// 仅在开启静态文件服务的时候有效
if s.config.FileServerEnabled {
// (开发环境)添加main源码包到搜索目录
if p := gfile.MainPkgPath(); p != "" && gfile.Exists(p) {
s.AddSearchPath(p)
}
}
// 添加当前可执行文件运行目录到搜索目录
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 +234,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 {
@ -304,11 +280,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 +296,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 +340,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 +465,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

@ -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,84 @@ 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访问
DenyIps []string // 不允许访问的ip列表支持ip前缀过滤如: 10 将不允许10开头的ip访问
AllowIps []string // 仅允许访问的ip列表支持ip前缀过滤如: 10 将仅允许10开头的ip访问
// 路由访问控制
DenyRoutes []string // 不允许访问的路由规则列表
DenyRoutes []string // 不允许访问的路由规则列表
// 日志配置
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,
}
// 获取默认的http server设置
@ -117,6 +125,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 +141,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 +166,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 +175,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 +192,25 @@ 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 == "" {
glog.Fatal(fmt.Sprintf(`[ghttp] EnableHTTPS failed: certFile "%s" does not exist`, certFile))
}
keyFileRealPath := gfile.RealPath(keyFile)
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 +219,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 +228,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,53 +237,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)
return
}
s.config.DenyIps = ips
}
@ -267,6 +263,7 @@ func (s *Server) SetDenyIps(ips []string) {
func (s *Server) SetAllowIps(ips []string) {
if s.Status() == SERVER_STATUS_RUNNING {
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
return
}
s.config.AllowIps = ips
}
@ -274,6 +271,7 @@ func (s *Server) SetAllowIps(ips []string) {
func (s *Server) SetDenyRoutes(routes []string) {
if s.Status() == SERVER_STATUS_RUNNING {
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
return
}
s.config.DenyRoutes = routes
}
@ -281,6 +279,7 @@ func (s *Server) SetDenyRoutes(routes []string) {
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 +288,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 +297,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

@ -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,127 @@
// 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 == "" {
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 == "" {
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 == "" {
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"
@ -30,7 +31,9 @@ func (s *Server)defaultHttpHandle(w http.ResponseWriter, r *http.Request) {
func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
// 去掉末尾的"/"号
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 +60,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 +82,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,6 +127,31 @@ 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) {
defer func() {
@ -176,9 +213,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

@ -8,14 +8,14 @@
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"
)
@ -55,9 +55,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 +69,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 +161,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 +178,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 +195,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 +257,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 +267,9 @@ func (s *Server) compareRouterPriority(newRouter, oldRouter *Router) bool {
if oldRouter.Method != gDEFAULT_METHOD {
return true
}
// 模糊规则数量相等,后续不用再判断*规则的数量比较了,
// 这种情况下新的规则比旧的规则优先级更高
return true
// 最后新的规则比旧的规则优先级
return false
}
// 将pattern不带method和domain解析成正则表达式匹配以及对应的query字符串

View File

@ -81,11 +81,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 +189,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

@ -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()
}
// 判断所给路径是否为文件

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

@ -295,46 +295,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

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

@ -8,28 +8,29 @@
package gview
import (
"bytes"
"errors"
"fmt"
"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/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 +42,17 @@ type Params = map[string]interface{}
// 函数映射表
type FuncMap = map[string]interface{}
// 视图表
var viewMap = gmap.NewStringInterfaceMap()
// 默认的视图对象
var viewObj *View
// 初始化默认的视图对象
func checkAndInitDefaultView() {
if viewObj == nil {
viewObj = Get(".")
if gfile.SelfDir() != gfile.TempDir() {
viewObj = New(gfile.SelfDir())
} else {
viewObj = New()
}
}
}
@ -60,25 +62,17 @@ 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.BindFunc("text", view.funcText)
@ -103,23 +97,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 +140,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

108
g/util/gconv/gconv_map.go Normal file
View File

@ -0,0 +1,108 @@
// 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 gconv
import (
"reflect"
)
// 任意类型转换为 map[string]interface{} 类型,
// 如果给定的输入参数i不是map类型那么转换会失败返回nil.
func Map(i interface{}) map[string]interface{} {
if i == nil {
return nil
}
if r, ok := i.(map[string]interface{}); ok {
return r
} else {
// 仅对常见的几种map组合进行断言最后才会使用反射
m := make(map[string]interface{})
switch i.(type) {
case map[interface{}]interface{}:
for k, v := range i.(map[interface{}]interface{}) {
m[String(k)] = v
}
case map[interface{}]string:
for k, v := range i.(map[interface{}]string) {
m[String(k)] = v
}
case map[interface{}]int:
for k, v := range i.(map[interface{}]int) {
m[String(k)] = v
}
case map[interface{}]uint:
for k, v := range i.(map[interface{}]uint) {
m[String(k)] = v
}
case map[interface{}]float32:
for k, v := range i.(map[interface{}]float32) {
m[String(k)] = v
}
case map[interface{}]float64:
for k, v := range i.(map[interface{}]float64) {
m[String(k)] = v
}
case map[string]bool:
for k, v := range i.(map[string]bool) {
m[k] = v
}
case map[string]int:
for k, v := range i.(map[string]int) {
m[k] = v
}
case map[string]uint:
for k, v := range i.(map[string]uint) {
m[k] = v
}
case map[string]float32:
for k, v := range i.(map[string]float32) {
m[k] = v
}
case map[string]float64:
for k, v := range i.(map[string]float64) {
m[k] = v
}
case map[int]interface{}:
for k, v := range i.(map[int]interface{}) {
m[String(k)] = v
}
case map[int]string:
for k, v := range i.(map[int]string) {
m[String(k)] = v
}
case map[uint]string:
for k, v := range i.(map[uint]string) {
m[String(k)] = v
}
// 不是常见类型,则使用反射
default:
rv := reflect.ValueOf(i)
kind := rv.Kind()
// 如果是指针,那么需要转换到指针对应的数据项,以便识别真实的类型
if kind == reflect.Ptr {
rv = rv.Elem()
kind = rv.Kind()
}
if kind == reflect.Map {
ks := rv.MapKeys()
for _, k := range ks {
m[String(k.Interface())] = rv.MapIndex(k).Interface()
}
} else if kind == reflect.Struct {
rt := rv.Type()
for i := 0; i < rv.NumField(); i++ {
m[rt.Field(i).Name] = rv.Field(i).Interface()
}
} else {
return nil
}
}
return m
}
}

View File

@ -6,7 +6,10 @@
package gconv
import "fmt"
import (
"fmt"
"reflect"
)
// 任意类型转换为[]int类型
func Ints(i interface{}) []int {
@ -296,6 +299,26 @@ func Interfaces(i interface{}) []interface{} {
for _, v := range i.([]float64) {
array = append(array, v)
}
// 不是常见类型,则使用反射
default:
rv := reflect.ValueOf(i)
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++ {
array = append(array, rv.Index(i).Interface())
}
case reflect.Struct:
for i := 0; i < rv.NumField(); i++ {
array = append(array, rv.Field(i).Interface())
}
}
}
if len(array) > 0 {
return array

View File

@ -205,10 +205,12 @@ func bindVarToStructIfDefaultConvertionFailed(structFieldValue reflect.Value, va
switch structFieldValue.Kind() {
case reflect.Struct:
Struct(value, structFieldValue)
case reflect.Slice:
case reflect.Slice: fallthrough
case reflect.Array:
a := reflect.Value{}
v := reflect.ValueOf(value)
if v.Kind() == reflect.Slice {
if v.Kind() == reflect.Slice || v.Kind() == reflect.Array {
a = reflect.MakeSlice(structFieldValue.Type(), v.Len(), v.Len())
for i := 0; i < v.Len(); i++ {
n := reflect.New(structFieldValue.Type().Elem()).Elem()

View File

@ -9,6 +9,7 @@ package grand
import (
"crypto/rand"
"encoding/binary"
"time"
)
const (
@ -21,14 +22,23 @@ var (
// 使用缓冲区实现快速的随机数生成
func init() {
step := 0
buffer := make([]byte, 1024)
go func() {
for {
if n, err := rand.Read(buffer); err != nil {
panic(err)
} else {
for i := 0; i < n - 8; i += 8 {
// 使用缓冲区数据进行一次完整的随机数生成
for i := 0; i < n - 8; {
bufferChan <- binary.LittleEndian.Uint64(buffer[i : i + 8])
i ++
}
// 充分利用缓冲区数据,字节倒序生成,随机索引递增
step = int(time.Now().UnixNano())%n
for i := 0; i < n - 8; {
bufferChan <- binary.BigEndian.Uint64(buffer[i : i + 8])
i += step
}
}
}

View File

@ -9,12 +9,22 @@
package grand_test
import (
"testing"
"gitee.com/johng/gf/g/util/grand"
"testing"
)
var buffer = make([]byte, 8)
func Benchmark_Rand(b *testing.B) {
for i := 0; i < b.N; i++ {
grand.Rand(0, 999999999)
}
}
//func Benchmark_Buffer(b *testing.B) {
// for i := 0; i < b.N; i++ {
// if _, err := rand.Read(buffer); err == nil {
// binary.LittleEndian.Uint64(buffer)
// }
// }
//}

View File

@ -5,7 +5,7 @@
// You can obtain one at https://gitee.com/johng/gf.
// 单元测试
// go test *.go -bench=".*"
// go test *.go
package gvalid

View File

@ -1,4 +1,3 @@
// 验证 map 的delete方法是否并发安全
package main
import (
@ -7,6 +6,7 @@ import (
"fmt"
)
// 验证 map 的delete方法是否并发安全
func main() {
// 创建一个初始化的map
m := make(map[int]int)

View File

@ -29,4 +29,8 @@ func main() {
s := new(Score)
v.Struct(s)
fmt.Println(s)
// 只读接口
r := v.ReadOnly()
fmt.Println(r.String())
}

View File

@ -0,0 +1,582 @@
package main
import (
"fmt"
"time"
//_ "github.com/denisenkom/go-mssqldb"
"gitee.com/johng/gf/g/database/gdb"
"gitee.com/johng/gf/g"
)
// 本文件用于gf框架的mssql数据库操作示例不作为单元测试使用
var db *gdb.Db
// 初始化配置及创建数据库
func init () {
gdb.AddDefaultConfigNode(gdb.ConfigNode {
Host : "127.0.0.1",
Port : "1433",
User : "test",
Pass : "test",
Name : "test",
Type : "mssql",
Role : "master",
Charset : "utf8",
})
db, _= gdb.New()
//gins.Config().SetPath("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/frame")
//db = g.Database()
//gdb.SetConfig(gdb.ConfigNode {
// Host : "127.0.0.1",
// Port : 3306,
// User : "root",
// Pass : "123456",
// Name : "test",
// Type : "mysql",
//})
//db, _ = gdb.Instance()
//gdb.SetConfig(gdb.Config {
// "default" : gdb.ConfigGroup {
// gdb.ConfigNode {
// Host : "127.0.0.1",
// Port : "3306",
// User : "root",
// Pass : "123456",
// Name : "test",
// Type : "mysql",
// Role : "master",
// Priority : 100,
// },
// gdb.ConfigNode {
// Host : "127.0.0.2",
// Port : "3306",
// User : "root",
// Pass : "123456",
// Name : "test",
// Type : "mysql",
// Role : "master",
// Priority : 100,
// },
// gdb.ConfigNode {
// Host : "127.0.0.3",
// Port : "3306",
// User : "root",
// Pass : "123456",
// Name : "test",
// Type : "mysql",
// Role : "master",
// Priority : 100,
// },
// gdb.ConfigNode {
// Host : "127.0.0.4",
// Port : "3306",
// User : "root",
// Pass : "123456",
// Name : "test",
// Type : "mysql",
// Role : "master",
// Priority : 100,
// },
// },
//})
//db, _ = gdb.Instance()
}
// 创建测试数据库
func create() error {
fmt.Println("drop table aa_user:")
_, err := db.Exec("drop table aa_user")
if err != nil {
fmt.Println("drop table aa_user error.",err)
}
s := `
CREATE TABLE aa_user (
id int not null,
name VARCHAR(60),
age int,
addr varchar(60),
PRIMARY KEY (id)
)
`
fmt.Println("create table aa_user:")
_, err = db.Exec(s)
if err != nil {
fmt.Println("create table error.",err)
return err
}
/*_, err = db.Exec("drop sequence id_seq")
if err != nil {
fmt.Println("drop sequence id_seq", err)
}
fmt.Println("create sequence id_seq")
_, err = db.Exec("create sequence id_seq increment by 1 start with 1 maxvalue 9999999999 cycle cache 10")
if err != nil {
fmt.Println("create sequence id_seq error.", err)
return err
}
s = `
CREATE TRIGGER id_trigger before insert on aa_user for each row
begin
select id_seq.nextval into :new.id from dual;
end;
`
_, err = db.Exec(s)
if err != nil {
fmt.Println("create trigger error.", err)
return err
}*/
_, err = db.Exec("drop table user_detail")
if err != nil {
fmt.Println("drop table user_detail", err)
}
s = `
CREATE TABLE user_detail (
id int not null,
site VARCHAR(255),
PRIMARY KEY (id)
)
`
fmt.Println("create table user_detail:")
_, err = db.Exec(s)
if err != nil {
fmt.Println("create table user_detail error.",err)
return err
}
fmt.Println("create table success.")
return nil
}
// 数据写入
func insert(id int) {
fmt.Println("insert:")
r, err := db.Insert("aa_user", gdb.Map {
"id": id,
"name": "john",
"age": id,
})
fmt.Println(r.LastInsertId())
fmt.Println(r.RowsAffected())
if err == nil {
r, err = db.Insert("user_detail", gdb.Map {
"id" : id,
"site" : "http://johng.cn",
})
if err == nil {
fmt.Printf("id: %d\n", id)
} else {
fmt.Println(err)
}
} else {
fmt.Println(err)
}
fmt.Println()
}
// 基本sql查询
func query() {
fmt.Println("query:")
list, err := db.GetAll("select * from aa_user where id='1'")
if err == nil {
fmt.Println(list)
} else {
fmt.Println(err)
}
list, err = db.Table("aa_user").OrderBy("id").Limit(0,2).Select()
if err == nil {
fmt.Println(list)
} else {
fmt.Println(err)
}
fmt.Println()
}
// replace into
func replace() {
fmt.Println("replace:")
r, err := db.Save("aa_user", gdb.Map {
"id" : 1,
"name" : "john",
})
if err == nil {
fmt.Println(r.LastInsertId())
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 数据保存
func save() {
fmt.Println("save:")
r, err := db.Save("aa_user", gdb.Map {
"id" : 1,
"name" : "john",
})
if err == nil {
fmt.Println(r.LastInsertId())
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 批量写入
func batchInsert() {
fmt.Println("batchInsert:")
_, err := db.BatchInsert("aa_user", gdb.List {
{"id":11,"name": "batchInsert_john_1", "age": 11},
{"id":12,"name": "batchInsert_john_2", "age": 12},
{"id":13,"name": "batchInsert_john_3", "age": 13},
{"id":14,"name": "batchInsert_john_4", "age": 14},
}, 10)
if err != nil {
fmt.Println(err)
}
fmt.Println()
}
// 数据更新
func update1() {
fmt.Println("update1:")
r, err := db.Update("aa_user", gdb.Map {"name": "john1","age":1}, "id=?", 1)
if err == nil {
fmt.Println(r.LastInsertId())
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 数据更新
func update2() {
fmt.Println("update2:")
r, err := db.Update("aa_user", gdb.Map{"name" : "john6","age":6}, "id=?", 2)
if err == nil {
fmt.Println(r.LastInsertId())
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 数据更新
func update3() {
fmt.Println("update3:")
r, err := db.Update("aa_user", "name=?", "id=?", "john2", 3)
if err == nil {
fmt.Println(r.LastInsertId())
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式查询操作1
func linkopSelect1() {
fmt.Println("linkopSelect1:")
r, err := db.Table("aa_user u").LeftJoin("user_detail ud", "u.id=ud.id").Fields("u.*, ud.site").Where("u.id > ?", 1).Limit(3, 5).Select()
if err == nil {
fmt.Println(r)
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式查询操作2
func linkopSelect2() {
fmt.Println("linkopSelect2:")
r, err := db.Table("aa_user u").LeftJoin("user_detail ud", "u.id=ud.id").Fields("u.*,ud.site").Where("u.id=?", 1).One()
if err == nil {
fmt.Println(r)
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式查询操作3
func linkopSelect3() {
fmt.Println("linkopSelect3:")
r, err := db.Table("aa_user u").LeftJoin("user_detail ud", "u.id=ud.id").Fields("ud.site").Where("u.id=?", 1).Value()
if err == nil {
fmt.Println(r.String())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式查询数量1
func linkopCount1() {
fmt.Println("linkopCount1:")
r, err := db.Table("aa_user u").LeftJoin("user_detail ud", "u.id=ud.id").Where("name like ?", "john").Count()
if err == nil {
fmt.Println(r)
} else {
fmt.Println(err)
}
fmt.Println()
}
// 错误操作
func linkopUpdate1() {
fmt.Println("linkopUpdate1:")
r, err := db.Table("henghe_setting").Update()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println("error",err)
}
fmt.Println()
}
// 通过Map指针方式传参方式
func linkopUpdate2() {
fmt.Println("linkopUpdate2:")
r, err := db.Table("aa_user").Data(gdb.Map{"name" : "john2"}).Where("name=?", "john").Update()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 通过字符串方式传参
func linkopUpdate3() {
fmt.Println("linkopUpdate3:")
r, err := db.Table("aa_user").Data("name='john3'").Where("name=?", "john2").Update()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// Where条件使用Map
func linkopUpdate4() {
fmt.Println("linkopUpdate4:")
r, err := db.Table("aa_user").Data(gdb.Map{"name" : "john11111"}).Where(g.Map{"id" : 1}).Update()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式批量写入
func linkopBatchInsert1() {
fmt.Println("linkopBatchInsert1:")
r, err := db.Table("aa_user").Data(gdb.List{
{"id":21,"name": "linkopBatchInsert1_john_1"},
{"id":22,"name": "linkopBatchInsert1_john_2"},
{"id":23,"name": "linkopBatchInsert1_john_3"},
{"id":24,"name": "linkopBatchInsert1_john_4"},
}).Insert()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式批量写入,指定每批次写入的条数
func linkopBatchInsert2() {
fmt.Println("linkopBatchInsert2:")
r, err := db.Table("aa_user").Data(gdb.List{
{"id":25,"name": "linkopBatchInsert2john_1"},
{"id":26,"name": "linkopBatchInsert2john_2"},
{"id":27,"name": "linkopBatchInsert2john_3"},
{"id":28,"name": "linkopBatchInsert2john_4"},
}).Batch(2).Insert()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 链式批量保存
func linkopBatchSave() {
fmt.Println("linkopBatchSave:")
r, err := db.Table("aa_user").Data(gdb.List{
{"id":1, "name": "john_1"},
{"id":2, "name": "john_2"},
{"id":3, "name": "john_3"},
{"id":4, "name": "john_4"},
}).Save()
if err == nil {
fmt.Println(r.RowsAffected())
} else {
fmt.Println(err)
}
fmt.Println()
}
// 事务操作示例1
func transaction1() {
fmt.Println("transaction1:")
if tx, err := db.Begin(); err == nil {
r, err := tx.Insert("aa_user", gdb.Map{
"id" : 30,
"name" : "transaction1",
})
tx.Rollback()
fmt.Println(r, err)
}
fmt.Println()
}
// 事务操作示例2
func transaction2() {
fmt.Println("transaction2:")
if tx, err := db.Begin(); err == nil {
r, err := tx.Table("user_detail").Data(gdb.Map{"id":6, "site": "www.baidu.com哈哈哈*?''\"~!@#$%^&*()"}).Insert()
tx.Commit()
fmt.Println(r, err)
}
fmt.Println()
}
// 主从io复用测试在mysql中使用 show full processlist 查看链接信息
func keepPing() {
fmt.Println("keepPing:")
for i := 0; i < 30; i++ {
fmt.Println("ping...",i)
err := db.PingMaster()
if err != nil {
fmt.Println(err)
return
}
err = db.PingSlave()
if err != nil {
fmt.Println(err)
return
}
time.Sleep(1*time.Second)
}
}
// like语句查询
func likeQuery() {
fmt.Println("likeQuery:")
if r, err := db.Table("aa_user").Where("name like ?", "%john%").Select(); err == nil {
fmt.Println(r)
} else {
fmt.Println(err)
}
}
// mapToStruct
func mapToStruct() {
type User struct {
Id int
Name string
Age int
Addr string
}
fmt.Println("mapToStruct:")
if r, err := db.Table("aa_user").Where("id=?", 1).One(); err == nil {
u := User{}
if err := r.ToStruct(&u); err == nil {
fmt.Println(r)
fmt.Println(u)
} else {
fmt.Println(err)
}
} else {
fmt.Println(err)
}
}
// getQueriedSqls
func getQueriedSqls() {
for k, v := range db.GetQueriedSqls() {
fmt.Println(k, ":")
fmt.Println("Sql :", v.Sql)
fmt.Println("Args :", v.Args)
fmt.Println("Error:", v.Error)
fmt.Println("Func :", v.Func)
}
}
func main() {
db.PingMaster()
db.SetDebug(true)
/*err := create()
if err != nil {
return
}*/
//test1
/*for i := 1; i < 5; i++ {
insert(i)
}*/
//insert(2)
query()
//batchInsert()
//query()
//replace()
//save()
/*update1()
update2()
update3()
*/
/*linkopSelect1()
linkopSelect2()
linkopSelect3()
linkopCount1()
*/
/*linkopUpdate1()
linkopUpdate2()
linkopUpdate3()
linkopUpdate4()
*/
//linkopBatchInsert1()
//linkopBatchInsert2()
//transaction1()
//transaction2()
//
//keepPing()
//likeQuery()
//mapToStruct()
//getQueriedSqls()
}

View File

@ -0,0 +1,13 @@
# MySQL数据库配置
[database]
[[database.default]]
host = "127.0.0.1"
port = "3306"
user = "root"
pass = "12345678"
name = "test"
type = "mysql"
role = "master"
charset = "utf8"
priority = "1"

View File

@ -0,0 +1,29 @@
package main
import (
"fmt"
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/os/gtime"
)
func main() {
db := g.Database()
db.SetDebug(true)
//r, err := db.Table("user").Data("create_time", gtime.Now().String()).Insert()
//if err == nil {
// fmt.Println(r.LastInsertId())
//} else {
// panic(err)
//}
r, err := db.Table("user").Data(g.Map{
"name" : "john",
"create_time" : gtime.Now().String(),
}).Insert()
if err == nil {
fmt.Println(r.LastInsertId())
} else {
panic(err)
}
}

View File

@ -7,10 +7,10 @@ import (
func main() {
gdb.AddDefaultConfigNode(gdb.ConfigNode {
Host : "127.0.0.1",
Host : "192.168.1.11",
Port : "3306",
User : "root",
Pass : "123456",
Pass : "8692651",
Name : "test",
Type : "mysql",
Role : "master",

View File

@ -3,7 +3,7 @@ package main
import (
"fmt"
"time"
_ "github.com/mattn/go-oci8"
//_ "github.com/mattn/go-oci8"
"gitee.com/johng/gf/g/database/gdb"
"gitee.com/johng/gf/g"
)

View File

@ -9,7 +9,7 @@ func main() {
s := g.Server()
s.SetIndexFolder(true)
s.BindHandler("/", func(r *ghttp.Request){
r.Response.Writeln("哈喽世界!")
r.Response.Write("Hello World")
})
s.SetPort(8199)
s.Run()

View File

@ -0,0 +1,20 @@
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(r.GetParam("name").String())
})
s.BindHookHandlerByMap("/", map[string]ghttp.HandlerFunc {
ghttp.HOOK_BEFORE_SERVE : func(r *ghttp.Request) {
r.SetParam("name", "john")
},
})
s.SetPort(8199)
s.Run()
}

View File

@ -0,0 +1,32 @@
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
)
// 优先调用的HOOK
func beforeServeHook1(r *ghttp.Request) {
r.SetParam("name", "GoFrame")
r.Response.Writeln("set name")
}
// 随后调用的HOOK
func beforeServeHook2(r *ghttp.Request) {
r.SetParam("site", "https://gfer.me")
r.Response.Writeln("set site")
}
// 允许对同一个路由同一个事件注册多个回调函数,按照注册顺序进行优先级调用。
// 为便于在路由表中对比查看优先级这里讲HOOK回调函数单独定义为了两个函数。
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Writeln(r.GetParam("name").String())
r.Response.Writeln(r.GetParam("site").String())
})
s.BindHookHandler("/", ghttp.HOOK_BEFORE_SERVE, beforeServeHook1)
s.BindHookHandler("/", ghttp.HOOK_BEFORE_SERVE, beforeServeHook2)
s.SetPort(8199)
s.Run()
}

View File

@ -4,7 +4,6 @@ import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
"gitee.com/johng/gf/g/util/gvalid"
"gitee.com/johng/gf/g/encoding/gparser"
)
func main() {
@ -19,9 +18,11 @@ func main() {
s.BindHandler("/user", func(r *ghttp.Request){
user := new(User)
r.GetToStruct(user)
result := gvalid.CheckStruct(user, nil)
json, _ := gparser.VarToJsonIndent(result)
r.Response.Write(json)
if err := gvalid.CheckStruct(user, nil); err != nil {
r.Response.WriteJson(err.Maps())
} else {
r.Response.Write("ok")
}
})
s.SetPort(8199)
s.Run()

View File

@ -2,11 +2,12 @@ package main
import "gitee.com/johng/gf/g"
// 静态文件服务器
// 静态文件服务器基本使用
func main() {
s := g.Server()
s.SetIndexFolder(true)
s.SetServerRoot("/Users/john/Temp")
s.AddSearchPath("/Users/john/Documents")
s.SetPort(8199)
s.Run()
}

View File

@ -0,0 +1,14 @@
package main
import "gitee.com/johng/gf/g"
// 静态文件服务器,支持自定义静态目录映射
func main() {
s := g.Server()
s.SetIndexFolder(true)
s.SetServerRoot("/Users/john/Temp")
s.AddSearchPath("/Users/john/Documents")
s.AddStaticPath("/my-doc", "/Users/john/Documents")
s.SetPort(8199)
s.Run()
}

View File

@ -0,0 +1,15 @@
package main
import "gitee.com/johng/gf/g"
// 静态文件服务器,支持自定义静态目录映射
func main() {
s := g.Server()
s.SetIndexFolder(true)
s.SetServerRoot("/Users/john/Temp")
s.AddSearchPath("/Users/john/Documents")
s.AddStaticPath("/my-doc", "/Users/john/Documents")
s.AddStaticPath("/my-doc/test", "/Users/john/Temp")
s.SetPort(8199)
s.Run()
}

33
geg/os/gcfg/config.json Normal file
View File

@ -0,0 +1,33 @@
{
"viewpath" : "/home/www/templates/",
"database" : {
"default" : [
{
"host" : "127.0.0.1",
"port" : "3306",
"user" : "root",
"pass" : "123456",
"name" : "test",
"type" : "mysql",
"role" : "master",
"charset" : "utf8",
"priority" : "1"
},
{
"host" : "127.0.0.1",
"port" : "3306",
"user" : "root",
"pass" : "123456",
"name" : "test",
"type" : "mysql",
"role" : "master",
"charset" : "utf8",
"priority" : "1"
}
]
},
"redis" : {
"disk" : "127.0.0.1:6379,0",
"cache" : "127.0.0.1:6379,1"
}
}

View File

@ -5,6 +5,7 @@ import (
"gitee.com/johng/gf/g"
)
// 使用第二个参数指定读取的配置文件
func main() {
c := g.Config()
redisConfig := c.GetArray("redis-cache", "redis.toml")

View File

@ -5,6 +5,7 @@ import (
"gitee.com/johng/gf/g"
)
// 使用默认的config.toml配置文件读取配置
func main() {
c := g.Config()
fmt.Println(c.GetArray("memcache"))

View File

@ -5,6 +5,7 @@ import (
"gitee.com/johng/gf/g"
)
// 使用GetVar获取动态变量
func main() {
fmt.Println(g.Config().GetVar("memcache.0").String())
}

12
geg/os/gcfg/gcfg4.go Normal file
View File

@ -0,0 +1,12 @@
package main
import (
"fmt"
"gitee.com/johng/gf/g"
)
// 使用g.Config方法获取配置管理对象并指定默认的配置文件名称
func main() {
fmt.Println(g.Config("config.json").Get("viewpath"))
}

View File

@ -2,13 +2,14 @@ package main
import (
"fmt"
"time"
"gitee.com/johng/gf/g/os/gcfg"
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/os/gtime"
"time"
)
// 配置文件热更新示例
func main() {
c := gcfg.New("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/os/gcfg")
c := g.Config()
// 每隔1秒打印当前配置项值用户可手动在外部修改文件内容gcfg读取到的配置项值会即时得到更新
gtime.SetInterval(time.Second, func() bool {
fmt.Println(c.Get("viewpath"))

12
geg/os/gcfg/gcfg_error.go Normal file
View File

@ -0,0 +1,12 @@
package main
import (
"fmt"
"gitee.com/johng/gf/g"
)
// 演示在找不到配置文件时的错误提示
func main() {
fmt.Println(g.Config("none-exist-config.toml").Get("none"))
}

View File

@ -16,7 +16,7 @@ func main() {
//gfsnotify.Remove(path)
if err != nil {
glog.Fatalln(err)
glog.Fatal(err)
} else {
select {}
}

View File

@ -14,10 +14,11 @@ func main() {
rp, err := sp.Add(path)
fmt.Println(err)
fmt.Println(rp)
fmt.Println(sp)
gtime.SetInterval(5*time.Second, func() bool {
g.Dump(sp.AllPaths())
return true
g.Dump(sp.AllPaths())
return true
})
select {

View File

@ -0,0 +1,21 @@
package main
import (
"fmt"
"gitee.com/johng/gf/g/os/gtime"
"time"
)
func main() {
// 先使用标准库打印当前时间
fmt.Println(time.Now().String())
// 设置进程时区,全局有效
err := gtime.SetTimeZone("Asia/Tokyo")
if err != nil {
panic(err)
}
// 使用gtime获取当前时间
fmt.Println(gtime.Now().String())
// 使用标准库获取当前时间
fmt.Println(time.Now().String())
}

View File

@ -11,6 +11,8 @@ func main() {
b, err := v.Parse("gview.tpl", map[string]interface{} {
"k" : "v",
})
fmt.Println(err)
if err != nil {
panic(err)
}
fmt.Println(string(b))
}

View File

@ -10,10 +10,12 @@ import (
func main() {
v := g.View()
// 设置模板目录为当前main.go所在目录下的template目录
v.AddPath(gfile.MainPkgPath() + gfile.Separator + "template")
v.AddPath(gfile.MainPkgPath() + gfile.Separator + "template2")
b, err := v.Parse("index.html", map[string]interface{} {
"k" : "v",
})
fmt.Println(err)
if err != nil {
panic(err)
}
fmt.Println(string(b))
}

View File

@ -0,0 +1,39 @@
package main
import (
"fmt"
)
func main() {
fmt.Println("")
// 前景 背景 颜色
// ---------------------------------------
// 30 40 黑色
// 31 41 红色
// 32 42 绿色
// 33 43 黄色
// 34 44 蓝色
// 35 45 紫红色
// 36 46 青蓝色
// 37 47 白色
//
// 代码 意义
// -------------------------
// 0 终端默认设置
// 1 高亮显示
// 4 使用下划线
// 5 闪烁
// 7 反白显示
// 8 不可见
for b := 40; b <= 47; b++ { // 背景色彩 = 40-47
for f := 30; f <= 37; f++ { // 前景色彩 = 30-37
for d := range []int{0, 1, 4, 5, 7, 8} { // 显示方式 = 0,1,4,5,7,8
fmt.Printf(" %c[%d;%d;%dm%s(f=%d,b=%d,d=%d)%c[0m ", 0x1B, d, b, f, "", f, b, d, 0x1B)
}
fmt.Println("")
}
fmt.Println("")
}
}

View File

@ -2,9 +2,38 @@ package main
import (
"fmt"
"gitee.com/johng/gf/g/encoding/gparser"
)
func main() {
s := ""
fmt.Println(s[0])
type DemoInfo struct {
Name string
Age string
}
//r.Response.Write("Hello World")
l := map[interface{}][]DemoInfo{}
el := [...]DemoInfo{
{Name:"Bala", Age:"15"},
{Name:"CeCe", Age:"18"},
{Name:"ChenLo", Age:"28"},
{Name:"Bii", Age:"22"},
{Name:"Ann", Age:"23"},
{Name:"Bmx", Age:"88"},
}
for _,v := range el{
l[string(v.Name[0])] = append(l[string(v.Name[0])],v)
}
//fmt.Println(l)
b, err := gparser.VarToJson(l)
fmt.Println(err)
fmt.Println(string(b))
}

View File

@ -0,0 +1,41 @@
package test
import (
"strings"
"testing"
)
var s = "/name/john///."
var c = "./"
func t1(s string) string {
if len(s) == 0 {
return s
}
for _, cut := range c {
for s[len(s) - 1] == uint8(cut) {
s = s[:len(s) - 1]
if len(s) == 0 {
return s
}
}
}
return s
}
func t2(s string) string {
return strings.TrimRight(s, c)
}
func Benchmark_t1(b *testing.B) {
for i := 0; i < b.N; i++ {
t1(s)
}
}
func Benchmark_t2(b *testing.B) {
for i := 0; i < b.N; i++ {
t2(s)
}
}

View File

@ -1,19 +0,0 @@
package main
import (
"testing"
"gitee.com/johng/gf/g/container/garray"
)
func TestGFArray001(t *testing.T) {
var source = []string{"59705a2c1fd50736a4c768a1", "597a95ff1fd5073e48bb2272", "597a960f1fd5073e48bb2274"}
var CacheChannelKeys = garray.NewSortedStringArray(0)
CacheChannelKeys.Add(source...)
t.Logf("%#v\n", CacheChannelKeys)
CacheChannelKeys.Clear()
CacheChannelKeys = garray.NewSortedStringArray(len(source))
t.Logf("%#v\n", CacheChannelKeys)
CacheChannelKeys.Add(source...)
t.Logf("%#v\n", CacheChannelKeys)
}

View File

@ -0,0 +1,25 @@
package main
import (
"fmt"
"gitee.com/johng/gf/g/util/gconv"
)
// struct转map
func main() {
type User struct {
Uid int
Name string
}
// 对象
fmt.Println(gconv.Map(User{
Uid : 1,
Name : "john",
}))
// 指针
fmt.Println(gconv.Map(&User{
Uid : 1,
Name : "john",
}))
}

View File

@ -0,0 +1,24 @@
package main
import (
"fmt"
"gitee.com/johng/gf/g/util/gconv"
)
// struct转slice
func main() {
type User struct {
Uid int
Name string
}
// 对象
fmt.Println(gconv.Interfaces(User{
Uid : 1,
Name : "john",
}))
// 指针
fmt.Println(gconv.Interfaces(&User{
Uid : 1,
Name : "john",
}))
}

View File

@ -1,5 +1,5 @@
package gf
const VERSION = "v1.2.10"
const VERSION = "v1.2.11"
const AUTHORS = "john<john@johng.cn>"