From c1325782575d1837c3619f4bac096fb0d8e9abd8 Mon Sep 17 00:00:00 2001 From: lulz1 Date: Wed, 16 Oct 2024 16:53:09 +0800 Subject: [PATCH] first commit --- .idea/.gitignore | 8 ++ .idea/framework1.iml | 9 ++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 ++ controller.go | 57 +++++++++++ framework/IGroup.go | 39 ++++++++ framework/Tree.go | 147 ++++++++++++++++++++++++++++ framework/context.go | 208 ++++++++++++++++++++++++++++++++++++++++ framework/controller.go | 3 + framework/core.go | 80 ++++++++++++++++ go.mod | 1 + main.go | 16 ++++ route.go | 15 +++ 13 files changed, 597 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/framework1.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 controller.go create mode 100644 framework/IGroup.go create mode 100644 framework/Tree.go create mode 100644 framework/context.go create mode 100644 framework/controller.go create mode 100644 framework/core.go create mode 100644 go.mod create mode 100644 main.go create mode 100644 route.go diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/framework1.iml b/.idea/framework1.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/framework1.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..46b67ea --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/controller.go b/controller.go new file mode 100644 index 0000000..94dc45a --- /dev/null +++ b/controller.go @@ -0,0 +1,57 @@ +package main + +import ( + "context" + "fmt" + "framework1/framework" + "time" +) + +func FooControllerHandler(ctx *framework.Context) error { + finish := make(chan struct{}, 1) + panicChan := make(chan interface{}, 1) + + durationCtx, cancel := context.WithTimeout(ctx.BaseContext(), 2*time.Second) // 这里记得当所有事情处理结束后调用 cancel,告知 durationCtx 的后续 Context 结束 + defer cancel() + + go func() { + time.Sleep(1 * time.Second) + ctx.Json(200, map[string]interface{}{"code": 0}) + finish <- struct{}{} + }() + + select { + case <-finish: + fmt.Println("调用结束了") + case <-durationCtx.Done(): + ctx.Json(500, "time out") + case <-panicChan: + ctx.Json(500, "panic") + } + return nil +} + +func UserLoginController(c *framework.Context) error { + c.Json(200, "ok UserLoginController") + return nil +} + +func SubjectDelController(c *framework.Context) error { + c.Json(200, "ok SubjectDelController") + return nil +} + +func SubjectUpdateController(c *framework.Context) error { + c.Json(200, "ok SubjectUpdateController") + return nil +} + +func SubjectGetController(c *framework.Context) error { + c.Json(200, "ok SubjectGetController") + return nil +} + +func SubjectListController(c *framework.Context) error { + c.Json(200, "ok SubjectListController") + return nil +} diff --git a/framework/IGroup.go b/framework/IGroup.go new file mode 100644 index 0000000..8006e97 --- /dev/null +++ b/framework/IGroup.go @@ -0,0 +1,39 @@ +package framework + +type IGroup interface { + Get(string, ControllerHandler) + Post(string, ControllerHandler) + Put(string, ControllerHandler) + Delete(string, ControllerHandler) +} + +type Group struct { + core *Core + prefix string +} + +func NewGroup(core *Core, prefix string) *Group { + return &Group{core, prefix} +} + +func (g *Group) Get(uri string, handler ControllerHandler) { + uri = g.prefix + uri + g.core.Get(uri, handler) +} + +func (g *Group) Post(uri string, handler ControllerHandler) { + uri = g.prefix + uri + g.core.POST(uri, handler) +} + +func (g *Group) Put(uri string, handler ControllerHandler) { + uri = g.prefix + uri + g.core.PUT(uri, handler) +} + +func (g *Group) Delete(uri string, handler ControllerHandler) { + uri = g.prefix + uri + g.core.DELETE(uri, handler) +} + +func (c *Core) Group(prefix string) IGroup { return NewGroup(c, prefix) } diff --git a/framework/Tree.go b/framework/Tree.go new file mode 100644 index 0000000..6e21f9c --- /dev/null +++ b/framework/Tree.go @@ -0,0 +1,147 @@ +package framework + +import ( + "errors" + "strings" +) + +type Tree struct { + root *node // 根节点 +} + +func NewTree() *Tree { + root := newNode() + return &Tree{root} +} + +type node struct { + isLast bool // 代表这个节点是否可以成为最终的路由规则。该节点是否能成为一个独立的uri, 是否自身就是一个终极节点 + segment string // uri中的字符串,代表这个节点表示的路由中某个段的字符串 + handler ControllerHandler // 代表这个节点中包含的控制器,用于最终加载调用 + children []*node // 代表这个节点下的子节点 +} + +func newNode() *node { + return &node{ + isLast: false, + segment: "", + children: []*node{}, + } +} + +func isWildSegment(segment string) bool { + return strings.HasPrefix(segment, ":") +} + +func (n *node) filterChildNodes(segment string) []*node { + if len(n.children) == 0 { + return nil + } + + if isWildSegment(segment) { + return n.children + } + + nodes := make([]*node, 0, len(n.children)) + for _, child := range n.children { + if isWildSegment(child.segment) { + nodes = append(nodes, child) + } else if child.segment == segment { + nodes = append(nodes, child) + } + } + return nodes +} + +func (n *node) matchNode(uri string) *node { + segments := strings.SplitN(uri, "/", 2) // 使用分隔符将uri切割为两个部分 + segment := segments[0] // 第一个部分用于匹配下一层子节点 + + if segment == "" { + return n.matchNode(segments[1]) + } + + if !isWildSegment(segment) { + segment = strings.ToUpper(segment) + } + + children := n.filterChildNodes(segment) // 匹配符合的下一层子节点 + if children == nil || len(children) == 0 { + return nil + } + + if len(segments) == 1 { + // 如果segment已经是最后一个节点,判断这些children是否有isLast标志 + for _, tn := range children { + if tn.isLast { + return tn + } + } + return nil + } + + // 如果有2个segment, 递归每个子节点继续进行查找 + for _, child := range children { + tnMatch := child.matchNode(segments[1]) + if tnMatch != nil { + return tnMatch + } + } + return nil +} + +func (tree *Tree) AddRouter(uri string, handler ControllerHandler) error { + n := tree.root + // 确认路由是否冲突 + if n.matchNode(uri) != nil { + return errors.New("route exist: " + uri) + } + + segments := strings.Split(uri, "/") + + for index, segment := range segments { + + if segment == "" { + continue + } + + if !isWildSegment(segment) { + segment = strings.ToUpper(segment) + } + + isLast := index == len(segments)-1 + var objNode *node // 标记是否有合适的子节点 + children := n.filterChildNodes(segment) + if len(children) > 0 { + // 如果有segment相同的子节点,则选择这个子节点 + for _, child := range children { + if child.segment == segment { + objNode = child + break + } + } + } + + //如果没有找到合适的子节点就要创建一个新节点 + if objNode == nil { + child := &node{} + child.segment = segment + if isLast { + child.isLast = true + child.handler = handler + } + n.children = append(n.children, child) + objNode = child + } + n = objNode + } + return nil +} + +func (tree *Tree) FindHandler(uri string) ControllerHandler { + match := tree.root.matchNode(uri) + if match == nil { + return nil + } + return match.handler +} diff --git a/framework/context.go b/framework/context.go new file mode 100644 index 0000000..3055542 --- /dev/null +++ b/framework/context.go @@ -0,0 +1,208 @@ +package framework + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "io/ioutil" + "net/http" + "strconv" + "sync" + "time" +) + +type Context struct { + request *http.Request + responseWriter http.ResponseWriter + ctx context.Context + handler ControllerHandler + + hasTimeout bool // 是否超时标记位 + writerMux *sync.Mutex // 写保护机制 +} + +func NewContext(r *http.Request, w http.ResponseWriter) *Context { + return &Context{ + request: r, + responseWriter: w, + ctx: r.Context(), + writerMux: &sync.Mutex{}, + } +} + +// #region implement context.Context +func (ctx *Context) BaseContext() context.Context { + return ctx.request.Context() +} + +func (ctx *Context) Deadline() (deadline time.Time, ok bool) { + return ctx.request.Context().Deadline() +} + +func (ctx *Context) Done() <-chan struct{} { + return ctx.BaseContext().Done() +} + +func (ctx *Context) Err() error { + return ctx.BaseContext().Err() +} + +func (ctx *Context) Value(key interface{}) interface{} { + return ctx.BaseContext().Value(key) +} + +// Base +func (ctx *Context) WriterMux() *sync.Mutex { + return ctx.writerMux +} + +func (ctx *Context) GetRequest() *http.Request { + return ctx.request +} + +func (ctx *Context) GetResponse() http.ResponseWriter { + return ctx.responseWriter +} + +func (ctx *Context) SetHasTimeout() { + ctx.hasTimeout = true +} + +func (ctx *Context) HasTimeout() bool { + return ctx.hasTimeout +} + +// #region query url +func (ctx *Context) QueryInt(key string, def int) int { + params := ctx.QueryAll() + if vals, ok := params[key]; ok { + valsLen := len(vals) + if valsLen > 0 { + intVal, err := strconv.Atoi(vals[valsLen-1]) + if err != nil { + return def + } + return intVal + } + } + return def +} + +func (ctx *Context) QueryString(key string, def string) string { + params := ctx.QueryAll() + if vals, ok := params[key]; ok { + valsLen := len(vals) + if valsLen > 0 { + return vals[valsLen-1] + } + } + return def +} + +func (ctx *Context) QueryArray(key string, def []string) []string { + params := ctx.QueryAll() + if vals, ok := params[key]; ok { + return vals + } + return def +} + +// 查询所有url后面的参数 +func (ctx *Context) QueryAll() map[string][]string { + if ctx.request != nil { + return ctx.request.URL.Query() + } + return map[string][]string{} +} + +// #region form post +func (ctx *Context) FormInt(key string, def int) int { + params := ctx.FormAll() + if vals, ok := params[key]; ok { + valsLen := len(vals) + if valsLen > 0 { + intval, err := strconv.Atoi(vals[valsLen-1]) + if err != nil { + return def + } + return intval + } + } + return def +} + +func (ctx *Context) FormString(key string, def string) string { + params := ctx.FormAll() + if vals, ok := params[key]; ok { + valsLen := len(vals) + if valsLen > 0 { + return vals[valsLen-1] + } + } + return def +} + +func (ctx *Context) FormArray(key string, def []string) []string { + params := ctx.FormAll() + if vals, ok := params[key]; ok { + return vals + } + return def +} + +func (ctx *Context) FormAll() map[string][]string { + if ctx.request != nil { + return ctx.request.PostForm + } + return map[string][]string{} +} + +// #region application/json post + +func (ctx *Context) BindJson(obj interface{}) error { + if ctx.request != nil { + body, err := ioutil.ReadAll(ctx.request.Body) + if err != nil { + return err + } + ctx.request.Body = ioutil.NopCloser(bytes.NewBuffer(body)) + + err = json.Unmarshal(body, obj) + if err != nil { + return err + } + } else { + return errors.New("ctx.request empty") + } + return nil +} + +// #endregion + +// #region response +func (ctx *Context) Json(status int, obj interface{}) error { + if ctx.HasTimeout() { + return nil + } + ctx.responseWriter.Header().Set("Content-Type", "application/json") + ctx.responseWriter.WriteHeader(status) + byt, err := json.Marshal(obj) + if err != nil { + ctx.responseWriter.WriteHeader(500) + return err + } + _, err = ctx.responseWriter.Write(byt) + if err != nil { + return err + } + return nil +} + +func (ctx *Context) HTML(status int, obj interface{}, template string) error { + return nil +} + +func (ctx *Context) Text(status int, obj string) error { + return nil +} diff --git a/framework/controller.go b/framework/controller.go new file mode 100644 index 0000000..6ed9890 --- /dev/null +++ b/framework/controller.go @@ -0,0 +1,3 @@ +package framework + +type ControllerHandler func(c *Context) error diff --git a/framework/core.go b/framework/core.go new file mode 100644 index 0000000..c006f32 --- /dev/null +++ b/framework/core.go @@ -0,0 +1,80 @@ +package framework + +import ( + "log" + "net/http" + "strings" +) + +type Serve struct { + Addr string + //Handler Hander + +} + +type Core struct { + router map[string]*Tree +} + +func NewCore() *Core { + router := map[string]*Tree{} + router["GET"] = NewTree() + router["POST"] = NewTree() + router["PUT"] = NewTree() + router["DELETE"] = NewTree() + + return &Core{router: router} +} + +func (c *Core) Get(url string, handler ControllerHandler) { + if err := c.router["GET"].AddRouter(url, handler); err != nil { + log.Fatal("add router error: ", err) + } +} + +func (c *Core) POST(url string, handler ControllerHandler) { + if err := c.router["POST"].AddRouter(url, handler); err != nil { + log.Fatal("add router error: ", err) + } +} + +func (c *Core) PUT(url string, handler ControllerHandler) { + if err := c.router["PUT"].AddRouter(url, handler); err != nil { + log.Fatal("add router error: ", err) + } +} + +func (c *Core) DELETE(url string, handler ControllerHandler) { + if err := c.router["DELETE"].AddRouter(url, handler); err != nil { + log.Fatal("add router error: ", err) + } +} + +func (c *Core) ServeHTTP(w http.ResponseWriter, r *http.Request) { + log.Println("core.serveHTTP") + ctx := NewContext(r, w) + // 寻找路由 + router := c.FindRouteByRequest(r) + if router == nil { // 如果没有找到,这里打印日志 + ctx.Json(404, "not found") + return + } + // 调用路由函数,如果返回err 代表存在内部错误,返回500状态码 + if err := router(ctx); err != nil { + ctx.Json(500, "inner error") + return + } +} + +func (c *Core) FindRouteByRequest(request *http.Request) ControllerHandler { + uri := request.URL.Path + method := request.Method + upperMethod := strings.ToUpper(method) + upperUri := strings.ToUpper(uri) + + if methodHandlers, ok := c.router[upperMethod]; ok { + return methodHandlers.FindHandler(upperUri) + } + + return nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8fd29e3 --- /dev/null +++ b/go.mod @@ -0,0 +1 @@ +module framework1 diff --git a/main.go b/main.go new file mode 100644 index 0000000..62784ad --- /dev/null +++ b/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "framework1/framework" + "net/http" +) + +func main() { + core := framework.NewCore() + registerRouter(core) + server := &http.Server{ + Handler: core, + Addr: ":8888", + } + server.ListenAndServe() +} diff --git a/route.go b/route.go new file mode 100644 index 0000000..f573eb7 --- /dev/null +++ b/route.go @@ -0,0 +1,15 @@ +package main + +import "framework1/framework" + +func registerRouter(core *framework.Core) { + core.Get("/user/login", UserLoginController) + + subjectGroup := core.Group("/subject") + { + subjectGroup.Delete("/:id", SubjectDelController) + subjectGroup.Put("/:id", SubjectUpdateController) + subjectGroup.Get("/:id", SubjectUpdateController) + subjectGroup.Get("/list/all", SubjectListController) + } +}