diff --git a/README.MD b/README.MD index f4c4c887b..4f1d1f23c 100644 --- a/README.MD +++ b/README.MD @@ -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) diff --git a/TODO b/TODO.MD similarity index 53% rename from TODO rename to TODO.MD index ae76a2dcc..a45402e0c 100644 --- a/TODO +++ b/TODO.MD @@ -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信息; \ No newline at end of file +43. ghttp日志增加客户端IP信息; +44. 完善gform配置管理说明,g.DB/Database和gdb.New的区别; +1. 完善配置管理章节,说明默认的配置文件更改方式; +1. 服务注册时判断方法定义满足规范时才执行绑定,否则提示WARN信息; + + diff --git a/g/database/gdb/gdb.go b/g/database/gdb/gdb.go index 1805c879f..88a4e6cca 100644 --- a/g/database/gdb/gdb.go +++ b/g/database/gdb/gdb.go @@ -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 -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 } } diff --git a/g/database/gdb/gdb_mysql.go b/g/database/gdb/gdb_mysql.go index d6b539a59..76775312b 100644 --- a/g/database/gdb/gdb_mysql.go +++ b/g/database/gdb/gdb_mysql.go @@ -12,6 +12,10 @@ import ( "database/sql" ) +// MySQL接口对象 +var linkMysql = &dbmysql{} + + // 数据库链接对象 type dbmysql struct { Db diff --git a/g/database/gdb/gdb_oracle.go b/g/database/gdb/gdb_oracle.go new file mode 100644 index 000000000..9e1b99409 --- /dev/null +++ b/g/database/gdb/gdb_oracle.go @@ -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 +@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 +} diff --git a/g/database/gdb/gdb_pgsql.go b/g/database/gdb/gdb_pgsql.go index e370541fa..a16a1efe4 100644 --- a/g/database/gdb/gdb_pgsql.go +++ b/g/database/gdb/gdb_pgsql.go @@ -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 -} - +} \ No newline at end of file diff --git a/g/database/gdb/gdb_sqlite.go b/g/database/gdb/gdb_sqlite.go index 6f5d91da9..0a92972bb 100644 --- a/g/database/gdb/gdb_sqlite.go +++ b/g/database/gdb/gdb_sqlite.go @@ -14,6 +14,11 @@ import ( // 使用时需要import: // _ "gitee.com/johng/gf/third/github.com/mattn/go-sqlite3" +// Sqlite接口对象 +// @author wxkj +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 -} +} \ No newline at end of file diff --git a/g/frame/gins/gins.go b/g/frame/gins/gins.go index 6404848e6..2325bb396 100644 --- a/g/frame/gins/gins.go +++ b/g/frame/gins/gins.go @@ -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) } diff --git a/g/g_logger.go b/g/g_logger.go index ac9f1d6cf..50f71c478 100644 --- a/g/g_logger.go +++ b/g/g_logger.go @@ -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) { diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 2a4979588..1ad12d116 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -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) } } diff --git a/g/net/ghttp/ghttp_server_service_controller.go b/g/net/ghttp/ghttp_server_service_controller.go index 4455f4697..e20fddb94 100644 --- a/g/net/ghttp/ghttp_server_service_controller.go +++ b/g/net/ghttp/ghttp_server_service_controller.go @@ -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] == '*' { diff --git a/g/net/ghttp/ghttp_server_service_object.go b/g/net/ghttp/ghttp_server_service_object.go index 8603ab6f1..ef64df4a1 100644 --- a/g/net/ghttp/ghttp_server_service_object.go +++ b/g/net/ghttp/ghttp_server_service_object.go @@ -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, } diff --git a/g/os/gview/gview.go b/g/os/gview/gview.go index 011024b2d..76852c9cf 100644 --- a/g/os/gview/gview.go +++ b/g/os/gview/gview.go @@ -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(`%s`, 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)) +} + diff --git a/g/util/gstr/gstr.go b/g/util/gstr/gstr.go index dd788afe1..8decd74ba 100644 --- a/g/util/gstr/gstr.go +++ b/g/util/gstr/gstr.go @@ -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中的
标签。 +func Nl2Br(str string) string { + str = Replace(str, "\r\n", "\n") + str = Replace(str, "\n\r", "\n") + str = Replace(str, "\n", "
") + return str } \ No newline at end of file diff --git a/geg/database/orm/oracle/gdb.go b/geg/database/orm/oracle/gdb.go new file mode 100644 index 000000000..4accdc246 --- /dev/null +++ b/geg/database/orm/oracle/gdb.go @@ -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() +} \ No newline at end of file diff --git a/geg/net/ghttp/server/controller/user.go b/geg/net/ghttp/server/controller/user.go index f0283ec90..50db001c9 100644 --- a/geg/net/ghttp/server/controller/user.go +++ b/geg/net/ghttp/server/controller/user.go @@ -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) diff --git a/geg/net/ghttp/server/object/user.go b/geg/net/ghttp/server/object/user.go new file mode 100644 index 000000000..cd39824c8 --- /dev/null +++ b/geg/net/ghttp/server/object/user.go @@ -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() +} + + + + + diff --git a/geg/os/gview/build_in_funcs/build_in_funcs.go b/geg/os/gview/build_in_funcs/build_in_funcs.go index 462b8d763..d39c683dd 100644 --- a/geg/os/gview/build_in_funcs/build_in_funcs.go +++ b/geg/os/gview/build_in_funcs/build_in_funcs.go @@ -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) diff --git a/geg/util/gstr/gstr_hidestr.go b/geg/util/gstr/gstr_hidestr.go new file mode 100644 index 000000000..3cc38d4f2 --- /dev/null +++ b/geg/util/gstr/gstr_hidestr.go @@ -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, "*")) +}