mirror of
https://gitee.com/johng/gf
synced 2026-06-07 10:22:11 +08:00
完成ghttp.Server路由改进工作,增加字段匹配规则特性
This commit is contained in:
@ -84,10 +84,10 @@ func (s *Server) setHandler(pattern string, item *HandlerItem) error {
|
||||
s.hmmu.Lock()
|
||||
defer s.hmmu.Unlock()
|
||||
defer s.clearHandlerCache()
|
||||
if s.isUriHasRule(uri) {
|
||||
// 动态注册,首先需要判断是否是动态注册,如果不是那么就没必要添加到动态注册记录变量中
|
||||
if s.isPatternUriHasFuzzRule(uri) {
|
||||
// 动态注册,首先需要判断是否是动态注册,如果不是那么就没必要添加到动态注册记录变量中。
|
||||
// 非叶节点为哈希表检索节点,按照URI注册的层级进行高效检索,直至到叶子链表节点;
|
||||
// 叶子节点是链表,按照优先级进行排序,优先级高的排前面,按照遍历检索,按照哈希表层级检索后的叶子链表一般数据量不大,所以效率比较高;
|
||||
// 叶子节点是链表,按照优先级进行排序,优先级高的排前面,按照遍历检索,按照哈希表层级检索后的叶子链表数据量不会很大,所以效率比较高;
|
||||
if _, ok := s.handlerTree[domain]; !ok {
|
||||
s.handlerTree[domain] = make(map[string]interface{})
|
||||
}
|
||||
@ -97,44 +97,46 @@ func (s *Server) setHandler(pattern string, item *HandlerItem) error {
|
||||
lists := make([]*list.List, 0)
|
||||
array := strings.Split(uri[1:], "/")
|
||||
item.router.Priority = len(array)
|
||||
// 键名"*fuzz"代表模糊匹配节点,其下会有一个链表;
|
||||
// 键名"*list"代表链表,叶子节点和模糊匹配节点都有该属性;
|
||||
for k, v := range array {
|
||||
if len(v) == 0 {
|
||||
continue
|
||||
}
|
||||
switch v[0] {
|
||||
case ':':
|
||||
fallthrough
|
||||
case '*':
|
||||
v = "/"
|
||||
if v, ok := p.(map[string]interface{})["*list"]; !ok {
|
||||
p.(map[string]interface{})["*list"] = list.New()
|
||||
lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
|
||||
} else {
|
||||
lists = append(lists, v.(*list.List))
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
if _, ok := p.(map[string]interface{})[v]; !ok {
|
||||
p.(map[string]interface{})[v] = make(map[string]interface{})
|
||||
}
|
||||
p = p.(map[string]interface{})[v]
|
||||
// 到达叶子节点,往list中增加匹配规则
|
||||
if k == len(array) - 1 && v != "/" {
|
||||
if v, ok := p.(map[string]interface{})["*list"]; !ok {
|
||||
p.(map[string]interface{})["*list"] = list.New()
|
||||
lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
|
||||
} else {
|
||||
lists = append(lists, v.(*list.List))
|
||||
}
|
||||
}
|
||||
// 判断是否模糊匹配规则
|
||||
if gregex.IsMatchString(`^[:\*]|{[\w\.\-]+}`, v) {
|
||||
v = "*fuzz"
|
||||
// 由于是模糊规则,因此这里会有一个*list,用以将后续的路由规则加进来,
|
||||
// 检索会从叶子节点的链表往根节点按照优先级进行检索
|
||||
if v, ok := p.(map[string]interface{})["*list"]; !ok {
|
||||
p.(map[string]interface{})["*list"] = list.New()
|
||||
lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
|
||||
} else {
|
||||
lists = append(lists, v.(*list.List))
|
||||
}
|
||||
}
|
||||
// 属性层级数据写入
|
||||
if _, ok := p.(map[string]interface{})[v]; !ok {
|
||||
p.(map[string]interface{})[v] = make(map[string]interface{})
|
||||
}
|
||||
p = p.(map[string]interface{})[v]
|
||||
// 到达叶子节点,往list中增加匹配规则(条件 v != "*fuzz" 是因为模糊节点的话在前面已经添加了*list链表)
|
||||
if k == len(array) - 1 && v != "*fuzz" {
|
||||
if v, ok := p.(map[string]interface{})["*list"]; !ok {
|
||||
p.(map[string]interface{})["*list"] = list.New()
|
||||
lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
|
||||
} else {
|
||||
lists = append(lists, v.(*list.List))
|
||||
}
|
||||
}
|
||||
}
|
||||
// 得到的lists是该路由规则一路匹配下来相关的模糊匹配链表(注意不是这棵树所有的链表),
|
||||
// 从头开始遍历每个节点的模糊匹配链表,将该路由项插入进去(按照优先级高的放在前面)
|
||||
for _, l := range lists {
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
if s.compareHandlerItemPriority(item, e.Value.(*HandlerItem)) {
|
||||
l.InsertBefore(item, e)
|
||||
return nil
|
||||
break
|
||||
}
|
||||
}
|
||||
l.PushBack(item)
|
||||
@ -155,6 +157,9 @@ func (s *Server) setHandler(pattern string, item *HandlerItem) error {
|
||||
}
|
||||
|
||||
// 对比两个HandlerItem的优先级,需要非常注意的是,注意新老对比项的参数先后顺序
|
||||
// 优先级比较规则:
|
||||
// 1、层级越深优先级越高(对比/数量);
|
||||
// 2、模糊规则优先级:{xxx} > :xxx > *xxx;
|
||||
func (s *Server) compareHandlerItemPriority(newItem, oldItem *HandlerItem) bool {
|
||||
if newItem.router.Priority > oldItem.router.Priority {
|
||||
return true
|
||||
@ -162,9 +167,18 @@ func (s *Server) compareHandlerItemPriority(newItem, oldItem *HandlerItem) bool
|
||||
if newItem.router.Priority < oldItem.router.Priority {
|
||||
return false
|
||||
}
|
||||
if strings.Count(newItem.router.Uri, "/:") > strings.Count(oldItem.router.Uri, "/:") {
|
||||
// 例如:/{user}/{act} 比 /:user/:act 优先级高
|
||||
if strings.Count(newItem.router.Uri, "{") > strings.Count(oldItem.router.Uri, "{") {
|
||||
return true
|
||||
}
|
||||
// 例如: /:name/update 比 /:name/:action优先级高
|
||||
if strings.Count(newItem.router.Uri, "/:") < strings.Count(oldItem.router.Uri, "/:") {
|
||||
// 例如: /:name/:action 比 /:name/*any 优先级高
|
||||
if strings.Count(newItem.router.Uri, "/*") < strings.Count(oldItem.router.Uri, "/*") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -202,7 +216,7 @@ func (s *Server) searchHandlerDynamic(r *Request) *handlerCacheItem {
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// 多层链表的目的是当叶子节点未有任何规则匹配时,让父级模糊匹配规则继续处理
|
||||
// 多层链表(每个节点都有一个*list链表)的目的是当叶子节点未有任何规则匹配时,让父级模糊匹配规则继续处理
|
||||
lists := make([]*list.List, 0)
|
||||
for k, v := range array {
|
||||
if _, ok := p.(map[string]interface{})["*list"]; ok {
|
||||
@ -215,14 +229,18 @@ func (s *Server) searchHandlerDynamic(r *Request) *handlerCacheItem {
|
||||
lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, ok := p.(map[string]interface{})["*fuzz"]; ok {
|
||||
p = p.(map[string]interface{})["*fuzz"]
|
||||
}
|
||||
}
|
||||
// 如果是叶子节点,同时判断当前层级的"/"键名,解决例如:/user/*action 匹配 /user 的规则
|
||||
// 如果是叶子节点,同时判断当前层级的"*fuzz"键名,解决例如:/user/*action 匹配 /user 的规则
|
||||
if k == len(array) - 1 {
|
||||
if _, ok := p.(map[string]interface{})["/"]; ok {
|
||||
p = p.(map[string]interface{})["/"]
|
||||
if _, ok := p.(map[string]interface{})["*list"]; ok {
|
||||
lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
|
||||
}
|
||||
if _, ok := p.(map[string]interface{})["*fuzz"]; ok {
|
||||
p = p.(map[string]interface{})["*fuzz"]
|
||||
}
|
||||
if _, ok := p.(map[string]interface{})["*list"]; ok {
|
||||
lists = append(lists, p.(map[string]interface{})["*list"].(*list.List))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -231,19 +249,26 @@ func (s *Server) searchHandlerDynamic(r *Request) *handlerCacheItem {
|
||||
for i := len(lists) - 1; i >= 0; i-- {
|
||||
for e := lists[i].Front(); e != nil; e = e.Next() {
|
||||
item := e.Value.(*HandlerItem)
|
||||
// 动态匹配规则带有gDEFAULT_METHOD的情况,不会像静态规则那样直接解析为所有的HTTP METHOD存储
|
||||
if strings.EqualFold(item.router.Method, gDEFAULT_METHOD) || strings.EqualFold(item.router.Method, r.Method) {
|
||||
// 不管正则关键字符转义问题
|
||||
//rule, names := s.patternToRegRule(gstr.ReplaceByMap(gregex.Quote(item.router.Uri), map[string]string {
|
||||
// `\{` : `{`,
|
||||
// `\}` : `}`,
|
||||
// `\*` : `*`,
|
||||
//}))
|
||||
rule, names := s.patternToRegRule(item.router.Uri)
|
||||
if gregex.IsMatchString(rule, r.URL.Path) {
|
||||
if match, err := gregex.MatchString(rule, r.URL.Path); err == nil && len(match) > 1 {
|
||||
//gutil.Dump(match)
|
||||
//gutil.Dump(names)
|
||||
handlerItem := &handlerCacheItem{item, nil}
|
||||
// 如果需要query匹配,那么需要重新解析URL
|
||||
// 如果需要query匹配,那么需要重新正则解析URL
|
||||
if len(names) > 0 {
|
||||
if match, err := gregex.MatchString(rule, r.URL.Path); err == nil {
|
||||
array := strings.Split(names, ",")
|
||||
if len(match) > len(array) {
|
||||
handlerItem.values = make(map[string][]string)
|
||||
for index, name := range array {
|
||||
handlerItem.values[name] = []string{match[index + 1]}
|
||||
}
|
||||
array := strings.Split(names, ",")
|
||||
if len(match) > len(array) {
|
||||
handlerItem.values = make(map[string][]string)
|
||||
for index, name := range array {
|
||||
handlerItem.values[name] = []string{match[index + 1]}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -281,7 +306,18 @@ func (s *Server) patternToRegRule(rule string) (regrule string, names string) {
|
||||
}
|
||||
names += v[1:]
|
||||
default:
|
||||
regrule += "/" + v
|
||||
s, _ := gregex.ReplaceStringFunc(`{[\w\.\-]+}`, v, func(s string) string {
|
||||
if len(names) > 0 {
|
||||
names += ","
|
||||
}
|
||||
names += s[1 : len(s) - 1]
|
||||
return `([\w\.\-]+)`
|
||||
})
|
||||
if strings.EqualFold(s, v) {
|
||||
regrule += "/" + v
|
||||
} else {
|
||||
regrule += "/" + s
|
||||
}
|
||||
}
|
||||
}
|
||||
regrule += `$`
|
||||
@ -289,8 +325,8 @@ func (s *Server) patternToRegRule(rule string) (regrule string, names string) {
|
||||
}
|
||||
|
||||
// 判断URI中是否包含动态注册规则
|
||||
func (s *Server) isUriHasRule(uri string) bool {
|
||||
if len(uri) > 1 && (strings.Index(uri, "/:") != -1 || strings.Index(uri, "/*") != -1) {
|
||||
func (s *Server) isPatternUriHasFuzzRule(uri string) bool {
|
||||
if len(uri) > 1 && gregex.IsMatchString(`^/[:\*]|{[\w\.\-]+}`, uri) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@ -34,26 +34,22 @@ func (s *Server)bindHandlerByMap(m HandlerMap) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 将方法名称按照设定的规则合并到pattern中.
|
||||
// 规则1:pattern中的URI包含{method}关键字,则替换该关键字为方法名称
|
||||
// 规则2:如果不满足规则1,那么直接将防发明附加到pattern中的URI后面
|
||||
func (s *Server)mergeMethodNameToPattern(pattern string, name string) string {
|
||||
// 方法名中间存在大写字母,转换为小写URI地址以“-”号链接每个单词
|
||||
method := ""
|
||||
for i := 0; i < len(name); i++ {
|
||||
if i > 0 && gstr.IsLetterUpper(name[i]) {
|
||||
method += "-"
|
||||
}
|
||||
method += strings.ToLower(string(name[i]))
|
||||
}
|
||||
if strings.Index(pattern, "{method}") != -1 {
|
||||
return strings.Replace(pattern, "{method}", method, -1)
|
||||
// 将内置的名称按照设定的规则合并到pattern中,内置名称按照{.xxx}规则命名。
|
||||
// 规则1:pattern中的URI包含{.struct}关键字,则替换该关键字为结构体名称;
|
||||
// 规则1:pattern中的URI包含{.method}关键字,则替换该关键字为方法名称;
|
||||
// 规则2:如果不满足规则1,那么直接将防发明附加到pattern中的URI后面;
|
||||
func (s *Server) mergeBuildInNameToPattern(pattern string, structName, methodName string) string {
|
||||
structName = s.nameToUrlPart(structName)
|
||||
methodName = s.nameToUrlPart(methodName)
|
||||
pattern = strings.Replace(pattern, "{.struct}", structName, -1)
|
||||
if strings.Index(pattern, "{.method}") != -1 {
|
||||
return strings.Replace(pattern, "{.method}", methodName, -1)
|
||||
}
|
||||
// 检测域名后缀
|
||||
array := strings.Split(pattern, "@")
|
||||
// 分离URI(其实可能包含HTTP Method)
|
||||
uri := array[0]
|
||||
uri = strings.TrimRight(uri, "/") + "/" + method
|
||||
uri = strings.TrimRight(uri, "/") + "/" + methodName
|
||||
// 加上指定域名后缀
|
||||
if len(array) > 1 {
|
||||
return uri + "@" + array[1]
|
||||
@ -61,6 +57,20 @@ func (s *Server)mergeMethodNameToPattern(pattern string, name string) string {
|
||||
return uri
|
||||
}
|
||||
|
||||
// 将给定的名称转换为URL规范格式。
|
||||
// 规范1: 全部转换为小写;
|
||||
// 规范2: 方法名中间存在大写字母,转换为小写URI地址以“-”号链接每个单词;
|
||||
func (s *Server) nameToUrlPart(name string) string {
|
||||
part := ""
|
||||
for i := 0; i < len(name); i++ {
|
||||
if i > 0 && gstr.IsLetterUpper(name[i]) {
|
||||
part += "-"
|
||||
}
|
||||
part += string(name[i])
|
||||
}
|
||||
return strings.ToLower(part)
|
||||
}
|
||||
|
||||
// 注意该方法是直接绑定函数的内存地址,执行的时候直接执行该方法,不会存在初始化新的控制器逻辑
|
||||
func (s *Server)BindHandler(pattern string, handler HandlerFunc) error {
|
||||
return s.bindHandlerItem(pattern, &HandlerItem{
|
||||
@ -76,16 +86,17 @@ func (s *Server)BindObject(pattern string, obj interface{}) error {
|
||||
m := make(HandlerMap)
|
||||
v := reflect.ValueOf(obj)
|
||||
t := v.Type()
|
||||
sname := t.Elem().Name()
|
||||
for i := 0; i < v.NumMethod(); i++ {
|
||||
name := t.Method(i).Name
|
||||
key := s.mergeMethodNameToPattern(pattern, name)
|
||||
m[key] = &HandlerItem {
|
||||
method := t.Method(i).Name
|
||||
key := s.mergeBuildInNameToPattern(pattern, sname, method)
|
||||
m[key] = &HandlerItem {
|
||||
ctype : nil,
|
||||
fname : "",
|
||||
faddr : v.Method(i).Interface().(func(*Request)),
|
||||
}
|
||||
// 如果方法中带有Index方法,那么额外自动增加一个路由规则匹配主URI
|
||||
if strings.EqualFold(name, "Index") {
|
||||
if strings.EqualFold(method, "Index") {
|
||||
m[pattern] = &HandlerItem {
|
||||
ctype : nil,
|
||||
fname : "",
|
||||
@ -99,21 +110,24 @@ func (s *Server)BindObject(pattern string, obj interface{}) error {
|
||||
// 绑定对象到URI请求处理中,会自动识别方法名称,并附加到对应的URI地址后面
|
||||
// 第三个参数methods支持多个方法注册,多个方法以英文“,”号分隔,区分大小写
|
||||
func (s *Server)BindObjectMethod(pattern string, obj interface{}, methods string) error {
|
||||
m := make(HandlerMap)
|
||||
for _, v := range strings.Split(methods, ",") {
|
||||
name := strings.TrimSpace(v)
|
||||
fval := reflect.ValueOf(obj).MethodByName(name)
|
||||
m := make(HandlerMap)
|
||||
v := reflect.ValueOf(obj)
|
||||
t := v.Type()
|
||||
sname := t.Elem().Name()
|
||||
for _, method := range strings.Split(methods, ",") {
|
||||
mname := strings.TrimSpace(method)
|
||||
fval := v.MethodByName(mname)
|
||||
if !fval.IsValid() {
|
||||
return errors.New("invalid method name:" + name)
|
||||
return errors.New("invalid method name:" + mname)
|
||||
}
|
||||
key := s.mergeMethodNameToPattern(pattern, name)
|
||||
key := s.mergeBuildInNameToPattern(pattern, sname, mname)
|
||||
m[key] = &HandlerItem{
|
||||
ctype : nil,
|
||||
fname : "",
|
||||
faddr : fval.Interface().(func(*Request)),
|
||||
}
|
||||
// 如果方法中带有Index方法,那么额外自动增加一个路由规则匹配主URI
|
||||
if strings.EqualFold(name, "Index") {
|
||||
if strings.EqualFold(mname, "Index") {
|
||||
m[pattern] = &HandlerItem {
|
||||
ctype : nil,
|
||||
fname : "",
|
||||
@ -150,25 +164,26 @@ func (s *Server)BindObjectRest(pattern string, obj interface{}) error {
|
||||
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话
|
||||
func (s *Server)BindController(pattern string, c Controller) error {
|
||||
// 遍历控制器,获取方法列表,并构造成uri
|
||||
m := make(HandlerMap)
|
||||
v := reflect.ValueOf(c)
|
||||
t := v.Type()
|
||||
m := make(HandlerMap)
|
||||
v := reflect.ValueOf(c)
|
||||
t := v.Type()
|
||||
sname := t.Elem().Name()
|
||||
for i := 0; i < v.NumMethod(); i++ {
|
||||
name := t.Method(i).Name
|
||||
if name == "Init" || name == "Shut" || name == "Exit" {
|
||||
mname := t.Method(i).Name
|
||||
if mname == "Init" || mname == "Shut" || mname == "Exit" {
|
||||
continue
|
||||
}
|
||||
key := s.mergeMethodNameToPattern(pattern, name)
|
||||
key := s.mergeBuildInNameToPattern(pattern, sname, mname)
|
||||
m[key] = &HandlerItem {
|
||||
ctype : v.Elem().Type(),
|
||||
fname : name,
|
||||
fname : mname,
|
||||
faddr : nil,
|
||||
}
|
||||
// 如果方法中带有Index方法,那么额外自动增加一个路由规则匹配主URI
|
||||
if strings.EqualFold(name, "Index") {
|
||||
if strings.EqualFold(mname, "Index") {
|
||||
m[pattern] = &HandlerItem {
|
||||
ctype : v.Elem().Type(),
|
||||
fname : name,
|
||||
fname : mname,
|
||||
faddr : nil,
|
||||
}
|
||||
}
|
||||
@ -179,25 +194,27 @@ func (s *Server)BindController(pattern string, c Controller) error {
|
||||
// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话
|
||||
// 第三个参数methods支持多个方法注册,多个方法以英文“,”号分隔,不区分大小写
|
||||
func (s *Server)BindControllerMethod(pattern string, c Controller, methods string) error {
|
||||
m := make(HandlerMap)
|
||||
cval := reflect.ValueOf(c)
|
||||
for _, v := range strings.Split(methods, ",") {
|
||||
name := strings.TrimSpace(v)
|
||||
ctype := reflect.ValueOf(c).Elem().Type()
|
||||
if !cval.MethodByName(name).IsValid() {
|
||||
return errors.New("invalid method name:" + name)
|
||||
m := make(HandlerMap)
|
||||
v := reflect.ValueOf(c)
|
||||
e := v.Type().Elem()
|
||||
t := v.Elem().Type()
|
||||
sname := e.Name()
|
||||
for _, method := range strings.Split(methods, ",") {
|
||||
mname := strings.TrimSpace(method)
|
||||
if !v.MethodByName(mname).IsValid() {
|
||||
return errors.New("invalid method name:" + mname)
|
||||
}
|
||||
key := s.mergeMethodNameToPattern(pattern, name)
|
||||
key := s.mergeBuildInNameToPattern(pattern, sname, mname)
|
||||
m[key] = &HandlerItem {
|
||||
ctype : ctype,
|
||||
fname : name,
|
||||
ctype : t,
|
||||
fname : mname,
|
||||
faddr : nil,
|
||||
}
|
||||
// 如果方法中带有Index方法,那么额外自动增加一个路由规则匹配主URI
|
||||
if strings.EqualFold(name, "Index") {
|
||||
if strings.EqualFold(mname, "Index") {
|
||||
m[pattern] = &HandlerItem {
|
||||
ctype : ctype,
|
||||
fname : name,
|
||||
ctype : t,
|
||||
fname : mname,
|
||||
faddr : nil,
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,11 @@ func getRegexp(pattern string) (*regexp.Regexp, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 转移正则规则字符串,例如:Quote(`[foo]`) 返回 `\[foo\]`
|
||||
func Quote(s string) string {
|
||||
return regexp.QuoteMeta(s)
|
||||
}
|
||||
|
||||
// 校验所给定的正则表达式是否符合规范
|
||||
func Validate(pattern string) error {
|
||||
_, err := getRegexp(pattern)
|
||||
@ -76,4 +81,21 @@ func Replace(pattern string, replace, src []byte) ([]byte, error) {
|
||||
func ReplaceString(pattern, replace, src string) (string, error) {
|
||||
r, e := Replace(pattern, []byte(replace), []byte(src))
|
||||
return string(r), e
|
||||
}
|
||||
|
||||
// 正则替换(全部替换),给定自定义替换方法
|
||||
func ReplaceFunc(pattern string, src []byte, repl func(b []byte) []byte) ([]byte, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.ReplaceAllFunc(src, repl), nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 正则替换(全部替换),给定自定义替换方法
|
||||
func ReplaceStringFunc(pattern string, src string, repl func(s string) string) (string, error) {
|
||||
bytes, err := ReplaceFunc(pattern, []byte(src), func(bytes []byte) []byte {
|
||||
return []byte(repl(string(bytes)))
|
||||
})
|
||||
return string(bytes), err
|
||||
}
|
||||
@ -8,11 +8,22 @@
|
||||
package gutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"encoding/json"
|
||||
"gitee.com/johng/gf/g/util/gstr"
|
||||
"gitee.com/johng/gf/g/util/gconv"
|
||||
)
|
||||
|
||||
// 格式化打印变量(类似于PHP-vardump)
|
||||
func Dump(i interface{}) {
|
||||
if b, err := json.MarshalIndent(i, "", "\t"); err == nil {
|
||||
fmt.Println(string(b))
|
||||
} else {
|
||||
fmt.Errorf("%s\n", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// 将map键值对映射到对应的struct对象属性上,需要注意:
|
||||
// 1、第二个参数为struct对象指针;
|
||||
// 2、struct对象的公开属性才能被映射赋值;
|
||||
|
||||
@ -1,240 +0,0 @@
|
||||
/*
|
||||
go get -u github.com/olivere/elastic
|
||||
go get -u github.com/mailru/easyjson
|
||||
go get -u github.com/pkg/errors
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/olivere/elastic"
|
||||
)
|
||||
|
||||
// Tweet is a structure used for serializing/deserializing data in Elasticsearch.
|
||||
type Tweet struct {
|
||||
User string `json:"user"`
|
||||
Message string `json:"message"`
|
||||
Retweets int `json:"retweets"`
|
||||
Image string `json:"image,omitempty"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Suggest *elastic.SuggestField `json:"suggest_field,omitempty"`
|
||||
}
|
||||
|
||||
const mapping = `
|
||||
{
|
||||
"settings":{
|
||||
"number_of_shards": 1,
|
||||
"number_of_replicas": 0
|
||||
},
|
||||
"mappings":{
|
||||
"tweet":{
|
||||
"properties":{
|
||||
"user":{
|
||||
"type":"keyword"
|
||||
},
|
||||
"message":{
|
||||
"type":"text",
|
||||
"store": true,
|
||||
"fielddata": true
|
||||
},
|
||||
"image":{
|
||||
"type":"keyword"
|
||||
},
|
||||
"created":{
|
||||
"type":"date"
|
||||
},
|
||||
"tags":{
|
||||
"type":"keyword"
|
||||
},
|
||||
"location":{
|
||||
"type":"geo_point"
|
||||
},
|
||||
"suggest_field":{
|
||||
"type":"completion"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
func main() {
|
||||
// Starting with elastic.v5, you must pass a context to execute each service
|
||||
ctx := context.Background()
|
||||
|
||||
// Obtain a client and connect to the default Elasticsearch installation
|
||||
// on 127.0.0.1:9200. Of course you can configure your client to connect
|
||||
// to other hosts and configure it in various other ways.
|
||||
client, err := elastic.NewClient()
|
||||
if err != nil {
|
||||
// Handle error
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Ping the Elasticsearch server to get e.g. the version number
|
||||
info, code, err := client.Ping("http://127.0.0.1:9200").Do(ctx)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Elasticsearch returned with code %d and version %s\n", code, info.Version.Number)
|
||||
|
||||
// Getting the ES version number is quite common, so there's a shortcut
|
||||
esversion, err := client.ElasticsearchVersion("http://127.0.0.1:9200")
|
||||
if err != nil {
|
||||
// Handle error
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Elasticsearch version %s\n", esversion)
|
||||
|
||||
// Use the IndexExists service to check if a specified index exists.
|
||||
exists, err := client.IndexExists("twitter").Do(ctx)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
panic(err)
|
||||
}
|
||||
if !exists {
|
||||
// Create a new index.
|
||||
createIndex, err := client.CreateIndex("twitter").BodyString(mapping).Do(ctx)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
panic(err)
|
||||
}
|
||||
if !createIndex.Acknowledged {
|
||||
// Not acknowledged
|
||||
}
|
||||
}
|
||||
|
||||
// Index a tweet (using JSON serialization)
|
||||
tweet1 := Tweet{User: "olivere", Message: "Take Five", Retweets: 0}
|
||||
put1, err := client.Index().
|
||||
Index("twitter").
|
||||
Type("tweet").
|
||||
Id("1").
|
||||
BodyJson(tweet1).
|
||||
Do(ctx)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Indexed tweet %s to index %s, type %s\n", put1.Id, put1.Index, put1.Type)
|
||||
|
||||
// Index a second tweet (by string)
|
||||
tweet2 := `{"user" : "olivere", "message" : "It's a Raggy Waltz"}`
|
||||
put2, err := client.Index().
|
||||
Index("twitter").
|
||||
Type("tweet").
|
||||
Id("2").
|
||||
BodyString(tweet2).
|
||||
Do(ctx)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Indexed tweet %s to index %s, type %s\n", put2.Id, put2.Index, put2.Type)
|
||||
|
||||
// Get tweet with specified ID
|
||||
get1, err := client.Get().
|
||||
Index("twitter").
|
||||
Type("tweet").
|
||||
Id("1").
|
||||
Do(ctx)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
panic(err)
|
||||
}
|
||||
if get1.Found {
|
||||
fmt.Printf("Got document %s in version %d from index %s, type %s\n", get1.Id, get1.Version, get1.Index, get1.Type)
|
||||
}
|
||||
|
||||
// Flush to make sure the documents got written.
|
||||
_, err = client.Flush().Index("twitter").Do(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Search with a term query
|
||||
termQuery := elastic.NewTermQuery("user", "olivere")
|
||||
searchResult, err := client.Search().
|
||||
Index("twitter"). // search in index "twitter"
|
||||
Query(termQuery). // specify the query
|
||||
Sort("user", true). // sort by "user" field, ascending
|
||||
From(0).Size(10). // take documents 0-9
|
||||
Pretty(true). // pretty print request and response JSON
|
||||
Do(ctx) // execute
|
||||
if err != nil {
|
||||
// Handle error
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// searchResult is of type SearchResult and returns hits, suggestions,
|
||||
// and all kinds of other information from Elasticsearch.
|
||||
fmt.Printf("Query took %d milliseconds\n", searchResult.TookInMillis)
|
||||
|
||||
// Each is a convenience function that iterates over hits in a search result.
|
||||
// It makes sure you don't need to check for nil values in the response.
|
||||
// However, it ignores errors in serialization. If you want full control
|
||||
// over iterating the hits, see below.
|
||||
var ttyp Tweet
|
||||
for _, item := range searchResult.Each(reflect.TypeOf(ttyp)) {
|
||||
if t, ok := item.(Tweet); ok {
|
||||
fmt.Printf("Tweet by %s: %s\n", t.User, t.Message)
|
||||
}
|
||||
}
|
||||
// TotalHits is another convenience function that works even when something goes wrong.
|
||||
fmt.Printf("Found a total of %d tweets\n", searchResult.TotalHits())
|
||||
|
||||
// Here's how you iterate through results with full control over each step.
|
||||
if searchResult.Hits.TotalHits > 0 {
|
||||
fmt.Printf("Found a total of %d tweets\n", searchResult.Hits.TotalHits)
|
||||
|
||||
// Iterate through results
|
||||
for _, hit := range searchResult.Hits.Hits {
|
||||
// hit.Index contains the name of the index
|
||||
|
||||
// Deserialize hit.Source into a Tweet (could also be just a map[string]interface{}).
|
||||
var t Tweet
|
||||
err := json.Unmarshal(*hit.Source, &t)
|
||||
if err != nil {
|
||||
// Deserialization failed
|
||||
}
|
||||
|
||||
// Work with tweet
|
||||
fmt.Printf("Tweet by %s: %s\n", t.User, t.Message)
|
||||
}
|
||||
} else {
|
||||
// No hits
|
||||
fmt.Print("Found no tweets\n")
|
||||
}
|
||||
|
||||
// Update a tweet by the update API of Elasticsearch.
|
||||
// We just increment the number of retweets.
|
||||
update, err := client.Update().Index("twitter").Type("tweet").Id("1").
|
||||
Script(elastic.NewScriptInline("ctx._source.retweets += params.num").Lang("painless").Param("num", 1)).
|
||||
Upsert(map[string]interface{}{"retweets": 0}).
|
||||
Do(ctx)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("New version of tweet %q is now %d\n", update.Id, update.Version)
|
||||
|
||||
// ...
|
||||
|
||||
// Delete an index.
|
||||
//deleteIndex, err := client.DeleteIndex("twitter").Do(ctx)
|
||||
//if err != nil {
|
||||
// // Handle error
|
||||
// panic(err)
|
||||
//}
|
||||
//if !deleteIndex.Acknowledged {
|
||||
// // Not acknowledged
|
||||
//}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
package main
|
||||
|
||||
import "gitee.com/johng/gf/g/net/ghttp"
|
||||
|
||||
func main() {
|
||||
s := ghttp.GetServer()
|
||||
s.BindHandler("/:name", func(r *ghttp.Request){
|
||||
r.Response.Writeln("pattern: /:name match")
|
||||
r.Response.Writeln(r.Get("name"))
|
||||
})
|
||||
s.BindHandler("/:name/:action", func(r *ghttp.Request){
|
||||
r.Response.Writeln("pattern: /:name/:action match")
|
||||
r.Response.Writeln(r.Get("name"))
|
||||
r.Response.Writeln(r.Get("action"))
|
||||
})
|
||||
s.BindHandler("/:name/*any", func(r *ghttp.Request){
|
||||
r.Response.Writeln("pattern: /:name/*any match")
|
||||
r.Response.Writeln(r.Get("name"))
|
||||
r.Response.Writeln(r.Get("any"))
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
27
geg/net/ghttp/server/router/router1.go
Normal file
27
geg/net/ghttp/server/router/router1.go
Normal file
@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
"gitee.com/johng/gf/g"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/:name", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Router.Uri)
|
||||
})
|
||||
s.BindHandler("/:name/update", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Router.Uri)
|
||||
})
|
||||
s.BindHandler("/:name/:action", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Router.Uri)
|
||||
})
|
||||
s.BindHandler("/:name/*any", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Router.Uri)
|
||||
})
|
||||
s.BindHandler("/user/list/{page}.html", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Router.Uri)
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
30
geg/net/ghttp/server/router/router2.go
Normal file
30
geg/net/ghttp/server/router/router2.go
Normal file
@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import "gitee.com/johng/gf/g/net/ghttp"
|
||||
|
||||
func main() {
|
||||
s := ghttp.GetServer()
|
||||
s.BindHandler("/user/:name", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Router.Uri)
|
||||
})
|
||||
s.BindHandler("/user/member/:name/*any", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Router.Uri)
|
||||
})
|
||||
s.BindHandler("/user/member/:name/edit/*any", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Router.Uri)
|
||||
})
|
||||
s.BindHandler("/user/member/:name/edit/sex", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Router.Uri)
|
||||
})
|
||||
s.BindHandler("/user/member/:name/edit/info/*any", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Router.Uri)
|
||||
})
|
||||
s.BindHandler("/user/community/female/:name", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Router.Uri)
|
||||
})
|
||||
s.BindHandler("/admin/stats/today/:hour", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Router.Uri)
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
29
geg/net/ghttp/server/router/router3.go
Normal file
29
geg/net/ghttp/server/router/router3.go
Normal file
@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/net/ghttp"
|
||||
"gitee.com/johng/gf/g"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
// 一个简单的分页路由示例
|
||||
s.BindHandler("/user/list/{page}.html", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Get("page"))
|
||||
})
|
||||
// {xxx} 规则与 :xxx 规则混合使用
|
||||
s.BindHandler("/{obj}/:attr/{act}.php", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Get("obj"))
|
||||
r.Response.Writeln(r.Get("attr"))
|
||||
r.Response.Writeln(r.Get("act"))
|
||||
})
|
||||
// 多种模糊匹配规则混合使用
|
||||
s.BindHandler("/{class}-{course}/:name/*act", func(r *ghttp.Request){
|
||||
r.Response.Writeln(r.Get("class"))
|
||||
r.Response.Writeln(r.Get("course"))
|
||||
r.Response.Writeln(r.Get("name"))
|
||||
r.Response.Writeln(r.Get("act"))
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
@ -1,17 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gitee.com/johng/gf/g/container/gpool"
|
||||
"fmt"
|
||||
"time"
|
||||
"gitee.com/johng/gf/g/util/gregex"
|
||||
)
|
||||
|
||||
|
||||
func main() {
|
||||
p := gpool.New(1000)
|
||||
for i := 0 ; i < 100; i++ {
|
||||
p.Put(i)
|
||||
}
|
||||
fmt.Println(p.Size())
|
||||
time.Sleep(2*time.Second)
|
||||
fmt.Println(p.Size())
|
||||
fmt.Println(gregex.Quote(`/user/list/1.html`))
|
||||
}
|
||||
Reference in New Issue
Block a user