Merge branch 'master' of https://gitee.com/johng/gf into qiangg_gfpool20181031

This commit is contained in:
john
2018-11-01 10:22:25 +08:00
19 changed files with 1049 additions and 115 deletions

View File

@ -36,10 +36,10 @@ golang版本 >= 1.9.2
1. 提供了对基本数据类型的并发安全封装,提供了常用的数据结构容器;
1. 支持Go变量/Json/Xml/Yml/Toml任意数据格式之间的相互转换及创建
1. 强大的数据库ORM支持应用层级的集群管理、读写分离、负载均衡查询缓存、方法及链式ORM操作
1. 更多特点请查阅框架[手册](http://gf.johng.cn)和[源码](https://godoc.org/github.com/johng-cn/gf)
1. 更多特点请查阅框架[手册](https://gfer.me)和[源码](https://godoc.org/github.com/johng-cn/gf)
# 文档
GoFrame开发文档[http://gf.johng.cn](http://gf.johng.cn)
GoFrame开发文档[gfer.me](https://gfer.me)
# 使用
@ -178,4 +178,4 @@ if tx, err := db.Begin(); err == nil {
...
更多特性及示例请查看官方开发文档:[gf.johng.cn](http://gf.johng.cn)
更多特性及示例请查看官方开发文档:[gfer.me](https://gfer.me)

View File

@ -1,52 +1,51 @@
ON THE WAY:
orm增加更多数据库支持
增加对于数据表Model的封装
更多数据库的ORM功能支持
考虑gdb对象管理增加二级连接池特性提高New&Close性能
增加图形验证码支持,至少支持数字和英文字母;
增加热编译工具,提高开发环境的开发/测试效率媲美PHP开发效率
增加可选择性的orm tag特性用以数据表记录与struct对象转换的键名属性映射
ghttp.Response增加输出内容后自动退出当前请求机制不需要用户手动return参考beego如何实现
Cookie&Session数据池化处理
ghttp.Client增加proxy特性
gtime增加对时区转换的封装并简化失去转换时对类似+80500时区的支持
orm增加sqlite对Save方法的支持(去掉触发器语句);
ghttp.Server增加Ip访问控制功能(DenyIps&AllowIps)
ghttp路由功能增加分组路由特性
ghttp增加返回数据压缩机制
gview中的template标签失效问题
gfile文件stat信息使用gfsnotify进行缓存更新改进
ghttp.Server增加proxy功能特性本地proxy和远程proxy本地即将路由规则映射远程即反向代理
gjson对大json数据的解析效率问题
ghttp增加route name特性并同时支持backend和template(提供内置函数)引用可以通过RedirectRoute方法给定route name和路由参数跳转到指定的路由地址上
ghttp.Client自动Close机制
gvalid校验支持当第一个规则失败后便不再校验后续的规则最好做成链式操作
检查ghttp.Server超时问题
gvalid增加支持对[]rune的长度校验(一个中文占3个字节)
ghttp.Request增加对输入参数的自动HtmlEncode机制
常量命名风格根据golint进行修改
开放rwmutex包并将gjson的互斥锁使用自定义的mutex替换
文档完善:
gconv struct tag、
控制器及执行对象注册的Init&Shut方法、
ghttp.Response&ServeFile、gfcache、gproc shell执行、
ghttp Server&Client basic auth、
glog分类&日志等级&链式操作、gdb debug自动输出调试信息、gmlock内存锁、
服务注册域名增加对泛域名的支持;
服务注册时判断方法定义满足规范时才执行绑定否则提示WARN信息
Cookie设置中文失效问题
ghttp hook回调使用方式在注册路由比较多的时候优先级可能使得开发者混乱考虑方式便于管理
使用gconv将slice映射到struct属性上例如redis hscan的结果集
项目参考:
https://github.com/namreg/godown
https://github.com/Masterminds/sprig
gform参考 https://gohouse.github.io/gorose/dist/index.html 进行改进
完善配置管理章节,说明默认的配置文件更改方式;
完善gform配置管理说明g.DB/Database和gdb.New的区别
# ON THE WAY
1. orm增加更多数据库支持
1. 增加对于数据表Model的封装
1. 更多数据库的ORM功能支持
1. 考虑gdb对象管理增加二级连接池特性提高New&Close性能
1. 增加图形验证码支持,至少支持数字和英文字母;
1. 增加热编译工具,提高开发环境的开发/测试效率媲美PHP开发效率
1. 增加可选择性的orm tag特性用以数据表记录与struct对象转换的键名属性映射
1. ghttp.Response增加输出内容后自动退出当前请求机制不需要用户手动return参考beego如何实现
1. Cookie&Session数据池化处理
1. ghttp.Client增加proxy特性
1. gtime增加对时区转换的封装并简化失去转换时对类似+80500时区的支持
1. orm增加sqlite对Save方法的支持(去掉触发器语句);
1. ghttp.Server增加Ip访问控制功能(DenyIps&AllowIps)
1. ghttp路由功能增加分组路由特性
1. ghttp增加返回数据压缩机制
1. gview中的template标签失效问题
1. gfile文件stat信息使用gfsnotify进行缓存更新改进
1. ghttp.Server增加proxy功能特性本地proxy和远程proxy本地即将路由规则映射远程即反向代理
1. gjson对大json数据的解析效率问题
1. ghttp增加route name特性并同时支持backend和template(提供内置函数)引用可以通过RedirectRoute方法给定route name和路由参数跳转到指定的路由地址上
1. ghttp.Client自动Close机制
1. gvalid校验支持当第一个规则失败后便不再校验后续的规则最好做成链式操作
1. 检查ghttp.Server超时问题
1. gvalid增加支持对[]rune的长度校验(一个中文占3个字节)
1. ghttp.Request增加对输入参数的自动HtmlEncode机制
1. 常量命名风格根据golint进行修改
1. 开放rwmutex包并将gjson的互斥锁使用自定义的mutex替换
1. 文档完善:
- gconv struct tag、
- 控制器及执行对象注册的Init&Shut方法、
- ghttp.Response&ServeFile、gfcache、gproc shell执行、
- ghttp Server&Client basic auth、
- 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 进行改进
DONE:
# DONE
1. gconv完善针对不同类型的判断例如尽量减少sprintf("%v", xxx)来执行string类型的转换
2. ghttp.Server请求执行中增加服务退出的方法不再执行后续操作
3. ghttp.Response对象完善并改进数据返回方法(Write/WriteString)
@ -89,4 +88,9 @@ DONE:
40. ghttp.Server的Cookie及Session锁机制优化(去掉map锁机制);
41. 解决glog串日志情况
42. glog增加对日志文件名称的生成规则设定支持时间格式规则
43. ghttp日志增加客户端IP信息
43. ghttp日志增加客户端IP信息
44. 完善gform配置管理说明g.DB/Database和gdb.New的区别
1. 完善配置管理章节,说明默认的配置文件更改方式;
1. 服务注册时判断方法定义满足规范时才执行绑定否则提示WARN信息

View File

@ -9,17 +9,17 @@
package gdb
import (
"fmt"
"time"
"errors"
"database/sql"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/container/gring"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/os/gcache"
"gitee.com/johng/gf/g/util/grand"
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
"gitee.com/johng/gf/g/container/gvar"
"errors"
"fmt"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/container/gring"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/container/gvar"
"gitee.com/johng/gf/g/os/gcache"
"gitee.com/johng/gf/g/util/grand"
_ "gitee.com/johng/gf/third/github.com/go-sql-driver/mysql"
"time"
)
const (
@ -122,18 +122,18 @@ type Map = map[string]interface{}
// 关联数组列表(索引从0开始的数组),绑定多条记录(使用别名)
type List = []Map
// MySQL接口对象
var linkMysql = &dbmysql{}
// PostgreSQL接口对象
var linkPgsql = &dbpgsql{}
// Sqlite接口对象
// @author wxkj<wxscz@qq.com>
var linkSqlite = &dbsqlite{}
// 数据库查询缓存对象map使用数据库连接名称作为键名键值为查询缓存对象
var dbCaches = gmap.NewStringInterfaceMap()
var (
// 支持的数据库类型map
driverMap = make(map[string]interface{})
// 数据库查询缓存对象map使用数据库连接名称作为键名键值为查询缓存对象
dbCaches = gmap.NewStringInterfaceMap()
)
func init() {
driverMap["mysql"] = linkMysql
driverMap["oracle"] = linkOracle
driverMap["sqllite"] = linkSqlite
driverMap["pgsql"] = linkPgsql
}
// 使用默认/指定分组配置进行连接数据库集群配置项default
func New(groupName ...string) (*Db, error) {
@ -239,15 +239,10 @@ func getConfigNodeByPriority(cg ConfigGroup) *ConfigNode {
// 根据配置的数据库类型获得Link接口对象
func getLinkByType(dbType string) (Link, error) {
switch dbType {
case "mysql":
return linkMysql, nil
case "pgsql":
return linkPgsql, nil
case "sqlite":
return linkSqlite, nil
default:
return nil, errors.New(fmt.Sprintf("unsupported db type '%s'", dbType))
if dblink, ok := driverMap[dbType]; ok == false {
return nil, errors.New(fmt.Sprintf("unsupported db type '%s'", dbType))
} else {
return dblink.(Link), nil
}
}

View File

@ -12,6 +12,10 @@ import (
"database/sql"
)
// MySQL接口对象
var linkMysql = &dbmysql{}
// 数据库链接对象
type dbmysql struct {
Db

View File

@ -0,0 +1,149 @@
// 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 20181026
说明:
1.需要导入oracle驱动 github.com/mattn/go-oci8
2.不支持save/replace方法可以调用这2个方法估计会报错还没测试过,(应该是可以通过oracle的merge来实现这2个功能的还没仔细研究)
3.不支持LastInsertId方法
*/
package gdb
import (
"database/sql"
"fmt"
"gitee.com/johng/gf/g/util/gregex"
"strconv"
"strings"
)
var linkOracle = &dboracle{}
// 数据库链接对象
type dboracle struct {
Db
}
// 创建SQL操作对象
func (db *dboracle) Open(c *ConfigNode) (*sql.DB, error) {
var source string
if c.Linkinfo != "" {
source = c.Linkinfo
} else {
source = fmt.Sprintf("%s/%s@%s", c.User, c.Pass, c.Name)
}
if db, err := sql.Open("oci8", source); err == nil {
return db, nil
} else {
return nil, err
}
}
// 获得关键字操作符 - 左
func (db *dboracle) getQuoteCharLeft() string {
return "\""
}
// 获得关键字操作符 - 右
func (db *dboracle) getQuoteCharRight() string {
return "\""
}
// 在执行sql之前对sql进行进一步处理
func (db *dboracle) handleSqlBeforeExec(q *string) *string {
index := 0
str, _ := gregex.ReplaceStringFunc("\\?", *q, func(s string) string {
index++
return fmt.Sprintf(":%d", index)
})
str, _ = gregex.ReplaceString("\"", "", str)
return db.parseSql(&str)
}
//由于ORACLE中对LIMIT和批量插入的语法与MYSQL不一致所以这里需要对LIMIT和批量插入做语法上的转换
func (db *dboracle) parseSql(sql *string) *string {
//下面的正则表达式匹配出SELECT和INSERT的关键字后分别做不同的处理如有LIMIT则将LIMIT的关键字也匹配出
patten := `^\s*(?i)(SELECT)|(INSERT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
if gregex.IsMatchString(patten, *sql) == false {
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
}
queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", *sql)
queryExpr[0] = strings.TrimRight(queryExpr[0], "LIMIT")
queryExpr[0] = strings.TrimRight(queryExpr[0], "limit")
//取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
}
}
//也可以使用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)
case "INSERT":
//获取VALUE的值匹配所有带括号的值,会将INSERT INTO后的值匹配到所以下面的判断语句会判断数组长度是否小于3
valueExpr, err := gregex.MatchAllString(`(\s*\(([^\(\)]*)\))`, *sql)
if err != nil {
return sql
}
//判断VALUE后的值是否有多个只有在批量插入的时候才需要做转换如只有1个VALUE则不需要做转换
if len(valueExpr) < 3 {
break
}
//获取INTO后面的值
tableExpr, err := gregex.MatchString(`(?i)\s*(INTO\s+\w+\(([^\(\)]*)\))`, *sql)
if err != nil {
return sql
}
tableExpr[0] = strings.TrimSpace(tableExpr[0])
*sql = "INSERT ALL"
for i := 1; i < len(valueExpr); i++ {
*sql += fmt.Sprintf(" %s VALUES%s", tableExpr[0], strings.TrimSpace(valueExpr[i][0]))
}
*sql += " SELECT 1 FROM DUAL"
default:
}
return sql
}

View File

@ -18,6 +18,10 @@ import (
// _ "gitee.com/johng/gf/third/github.com/lib/pq"
// @todo 需要完善replace和save的操作覆盖
// PostgreSQL接口对象
var linkPgsql = &dbpgsql{}
// 数据库链接对象
type dbpgsql struct {
Db
@ -57,5 +61,4 @@ func (db *dbpgsql) handleSqlBeforeExec(q *string) *string {
return fmt.Sprintf("$%d", index)
})
return &str
}
}

View File

@ -14,6 +14,11 @@ import (
// 使用时需要import:
// _ "gitee.com/johng/gf/third/github.com/mattn/go-sqlite3"
// Sqlite接口对象
// @author wxkj<wxscz@qq.com>
var linkSqlite = &dbsqlite{}
// 数据库链接对象
type dbsqlite struct {
Db
@ -49,4 +54,4 @@ func (db *dbsqlite) getQuoteCharRight() string {
func (db *dbsqlite) handleSqlBeforeExec(q *string) *string {
return q
}
}

View File

@ -74,7 +74,7 @@ func View(name...string) *gview.View {
return instances.GetOrSetFuncLock(key, func() interface{} {
path := gcmd.Option.Get("gf.viewpath")
if path == "" {
path = genv.Get("gf.viewpath")
path = genv.Get("GF_VIEWPATH")
if path == "" {
path = gfile.SelfDir()
}
@ -101,7 +101,7 @@ func Config(file...string) *gcfg.Config {
func() interface{} {
path := gcmd.Option.Get("gf.cfgpath")
if path == "" {
path = genv.Get("gf.cfgpath")
path = genv.Get("GF_CFGPATH")
if path == "" {
path = gfile.SelfDir()
}
@ -161,6 +161,9 @@ func Database(name...string) *gdb.Db {
if value, ok := nodem["priority"]; ok {
node.Priority = gconv.Int(value)
}
if value, ok := nodem["linkinfo"]; ok {
node.Linkinfo = gconv.String(value)
}
if value, ok := nodem["max-idle"]; ok {
node.MaxIdleConnCount = gconv.Int(value)
}

View File

@ -6,7 +6,21 @@
package g
import "gitee.com/johng/gf/g/os/glog"
import (
"gitee.com/johng/gf/g/os/gcmd"
"gitee.com/johng/gf/g/os/genv"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/util/gconv"
)
func init() {
if v := genv.Get("GF_DEBUG"); v != "" {
SetDebug(gconv.Bool(v))
}
if v := gcmd.Option.Get("gf.debug"); v != "" {
SetDebug(gconv.Bool(v))
}
}
// 是否显示调试信息
func SetDebug(debug bool) {

View File

@ -276,7 +276,7 @@ func (s *Server) Start() error {
// 打印展示路由表
func (s *Server) DumpRoutesMap() {
if s.config.DumpRouteMap {
if s.config.DumpRouteMap && len(s.routesMap) > 0 {
// (等待一定时间后)当所有框架初始化信息打印完毕之后才打印路由表信息
gtime.SetTimeout(50*time.Millisecond, func() {
glog.Header(false).Println(fmt.Sprintf("\n%s\n", s.GetRouteMap()))
@ -296,7 +296,7 @@ func (s *Server) GetRouteMap() string {
buf := bytes.NewBuffer(nil)
table := tablewriter.NewWriter(buf)
table.SetHeader([]string{"SERVER", "ADDRESS", "DOMAIN", "METHOD", "ROUTE", "HANDLER", "HOOK"})
table.SetHeader([]string{"SERVER", "ADDRESS", "DOMAIN", "METHOD", "P", "ROUTE", "HANDLER", "HOOK"})
table.SetRowLine(true)
table.SetBorder(false)
table.SetCenterSeparator("|")
@ -333,16 +333,17 @@ func (s *Server) GetRouteMap() string {
addr += ",tls" + s.config.HTTPSAddr
}
for _, a := range m {
data := make([]string, 7)
data := make([]string, 8)
for _, v := range a.Slice() {
item := v.(*tableItem)
data[0] = s.name
data[1] = addr
data[2] = item.domain
data[3] = item.method
data[4] = item.route
data[5] = item.handler
data[6] = item.hook
data[4] = gconv.String(len(strings.Split(item.route, "/")) - 1)
data[5] = item.route
data[6] = item.handler
data[7] = item.hook
table.Append(data)
}
}

View File

@ -9,6 +9,7 @@ package ghttp
import (
"errors"
"gitee.com/johng/gf/g/os/glog"
"strings"
"reflect"
"fmt"
@ -42,6 +43,14 @@ func (s *Server)BindController(pattern string, c Controller, methods...string) e
if mname == "Init" || mname == "Shut" || mname == "Exit" {
continue
}
if _, ok := v.Method(i).Interface().(func()); !ok {
if methodMap != nil {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
}
continue
}
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if ctlName[0] == '*' {
ctlName = fmt.Sprintf(`(%s)`, ctlName)
@ -82,9 +91,15 @@ func (s *Server)BindControllerMethod(pattern string, c Controller, method string
t := v.Type()
sname := t.Elem().Name()
mname := strings.TrimSpace(method)
if !v.MethodByName(mname).IsValid() {
fval := v.MethodByName(mname)
if !fval.IsValid() {
return errors.New("invalid method name:" + mname)
}
if _, ok := fval.Interface().(func()); !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, fval.Type().String())
glog.Error(s)
return errors.New(s)
}
pkgPath := t.Elem().PkgPath()
pkgName := gfile.Basename(pkgPath)
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
@ -119,6 +134,11 @@ func (s *Server)BindControllerRest(pattern string, c Controller) error {
if _, ok := s.methodsMap[method]; !ok {
continue
}
if _, ok := v.Method(i).Interface().(func()); !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
}
pkgName := gfile.Basename(pkgPath)
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if ctlName[0] == '*' {

View File

@ -9,6 +9,7 @@ package ghttp
import (
"errors"
"gitee.com/johng/gf/g/os/glog"
"strings"
"reflect"
"fmt"
@ -48,6 +49,15 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
if mname == "Init" || mname == "Shut" {
continue
}
faddr, ok := v.Method(i).Interface().(func(*Request))
if !ok {
if methodMap != nil {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func(*Request))" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
}
continue
}
objName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if objName[0] == '*' {
objName = fmt.Sprintf(`(%s)`, objName)
@ -58,7 +68,7 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
rtype : gROUTE_REGISTER_OBJECT,
ctype : nil,
fname : "",
faddr : v.Method(i).Interface().(func(*Request)),
faddr : faddr,
finit : finit,
fshut : fshut,
}
@ -76,7 +86,7 @@ func (s *Server)BindObject(pattern string, obj interface{}, methods...string) er
rtype : gROUTE_REGISTER_OBJECT,
ctype : nil,
fname : "",
faddr : v.Method(i).Interface().(func(*Request)),
faddr : faddr,
finit : finit,
fshut : fshut,
}
@ -97,6 +107,12 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string)
if !fval.IsValid() {
return errors.New("invalid method name:" + mname)
}
faddr, ok := fval.Interface().(func(*Request))
if !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func(*Request)" is required`, fval.Type().String())
glog.Error(s)
return errors.New(s)
}
finit := (func(*Request))(nil)
fshut := (func(*Request))(nil)
if v.MethodByName("Init").IsValid() {
@ -117,7 +133,7 @@ func (s *Server)BindObjectMethod(pattern string, obj interface{}, method string)
rtype : gROUTE_REGISTER_OBJECT,
ctype : nil,
fname : "",
faddr : fval.Interface().(func(*Request)),
faddr : faddr,
finit : finit,
fshut : fshut,
}
@ -146,6 +162,12 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
if _, ok := s.methodsMap[method]; !ok {
continue
}
faddr, ok := v.Method(i).Interface().(func(*Request))
if !ok {
s := fmt.Sprintf(`invalid medthod definition "%s", while "func()" is required`, v.Method(i).Type().String())
glog.Error(s)
return errors.New(s)
}
pkgName := gfile.Basename(pkgPath)
objName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if objName[0] == '*' {
@ -157,7 +179,7 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
rtype : gROUTE_REGISTER_OBJECT,
ctype : nil,
fname : "",
faddr : v.Method(i).Interface().(func(*Request)),
faddr : faddr,
finit : finit,
fshut : fshut,
}

View File

@ -8,6 +8,7 @@
package gview
import (
"fmt"
"gitee.com/johng/gf/g/encoding/gurl"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/os/gtime"
@ -84,14 +85,18 @@ func New(path string) *View {
view.BindFunc("html", view.funcHtmlEncode)
view.BindFunc("htmlencode", view.funcHtmlEncode)
view.BindFunc("htmldecode", view.funcHtmlDecode)
//view.BindFunc("htmlchars", view.funcHtmlChars)
//view.BindFunc("htmldechars", view.funcHtmlCharsDecode)
view.BindFunc("url", view.funcUrlEncode)
view.BindFunc("urlencode", view.funcUrlEncode)
view.BindFunc("urldecode", view.funcUrlDecode)
view.BindFunc("date", view.funcDate)
view.BindFunc("substr", view.funcSubStr)
view.BindFunc("strlimit", view.funcStrLimit)
view.BindFunc("compare", view.funcCompare)
view.BindFunc("hidestr", view.funcHideStr)
view.BindFunc("highlight", view.funcHighlight)
view.BindFunc("toupper", view.funcToUpper)
view.BindFunc("tolower", view.funcToLower)
view.BindFunc("nl2br", view.funcNl2Br)
view.BindFunc("include", view.funcInclude)
return view
}
@ -256,16 +261,6 @@ func (view *View) funcHtmlDecode(html interface{}) string {
return ghtml.EntitiesDecode(gconv.String(html))
}
// 模板内置方法htmlchars
func (view *View) funcHtmlChars(html interface{}) string {
return ghtml.SpecialChars(gconv.String(html))
}
// 模板内置方法htmlcharsdecode
func (view *View) funcHtmlCharsDecode(html interface{}) string {
return ghtml.SpecialCharsDecode(gconv.String(html))
}
// 模板内置方法url
func (view *View) funcUrlEncode(url interface{}) string {
return gurl.Encode(gconv.String(url))
@ -295,4 +290,34 @@ func (view *View) funcSubStr(start, end int, str interface{}) string {
return gstr.SubStr(gconv.String(str), start, end)
}
// 模板内置方法strlimit
func (view *View) funcStrLimit(length int, suffix string, str interface{}) string {
return gstr.StrLimit(gconv.String(str), length, suffix)
}
// 模板内置方法highlight
func (view *View) funcHighlight(key string, color string, str interface{}) string {
return gstr.Replace(gconv.String(str), key, fmt.Sprintf(`<span style="color:%s;">%s</span>`, color, key))
}
// 模板内置方法hidestr
func (view *View) funcHideStr(percent int, hide string, str interface{}) string {
return gstr.HideStr(gconv.String(str), percent, hide)
}
// 模板内置方法toupper
func (view *View) funcToUpper(str interface{}) string {
return gstr.ToUpper(gconv.String(str))
}
// 模板内置方法toupper
func (view *View) funcToLower(str interface{}) string {
return gstr.ToLower(gconv.String(str))
}
// 模板内置方法nl2br
func (view *View) funcNl2Br(str interface{}) string {
return gstr.Nl2Br(gconv.String(str))
}

View File

@ -7,11 +7,19 @@
// 字符串操作.
package gstr
import "strings"
import (
"bytes"
"math"
"strings"
)
// 字符串替换
func Replace(origin, search, replace string) string {
return strings.Replace(origin, search, replace, -1)
func Replace(origin, search, replace string, count...int) string {
n := -1
if len(count) > 0 {
n = count[0]
}
return strings.Replace(origin, search, replace, n)
}
// 使用map进行字符串替换
@ -120,4 +128,51 @@ func SubStr(str string, start int, length...int) (substr string) {
}
// 返回子串
return string(rs[start : end])
}
// 字符串长度截取限制,超过长度限制被截取并在字符串末尾追加指定的内容,支持中文
func StrLimit(str string, length int, suffix...string) (string) {
rs := []rune(str)
if len(str) < length {
return str
}
addstr := "..."
if len(suffix) > 0 {
addstr = suffix[0]
}
return string(rs[0 : length]) + addstr
}
// 按照百分比从字符串中间向两边隐藏字符(主要用于姓名、手机号、邮箱地址、身份证号等的隐藏)支持utf-8中文支持email格式。
func HideStr(str string, percent int, hide string) string {
array := strings.Split(str, "@")
if len(array) > 1 {
str = array[0]
}
rs := []rune(str)
length := len(rs)
mid := math.Floor(float64(length/2))
hideLen := int(math.Floor(float64(length) * (float64(percent)/100)))
start := int(mid - math.Floor(float64(hideLen) / 2))
hideStr := []rune("")
hideRune := []rune(hide)
for i := 0; i < int(hideLen); i++ {
hideStr = append(hideStr, hideRune...)
}
buffer := bytes.NewBuffer(nil)
buffer.WriteString(string(rs[0 : start]))
buffer.WriteString(string(hideStr))
buffer.WriteString(string(rs[start + hideLen : ]))
if len(array) > 1 {
buffer.WriteString(array[1])
}
return buffer.String()
}
// 将\n\r替换为html中的<br>标签。
func Nl2Br(str string) string {
str = Replace(str, "\r\n", "\n")
str = Replace(str, "\n\r", "\n")
str = Replace(str, "\n", "<br />")
return str
}

View File

@ -0,0 +1,580 @@
package main
import (
"fmt"
"time"
_ "github.com/mattn/go-oci8"
"gitee.com/johng/gf/g/database/gdb"
"gitee.com/johng/gf/g"
)
// 本文件用于gf框架的mysql数据库操作示例不作为单元测试使用
var db *gdb.Db
// 初始化配置及创建数据库
func init () {
gdb.AddDefaultConfigNode(gdb.ConfigNode {
Host : "192.168.146.0",
Port : "1521",
User : "test",
Pass : "test",
Name : "orcl",
Type : "oracle",
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 number(10) not null,
name VARCHAR2(45),
age number(8),
addr varchar2(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 number(10) not null,
site VARCHAR2(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")
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(0, 2).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":5, "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)
}
query()
*/
//batchInsert()
//query()
//replace()
//save()
//update1()
//update2()
//update3()
/*linkopSelect1()
linkopSelect2()
linkopSelect3()
linkopCount1()
*/
/*linkopUpdate1()
linkopUpdate2()
linkopUpdate3()
linkopUpdate4()
*/
//linkopBatchInsert1()
//linkopBatchInsert2()
//transaction1()
//transaction2()
//
//keepPing()
//likeQuery()
mapToStruct()
//getQueriedSqls()
}

View File

@ -1,8 +1,8 @@
package main
import (
"gitee.com/johng/gf/g/frame/gmvc"
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/frame/gmvc"
)
type User struct {
@ -13,8 +13,13 @@ func (c *User) Index() {
c.View.Display("index.html")
}
// 不符合规范,不会被自动注册
func (c *User) Test(value interface{}) {
c.View.Display("index.html")
}
func main() {
g.View().SetPath("C:/www/static")
//g.View().SetPath("C:/www/static")
s := g.Server()
s.BindController("/user", new(User))
s.SetPort(8199)

View File

@ -0,0 +1,31 @@
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
)
type User struct {
}
func (c *User) Index(r *ghttp.Request) {
r.Response.Write("Index")
}
// 不符合规范,不会被注册
func (c *User) Test(r *ghttp.Request, value interface{}) {
r.Response.Write("Test")
}
func main() {
s := g.Server()
s.BindObjectMethod("/user", new(User), "Test")
s.SetPort(8199)
s.Run()
}

View File

@ -20,6 +20,13 @@ func main() {
{{compare 1 1}}
{{"我是中国人" | substr 2 -1}}
{{"我是中国人" | substr 2 2}}
{{"我是中国人" | strlimit 2 "..."}}
{{"热爱GF热爱生活" | hidestr 20 "*"}}
{{"热爱GF热爱生活" | hidestr 50 "*"}}
{{"热爱GF热爱生活" | highlight "GF" "red"}}
{{"gf" | toupper}}
{{"GF" | tolower}}
{{"Go\nFrame" | nl2br}}
`
content, err := g.View().ParseContent(tplContent, nil)
fmt.Println(err)

View File

@ -0,0 +1,11 @@
package main
import (
"fmt"
"gitee.com/johng/gf/g/util/gstr"
)
func main() {
fmt.Println(gstr.HideStr("热爱GF热爱生活", 20, "*"))
fmt.Println(gstr.HideStr("热爱GF热爱生活", 50, "*"))
}