mirror of
https://gitee.com/johng/gf
synced 2026-06-18 06:23:59 +08:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a52175bd6 | |||
| 4275218841 | |||
| 663a2c2a16 | |||
| da58a60ad5 | |||
| 2c26063f4b | |||
| b19e47783b | |||
| aee266eea0 | |||
| 8304769953 | |||
| 914a74bca9 | |||
| b965dbff70 | |||
| 9f9bcd2467 | |||
| b3353afe3c | |||
| 4e3081afee | |||
| 578a6a2df3 | |||
| aa42ddd3f1 | |||
| 69738c337f | |||
| 12f099fd54 | |||
| 38932f306d | |||
| 5e7e1077a1 | |||
| 8f85311332 | |||
| 6eb2887a5a | |||
| 54f4fd3101 | |||
| 64a22acf84 | |||
| 4e5877923d | |||
| ceaa1a4dd1 | |||
| 9e1ad46c90 | |||
| d5a3fefd8b | |||
| d85332aca1 | |||
| 10c3f6d85a | |||
| ea4764f1f9 | |||
| fe753b0bc8 | |||
| 04608269fe | |||
| 6addd64cf0 |
169
README.MD
169
README.MD
@ -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
47
README_ZH.MD
Normal 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()
|
||||
}
|
||||
```
|
||||
302
RELEASE.MD
302
RELEASE.MD
@ -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
11
TODO.MD
@ -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类型的时区问题分析;
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gchan provides graceful operations for channel.
|
||||
// 优雅的Channel操作.
|
||||
package gchan
|
||||
|
||||
@ -40,8 +41,7 @@ func (q *Chan) Pop() interface{} {
|
||||
|
||||
// 关闭队列(通知所有通过Pop阻塞的协程退出)
|
||||
func (q *Chan) Close() {
|
||||
if !q.closed.Val() {
|
||||
q.closed.Set(true)
|
||||
if !q.closed.Set(true) {
|
||||
close(q.list)
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
//
|
||||
|
||||
// Package glist provides a concurrent-safe(alternative) doubly linked list.
|
||||
// 并发安全的双向链表.
|
||||
package glist
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gmap provides kinds of concurrent-safe(alternative) maps.
|
||||
// 并发安全的哈希MAP.
|
||||
package gmap
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gpool provides a object-reusable concurrent-safe pool.
|
||||
// 对象复用池.
|
||||
package gpool
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gqueue provides a dynamic/static concurrent-safe(alternative) queue.
|
||||
// 并发安全的动态队列.
|
||||
// 特点:
|
||||
// 1、动态队列初始化速度快;
|
||||
@ -29,26 +30,22 @@ type Queue struct {
|
||||
}
|
||||
|
||||
const (
|
||||
// 默认临时队列大小,注意是临时的
|
||||
gDEFAULT_QUEUE_SIZE = 10000
|
||||
// 动态队列缓冲区大小
|
||||
gQUEUE_SIZE = 10000
|
||||
)
|
||||
|
||||
// 队列大小为非必须参数,默认不限制
|
||||
func New(limit...int) *Queue {
|
||||
size := gDEFAULT_QUEUE_SIZE
|
||||
if len(limit) > 0 {
|
||||
size = limit[0]
|
||||
}
|
||||
q := &Queue {
|
||||
list : glist.New(),
|
||||
queue : make(chan interface{}, size),
|
||||
events : make(chan struct{}, math.MaxInt32),
|
||||
closeChan : make(chan struct{}, 0),
|
||||
}
|
||||
if len(limit) > 0 {
|
||||
q.limit = size
|
||||
q.limit = limit[0]
|
||||
q.queue = make(chan interface{}, limit[0])
|
||||
} else {
|
||||
// 如果是动态队列大小,那么额外会运行一个goroutine
|
||||
q.list = glist.New()
|
||||
q.queue = make(chan interface{}, gQUEUE_SIZE)
|
||||
q.events = make(chan struct{}, math.MaxInt32)
|
||||
go q.startAsyncLoop()
|
||||
}
|
||||
return q
|
||||
|
||||
@ -13,36 +13,38 @@ import (
|
||||
"gitee.com/johng/gf/g/container/gqueue"
|
||||
)
|
||||
|
||||
var length = 10000000
|
||||
var bn = 20000000
|
||||
var length = 1000000
|
||||
var qstatic = gqueue.New(length)
|
||||
var qdynamic = gqueue.New()
|
||||
var cany = make(chan interface{}, length)
|
||||
var cint = make(chan int, length)
|
||||
|
||||
func Benchmark_GqueueStaticPushAndPop(b *testing.B) {
|
||||
func Benchmark_Gqueue_StaticPushAndPop(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
qstatic.Push(i)
|
||||
qstatic.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_GqueueDynamicPush(b *testing.B) {
|
||||
func Benchmark_Gqueue_DynamicPush(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
qdynamic.Push(i)
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_ChannelInterfacePushAndPop(b *testing.B) {
|
||||
func Benchmark_Gqueue_DynamicPop(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
qdynamic.Pop()
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_Channel_PushAndPop(b *testing.B) {
|
||||
b.N = bn
|
||||
for i := 0; i < b.N; i++ {
|
||||
cany <- i
|
||||
<- cany
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_ChannelIntPushAndPop(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
cint <- i
|
||||
<- cint
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gring provides a concurrent-safe(alternative) ring(circular lists).
|
||||
// 并发安全的环.
|
||||
package gring
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gset provides kinds of concurrent-safe(alternative) sets.
|
||||
// 并发安全的集合SET.
|
||||
package gset
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// Package gtype provides kinds of concurrent-safe basic-types.
|
||||
// 并发安全的基本类型.
|
||||
package gtype
|
||||
|
||||
|
||||
@ -4,13 +4,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()
|
||||
}
|
||||
|
||||
38
g/container/gvar/gvar_read.go
Normal file
38
g/container/gvar/gvar_read.go
Normal 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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
159
g/database/gdb/gdb_mssql.go
Normal 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
|
||||
}
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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...)
|
||||
|
||||
@ -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{}
|
||||
|
||||
@ -20,24 +20,23 @@ import (
|
||||
// 请求对象
|
||||
type Request struct {
|
||||
http.Request
|
||||
parsedGet bool // GET参数是否已经解析
|
||||
parsedPost bool // POST参数是否已经解析
|
||||
queryVars map[string][]string // GET参数
|
||||
routerVars map[string][]string // 路由解析参数
|
||||
exit bool // 是否退出当前请求流程执行
|
||||
Id int // 请求id(唯一)
|
||||
Server *Server // 请求关联的服务器对象
|
||||
Cookie *Cookie // 与当前请求绑定的Cookie对象(并发安全)
|
||||
Session *Session // 与当前请求绑定的Session对象(并发安全)
|
||||
Response *Response // 对应请求的返回数据操作对象
|
||||
Router *Router // 匹配到的路由对象
|
||||
EnterTime int64 // 请求进入时间(微秒)
|
||||
LeaveTime int64 // 请求完成时间(微秒)
|
||||
Param interface{} // 开发者自定义参数
|
||||
parsedHost string // 解析过后不带端口号的服务器域名名称
|
||||
clientIp string // 解析过后的客户端IP地址
|
||||
isFileRequest bool // 是否为静态文件请求(非服务请求,当静态文件存在时,优先级会被服务请求高,被识别为文件请求)
|
||||
isFileServe bool // 是否为文件处理(调用Server.serveFile时设置为true), isFileRequest为true时isFileServe也为true
|
||||
parsedGet bool // GET参数是否已经解析
|
||||
parsedPost bool // POST参数是否已经解析
|
||||
queryVars map[string][]string // GET参数
|
||||
routerVars map[string][]string // 路由解析参数
|
||||
exit bool // 是否退出当前请求流程执行
|
||||
Id int // 请求id(唯一)
|
||||
Server *Server // 请求关联的服务器对象
|
||||
Cookie *Cookie // 与当前请求绑定的Cookie对象(并发安全)
|
||||
Session *Session // 与当前请求绑定的Session对象(并发安全)
|
||||
Response *Response // 对应请求的返回数据操作对象
|
||||
Router *Router // 匹配到的路由对象
|
||||
EnterTime int64 // 请求进入时间(微秒)
|
||||
LeaveTime int64 // 请求完成时间(微秒)
|
||||
params map[string]interface{} // 开发者自定义参数(请求流程中有效)
|
||||
parsedHost string // 解析过后不带端口号的服务器域名名称
|
||||
clientIp string // 解析过后的客户端IP地址
|
||||
isFileRequest bool // 是否为静态文件请求(非服务请求,当静态文件存在时,优先级会被服务请求高,被识别为文件请求)
|
||||
}
|
||||
|
||||
// 创建一个Request对象
|
||||
@ -74,7 +73,8 @@ func (r *Request) Get(key string, def ... string) string {
|
||||
return r.GetRequestString(key, def...)
|
||||
}
|
||||
|
||||
func (r *Request) GetVar(key string, def ... interface{}) *gvar.Var {
|
||||
// 建议都用该参数替代参数获取
|
||||
func (r *Request) GetVar(key string, def ... interface{}) gvar.VarRead {
|
||||
return r.GetRequestVar(key, def...)
|
||||
}
|
||||
|
||||
@ -173,11 +173,6 @@ func (r *Request) IsFileRequest() bool {
|
||||
return r.isFileRequest
|
||||
}
|
||||
|
||||
// 判断请求是否为文件处理
|
||||
func (r *Request) IsFileServe() bool {
|
||||
return r.isFileServe
|
||||
}
|
||||
|
||||
// 判断是否为AJAX请求
|
||||
func (r *Request) IsAjaxRequest() bool {
|
||||
return strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest")
|
||||
@ -203,7 +198,7 @@ func (r *Request) GetReferer() string {
|
||||
return r.Header.Get("Referer")
|
||||
}
|
||||
|
||||
// 获得结构体顶替的参数名称标签,构成map返回
|
||||
// 获得结构体对象的参数名称标签,构成map返回
|
||||
func (r *Request) getStructParamsTagMap(object interface{}) map[string]string {
|
||||
tagmap := make(map[string]string)
|
||||
fields := structs.Fields(object)
|
||||
|
||||
28
g/net/ghttp/ghttp_request_params.go
Normal file
28
g/net/ghttp/ghttp_request_params.go
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
package ghttp
|
||||
|
||||
import "gitee.com/johng/gf/g/container/gvar"
|
||||
|
||||
// 设置请求流程共享变量
|
||||
func (r *Request) SetParam(key string, value interface{}) {
|
||||
if r.params == nil {
|
||||
r.params = make(map[string]interface{})
|
||||
}
|
||||
r.params[key] = value
|
||||
}
|
||||
|
||||
// 获取请求流程共享变量
|
||||
func (r *Request) GetParam(key string) gvar.VarRead {
|
||||
if r.params != nil {
|
||||
if v, ok := r.params[key]; ok {
|
||||
return gvar.New(v, false)
|
||||
}
|
||||
}
|
||||
return gvar.New(nil, false)
|
||||
}
|
||||
|
||||
@ -26,15 +26,15 @@ func (r *Request) GetRequest(key string, def ... []string) []string {
|
||||
return v
|
||||
}
|
||||
|
||||
func (r *Request) GetRequestVar(key string, def ... interface{}) *gvar.Var {
|
||||
func (r *Request) GetRequestVar(key string, def ... interface{}) gvar.VarRead {
|
||||
value := r.GetRequest(key)
|
||||
if value != nil {
|
||||
return gvar.New(value)
|
||||
return gvar.New(value, false)
|
||||
}
|
||||
if len(def) > 0 {
|
||||
return gvar.New(def[0])
|
||||
return gvar.New(def[0], false)
|
||||
}
|
||||
return nil
|
||||
return gvar.New(nil, false)
|
||||
}
|
||||
|
||||
func (r *Request) GetRequestString(key string, def ... string) string {
|
||||
|
||||
@ -9,12 +9,12 @@ package ghttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"net/http"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/encoding/gparser"
|
||||
"strconv"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/encoding/gparser"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// 服务端请求返回对象。
|
||||
@ -158,11 +158,8 @@ func (r *Response) WriteStatus(status int, content...string) {
|
||||
|
||||
// 静态文件处理
|
||||
func (r *Response) ServeFile(path string) {
|
||||
r.request.isFileServe = true
|
||||
// 首先判断是否给定的path已经是一个绝对路径
|
||||
if !gfile.Exists(path) {
|
||||
path, _ = r.Server.paths.Search(path)
|
||||
}
|
||||
path = gfile.RealPath(path)
|
||||
if path == "" {
|
||||
r.WriteStatus(http.StatusNotFound)
|
||||
return
|
||||
@ -170,10 +167,31 @@ func (r *Response) ServeFile(path string) {
|
||||
r.Server.serveFile(r.request, path)
|
||||
}
|
||||
|
||||
// 静态文件下载处理
|
||||
func (r *Response) ServeFileDownload(path string, name...string) {
|
||||
// 首先判断是否给定的path已经是一个绝对路径
|
||||
path = gfile.RealPath(path)
|
||||
if path == "" {
|
||||
r.WriteStatus(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
downloadName := ""
|
||||
if len(name) > 0 {
|
||||
downloadName = name[0]
|
||||
} else {
|
||||
downloadName = gfile.Basename(path)
|
||||
}
|
||||
r.Header().Set("Content-Type", "application/force-download")
|
||||
r.Header().Set("Accept-Ranges", "bytes")
|
||||
r.Header().Set("Content-Disposition", fmt.Sprintf(`attachment;filename="%s"`, downloadName))
|
||||
r.Server.serveFile(r.request, path)
|
||||
}
|
||||
|
||||
// 返回location标识,引导客户端跳转
|
||||
func (r *Response) RedirectTo(location string) {
|
||||
r.Header().Set("Location", location)
|
||||
r.WriteHeader(http.StatusFound)
|
||||
r.request.Exit()
|
||||
}
|
||||
|
||||
// 返回location标识,引导客户端跳转到来源页面
|
||||
|
||||
@ -18,7 +18,6 @@ import (
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/os/gproc"
|
||||
"gitee.com/johng/gf/g/os/gspath"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
@ -33,6 +32,74 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
// Server结构体
|
||||
Server struct {
|
||||
// 基本属性变量
|
||||
name string // 服务名称,方便识别
|
||||
config ServerConfig // 配置对象
|
||||
servers []*gracefulServer // 底层http.Server列表
|
||||
methodsMap map[string]struct{} // 所有支持的HTTP Method(初始化时自动填充)
|
||||
servedCount *gtype.Int // 已经服务的请求数(4-8字节,不考虑溢出情况),同时作为请求ID
|
||||
// 服务注册相关
|
||||
serveTree map[string]interface{} // 所有注册的服务回调函数(路由表,树型结构,哈希表+链表优先级匹配)
|
||||
hooksTree map[string]interface{} // 所有注册的事件回调函数(路由表,树型结构,哈希表+链表优先级匹配)
|
||||
serveCache *gcache.Cache // 服务注册路由内存缓存
|
||||
hooksCache *gcache.Cache // 事件回调路由内存缓存
|
||||
routesMap map[string][]registeredRouteItem // 已经注册的路由及对应的注册方法文件地址(用以路由重复注册判断)
|
||||
// 自定义状态码回调
|
||||
hsmu sync.RWMutex // status handler互斥锁
|
||||
statusHandlerMap map[string]HandlerFunc // 不同状态码下的注册处理方法(例如404状态时的处理方法)
|
||||
// SESSION
|
||||
sessions *gcache.Cache // Session内存缓存
|
||||
// Logger
|
||||
logger *glog.Logger // 日志管理对象
|
||||
}
|
||||
|
||||
// 路由对象
|
||||
Router struct {
|
||||
Uri string // 注册时的pattern - uri
|
||||
Method string // 注册时的pattern - method
|
||||
Domain string // 注册时的pattern - domain
|
||||
RegRule string // 路由规则解析后对应的正则表达式
|
||||
RegNames []string // 路由规则解析后对应的变量名称数组
|
||||
Priority int // 优先级,用于链表排序,值越大优先级越高
|
||||
}
|
||||
|
||||
// http回调函数注册信息
|
||||
handlerItem struct {
|
||||
name string // 注册的方法名称信息
|
||||
rtype int // 注册方式(执行对象/回调函数/控制器)
|
||||
ctype reflect.Type // 控制器类型(反射类型)
|
||||
fname string // 回调方法名称
|
||||
faddr HandlerFunc // 准确的执行方法内存地址(与以上两个参数二选一)
|
||||
finit HandlerFunc // 初始化请求回调方法(执行对象注册方式下有效)
|
||||
fshut HandlerFunc // 完成请求回调方法(执行对象注册方式下有效)
|
||||
router *Router // 注册时绑定的路由对象
|
||||
}
|
||||
|
||||
// 根据特定URL.Path解析后的路由检索结果项
|
||||
handlerParsedItem struct {
|
||||
handler *handlerItem // 路由注册项
|
||||
values map[string][]string // 特定URL.Path的Router解析参数
|
||||
}
|
||||
|
||||
// 已注册的路由项
|
||||
registeredRouteItem struct {
|
||||
file string // 文件路径及行数地址
|
||||
handler *handlerItem // 路由注册项
|
||||
}
|
||||
|
||||
// pattern与回调函数的绑定map
|
||||
handlerMap map[string]*handlerItem
|
||||
|
||||
// HTTP注册函数
|
||||
HandlerFunc func(r *Request)
|
||||
|
||||
// 文件描述符map
|
||||
listenerFdMap map[string]string
|
||||
)
|
||||
|
||||
const (
|
||||
SERVER_STATUS_STOPPED = 0 // Server状态:停止
|
||||
SERVER_STATUS_RUNNING = 1 // Server状态:运行
|
||||
@ -42,8 +109,7 @@ const (
|
||||
HOOK_AFTER_OUTPUT = "AfterOutput"
|
||||
HOOK_BEFORE_CLOSE = "BeforeClose"
|
||||
HOOK_AFTER_CLOSE = "AfterClose"
|
||||
)
|
||||
const (
|
||||
|
||||
gHTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
|
||||
gDEFAULT_SERVER = "default"
|
||||
gDEFAULT_DOMAIN = "default"
|
||||
@ -54,93 +120,27 @@ const (
|
||||
gEXCEPTION_EXIT = "exit"
|
||||
)
|
||||
|
||||
// ghttp.Server结构体
|
||||
type Server struct {
|
||||
// 基本属性变量
|
||||
name string // 服务名称,方便识别
|
||||
paths *gspath.SPath // 静态文件检索对象(类似nginx tryfile功能)
|
||||
config ServerConfig // 配置对象
|
||||
servers []*gracefulServer // 底层http.Server列表
|
||||
methodsMap map[string]struct{} // 所有支持的HTTP Method(初始化时自动填充)
|
||||
servedCount *gtype.Int // 已经服务的请求数(4-8字节,不考虑溢出情况),同时作为请求ID
|
||||
// 服务注册相关
|
||||
serveTree map[string]interface{} // 所有注册的服务回调函数(路由表,树型结构,哈希表+链表优先级匹配)
|
||||
hooksTree map[string]interface{} // 所有注册的事件回调函数(路由表,树型结构,哈希表+链表优先级匹配)
|
||||
serveCache *gmap.StringInterfaceMap // 服务注册路由内存缓存
|
||||
hooksCache *gmap.StringInterfaceMap // 事件回调路由内存缓存
|
||||
routesMap map[string]registeredRouteItem // 已经注册的路由及对应的注册方法文件地址(用以路由重复注册判断)
|
||||
// 自定义状态码回调
|
||||
hsmu sync.RWMutex // status handler互斥锁
|
||||
statusHandlerMap map[string]HandlerFunc // 不同状态码下的注册处理方法(例如404状态时的处理方法)
|
||||
// SESSION
|
||||
sessions *gcache.Cache // Session内存缓存
|
||||
// Logger
|
||||
logger *glog.Logger // 日志管理对象
|
||||
}
|
||||
var (
|
||||
// Server表,用以存储和检索名称与Server对象之间的关联关系
|
||||
serverMapping = gmap.NewStringInterfaceMap()
|
||||
|
||||
// 路由对象
|
||||
type Router struct {
|
||||
Uri string // 注册时的pattern - uri
|
||||
Method string // 注册时的pattern - method
|
||||
Domain string // 注册时的pattern - domain
|
||||
RegRule string // 路由规则解析后对应的正则表达式
|
||||
RegNames []string // 路由规则解析后对应的变量名称数组
|
||||
Priority int // 优先级,用于链表排序,值越大优先级越高
|
||||
}
|
||||
// 正常运行的Server数量,如果没有运行、失败或者全部退出,那么该值为0
|
||||
serverRunning = gtype.NewInt()
|
||||
|
||||
// pattern与回调函数的绑定map
|
||||
type handlerMap map[string]*handlerItem
|
||||
// Web Socket默认配置
|
||||
wsUpgrader = websocket.Upgrader {
|
||||
// 默认允许WebSocket请求跨域,权限控制可以由业务层自己负责,灵活度更高
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
// Web Server已完成服务事件通道,当有事件时表示服务完成,当前进程退出
|
||||
doneChan = make(chan struct{}, 1000)
|
||||
|
||||
// http回调函数注册信息
|
||||
type handlerItem struct {
|
||||
name string // 注册的方法名称信息
|
||||
rtype int // 注册方式(执行对象/回调函数/控制器)
|
||||
ctype reflect.Type // 控制器类型(反射类型)
|
||||
fname string // 回调方法名称
|
||||
faddr HandlerFunc // 准确的执行方法内存地址(与以上两个参数二选一)
|
||||
finit HandlerFunc // 初始化请求回调方法(执行对象注册方式下有效)
|
||||
fshut HandlerFunc // 完成请求回调方法(执行对象注册方式下有效)
|
||||
router *Router // 注册时绑定的路由对象
|
||||
}
|
||||
// 用于服务进程初始化,只能初始化一次,采用“懒初始化”(在server运行时才初始化)
|
||||
serverProcInited = gtype.NewBool()
|
||||
)
|
||||
|
||||
// 根据特定URL.Path解析后的路由检索结果项
|
||||
type handlerParsedItem struct {
|
||||
handler *handlerItem // 路由注册项
|
||||
values map[string][]string // 特定URL.Path的Router解析参数
|
||||
}
|
||||
|
||||
// 已注册的路由项
|
||||
type registeredRouteItem struct {
|
||||
file string // 文件路径及行数地址
|
||||
handler *handlerItem // 路由注册项
|
||||
}
|
||||
|
||||
// HTTP注册函数
|
||||
type HandlerFunc func(r *Request)
|
||||
|
||||
// 文件描述符map
|
||||
type listenerFdMap map[string]string
|
||||
|
||||
|
||||
// Server表,用以存储和检索名称与Server对象之间的关联关系
|
||||
var serverMapping = gmap.NewStringInterfaceMap()
|
||||
|
||||
// 正常运行的Server数量,如果没有运行、失败或者全部退出,那么该值为0
|
||||
var serverRunning = gtype.NewInt()
|
||||
|
||||
// Web Socket默认配置
|
||||
var wsUpgrader = websocket.Upgrader {
|
||||
// 默认允许WebSocket请求跨域,权限控制可以由业务层自己负责,灵活度更高
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
// Web Server已完成服务事件通道,当有事件时表示服务完成,当前进程退出
|
||||
var doneChan = make(chan struct{}, 1000)
|
||||
|
||||
// 用于服务进程初始化,只能初始化一次,采用“懒初始化”(在server运行时才初始化)
|
||||
var serverProcInited = gtype.NewBool()
|
||||
|
||||
// Web Server进程初始化.
|
||||
// 注意该方法不能放置于包初始化方法init中,不使用ghttp.Server的功能便不能初始化对应的协程goroutine逻辑.
|
||||
@ -177,15 +177,14 @@ func GetServer(name...interface{}) (*Server) {
|
||||
}
|
||||
s := &Server {
|
||||
name : sname,
|
||||
paths : gspath.New(),
|
||||
servers : make([]*gracefulServer, 0),
|
||||
methodsMap : make(map[string]struct{}),
|
||||
statusHandlerMap : make(map[string]HandlerFunc),
|
||||
serveTree : make(map[string]interface{}),
|
||||
hooksTree : make(map[string]interface{}),
|
||||
serveCache : gmap.NewStringInterfaceMap(),
|
||||
hooksCache : gmap.NewStringInterfaceMap(),
|
||||
routesMap : make(map[string]registeredRouteItem),
|
||||
serveCache : gcache.New(),
|
||||
hooksCache : gcache.New(),
|
||||
routesMap : make(map[string][]registeredRouteItem),
|
||||
sessions : gcache.New(),
|
||||
servedCount : gtype.NewInt(),
|
||||
logger : glog.New(),
|
||||
@ -213,30 +212,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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -3,7 +3,6 @@
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// 配置管理数据结构定义.
|
||||
|
||||
package ghttp
|
||||
|
||||
@ -16,6 +15,7 @@ import (
|
||||
func (s *Server)SetCookieMaxAge(age int) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.CookieMaxAge = age
|
||||
}
|
||||
@ -24,6 +24,7 @@ func (s *Server)SetCookieMaxAge(age int) {
|
||||
func (s *Server)SetCookiePath(path string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.CookiePath = path
|
||||
}
|
||||
@ -32,6 +33,7 @@ func (s *Server)SetCookiePath(path string) {
|
||||
func (s *Server)SetCookieDomain(domain string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.CookieDomain = domain
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
// 配置管理数据结构定义.
|
||||
|
||||
package ghttp
|
||||
|
||||
@ -15,6 +14,7 @@ import (
|
||||
func (s *Server)SetLogPath(path string) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
if len(path) == 0 {
|
||||
return
|
||||
@ -27,6 +27,7 @@ func (s *Server)SetLogPath(path string) {
|
||||
func (s *Server)SetAccessLogEnabled(enabled bool) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.AccessLogEnabled = enabled
|
||||
}
|
||||
@ -35,6 +36,7 @@ func (s *Server)SetAccessLogEnabled(enabled bool) {
|
||||
func (s *Server)SetErrorLogEnabled(enabled bool) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.ErrorLogEnabled = enabled
|
||||
}
|
||||
@ -43,6 +45,7 @@ func (s *Server)SetErrorLogEnabled(enabled bool) {
|
||||
func (s *Server) SetLogHandler(handler LogHandler) {
|
||||
if s.Status() == SERVER_STATUS_RUNNING {
|
||||
glog.Error(gCHANGE_CONFIG_WHILE_RUNNING_ERROR)
|
||||
return
|
||||
}
|
||||
s.config.LogHandler = handler
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
127
g/net/ghttp/ghttp_server_config_static.go
Normal file
127
g/net/ghttp/ghttp_server_config_static.go
Normal 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 }
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,39 +35,40 @@ type CookieItem struct {
|
||||
httpOnly bool
|
||||
}
|
||||
|
||||
// 获取或者创建一个cookie对象,与传入的请求对应
|
||||
// 获取或者创建一个COOKIE对象,与传入的请求对应(延迟初始化)
|
||||
func GetCookie(r *Request) *Cookie {
|
||||
if r.Cookie != nil {
|
||||
return r.Cookie
|
||||
}
|
||||
r.Cookie = &Cookie {
|
||||
data : make(map[string]CookieItem),
|
||||
path : r.Server.GetCookiePath(),
|
||||
domain : r.Server.GetCookieDomain(),
|
||||
maxage : r.Server.GetCookieMaxAge(),
|
||||
server : r.Server,
|
||||
request : r,
|
||||
response : r.Response,
|
||||
return &Cookie {
|
||||
request : r,
|
||||
}
|
||||
// 默认有效域名
|
||||
if r.Cookie.domain == "" {
|
||||
r.Cookie.domain = r.GetHost()
|
||||
}
|
||||
r.Cookie.init()
|
||||
return r.Cookie
|
||||
}
|
||||
|
||||
// 从请求流中初始化,无锁
|
||||
// 从请求流中初始化,无锁,延迟初始化
|
||||
func (c *Cookie) init() {
|
||||
for _, v := range c.request.Cookies() {
|
||||
c.data[v.Name] = CookieItem {
|
||||
v.Value, v.Domain, v.Path, v.Expires.Second(), v.HttpOnly,
|
||||
if c.data == nil {
|
||||
c.data = make(map[string]CookieItem)
|
||||
c.path = c.request.Server.GetCookiePath()
|
||||
c.domain = c.request.Server.GetCookieDomain()
|
||||
c.maxage = c.request.Server.GetCookieMaxAge()
|
||||
c.server = c.request.Server
|
||||
c.response = c.request.Response
|
||||
// 如果没有设置COOKIE有效域名,那么设置HOST为默认有效域名
|
||||
if c.domain == "" {
|
||||
c.domain = c.request.GetHost()
|
||||
}
|
||||
for _, v := range c.request.Cookies() {
|
||||
c.data[v.Name] = CookieItem {
|
||||
v.Value, v.Domain, v.Path, v.Expires.Second(), v.HttpOnly,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有的Cookie并构造成map返回
|
||||
// 获取所有的Cookie并构造成map[string]string返回.
|
||||
func (c *Cookie) Map() map[string]string {
|
||||
c.init()
|
||||
m := make(map[string]string)
|
||||
for k, v := range c.data {
|
||||
m[k] = v.value
|
||||
@ -77,6 +78,7 @@ func (c *Cookie) Map() map[string]string {
|
||||
|
||||
// 获取SessionId,不存在时则创建
|
||||
func (c *Cookie) SessionId() string {
|
||||
c.init()
|
||||
id := c.Get(c.server.GetSessionIdName())
|
||||
if id == "" {
|
||||
id = makeSessionId()
|
||||
@ -87,6 +89,7 @@ func (c *Cookie) SessionId() string {
|
||||
|
||||
// 判断Cookie中是否存在制定键名(并且没有过期)
|
||||
func (c *Cookie) Contains(key string) bool {
|
||||
c.init()
|
||||
if r, ok := c.data[key]; ok {
|
||||
if r.expire >= 0 {
|
||||
return true
|
||||
@ -95,11 +98,6 @@ func (c *Cookie) Contains(key string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// 设置SessionId
|
||||
func (c *Cookie) SetSessionId(id string) {
|
||||
c.Set(c.server.GetSessionIdName(), id)
|
||||
}
|
||||
|
||||
// 设置cookie,使用默认参数
|
||||
func (c *Cookie) Set(key, value string) {
|
||||
c.SetCookie(key, value, c.domain, c.path, c.server.GetCookieMaxAge())
|
||||
@ -107,6 +105,7 @@ func (c *Cookie) Set(key, value string) {
|
||||
|
||||
// 设置cookie,带详细cookie参数
|
||||
func (c *Cookie) SetCookie(key, value, domain, path string, maxAge int, httpOnly ... bool) {
|
||||
c.init()
|
||||
isHttpOnly := false
|
||||
if len(httpOnly) > 0 {
|
||||
isHttpOnly = httpOnly[0]
|
||||
@ -116,8 +115,14 @@ func (c *Cookie) SetCookie(key, value, domain, path string, maxAge int, httpOnly
|
||||
}
|
||||
}
|
||||
|
||||
// 设置SessionId
|
||||
func (c *Cookie) SetSessionId(id string) {
|
||||
c.Set(c.server.GetSessionIdName(), id)
|
||||
}
|
||||
|
||||
// 查询cookie
|
||||
func (c *Cookie) Get(key string) string {
|
||||
c.init()
|
||||
if r, ok := c.data[key]; ok {
|
||||
if r.expire >= 0 {
|
||||
return r.value
|
||||
|
||||
@ -10,6 +10,7 @@ package ghttp
|
||||
import (
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/encoding/ghtml"
|
||||
"gitee.com/johng/gf/g/os/gspath"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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字符串
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ func (s *Server) getServeHandlerWithCache(r *Request) *handlerParsedItem {
|
||||
if v := s.serveCache.Get(cacheKey); v == nil {
|
||||
cacheItem = s.searchServeHandler(r.Method, r.URL.Path, r.GetHost())
|
||||
if cacheItem != nil {
|
||||
s.serveCache.Set(cacheKey, cacheItem)
|
||||
s.serveCache.Set(cacheKey, cacheItem, s.config.RouterCacheExpire*1000)
|
||||
}
|
||||
} else {
|
||||
cacheItem = v.(*handlerParsedItem)
|
||||
|
||||
@ -9,6 +9,7 @@ package ghttp
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"gitee.com/johng/gf/g/container/gvar"
|
||||
"gitee.com/johng/gf/g/os/gtime"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
"gitee.com/johng/gf/g/util/grand"
|
||||
@ -30,11 +31,12 @@ func makeSessionId() string {
|
||||
return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 32) + grand.RandStr(3))
|
||||
}
|
||||
|
||||
// 获取或者生成一个session对象
|
||||
// 获取或者生成一个session对象(延迟初始化)
|
||||
func GetSession(r *Request) *Session {
|
||||
if r.Session != nil {
|
||||
return r.Session
|
||||
}
|
||||
return &Session {
|
||||
data : gmap.NewStringInterfaceMap(),
|
||||
server : r.Server,
|
||||
request : r,
|
||||
}
|
||||
}
|
||||
@ -42,8 +44,9 @@ func GetSession(r *Request) *Session {
|
||||
// 执行初始化(用于延迟初始化)
|
||||
func (s *Session) init() {
|
||||
if len(s.id) == 0 {
|
||||
s.id = s.request.Cookie.SessionId()
|
||||
s.data = s.server.sessions.GetOrSetFuncLock(s.id, func() interface{} {
|
||||
s.id = s.request.Cookie.SessionId()
|
||||
s.server = s.request.Server
|
||||
s.data = s.server.sessions.GetOrSetFuncLock(s.id, func() interface{} {
|
||||
return gmap.NewStringInterfaceMap()
|
||||
}, s.server.GetSessionMaxAge()).(*gmap.StringInterfaceMap)
|
||||
}
|
||||
@ -68,13 +71,13 @@ func (s *Session) Set(key string, value interface{}) {
|
||||
}
|
||||
|
||||
// 批量设置(BatchSet别名)
|
||||
func (s *Session) Sets (m map[string]interface{}) {
|
||||
func (s *Session) Sets(m map[string]interface{}) {
|
||||
s.init()
|
||||
s.BatchSet(m)
|
||||
}
|
||||
|
||||
// 批量设置
|
||||
func (s *Session) BatchSet (m map[string]interface{}) {
|
||||
func (s *Session) BatchSet(m map[string]interface{}) {
|
||||
s.init()
|
||||
s.data.BatchSet(m)
|
||||
}
|
||||
@ -85,13 +88,21 @@ func (s *Session) Contains (key string) bool {
|
||||
return s.data.Contains(key)
|
||||
}
|
||||
|
||||
// 获取session
|
||||
// 获取SESSION
|
||||
func (s *Session) Get (key string) interface{} {
|
||||
s.init()
|
||||
return s.data.Get(key)
|
||||
}
|
||||
func (s *Session) GetString (key string) string { return gconv.String(s.Get(key)) }
|
||||
func (s *Session) GetBool(key string) bool { return gconv.Bool(s.Get(key)) }
|
||||
|
||||
// 获取SESSION,建议都用该方法获取参数
|
||||
func (s *Session) GetVar(key string) gvar.VarRead {
|
||||
s.init()
|
||||
return gvar.NewRead(s.data.Get(key), false)
|
||||
}
|
||||
|
||||
|
||||
func (s *Session) GetString (key string) string { return gconv.String(s.Get(key)) }
|
||||
func (s *Session) GetBool(key string) bool { return gconv.Bool(s.Get(key)) }
|
||||
|
||||
func (s *Session) GetInt(key string) int { return gconv.Int(s.Get(key)) }
|
||||
func (s *Session) GetInt8(key string) int8 { return gconv.Int8(s.Get(key)) }
|
||||
@ -105,8 +116,8 @@ func (s *Session) GetUint16(key string) uint16 { return gconv.U
|
||||
func (s *Session) GetUint32(key string) uint32 { return gconv.Uint32(s.Get(key)) }
|
||||
func (s *Session) GetUint64(key string) uint64 { return gconv.Uint64(s.Get(key)) }
|
||||
|
||||
func (s *Session) GetFloat32 (key string) float32 { return gconv.Float32(s.Get(key)) }
|
||||
func (s *Session) GetFloat64 (key string) float64 { return gconv.Float64(s.Get(key)) }
|
||||
func (s *Session) GetFloat32 (key string) float32 { return gconv.Float32(s.Get(key)) }
|
||||
func (s *Session) GetFloat64 (key string) float64 { return gconv.Float64(s.Get(key)) }
|
||||
|
||||
func (s *Session) GetBytes(key string) []byte { return gconv.Bytes(s.Get(key)) }
|
||||
func (s *Session) GetInts(key string) []int { return gconv.Ints(s.Get(key)) }
|
||||
@ -123,13 +134,13 @@ func (s *Session) GetStruct(key string, objPointer interface{}, attrMapping...ma
|
||||
}
|
||||
|
||||
// 删除session
|
||||
func (s *Session) Remove (key string) {
|
||||
func (s *Session) Remove(key string) {
|
||||
s.init()
|
||||
s.data.Remove(key)
|
||||
}
|
||||
|
||||
// 清空session
|
||||
func (s *Session) Clear () {
|
||||
func (s *Session) Clear() {
|
||||
s.init()
|
||||
s.data.Clear()
|
||||
}
|
||||
|
||||
@ -9,11 +9,15 @@
|
||||
package gcfg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitee.com/johng/gf/g/container/garray"
|
||||
"gitee.com/johng/gf/g/container/gmap"
|
||||
"gitee.com/johng/gf/g/container/gtype"
|
||||
"gitee.com/johng/gf/g/container/gvar"
|
||||
"gitee.com/johng/gf/g/encoding/gjson"
|
||||
"gitee.com/johng/gf/g/os/gfile"
|
||||
"gitee.com/johng/gf/g/os/gfsnotify"
|
||||
"gitee.com/johng/gf/g/os/glog"
|
||||
"gitee.com/johng/gf/g/os/gspath"
|
||||
@ -26,7 +30,7 @@ const (
|
||||
// 配置管理对象
|
||||
type Config struct {
|
||||
name *gtype.String // 默认配置文件名称
|
||||
paths *gspath.SPath // 搜索目录路径
|
||||
paths *garray.StringArray // 搜索目录路径
|
||||
jsons *gmap.StringInterfaceMap // 配置文件对象
|
||||
vc *gtype.Bool // 层级检索是否执行分隔符冲突检测(默认为false,检测会比较影响检索效率)
|
||||
}
|
||||
@ -39,33 +43,54 @@ func New(path string, file...string) *Config {
|
||||
}
|
||||
c := &Config {
|
||||
name : gtype.NewString(name),
|
||||
paths : gspath.New(),
|
||||
paths : garray.NewStringArray(0, 1),
|
||||
jsons : gmap.NewStringInterfaceMap(),
|
||||
vc : gtype.NewBool(),
|
||||
}
|
||||
c.SetPath(path)
|
||||
if len(path) > 0 {
|
||||
c.SetPath(path)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// 判断从哪个配置文件中获取内容,返回配置文件的绝对路径
|
||||
func (c *Config) filePath(file...string) string {
|
||||
func (c *Config) filePath(file...string) (path string) {
|
||||
name := c.name.Val()
|
||||
if len(file) > 0 {
|
||||
name = file[0]
|
||||
}
|
||||
path, _ := c.paths.Search(name)
|
||||
c.paths.RLockFunc(func(array []string) {
|
||||
for _, v := range array {
|
||||
if path, _ = gspath.Search(v, name); path != "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
if path == "" {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
buffer.WriteString(fmt.Sprintf("[gcfg] cannot find config file \"%s\" in following paths:", name))
|
||||
c.paths.RLockFunc(func(array []string) {
|
||||
for k, v := range array {
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s",k + 1, v))
|
||||
}
|
||||
})
|
||||
glog.Error(buffer.String())
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// 设置配置管理器的配置文件存放目录绝对路径
|
||||
func (c *Config) SetPath(path string) error {
|
||||
if rp, err := c.paths.Set(path); err != nil {
|
||||
glog.Error("gcfg.SetPath failed:", err.Error())
|
||||
realPath := gfile.RealPath(path)
|
||||
if realPath == "" {
|
||||
err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
|
||||
glog.Error(fmt.Sprintf(`[gcfg] SetPath failed: %s`, err.Error()))
|
||||
return err
|
||||
} else {
|
||||
c.jsons.Clear()
|
||||
glog.Debug("gcfg.SetPath:", rp)
|
||||
}
|
||||
c.jsons.Clear()
|
||||
c.paths.Clear()
|
||||
c.paths.Append(realPath)
|
||||
glog.Debug("[gcfg] SetPath:", realPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -78,44 +103,44 @@ func (c *Config) SetViolenceCheck(check bool) {
|
||||
|
||||
// 添加配置管理器的配置文件搜索路径
|
||||
func (c *Config) AddPath(path string) error {
|
||||
if rp, err := c.paths.Add(path); err != nil {
|
||||
glog.Debug("gcfg.AddPath failed:", err.Error())
|
||||
realPath := gfile.RealPath(path)
|
||||
if realPath == "" {
|
||||
err := errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
|
||||
glog.Error(fmt.Sprintf(`[gcfg] AddPath failed: %s`, err.Error()))
|
||||
return err
|
||||
} else {
|
||||
glog.Debug("gcfg.AddPath:", rp)
|
||||
}
|
||||
c.paths.Append(realPath)
|
||||
glog.Debug("[gcfg] AddPath:", realPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取指定文件的绝对路径,默认获取默认的配置文件路径
|
||||
func (c *Config) GetFilePath(file...string) string {
|
||||
name := c.name.Val()
|
||||
if len(file) > 0 {
|
||||
name = file[0]
|
||||
}
|
||||
path, _ := c.paths.Search(name)
|
||||
return path
|
||||
return c.filePath(file...)
|
||||
}
|
||||
|
||||
// 设置配置管理对象的默认文件名称
|
||||
func (c *Config) SetFileName(name string) {
|
||||
glog.Debug("gcfg.SetFileName:", name)
|
||||
glog.Debug("[gcfg] SetFileName:", name)
|
||||
c.name.Set(name)
|
||||
}
|
||||
|
||||
// 添加配置文件到配置管理器中,第二个参数为非必须,如果不输入表示添加进入默认的配置名称中
|
||||
func (c *Config) getJson(file...string) *gjson.Json {
|
||||
fpath := c.filePath(file...)
|
||||
if r := c.jsons.Get(fpath); r != nil {
|
||||
filePath := c.filePath(file...)
|
||||
if filePath == "" {
|
||||
return nil
|
||||
}
|
||||
if r := c.jsons.Get(filePath); r != nil {
|
||||
return r.(*gjson.Json)
|
||||
}
|
||||
if j, err := gjson.Load(fpath); err == nil {
|
||||
if j, err := gjson.Load(filePath); err == nil {
|
||||
j.SetViolenceCheck(c.vc.Val())
|
||||
c.addMonitor(fpath)
|
||||
c.jsons.Set(fpath, j)
|
||||
c.addMonitor(filePath)
|
||||
c.jsons.Set(filePath, j)
|
||||
return j
|
||||
} else {
|
||||
glog.Errorfln(`gcfg.Load config file "%s" failed: %s`, fpath, err.Error())
|
||||
glog.Errorfln(`[gcfg] Load config file "%s" failed: %s`, filePath, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -129,11 +154,11 @@ func (c *Config) Get(pattern string, file...string) interface{} {
|
||||
}
|
||||
|
||||
// 获得配置项,返回动态变量
|
||||
func (c *Config) GetVar(pattern string, file...string) *gvar.Var {
|
||||
func (c *Config) GetVar(pattern string, file...string) gvar.VarRead {
|
||||
if j := c.getJson(file...); j != nil {
|
||||
return gvar.New(j.Get(pattern))
|
||||
return gvar.New(j.Get(pattern), false)
|
||||
}
|
||||
return nil
|
||||
return gvar.New(nil, false)
|
||||
}
|
||||
|
||||
// 获得一个键值对关联数组/哈希表,方便操作,不需要自己做类型转换
|
||||
|
||||
@ -108,10 +108,9 @@ func IsDir(path string) bool {
|
||||
return s.IsDir()
|
||||
}
|
||||
|
||||
// 获取当前工作目录
|
||||
// 获取当前工作目录(SelfDir()方法的别名)
|
||||
func Pwd() string {
|
||||
pwd, _ := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
return pwd
|
||||
return SelfDir()
|
||||
}
|
||||
|
||||
// 判断所给路径是否为文件
|
||||
|
||||
@ -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 ...)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
|
||||
@ -23,24 +23,46 @@ import (
|
||||
|
||||
// 文件目录搜索管理对象
|
||||
type SPath struct {
|
||||
paths *garray.StringArray // 搜索路径,按照优先级进行排序
|
||||
cache *gmap.StringInterfaceMap // 搜索结果缓存map
|
||||
paths *garray.StringArray // 搜索路径,按照优先级进行排序
|
||||
cache *gmap.StringStringMap // 搜索结果缓存map
|
||||
}
|
||||
|
||||
// 文件搜索缓存项
|
||||
type SPathCacheItem struct {
|
||||
path string // 文件/目录绝对路径
|
||||
isDir bool // 是否目录
|
||||
path string // 文件/目录绝对路径
|
||||
isDir bool // 是否目录
|
||||
}
|
||||
|
||||
var (
|
||||
// 单个目录路径对应的SPath对象指针,用于路径检索对象复用
|
||||
pathsMap = gmap.NewStringInterfaceMap()
|
||||
)
|
||||
|
||||
// 创建一个搜索对象
|
||||
func New () *SPath {
|
||||
return &SPath {
|
||||
paths : garray.NewStringArray(0, 2),
|
||||
cache : gmap.NewStringInterfaceMap(),
|
||||
func New(path...string) *SPath {
|
||||
sp := &SPath {
|
||||
paths : garray.NewStringArray(0, 1),
|
||||
cache : gmap.NewStringStringMap(),
|
||||
}
|
||||
if len(path) > 0 {
|
||||
sp.Add(path[0])
|
||||
}
|
||||
return sp
|
||||
}
|
||||
|
||||
// 创建/获取一个单例的搜索对象, root必须为目录的绝对路径
|
||||
func Get(root string) *SPath {
|
||||
return pathsMap.GetOrSetFuncLock(root, func() interface{} {
|
||||
return New(root)
|
||||
}).(*SPath)
|
||||
}
|
||||
|
||||
// 检索root目录(必须为绝对路径)下面的name文件的绝对路径,indexFiles用于指定当检索到的结果为目录时,同时检索是否存在这些indexFiles文件
|
||||
func Search(root string, name string, indexFiles...string) (filePath string, isDir bool) {
|
||||
return Get(root).Search(name, indexFiles...)
|
||||
}
|
||||
|
||||
|
||||
// 设置搜索路径,只保留当前设置项,其他搜索路径被清空
|
||||
func (sp *SPath) Set(path string) (realPath string, err error) {
|
||||
realPath = gfile.RealPath(path)
|
||||
@ -53,9 +75,6 @@ func (sp *SPath) Set(path string) (realPath string, err error) {
|
||||
if realPath == "" {
|
||||
return realPath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
|
||||
}
|
||||
if realPath == "" {
|
||||
return realPath, errors.New("invalid path:" + path)
|
||||
}
|
||||
// 设置的搜索路径必须为目录
|
||||
if gfile.IsDir(realPath) {
|
||||
realPath = strings.TrimRight(realPath, gfile.Separator)
|
||||
@ -66,6 +85,7 @@ func (sp *SPath) Set(path string) (realPath string, err error) {
|
||||
}
|
||||
sp.paths.Clear()
|
||||
sp.cache.Clear()
|
||||
|
||||
sp.paths.Append(realPath)
|
||||
sp.updateCacheByPath(realPath)
|
||||
sp.addMonitorByPath(realPath)
|
||||
@ -87,9 +107,6 @@ func (sp *SPath) Add(path string) (realPath string, err error) {
|
||||
if realPath == "" {
|
||||
return realPath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
|
||||
}
|
||||
if realPath == "" {
|
||||
return realPath, errors.New("invalid path:" + path)
|
||||
}
|
||||
// 添加的搜索路径必须为目录
|
||||
if gfile.IsDir(realPath) {
|
||||
// 如果已经添加则不再添加
|
||||
@ -108,24 +125,22 @@ func (sp *SPath) Add(path string) (realPath string, err error) {
|
||||
// 给定的name只是相对文件路径,找不到该文件时,返回空字符串;
|
||||
// 当给定indexFiles时,如果name时一个目录,那么会进一步检索其下对应的indexFiles文件是否存在,存在则返回indexFile绝对路径;
|
||||
// 否则返回name目录绝对路径。
|
||||
func (sp *SPath) Search(name string, indexFiles...string) (path string, isDir bool) {
|
||||
func (sp *SPath) Search(name string, indexFiles...string) (filePath string, isDir bool) {
|
||||
name = sp.formatCacheName(name)
|
||||
if v := sp.cache.Get(name); v != nil {
|
||||
item := v.(*SPathCacheItem)
|
||||
if len(indexFiles) > 0 && item.isDir {
|
||||
if v := sp.cache.Get(name); v != "" {
|
||||
filePath, isDir = sp.parseCacheValue(v)
|
||||
if len(indexFiles) > 0 && isDir {
|
||||
if name == "/" {
|
||||
name = ""
|
||||
}
|
||||
for _, file := range indexFiles {
|
||||
if v := sp.cache.Get(name + "/" + file); v != nil {
|
||||
item := v.(*SPathCacheItem)
|
||||
return item.path, item.isDir
|
||||
if v := sp.cache.Get(name + "/" + file); v != "" {
|
||||
return sp.parseCacheValue(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
return item.path, item.isDir
|
||||
}
|
||||
return "", false
|
||||
return
|
||||
}
|
||||
|
||||
// 从搜索路径中移除指定的文件,这样该文件无法给搜索。
|
||||
@ -143,6 +158,11 @@ func (sp *SPath) Remove(path string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 返回当前对象搜索目录路径列表
|
||||
func (sp *SPath) Paths() []string {
|
||||
return sp.paths.Slice()
|
||||
}
|
||||
|
||||
// 返回当前对象缓存的所有路径列表
|
||||
func (sp *SPath) AllPaths() []string {
|
||||
paths := sp.cache.Keys()
|
||||
@ -164,35 +184,45 @@ func (sp *SPath) updateCacheByPath(path string) {
|
||||
|
||||
// 格式化name返回符合规范的缓存名称,分隔符号统一为'/',且前缀必须以'/'开头(类似HTTP URI).
|
||||
func (sp *SPath) formatCacheName(name string) string {
|
||||
name = strings.Trim(name, "./")
|
||||
if runtime.GOOS != "linux" {
|
||||
name = gstr.Replace(name, "\\", "/")
|
||||
}
|
||||
return "/" + name
|
||||
return "/" + strings.Trim(name, "./")
|
||||
}
|
||||
|
||||
// 根据path计算出对应的缓存name
|
||||
func (sp *SPath) nameFromPath(filePath, dirPath string) string {
|
||||
name := gstr.Replace(filePath, dirPath, "")
|
||||
// 根据path计算出对应的缓存name, dirPath为检索根目录路径
|
||||
func (sp *SPath) nameFromPath(filePath, rootPath string) string {
|
||||
name := gstr.Replace(filePath, rootPath, "")
|
||||
name = sp.formatCacheName(name)
|
||||
return name
|
||||
}
|
||||
|
||||
// 按照一定数据结构生成缓存的数据项字符串
|
||||
func (sp *SPath) makeCacheValue(filePath string, isDir bool) string {
|
||||
if isDir {
|
||||
return filePath + "_D_"
|
||||
}
|
||||
return filePath + "_F_"
|
||||
}
|
||||
|
||||
// 按照一定数据结构解析数据项字符串
|
||||
func (sp *SPath) parseCacheValue(value string) (filePath string, isDir bool) {
|
||||
if value[len(value) - 2 : len(value) - 1][0] == 'F' {
|
||||
return value[: len(value) - 3], false
|
||||
}
|
||||
return value[: len(value) - 3], true
|
||||
}
|
||||
|
||||
// 添加path到缓存中(递归)
|
||||
func (sp *SPath) addToCache(filePath, dirPath string) {
|
||||
func (sp *SPath) addToCache(filePath, rootPath string) {
|
||||
// 首先添加自身
|
||||
idDir := gfile.IsDir(filePath)
|
||||
sp.cache.SetIfNotExist(sp.nameFromPath(filePath, dirPath), func() interface{} {
|
||||
return &SPathCacheItem {
|
||||
path : filePath,
|
||||
isDir : idDir,
|
||||
}
|
||||
})
|
||||
// 如果添加的是目录,那么需要递归
|
||||
sp.cache.SetIfNotExist(sp.nameFromPath(filePath, rootPath), sp.makeCacheValue(filePath, idDir))
|
||||
// 如果添加的是目录,那么需要递归添加
|
||||
if idDir {
|
||||
if files, err := gfile.ScanDir(filePath, "*", true); err == nil {
|
||||
for _, path := range files {
|
||||
sp.addToCache(path, dirPath)
|
||||
sp.cache.SetIfNotExist(sp.nameFromPath(path, rootPath), sp.makeCacheValue(path, gfile.IsDir(path)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,32 +10,44 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkSecond(b *testing.B) {
|
||||
func Benchmark_Second(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Second()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMillisecond(b *testing.B) {
|
||||
func Benchmark_Millisecond(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Millisecond()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMicrosecond(b *testing.B) {
|
||||
func Benchmark_Microsecond(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Microsecond()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNanosecond(b *testing.B) {
|
||||
func Benchmark_Nanosecond(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
Nanosecond()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStrToTime(b *testing.B) {
|
||||
func Benchmark_StrToTime(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
StrToTime("2018-02-09T20:46:17.897Z")
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_ParseTimeFromContent(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ParseTimeFromContent("2018-02-09T20:46:17.897Z")
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark_NewFromTimeStamp(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
NewFromTimeStamp(1542674930)
|
||||
}
|
||||
}
|
||||
@ -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
108
g/util/gconv/gconv_map.go
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
// You can obtain one at https://gitee.com/johng/gf.
|
||||
|
||||
// 单元测试
|
||||
// go test *.go -bench=".*"
|
||||
// go test *.go
|
||||
|
||||
package gvalid
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -29,4 +29,8 @@ func main() {
|
||||
s := new(Score)
|
||||
v.Struct(s)
|
||||
fmt.Println(s)
|
||||
|
||||
// 只读接口
|
||||
r := v.ReadOnly()
|
||||
fmt.Println(r.String())
|
||||
}
|
||||
|
||||
582
geg/database/orm/mssql/gdb_sqlserver.go
Normal file
582
geg/database/orm/mssql/gdb_sqlserver.go
Normal 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()
|
||||
}
|
||||
13
geg/database/orm/mysql/config.toml
Normal file
13
geg/database/orm/mysql/config.toml
Normal 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"
|
||||
29
geg/database/orm/mysql/gdb_datetime.go
Normal file
29
geg/database/orm/mysql/gdb_datetime.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -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()
|
||||
|
||||
20
geg/net/ghttp/server/hooks/hooks_param.go
Normal file
20
geg/net/ghttp/server/hooks/hooks_param.go
Normal 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()
|
||||
}
|
||||
32
geg/net/ghttp/server/hooks/same_route_multi_hook.go
Normal file
32
geg/net/ghttp/server/hooks/same_route_multi_hook.go
Normal 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()
|
||||
}
|
||||
@ -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()
|
||||
|
||||
@ -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()
|
||||
}
|
||||
14
geg/net/ghttp/server/static/static_path.go
Normal file
14
geg/net/ghttp/server/static/static_path.go
Normal 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()
|
||||
}
|
||||
15
geg/net/ghttp/server/static/static_path2.go
Normal file
15
geg/net/ghttp/server/static/static_path2.go
Normal 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
33
geg/os/gcfg/config.json
Normal 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"
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"gitee.com/johng/gf/g"
|
||||
)
|
||||
|
||||
// 使用第二个参数指定读取的配置文件
|
||||
func main() {
|
||||
c := g.Config()
|
||||
redisConfig := c.GetArray("redis-cache", "redis.toml")
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"gitee.com/johng/gf/g"
|
||||
)
|
||||
|
||||
// 使用默认的config.toml配置文件读取配置
|
||||
func main() {
|
||||
c := g.Config()
|
||||
fmt.Println(c.GetArray("memcache"))
|
||||
|
||||
@ -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
12
geg/os/gcfg/gcfg4.go
Normal 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"))
|
||||
}
|
||||
|
||||
@ -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
12
geg/os/gcfg/gcfg_error.go
Normal 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"))
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ func main() {
|
||||
//gfsnotify.Remove(path)
|
||||
|
||||
if err != nil {
|
||||
glog.Fatalln(err)
|
||||
glog.Fatal(err)
|
||||
} else {
|
||||
select {}
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
21
geg/os/gtime/gtime_zone.go
Normal file
21
geg/os/gtime/gtime_zone.go
Normal 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())
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
39
geg/other/terminal-color.go
Normal file
39
geg/other/terminal-color.go
Normal 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("")
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
|
||||
41
geg/other/test/test_test.go
Normal file
41
geg/other/test/test_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
25
geg/util/gconv/gconv_map.go
Normal file
25
geg/util/gconv/gconv_map.go
Normal 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",
|
||||
}))
|
||||
}
|
||||
24
geg/util/gconv/gconv_slice.go
Normal file
24
geg/util/gconv/gconv_slice.go
Normal 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",
|
||||
}))
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
package gf
|
||||
|
||||
const VERSION = "v1.2.10"
|
||||
const VERSION = "v1.2.11"
|
||||
const AUTHORS = "john<john@johng.cn>"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user