mirror of
https://gitee.com/johng/gf
synced 2026-06-07 02:12:11 +08:00
improve router feature for ghttp.Server
This commit is contained in:
@ -1,37 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/container/garray"
|
||||
)
|
||||
|
||||
func MiddlewareAuth(r *ghttp.Request) {
|
||||
token := r.Get("token")
|
||||
if token == "123456" {
|
||||
r.Response.Writeln("auth")
|
||||
r.Middleware.Next()
|
||||
} else {
|
||||
r.Response.WriteStatus(http.StatusForbidden)
|
||||
}
|
||||
}
|
||||
|
||||
func MiddlewareCORS(r *ghttp.Request) {
|
||||
r.Response.Writeln("cors")
|
||||
r.Response.CORSDefault()
|
||||
r.Middleware.Next()
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.Use(MiddlewareCORS)
|
||||
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
|
||||
group.Middleware(MiddlewareAuth)
|
||||
group.ALL("/user/list", func(r *ghttp.Request) {
|
||||
r.Response.Writeln("list")
|
||||
})
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
arr := garray.NewStrArray(false)
|
||||
arr.Unique()
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ We currently accept donation by Alipay/WechatPay, please note your github/gitee
|
||||
|[zhuhuan12](https://gitee.com/zhuhuan12)|gitee|¥50.00 |
|
||||
|[zfan_codes](https://gitee.com/zfan_codes)|gitee|¥10.00 |
|
||||
|[arden](https://github.com/arden)|alipay|¥10.00 |
|
||||
|[macnie](https://www.macnie.com)|wechat|¥100.00 |
|
||||
|[macnie](https://www.macnie.com)|wechat|¥110.00 |
|
||||
|lah|wechat|¥100.00 |
|
||||
|x*z|wechat|¥20.00 |
|
||||
|潘兄|wechat|¥100.00 |
|
||||
|
||||
@ -439,6 +439,9 @@ func (a *SortedArray) SetUnique(unique bool) *SortedArray {
|
||||
func (a *SortedArray) Unique() *SortedArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if len(a.array) == 0 {
|
||||
return a
|
||||
}
|
||||
i := 0
|
||||
for {
|
||||
if i == len(a.array)-1 {
|
||||
|
||||
@ -429,6 +429,10 @@ func (a *SortedIntArray) SetUnique(unique bool) *SortedIntArray {
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *SortedIntArray) Unique() *SortedIntArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if len(a.array) == 0 {
|
||||
return a
|
||||
}
|
||||
i := 0
|
||||
for {
|
||||
if i == len(a.array)-1 {
|
||||
@ -440,7 +444,6 @@ func (a *SortedIntArray) Unique() *SortedIntArray {
|
||||
i++
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
|
||||
@ -414,6 +414,10 @@ func (a *SortedStrArray) SetUnique(unique bool) *SortedStrArray {
|
||||
// Unique uniques the array, clear repeated items.
|
||||
func (a *SortedStrArray) Unique() *SortedStrArray {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if len(a.array) == 0 {
|
||||
return a
|
||||
}
|
||||
i := 0
|
||||
for {
|
||||
if i == len(a.array)-1 {
|
||||
@ -425,7 +429,6 @@ func (a *SortedStrArray) Unique() *SortedStrArray {
|
||||
i++
|
||||
}
|
||||
}
|
||||
a.mu.Unlock()
|
||||
return a
|
||||
}
|
||||
|
||||
|
||||
@ -50,9 +50,7 @@ func niceCallFunc(f func()) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
switch err {
|
||||
case gEXCEPTION_EXIT:
|
||||
fallthrough
|
||||
case gEXCEPTION_EXIT_ALL:
|
||||
case gEXCEPTION_EXIT, gEXCEPTION_EXIT_ALL:
|
||||
return
|
||||
default:
|
||||
panic(err)
|
||||
|
||||
@ -8,12 +8,12 @@ package ghttp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/os/gtime"
|
||||
"github.com/gogf/gf/util/grand"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@ -45,22 +45,21 @@ func (f *UploadFile) Save(path string, randomlyRename ...bool) error {
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var newFile *os.File
|
||||
filePath := path
|
||||
if gfile.IsDir(path) {
|
||||
filename := gfile.Basename(f.Filename)
|
||||
if len(randomlyRename) > 0 && randomlyRename[0] {
|
||||
filename = strings.ToLower(strconv.FormatInt(gtime.TimestampNano(), 36) + grand.S(6))
|
||||
filename = filename + gfile.Ext(f.Filename)
|
||||
}
|
||||
newFile, err = gfile.Create(gfile.Join(path, filename))
|
||||
} else {
|
||||
newFile, err = gfile.Create(path)
|
||||
filePath = gfile.Join(path, filename)
|
||||
}
|
||||
newFile, err := gfile.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer newFile.Close()
|
||||
|
||||
intlog.Printf(`save upload file: %s`, filePath)
|
||||
if _, err := io.Copy(newFile, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -27,6 +27,20 @@ type CORSOptions struct {
|
||||
AllowHeaders string // Access-Control-Allow-Headers
|
||||
}
|
||||
|
||||
var (
|
||||
// defaultAllowHeaders is the default allowed headers for CORS.
|
||||
// It's defined as map for better header key searching performance.
|
||||
defaultAllowHeaders = map[string]struct{}{
|
||||
"Origin": {},
|
||||
"Accept": {},
|
||||
"Cookie": {},
|
||||
"Authorization": {},
|
||||
"X-Auth-Token": {},
|
||||
"X-Requested-With": {},
|
||||
"Content-Type": {},
|
||||
}
|
||||
)
|
||||
|
||||
// DefaultCORSOptions returns the default CORS options,
|
||||
// which allows any cross-domain request.
|
||||
func (r *Response) DefaultCORSOptions() CORSOptions {
|
||||
@ -34,9 +48,24 @@ func (r *Response) DefaultCORSOptions() CORSOptions {
|
||||
AllowOrigin: "*",
|
||||
AllowMethods: HTTP_METHODS,
|
||||
AllowCredentials: "true",
|
||||
AllowHeaders: "Origin,Content-Type,Accept,User-Agent,Cookie,Authorization,X-Auth-Token,X-Requested-With",
|
||||
MaxAge: 3628800,
|
||||
}
|
||||
// Allow all client's custom headers in default.
|
||||
if headers := r.Request.Header.Get("Access-Control-Request-Headers"); headers != "" {
|
||||
array := gstr.SplitAndTrim(headers, ",")
|
||||
for _, header := range array {
|
||||
if _, ok := defaultAllowHeaders[header]; !ok {
|
||||
options.AllowHeaders += header + ","
|
||||
}
|
||||
}
|
||||
for header, _ := range defaultAllowHeaders {
|
||||
if len(options.AllowHeaders) > 0 {
|
||||
options.AllowHeaders += ","
|
||||
}
|
||||
options.AllowHeaders += header
|
||||
}
|
||||
}
|
||||
// Allow all anywhere origin in default.
|
||||
if origin := r.Request.Header.Get("Origin"); origin != "" {
|
||||
options.AllowOrigin = origin
|
||||
} else if referer := r.Request.Referer(); referer != "" {
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
@ -78,7 +79,6 @@ type (
|
||||
// handlerItem is the registered handler for route handling,
|
||||
// including middleware and hook functions.
|
||||
handlerItem struct {
|
||||
itemId int // Unique ID mark.
|
||||
itemName string // Handler name, which is automatically retrieved from runtime stack when registered.
|
||||
itemType int // Handler type: object/handler/controller/middleware/hook.
|
||||
itemFunc HandlerFunc // Handler address.
|
||||
@ -143,7 +143,7 @@ var (
|
||||
// it is used for quick HTTP method searching using map.
|
||||
methodsMap = make(map[string]struct{})
|
||||
|
||||
// serverMapping stores more than one server instances.
|
||||
// serverMapping stores more than one server instances for current process.
|
||||
// The key is the name of the server, and the value is its instance.
|
||||
serverMapping = gmap.NewStrAnyMap(true)
|
||||
|
||||
@ -444,14 +444,20 @@ func (s *Server) GetRouterArray() []RouterItem {
|
||||
}
|
||||
|
||||
// Run starts server listening in blocking way.
|
||||
// It's commonly used for single server situation.
|
||||
func (s *Server) Run() {
|
||||
if err := s.Start(); err != nil {
|
||||
s.Logger().Fatal(err)
|
||||
}
|
||||
|
||||
// Blocking using channel.
|
||||
<-s.closeChan
|
||||
|
||||
// Remove plugins.
|
||||
if len(s.plugins) > 0 {
|
||||
for _, p := range s.plugins {
|
||||
intlog.Printf(`remove plugin: %s`, p.Name())
|
||||
p.Remove()
|
||||
}
|
||||
}
|
||||
s.Logger().Printf("[ghttp] %d: all servers shutdown", gproc.Pid())
|
||||
}
|
||||
|
||||
@ -459,7 +465,17 @@ func (s *Server) Run() {
|
||||
// It's commonly used in multiple servers situation.
|
||||
func Wait() {
|
||||
<-allDoneChan
|
||||
|
||||
// Remove plugins.
|
||||
serverMapping.Iterator(func(k string, v interface{}) bool {
|
||||
s := v.(*Server)
|
||||
if len(s.plugins) > 0 {
|
||||
for _, p := range s.plugins {
|
||||
intlog.Printf(`remove plugin: %s`, p.Name())
|
||||
p.Remove()
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
glog.Printf("[ghttp] %d: all servers shutdown", gproc.Pid())
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ package ghttp
|
||||
type Plugin interface {
|
||||
Name() string // Name returns the name of the plugin.
|
||||
Author() string // Author returns the author of the plugin.
|
||||
Version() string // Version returns the version of the plugin.
|
||||
Version() string // Version returns the version of the plugin, like "v1.0.0".
|
||||
Description() string // Description returns the description of the plugin.
|
||||
Install(s *Server) error // Install installs the plugin before server starts.
|
||||
Remove() error // Remove removes the plugin.
|
||||
|
||||
@ -9,7 +9,7 @@ package ghttp
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/util/gutil"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
@ -23,12 +23,12 @@ const (
|
||||
gFILTER_KEY = "/net/ghttp/ghttp"
|
||||
)
|
||||
|
||||
var (
|
||||
// 用于服务函数的ID生成变量
|
||||
handlerIdGenerator = gtype.NewInt()
|
||||
)
|
||||
// handlerKey creates and returns an unique router key for given parameters.
|
||||
func (s *Server) handlerKey(hook, method, path, domain string) string {
|
||||
return hook + "%" + s.serveHandlerKey(method, path, domain)
|
||||
}
|
||||
|
||||
// 解析pattern
|
||||
// parsePattern parses the given pattern to domain, method and path variable.
|
||||
func (s *Server) parsePattern(pattern string) (domain, method, path string, err error) {
|
||||
path = strings.TrimSpace(pattern)
|
||||
domain = gDEFAULT_DOMAIN
|
||||
@ -48,18 +48,17 @@ func (s *Server) parsePattern(pattern string) (domain, method, path string, err
|
||||
if path == "" {
|
||||
err = errors.New("invalid pattern: URI should not be empty")
|
||||
}
|
||||
// 去掉末尾的"/"符号,与路由匹配时处理一致
|
||||
if path != "/" {
|
||||
path = strings.TrimRight(path, "/")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 路由注册处理方法。
|
||||
// 非叶节点为哈希表检索节点,按照URI注册的层级进行高效检索,直至到叶子链表节点;
|
||||
// 叶子节点是链表,按照优先级进行排序,优先级高的排前面,按照遍历检索,按照哈希表层级检索后的叶子链表数据量不会很大,所以效率比较高;
|
||||
// setHandler creates router item with given handler and pattern and registers the handler to the router tree.
|
||||
// The router tree can be treated as a multilayer hash table, please refer to the comment in following codes.
|
||||
// This function is called during server starts up, which cares little about the performance. What really cares
|
||||
// is the well designed router storage structure for router searching when the request is under serving.
|
||||
func (s *Server) setHandler(pattern string, handler *handlerItem) {
|
||||
handler.itemId = handlerIdGenerator.Add(1)
|
||||
domain, method, uri, err := s.parsePattern(pattern)
|
||||
if err != nil {
|
||||
s.Logger().Fatal("invalid pattern:", pattern, err)
|
||||
@ -69,7 +68,8 @@ func (s *Server) setHandler(pattern string, handler *handlerItem) {
|
||||
s.Logger().Fatal("invalid pattern:", pattern, "URI should lead with '/'")
|
||||
return
|
||||
}
|
||||
// 注册地址记录及重复注册判断
|
||||
|
||||
// Repeated router checks, this feature can be disabled by server configuration.
|
||||
regKey := s.handlerKey(handler.hookName, method, uri, domain)
|
||||
if !s.config.RouteOverWrite {
|
||||
switch handler.itemType {
|
||||
@ -80,11 +80,11 @@ func (s *Server) setHandler(pattern string, handler *handlerItem) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 注册的路由信息对象
|
||||
// Create a new router by given parameter.
|
||||
handler.router = &Router{
|
||||
Uri: uri,
|
||||
Domain: domain,
|
||||
Method: method,
|
||||
Method: strings.ToUpper(method),
|
||||
Priority: strings.Count(uri[1:], "/"),
|
||||
}
|
||||
handler.router.RegRule, handler.router.RegNames = s.patternToRegRule(uri)
|
||||
@ -92,7 +92,8 @@ func (s *Server) setHandler(pattern string, handler *handlerItem) {
|
||||
if _, ok := s.serveTree[domain]; !ok {
|
||||
s.serveTree[domain] = make(map[string]interface{})
|
||||
}
|
||||
// 当前节点的规则链表
|
||||
// List array, very important for router register.
|
||||
// There may be multiple lists adding into this array when searching from root to leaf.
|
||||
lists := make([]*glist.List, 0)
|
||||
array := ([]string)(nil)
|
||||
if strings.EqualFold("/", uri) {
|
||||
@ -100,9 +101,57 @@ func (s *Server) setHandler(pattern string, handler *handlerItem) {
|
||||
} else {
|
||||
array = strings.Split(uri[1:], "/")
|
||||
}
|
||||
// 键名"*fuzz"代表当前节点为模糊匹配节点,该节点也会有一个*list链表;
|
||||
// 键名"*list"代表链表,叶子节点和模糊匹配节点都有该属性,优先级越高越排前;
|
||||
// Multilayer hash table:
|
||||
// 1. Each node of the table is separated by URI path which is split by char '/'.
|
||||
// 2. The key "*fuzz" specifies this node is a fuzzy node, which has no certain name.
|
||||
// 3. The key "*list" is the list item of the node, MOST OF THE NODES HAVE THIS ITEM,
|
||||
// especially the fuzzy node. NOTE THAT the fuzzy node must have the "*list" item,
|
||||
// and the leaf node also has "*list" item. If the node is not a fuzzy node either
|
||||
// a leaf, it neither has "*list" item.
|
||||
// 2. The "*list" item is a list containing registered router items ordered by their
|
||||
// priorities from high to low.
|
||||
// 3. There may be repeated router items in the router lists. The lists' priorities
|
||||
// from root to leaf are from low to high.
|
||||
p := s.serveTree[domain]
|
||||
for i, part := range array {
|
||||
// Ignore empty URI part, like: /user//index
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
// Check if it's a fuzzy node.
|
||||
if gregex.IsMatchString(`^[:\*]|\{[\w\.\-]+\}|\*`, part) {
|
||||
part = "*fuzz"
|
||||
// If it's a fuzzy node, it creates a "*list" item - which is a list - in the hash map.
|
||||
// All the sub router items from this fuzzy node will also be added to its "*list" item.
|
||||
if v, ok := p.(map[string]interface{})["*list"]; !ok {
|
||||
newListForFuzzy := glist.New()
|
||||
p.(map[string]interface{})["*list"] = newListForFuzzy
|
||||
lists = append(lists, newListForFuzzy)
|
||||
} else {
|
||||
lists = append(lists, v.(*glist.List))
|
||||
}
|
||||
}
|
||||
// Make a new bucket for current node.
|
||||
if _, ok := p.(map[string]interface{})[part]; !ok {
|
||||
p.(map[string]interface{})[part] = make(map[string]interface{})
|
||||
}
|
||||
// Loop to next bucket.
|
||||
p = p.(map[string]interface{})[part]
|
||||
// The leaf is a hash map and must have an item named "*list", which contains the router item.
|
||||
// The leaf can be furthermore extended by adding more ket-value pairs into its map.
|
||||
// Note that the `v != "*fuzz"` comparison is required as the list might be added in the former
|
||||
// fuzzy checks.
|
||||
if i == len(array)-1 && part != "*fuzz" {
|
||||
if v, ok := p.(map[string]interface{})["*list"]; !ok {
|
||||
list := glist.New()
|
||||
p.(map[string]interface{})["*list"] = list
|
||||
lists = append(lists, list)
|
||||
} else {
|
||||
lists = append(lists, v.(*glist.List))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range array {
|
||||
if len(v) == 0 {
|
||||
continue
|
||||
@ -135,8 +184,8 @@ func (s *Server) setHandler(pattern string, handler *handlerItem) {
|
||||
}
|
||||
}
|
||||
|
||||
// 上面循环后得到的lists是该路由规则一路匹配下来相关的模糊匹配链表(注意不是这棵树所有的链表)。
|
||||
// 下面从头开始遍历每个节点的模糊匹配链表,将该路由项插入进去(按照优先级高的放在lists链表的前面)
|
||||
// It iterates the list array of <lists>, compares priorities and inserts the new router item in
|
||||
// the proper position of each list. The priority of the list is ordered from high to low.
|
||||
item := (*handlerItem)(nil)
|
||||
for _, l := range lists {
|
||||
pushed := false
|
||||
@ -173,6 +222,7 @@ func (s *Server) setHandler(pattern string, handler *handlerItem) {
|
||||
// Append the route.
|
||||
s.routesMap[regKey] = append(s.routesMap[regKey], routeItem)
|
||||
}
|
||||
gutil.Dump(s.serveTree)
|
||||
}
|
||||
|
||||
// 对比两个handlerItem的优先级,需要非常注意的是,注意新老对比项的参数先后顺序。
|
||||
@ -312,7 +362,7 @@ func (s *Server) patternToRegRule(rule string) (regrule string, names []string)
|
||||
regrule += `/{0,1}.*`
|
||||
}
|
||||
default:
|
||||
// 特殊字符替换
|
||||
// Special chars replacement.
|
||||
v = gstr.ReplaceByMap(v, map[string]string{
|
||||
`.`: `\.`,
|
||||
`+`: `\+`,
|
||||
|
||||
@ -66,8 +66,3 @@ func (s *Server) niceCallHookHandler(f HandlerFunc, r *Request) (err interface{}
|
||||
f(r)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成hook key,如果是hook key,那么使用'%'符号分隔
|
||||
func (s *Server) handlerKey(hook, method, path, domain string) string {
|
||||
return hook + "%" + s.serveHandlerKey(method, path, domain)
|
||||
}
|
||||
|
||||
@ -15,15 +15,14 @@ import (
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
)
|
||||
|
||||
// 缓存数据项
|
||||
// handlerCacheItem is a item for router cache.
|
||||
type handlerCacheItem struct {
|
||||
parsedItems []*handlerParsedItem
|
||||
hasHook bool
|
||||
hasServe bool
|
||||
}
|
||||
|
||||
// 查询请求处理方法.
|
||||
// 内部带锁机制,可以并发读,但是不能并发写;并且有缓存机制,按照Host、Method、Path进行缓存.
|
||||
// getHandlersWithCache searches the router item with cache feature for given request.
|
||||
func (s *Server) getHandlersWithCache(r *Request) (parsedItems []*handlerParsedItem, hasHook, hasServe bool) {
|
||||
value := s.serveCache.GetOrSetFunc(s.serveHandlerKey(r.Method, r.URL.Path, r.GetHost()), func() interface{} {
|
||||
parsedItems, hasHook, hasServe = s.searchHandlers(r.Method, r.URL.Path, r.GetHost())
|
||||
@ -39,18 +38,19 @@ func (s *Server) getHandlersWithCache(r *Request) (parsedItems []*handlerParsedI
|
||||
return
|
||||
}
|
||||
|
||||
// 路由注册方法检索,返回所有该路由的注册函数,构造成数组返回
|
||||
// searchHandlers retrieves and returns the routers with given parameters.
|
||||
// Note that the returned routers contain serving handler, middleware handlers and hook handlers.
|
||||
func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*handlerParsedItem, hasHook, hasServe bool) {
|
||||
if len(path) == 0 {
|
||||
return nil, false, false
|
||||
}
|
||||
// 遍历检索的域名列表,优先遍历默认域名
|
||||
// Default domain has the most priority when iteration.
|
||||
domains := []string{gDEFAULT_DOMAIN}
|
||||
if !strings.EqualFold(gDEFAULT_DOMAIN, domain) {
|
||||
domains = append(domains, domain)
|
||||
}
|
||||
// URL.Path层级拆分
|
||||
array := ([]string)(nil)
|
||||
// Split the URL.path to separate parts.
|
||||
var array []string
|
||||
if strings.EqualFold("/", path) {
|
||||
array = []string{"/"}
|
||||
} else {
|
||||
@ -58,42 +58,40 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han
|
||||
}
|
||||
parsedItemList := glist.New()
|
||||
lastMiddlewareElem := (*glist.Element)(nil)
|
||||
repeatHandlerCheckMap := make(map[int]struct{})
|
||||
for _, domain := range domains {
|
||||
p, ok := s.serveTree[domain]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// 多层链表(每个节点都有一个*list链表)的目的是当叶子节点未有任何规则匹配时,让父级模糊匹配规则继续处理
|
||||
lists := make([]*glist.List, 0, 16)
|
||||
for k, v := range array {
|
||||
for i, part := range array {
|
||||
// In case of double '/' URI, eg: /user//index
|
||||
if v == "" {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := p.(map[string]interface{})["*list"]; ok {
|
||||
lists = append(lists, p.(map[string]interface{})["*list"].(*glist.List))
|
||||
if v, ok := p.(map[string]interface{})["*list"]; ok {
|
||||
lists = append(lists, v.(*glist.List))
|
||||
}
|
||||
if _, ok := p.(map[string]interface{})[v]; ok {
|
||||
p = p.(map[string]interface{})[v]
|
||||
if k == len(array)-1 {
|
||||
if _, ok := p.(map[string]interface{})["*list"]; ok {
|
||||
lists = append(lists, p.(map[string]interface{})["*list"].(*glist.List))
|
||||
if _, ok := p.(map[string]interface{})[part]; ok {
|
||||
p = p.(map[string]interface{})[part]
|
||||
if i == len(array)-1 {
|
||||
if v, ok := p.(map[string]interface{})["*list"]; ok {
|
||||
lists = append(lists, v.(*glist.List))
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, ok := p.(map[string]interface{})["*fuzz"]; ok {
|
||||
p = p.(map[string]interface{})["*fuzz"]
|
||||
if v, ok := p.(map[string]interface{})["*fuzz"]; ok {
|
||||
p = v
|
||||
}
|
||||
}
|
||||
// 如果是叶子节点,同时判断当前层级的"*fuzz"键名,解决例如:/user/*action 匹配 /user 的规则
|
||||
if k == len(array)-1 {
|
||||
if _, ok := p.(map[string]interface{})["*fuzz"]; ok {
|
||||
p = p.(map[string]interface{})["*fuzz"]
|
||||
if i == len(array)-1 {
|
||||
if v, ok := p.(map[string]interface{})["*fuzz"]; ok {
|
||||
p = v
|
||||
}
|
||||
if _, ok := p.(map[string]interface{})["*list"]; ok {
|
||||
lists = append(lists, p.(map[string]interface{})["*list"].(*glist.List))
|
||||
if v, ok := p.(map[string]interface{})["*list"]; ok {
|
||||
lists = append(lists, v.(*glist.List))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -102,12 +100,6 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han
|
||||
for i := len(lists) - 1; i >= 0; i-- {
|
||||
for e := lists[i].Front(); e != nil; e = e.Next() {
|
||||
item := e.Value.(*handlerItem)
|
||||
// 主要是用于路由注册函数的重复添加判断(特别是中间件和钩子函数)
|
||||
if _, ok := repeatHandlerCheckMap[item.itemId]; ok {
|
||||
continue
|
||||
} else {
|
||||
repeatHandlerCheckMap[item.itemId] = struct{}{}
|
||||
}
|
||||
// 服务路由函数只能添加一次,将重复判断放在这里提高检索效率
|
||||
if hasServe {
|
||||
switch item.itemType {
|
||||
@ -115,8 +107,7 @@ func (s *Server) searchHandlers(method, path, domain string) (parsedItems []*han
|
||||
continue
|
||||
}
|
||||
}
|
||||
// 动态匹配规则带有gDEFAULT_METHOD的情况,不会像静态规则那样直接解析为所有的HTTP METHOD存储
|
||||
if strings.EqualFold(item.router.Method, gDEFAULT_METHOD) || strings.EqualFold(item.router.Method, method) {
|
||||
if item.router.Method == gDEFAULT_METHOD || item.router.Method == method {
|
||||
// 注意当不带任何动态路由规则时,len(match) == 1
|
||||
if match, err := gregex.MatchString(item.router.RegRule, path); err == nil && len(match) > 0 {
|
||||
parsedItem := &handlerParsedItem{item, nil}
|
||||
@ -207,7 +198,7 @@ func (item *handlerParsedItem) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(item.handler)
|
||||
}
|
||||
|
||||
// 生成回调方法查询的Key
|
||||
// serveHandlerKey creates and returns a cache key for router.
|
||||
func (s *Server) serveHandlerKey(method, path, domain string) string {
|
||||
if len(domain) > 0 {
|
||||
domain = "@" + domain
|
||||
|
||||
@ -46,14 +46,6 @@ func (c *ControllerRest) Delete() {
|
||||
c.Response.Write("Controller Delete")
|
||||
}
|
||||
|
||||
func (c *ControllerRest) Patch() {
|
||||
c.Response.Write("Controller Patch")
|
||||
}
|
||||
|
||||
func (c *ControllerRest) Options() {
|
||||
c.Response.Write("Controller Options")
|
||||
}
|
||||
|
||||
func (c *ControllerRest) Head() {
|
||||
c.Response.Header().Set("head-ok", "1")
|
||||
}
|
||||
@ -78,8 +70,6 @@ func Test_Router_ControllerRest(t *testing.T) {
|
||||
gtest.Assert(client.PutContent("/"), "1Controller Put2")
|
||||
gtest.Assert(client.PostContent("/"), "1Controller Post2")
|
||||
gtest.Assert(client.DeleteContent("/"), "1Controller Delete2")
|
||||
gtest.Assert(client.PatchContent("/"), "1Controller Patch2")
|
||||
gtest.Assert(client.OptionsContent("/"), "1Controller Options2")
|
||||
resp1, err := client.Head("/")
|
||||
if err == nil {
|
||||
defer resp1.Close()
|
||||
@ -91,8 +81,6 @@ func Test_Router_ControllerRest(t *testing.T) {
|
||||
gtest.Assert(client.PutContent("/controller-rest/put"), "1Controller Put2")
|
||||
gtest.Assert(client.PostContent("/controller-rest/post"), "1Controller Post2")
|
||||
gtest.Assert(client.DeleteContent("/controller-rest/delete"), "1Controller Delete2")
|
||||
gtest.Assert(client.PatchContent("/controller-rest/patch"), "1Controller Patch2")
|
||||
gtest.Assert(client.OptionsContent("/controller-rest/options"), "1Controller Options2")
|
||||
resp2, err := client.Head("/controller-rest/head")
|
||||
if err == nil {
|
||||
defer resp2.Close()
|
||||
|
||||
@ -50,6 +50,7 @@ func Test_Router_Hook_Fuzzy_Router(t *testing.T) {
|
||||
pattern1 := "/:name/info"
|
||||
s.BindHookHandlerByMap(pattern1, map[string]ghttp.HandlerFunc{
|
||||
ghttp.HOOK_BEFORE_SERVE: func(r *ghttp.Request) {
|
||||
fmt.Println("called")
|
||||
r.SetParam("uid", i)
|
||||
i++
|
||||
},
|
||||
@ -58,19 +59,19 @@ func Test_Router_Hook_Fuzzy_Router(t *testing.T) {
|
||||
r.Response.Write(r.Get("uid"))
|
||||
})
|
||||
|
||||
pattern2 := "/{object}/list/{page}.java"
|
||||
s.BindHookHandlerByMap(pattern2, map[string]ghttp.HandlerFunc{
|
||||
ghttp.HOOK_BEFORE_OUTPUT: func(r *ghttp.Request) {
|
||||
r.Response.SetBuffer([]byte(
|
||||
fmt.Sprint(r.Get("object"), "&", r.Get("page"), "&", i),
|
||||
))
|
||||
},
|
||||
})
|
||||
s.BindHandler(pattern2, func(r *ghttp.Request) {
|
||||
r.Response.Write(r.Router.Uri)
|
||||
})
|
||||
//pattern2 := "/{object}/list/{page}.java"
|
||||
//s.BindHookHandlerByMap(pattern2, map[string]ghttp.HandlerFunc{
|
||||
// ghttp.HOOK_BEFORE_OUTPUT: func(r *ghttp.Request) {
|
||||
// r.Response.SetBuffer([]byte(
|
||||
// fmt.Sprint(r.Get("object"), "&", r.Get("page"), "&", i),
|
||||
// ))
|
||||
// },
|
||||
//})
|
||||
//s.BindHandler(pattern2, func(r *ghttp.Request) {
|
||||
// r.Response.Write(r.Router.Uri)
|
||||
//})
|
||||
s.SetPort(p)
|
||||
s.SetDumpRouterMap(false)
|
||||
//s.SetDumpRouterMap(false)
|
||||
s.Start()
|
||||
defer s.Shutdown()
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ package gfile
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
@ -58,7 +59,9 @@ func Mkdir(path string) error {
|
||||
func Create(path string) (*os.File, error) {
|
||||
dir := Dir(path)
|
||||
if !Exists(dir) {
|
||||
Mkdir(dir)
|
||||
if err := Mkdir(dir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return os.Create(path)
|
||||
}
|
||||
@ -93,7 +96,14 @@ func OpenWithFlagPerm(path string, flag int, perm os.FileMode) (*os.File, error)
|
||||
|
||||
// Join joins string array paths with file separator of current system.
|
||||
func Join(paths ...string) string {
|
||||
return strings.Join(paths, Separator)
|
||||
var s string
|
||||
for _, path := range paths {
|
||||
if s != "" {
|
||||
s += Separator
|
||||
}
|
||||
s += gstr.TrimRight(path, Separator)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Exists checks whether given <path> exist.
|
||||
|
||||
@ -14,7 +14,7 @@ import (
|
||||
var (
|
||||
regexMu = sync.RWMutex{}
|
||||
// Cache for regex object.
|
||||
// TODO There's no expiring logic for this map.
|
||||
// Note that there's no expiring logic for this map.
|
||||
regexMap = make(map[string]*regexp.Regexp)
|
||||
)
|
||||
|
||||
@ -22,29 +22,25 @@ var (
|
||||
// It uses cache to enhance the performance for compiling regular expression pattern,
|
||||
// which means, it will return the same *regexp.Regexp object with the same regular
|
||||
// expression pattern.
|
||||
func getRegexp(pattern string) (*regexp.Regexp, error) {
|
||||
if r := getCache(pattern); r != nil {
|
||||
return r, nil
|
||||
}
|
||||
if r, err := regexp.Compile(pattern); err == nil {
|
||||
setCache(pattern, r)
|
||||
return r, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// getCache returns *regexp.Regexp object from cache by given <pattern>, for internal usage.
|
||||
func getCache(pattern string) (regex *regexp.Regexp) {
|
||||
//
|
||||
// It is concurrent-safe for multiple goroutines.
|
||||
func getRegexp(pattern string) (regex *regexp.Regexp, err error) {
|
||||
// Retrieve the regular expression object using reading lock.
|
||||
regexMu.RLock()
|
||||
regex = regexMap[pattern]
|
||||
regexMu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// setCache stores *regexp.Regexp object into cache, for internal usage.
|
||||
func setCache(pattern string, regex *regexp.Regexp) {
|
||||
if regex != nil {
|
||||
return
|
||||
}
|
||||
// If it does not exist in the cache,
|
||||
// it compiles the pattern and creates one.
|
||||
regex, err = regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Cache the result object using writing lock.
|
||||
regexMu.Lock()
|
||||
regexMap[pattern] = regex
|
||||
regexMu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ package gpage
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
url2 "net/url"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
@ -19,30 +19,27 @@ import (
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
// 分页对象
|
||||
// Page is the pagination implementer.
|
||||
type Page struct {
|
||||
Url *url2.URL // 当前页面的URL对象
|
||||
Router *ghttp.Router // 当前页面的路由对象(与gf框架耦合,在静态分页下有效)
|
||||
UrlTemplate string // URL生成规则,内部可使用{.page}变量指定页码
|
||||
TotalSize int // 总共数据条数
|
||||
TotalPage int // 总页数
|
||||
CurrentPage int // 当前页码
|
||||
PageName string // 分页参数名称(GET参数)
|
||||
NextPageTag string // 下一页标签
|
||||
PrevPageTag string // 上一页标签
|
||||
FirstPageTag string // 首页标签
|
||||
LastPageTag string // 尾页标签
|
||||
PrevBar string // 上一分页条
|
||||
NextBar string // 下一分页条
|
||||
PageBarNum int // 控制分页条的数量
|
||||
AjaxActionName string // AJAX方法名,当该属性有值时,表示使用AJAX分页
|
||||
UrlTemplate string // Custom url template for page url producing.
|
||||
TotalSize int // Total size.
|
||||
TotalPage int // Total page, which is automatically calculated.
|
||||
CurrentPage int // Current page number >= 1.
|
||||
PageName string // Page variable name. It's "page" in default.
|
||||
NextPageTag string // Tag name for next p.
|
||||
PrevPageTag string // Tag name for prev p.
|
||||
FirstPageTag string // Tag name for first p.
|
||||
LastPageTag string // Tag name for last p.
|
||||
PrevBar string // Tag string for prev bar.
|
||||
NextBar string // Tag string for next bar.
|
||||
PageBarNum int // Page bar number for displaying.
|
||||
AjaxActionName string // Ajax function name. Ajax is enabled if this attribute is not empty.
|
||||
}
|
||||
|
||||
// 创建一个分页对象,输入参数分别为:
|
||||
// 总数量、每页数量、当前页码、当前的URL(URI+QUERY)、(可选)路由规则(例如: /user/list/:page、/order/list/*page、/order/list/{page}.html)
|
||||
func New(TotalSize, perPage int, CurrentPage interface{}, url string, router ...*ghttp.Router) *Page {
|
||||
u, _ := url2.Parse(url)
|
||||
page := &Page{
|
||||
func New(totalSize, pageSize, currentPage int, urlTemplate string) *Page {
|
||||
p := &Page{
|
||||
PageName: "page",
|
||||
PrevPageTag: "<",
|
||||
NextPageTag: ">",
|
||||
@ -50,34 +47,20 @@ func New(TotalSize, perPage int, CurrentPage interface{}, url string, router ...
|
||||
LastPageTag: ">|",
|
||||
PrevBar: "<<",
|
||||
NextBar: ">>",
|
||||
TotalSize: TotalSize,
|
||||
TotalPage: int(math.Ceil(float64(TotalSize) / float64(perPage))),
|
||||
TotalSize: totalSize,
|
||||
TotalPage: int(math.Ceil(float64(totalSize) / float64(pageSize))),
|
||||
CurrentPage: 1,
|
||||
PageBarNum: 10,
|
||||
Url: u,
|
||||
UrlTemplate: urlTemplate,
|
||||
}
|
||||
curPage := gconv.Int(CurrentPage)
|
||||
if curPage > 0 {
|
||||
page.CurrentPage = curPage
|
||||
if currentPage > 0 {
|
||||
p.CurrentPage = currentPage
|
||||
}
|
||||
if len(router) > 0 {
|
||||
page.Router = router[0]
|
||||
}
|
||||
return page
|
||||
}
|
||||
|
||||
// 启用AJAX分页
|
||||
func (page *Page) EnableAjax(actionName string) {
|
||||
page.AjaxActionName = actionName
|
||||
}
|
||||
|
||||
// 设置URL生成规则模板,模板中可使用{.page}变量指定页码位置
|
||||
func (page *Page) SetUrlTemplate(template string) {
|
||||
page.UrlTemplate = template
|
||||
return p
|
||||
}
|
||||
|
||||
// 获取显示"下一页"的内容.
|
||||
func (page *Page) NextPage(styles ...string) string {
|
||||
func (p *Page) NextPage(styles ...string) string {
|
||||
var curStyle, style string
|
||||
if len(styles) > 0 {
|
||||
curStyle = styles[0]
|
||||
@ -85,14 +68,14 @@ func (page *Page) NextPage(styles ...string) string {
|
||||
if len(styles) > 1 {
|
||||
style = styles[0]
|
||||
}
|
||||
if page.CurrentPage < page.TotalPage {
|
||||
return page.GetLink(page.GetUrl(page.CurrentPage+1), page.NextPageTag, "下一页", style)
|
||||
if p.CurrentPage < p.TotalPage {
|
||||
return p.GetLink(p.GetUrl(p.CurrentPage+1), p.NextPageTag, "下一页", style)
|
||||
}
|
||||
return fmt.Sprintf(`<span class="%s">%s</span>`, curStyle, page.NextPageTag)
|
||||
return fmt.Sprintf(`<span class="%s">%s</span>`, curStyle, p.NextPageTag)
|
||||
}
|
||||
|
||||
// 获取显示“上一页”的内容
|
||||
func (page *Page) PrevPage(styles ...string) string {
|
||||
func (p *Page) PrevPage(styles ...string) string {
|
||||
var curStyle, style string
|
||||
if len(styles) > 0 {
|
||||
curStyle = styles[0]
|
||||
@ -100,14 +83,14 @@ func (page *Page) PrevPage(styles ...string) string {
|
||||
if len(styles) > 1 {
|
||||
style = styles[0]
|
||||
}
|
||||
if page.CurrentPage > 1 {
|
||||
return page.GetLink(page.GetUrl(page.CurrentPage-1), page.PrevPageTag, "上一页", style)
|
||||
if p.CurrentPage > 1 {
|
||||
return p.GetLink(p.GetUrl(p.CurrentPage-1), p.PrevPageTag, "上一页", style)
|
||||
}
|
||||
return fmt.Sprintf(`<span class="%s">%s</span>`, curStyle, page.PrevPageTag)
|
||||
return fmt.Sprintf(`<span class="%s">%s</span>`, curStyle, p.PrevPageTag)
|
||||
}
|
||||
|
||||
// 获取显示“首页”的代码
|
||||
func (page *Page) FirstPage(styles ...string) string {
|
||||
func (p *Page) FirstPage(styles ...string) string {
|
||||
var curStyle, style string
|
||||
if len(styles) > 0 {
|
||||
curStyle = styles[0]
|
||||
@ -115,14 +98,14 @@ func (page *Page) FirstPage(styles ...string) string {
|
||||
if len(styles) > 1 {
|
||||
style = styles[0]
|
||||
}
|
||||
if page.CurrentPage == 1 {
|
||||
return fmt.Sprintf(`<span class="%s">%s</span>`, curStyle, page.FirstPageTag)
|
||||
if p.CurrentPage == 1 {
|
||||
return fmt.Sprintf(`<span class="%s">%s</span>`, curStyle, p.FirstPageTag)
|
||||
}
|
||||
return page.GetLink(page.GetUrl(1), page.FirstPageTag, "第一页", style)
|
||||
return p.GetLink(p.GetUrl(1), p.FirstPageTag, "第一页", style)
|
||||
}
|
||||
|
||||
// 获取显示“尾页”的内容
|
||||
func (page *Page) LastPage(styles ...string) string {
|
||||
func (p *Page) LastPage(styles ...string) string {
|
||||
var curStyle, style string
|
||||
if len(styles) > 0 {
|
||||
curStyle = styles[0]
|
||||
@ -130,14 +113,14 @@ func (page *Page) LastPage(styles ...string) string {
|
||||
if len(styles) > 1 {
|
||||
style = styles[0]
|
||||
}
|
||||
if page.CurrentPage == page.TotalPage {
|
||||
return fmt.Sprintf(`<span class="%s">%s</span>`, curStyle, page.LastPageTag)
|
||||
if p.CurrentPage == p.TotalPage {
|
||||
return fmt.Sprintf(`<span class="%s">%s</span>`, curStyle, p.LastPageTag)
|
||||
}
|
||||
return page.GetLink(page.GetUrl(page.TotalPage), page.LastPageTag, "最后页", style)
|
||||
return p.GetLink(p.GetUrl(p.TotalPage), p.LastPageTag, "最后页", style)
|
||||
}
|
||||
|
||||
// 获得分页条列表内容
|
||||
func (page *Page) PageBar(styles ...string) string {
|
||||
func (p *Page) PageBar(styles ...string) string {
|
||||
var curStyle, style string
|
||||
if len(styles) > 0 {
|
||||
curStyle = styles[0]
|
||||
@ -145,19 +128,19 @@ func (page *Page) PageBar(styles ...string) string {
|
||||
if len(styles) > 1 {
|
||||
style = styles[0]
|
||||
}
|
||||
plus := int(math.Ceil(float64(page.PageBarNum / 2)))
|
||||
if page.PageBarNum-plus+page.CurrentPage > page.TotalPage {
|
||||
plus = page.PageBarNum - page.TotalPage + page.CurrentPage
|
||||
plus := int(math.Ceil(float64(p.PageBarNum / 2)))
|
||||
if p.PageBarNum-plus+p.CurrentPage > p.TotalPage {
|
||||
plus = p.PageBarNum - p.TotalPage + p.CurrentPage
|
||||
}
|
||||
begin := page.CurrentPage - plus + 1
|
||||
begin := p.CurrentPage - plus + 1
|
||||
if begin < 1 {
|
||||
begin = 1
|
||||
}
|
||||
ret := ""
|
||||
for i := begin; i < begin+page.PageBarNum; i++ {
|
||||
if i <= page.TotalPage {
|
||||
if i != page.CurrentPage {
|
||||
ret += page.GetLink(page.GetUrl(i), gconv.String(i), style, "")
|
||||
for i := begin; i < begin+p.PageBarNum; i++ {
|
||||
if i <= p.TotalPage {
|
||||
if i != p.CurrentPage {
|
||||
ret += p.GetLink(p.GetUrl(i), gconv.String(i), style, "")
|
||||
} else {
|
||||
ret += fmt.Sprintf(`<span class="%s">%d</span>`, curStyle, i)
|
||||
}
|
||||
@ -169,13 +152,13 @@ func (page *Page) PageBar(styles ...string) string {
|
||||
}
|
||||
|
||||
// 获取基于select标签的显示跳转按钮的代码
|
||||
func (page *Page) SelectBar() string {
|
||||
ret := `<select name="gpage_select" onchange="window.location.href=this.value">`
|
||||
for i := 1; i <= page.TotalPage; i++ {
|
||||
if i == page.CurrentPage {
|
||||
ret += fmt.Sprintf(`<option value="%s" selected>%d</option>`, page.GetUrl(i), i)
|
||||
func (p *Page) SelectBar() string {
|
||||
ret := `<select name="GPageSelect" onchange="window.location.href=this.value">`
|
||||
for i := 1; i <= p.TotalPage; i++ {
|
||||
if i == p.CurrentPage {
|
||||
ret += fmt.Sprintf(`<option value="%s" selected>%d</option>`, p.GetUrl(i), i)
|
||||
} else {
|
||||
ret += fmt.Sprintf(`<option value="%s">%d</option>`, page.GetUrl(i), i)
|
||||
ret += fmt.Sprintf(`<option value="%s">%d</option>`, p.GetUrl(i), i)
|
||||
}
|
||||
}
|
||||
ret += "</select>"
|
||||
@ -183,117 +166,87 @@ func (page *Page) SelectBar() string {
|
||||
}
|
||||
|
||||
// 预定义的分页显示风格内容
|
||||
func (page *Page) GetContent(mode int) string {
|
||||
func (p *Page) GetContent(mode int) string {
|
||||
switch mode {
|
||||
case 1:
|
||||
page.NextPageTag = "下一页"
|
||||
page.PrevPageTag = "上一页"
|
||||
p.NextPageTag = "下一页"
|
||||
p.PrevPageTag = "上一页"
|
||||
return fmt.Sprintf(
|
||||
`%s <span class="current">%d</span> %s`,
|
||||
page.PrevPage(),
|
||||
page.CurrentPage,
|
||||
page.NextPage(),
|
||||
p.PrevPage(),
|
||||
p.CurrentPage,
|
||||
p.NextPage(),
|
||||
)
|
||||
|
||||
case 2:
|
||||
page.NextPageTag = "下一页>>"
|
||||
page.PrevPageTag = "<<上一页"
|
||||
page.FirstPageTag = "首页"
|
||||
page.LastPageTag = "尾页"
|
||||
p.NextPageTag = "下一页>>"
|
||||
p.PrevPageTag = "<<上一页"
|
||||
p.FirstPageTag = "首页"
|
||||
p.LastPageTag = "尾页"
|
||||
return fmt.Sprintf(
|
||||
`%s%s<span class="current">[第%d页]</span>%s%s第%s页`,
|
||||
page.FirstPage(),
|
||||
page.PrevPage(),
|
||||
page.CurrentPage,
|
||||
page.NextPage(),
|
||||
page.LastPage(),
|
||||
page.SelectBar(),
|
||||
p.FirstPage(),
|
||||
p.PrevPage(),
|
||||
p.CurrentPage,
|
||||
p.NextPage(),
|
||||
p.LastPage(),
|
||||
p.SelectBar(),
|
||||
)
|
||||
|
||||
case 3:
|
||||
page.NextPageTag = "下一页"
|
||||
page.PrevPageTag = "上一页"
|
||||
page.FirstPageTag = "首页"
|
||||
page.LastPageTag = "尾页"
|
||||
pageStr := page.FirstPage()
|
||||
pageStr += page.PrevPage()
|
||||
pageStr += page.PageBar("current")
|
||||
pageStr += page.NextPage()
|
||||
pageStr += page.LastPage()
|
||||
p.NextPageTag = "下一页"
|
||||
p.PrevPageTag = "上一页"
|
||||
p.FirstPageTag = "首页"
|
||||
p.LastPageTag = "尾页"
|
||||
pageStr := p.FirstPage()
|
||||
pageStr += p.PrevPage()
|
||||
pageStr += p.PageBar("current")
|
||||
pageStr += p.NextPage()
|
||||
pageStr += p.LastPage()
|
||||
pageStr += fmt.Sprintf(
|
||||
`<span>当前页%d/%d</span> <span>共%d条</span>`,
|
||||
page.CurrentPage,
|
||||
page.TotalPage,
|
||||
page.TotalSize,
|
||||
p.CurrentPage,
|
||||
p.TotalPage,
|
||||
p.TotalSize,
|
||||
)
|
||||
return pageStr
|
||||
|
||||
case 4:
|
||||
page.NextPageTag = "下一页"
|
||||
page.PrevPageTag = "上一页"
|
||||
page.FirstPageTag = "首页"
|
||||
page.LastPageTag = "尾页"
|
||||
pageStr := page.FirstPage()
|
||||
pageStr += page.PrevPage()
|
||||
pageStr += page.PageBar("current")
|
||||
pageStr += page.NextPage()
|
||||
pageStr += page.LastPage()
|
||||
p.NextPageTag = "下一页"
|
||||
p.PrevPageTag = "上一页"
|
||||
p.FirstPageTag = "首页"
|
||||
p.LastPageTag = "尾页"
|
||||
pageStr := p.FirstPage()
|
||||
pageStr += p.PrevPage()
|
||||
pageStr += p.PageBar("current")
|
||||
pageStr += p.NextPage()
|
||||
pageStr += p.LastPage()
|
||||
return pageStr
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 为指定的页面返回地址值
|
||||
func (page *Page) GetUrl(pageNo int) string {
|
||||
// 复制一个URL对象
|
||||
url := *page.Url
|
||||
if len(page.UrlTemplate) == 0 && page.Router != nil {
|
||||
page.UrlTemplate = page.makeUrlTemplate(url.Path, page.Router)
|
||||
}
|
||||
if len(page.UrlTemplate) > 0 {
|
||||
// 指定URL生成模板
|
||||
url.Path = gstr.Replace(page.UrlTemplate, "{.page}", gconv.String(pageNo))
|
||||
func (p *Page) GetUrl(pageNo int) string {
|
||||
pattern := fmt.Sprintf(`(:%s|\*%s|\.%s)`, p.PageName, p.PageName, p.PageName)
|
||||
result, _ := gregex.ReplaceString(pattern, pageNo, p.UrlTemplate)
|
||||
url.Path = gstr.Replace(p.UrlTemplate, "{.page}", gconv.String(pageNo))
|
||||
return url.String()
|
||||
}
|
||||
|
||||
values := page.Url.Query()
|
||||
values.Set(page.PageName, gconv.String(pageNo))
|
||||
values := p.Url.Query()
|
||||
values.Set(p.PageName, gconv.String(pageNo))
|
||||
url.RawQuery = values.Encode()
|
||||
return url.String()
|
||||
}
|
||||
|
||||
// 根据当前URL以及注册路由信息计算出对应的URL模板
|
||||
func (page *Page) makeUrlTemplate(url string, router *ghttp.Router) (tpl string) {
|
||||
if page.Router != nil && len(router.RegNames) > 0 {
|
||||
if match, err := gregex.MatchString(router.RegRule, url); err == nil && len(match) > 0 {
|
||||
if len(match) > len(router.RegNames) {
|
||||
tpl = router.Uri
|
||||
hasPageName := false
|
||||
for i, name := range router.RegNames {
|
||||
rule := fmt.Sprintf(`[:\*]%s|\{%s\}`, name, name)
|
||||
if !hasPageName && strings.Compare(name, page.PageName) == 0 {
|
||||
hasPageName = true
|
||||
tpl, _ = gregex.ReplaceString(rule, `{.page}`, tpl)
|
||||
} else {
|
||||
tpl, _ = gregex.ReplaceString(rule, match[i+1], tpl)
|
||||
}
|
||||
}
|
||||
if !hasPageName {
|
||||
tpl = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 获取链接地址
|
||||
func (page *Page) GetLink(url, text, title, style string) string {
|
||||
func (p *Page) GetLink(url, text, title, style string) string {
|
||||
if len(style) > 0 {
|
||||
style = fmt.Sprintf(`class="%s" `, style)
|
||||
}
|
||||
if len(page.AjaxActionName) > 0 {
|
||||
return fmt.Sprintf(`<a %shref='#' onclick="%s('%s')">%s</a>`, style, page.AjaxActionName, url, text)
|
||||
if len(p.AjaxActionName) > 0 {
|
||||
return fmt.Sprintf(`<a %shref='#' onclick="%s('%s')">%s</a>`, style, p.AjaxActionName, url, text)
|
||||
} else {
|
||||
return fmt.Sprintf(`<a %shref="%s" title="%s">%s</a>`, style, url, title, text)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user