{"id":26725657,"url":"https://github.com/idrunk/dce-go","last_synced_at":"2026-04-06T03:02:07.566Z","repository":{"id":272105269,"uuid":"911521228","full_name":"idrunk/dce-go","owner":"idrunk","description":"DCE-GO is a universal routing library that can not only route HTTP protocols, but also non-standard routable protocols such as CLI, WebSocket, TCP/UDP.","archived":false,"fork":false,"pushed_at":"2025-03-17T10:38:46.000Z","size":86,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-27T21:19:52.528Z","etag":null,"topics":["api","dce","router","session"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/idrunk.png","metadata":{"files":{"readme":"README-zh.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-03T08:02:31.000Z","updated_at":"2025-03-27T00:21:23.000Z","dependencies_parsed_at":null,"dependency_job_id":"b76db54c-cae4-49c0-ba04-ec856a732b37","html_url":"https://github.com/idrunk/dce-go","commit_stats":null,"previous_names":["idrunk/dce-go"],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idrunk%2Fdce-go","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idrunk%2Fdce-go/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idrunk%2Fdce-go/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/idrunk%2Fdce-go/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/idrunk","download_url":"https://codeload.github.com/idrunk/dce-go/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248841571,"owners_count":21170210,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","dce","router","session"],"created_at":"2025-03-27T21:19:56.688Z","updated_at":"2026-04-06T03:02:07.560Z","avatar_url":"https://github.com/idrunk.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"中文 | [English](README.md)\n\n---\n\n**DCE-GO** 是一个功能强大的通用路由库，不仅支持 HTTP 协议，还能路由 CLI、WebSocket、TCP/UDP 等非标准协议。它采用模块化设计，按功能划分为以下核心模块：\n\n1. **路由器模块**  \n   作为 DCE 的核心模块，定义了 API、上下文及路由器库，同时提供了转换器、可路由协议等接口，确保灵活性和扩展性。\n\n2. **可路由协议模块**  \n   封装了多种常见协议的可路由实现，包括 HTTP、CLI、WebSocket、TCP、UDP、QUIC 等，满足多样化场景需求。\n\n3. **转换器模块**  \n   内置 JSON 和模板转换器，支持串行数据的序列化与反序列化，以及传输对象与实体对象的双向转换。\n\n4. **会话管理器模块**  \n   定义了基础会话、用户会话、连接会话及自重生会话接口，并提供了 Redis 和共享内存的实现类库，方便开发者快速集成。\n\n5. **工具模块**  \n   提供了一系列实用工具，简化开发流程。\n\nDCE-GO 的所有功能特性均配有详细用例，位于 [_examples](_examples) 目录下。其路由性能与 Gin 相当，具体性能测试报告可查看 [ab 测试结果](_examples/attachs/report/ab-test-result.txt)，其中端口 `2046` 为 DCE 的测试结果。\n\nDCE-GO 源自 [DCE-RUST](https://github.com/idrunk/dce-rust)，而两者均基于 [DCE-PHP](https://github.com/idrunk/dce-php) 的核心路由模块升级而来。DCE-PHP 是一个完整的网络编程框架，现已停止更新，其核心功能已迁移至 DCE-RUST 和 DCE-GO。目前，DCE-GO 的功能版本较新，未来 DCE-RUST 将与之同步。\n\nDCE 致力于打造一个高效、开放、安全的通用路由库，欢迎社区贡献，共同推动其发展。\n\n---\n\n**TODO**：\n- [x] 优化 JS 版 WebSocket 可路由协议客户端。（暂时搁置，可参见`easy-tools`库的实现）\n- [x] 升级控制器前后置事件接口，支持与程序接口绑定。\n- [ ] 完善数字路径支持。\n- [x] 调整弹性数字函数为结构方法式。（取消）\n- [x] 研究可路由协议中支持自定义业务属性的可能性。（已实现）\n- [x] 支持文件上传等超大数据包的可路由协议。（暂时搁置，可参见`easy-tools`库的实现）\n- [ ] 升级 DCE-RUST 功能版本。\n- [x] 完善各协议的 Golang 客户端实现。（取消）\n- [ ] 逐步替换 AI 生成的文档为人工编写文档。\n- [ ] Api扩展属性支持继承。\n\n*由于工作原因，加上不知此框架有任何实际应用者，作者将不计划新功能开发，包括上述全部TODO。后续若作者自身有需求，或者达到一定数量的用户需求，作者将继续开发，BUG修复将优先于新功能开发。若你认同此项目，想推动其发展，欢迎协作开发，欢迎对外推广。*\n\n---\n\n**使用示例**\n\n```golang\npackage main\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net\"\n\t\"os\"\n\t\"slices\"\n\t\"strings\"\n\n\t\"go.drunkce.com/dce/converter\"\n\t\"go.drunkce.com/dce/proto\"\n\t\"go.drunkce.com/dce/proto/flex\"\n\t\"go.drunkce.com/dce/router\"\n\t\"go.drunkce.com/dce/session\"\n\t\"go.drunkce.com/dce/util\"\n)\n\nfunc main() {\n\t// go run main.go tcp start\n\tproto.CliRouter.Push(\"tcp/start/{address?}\", func(c *proto.Cli) {\n\t\tbindServer()\n\n\t\taddr := c.ParamOr(\"address\", \":2048\")\n\t\tlistener, err := net.Listen(\"tcp\", addr)\n\t\tif err != nil {\n\t\t\tpanic(err.Error())\n\t\t}\n\t\tdefer listener.Close()\n\n\t\tfmt.Printf(\"tcp server start at %s\\n\", addr)\n\t\tfor {\n\t\t\tconn, err := listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\tslog.Warn(fmt.Sprintf(\"accept error: %s\", err))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tgo func(conn net.Conn) {\n\t\t\t\tdefer conn.Close()\n\t\t\t\t// Connection sessions are used to store the connection information for sending message across hosts to clients in a distributed environment.\n\t\t\t\tshadow, err := session.NewShmSession[*Member](nil, session.DefaultTtlMinutes)\n\t\t\t\tif err != nil {\n\t\t\t\t\tslog.Warn(fmt.Sprintf(\"new session error: %s\", err))\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tshadow.Connect(conn.LocalAddr().String(), conn.RemoteAddr().String())\n\t\t\t\tdefer shadow.Disconnect()\n\t\t\t\tfor {\n\t\t\t\t\tif !flex.TcpRouter.Route(conn, map[string]any{\"$shadowSession\": shadow}) {\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}(conn)\n\t\t}\n\t})\n\tbindClient()\n\tproto.CliRoute(1)\n}\n\nfunc bindServer() {\n\tflex.TcpRouter.Push(\"sign\", func(c *flex.Tcp) {\n\t\tsignInfo, ok := converter.JsonRawRequester[*flex.TcpProtocol, *Member](c).Parse()\n\t\tjsr := converter.JsonStatusResponser(c)\n\t\tif !ok {\n\t\t\treturn\n\t\t}\n\t\tif (len(signInfo.Name) == 0 || len(signInfo.Password) == 0) \u0026\u0026 jsr.Fail(\"name or password is empty\", 0) {\n\t\t\treturn\n\t\t}\n\t\tmember, ok := members[signInfo.Name]\n\t\tif !ok {\n\t\t\t// Notfound then auto register\n\t\t\tmemberId++\n\t\t\tmember = signInfo\n\t\t\tmember.Id = memberId\n\t\t\tmember.Role = 1\n\t\t\tmembers[member.Name] = member\n\t\t}\n\t\tif member.Password != signInfo.Password \u0026\u0026 jsr.Fail(\"password error\", 0) {\n\t\t\treturn\n\t\t}\n\t\t// Must be have a session obj after `BeforeController` event, so we no need to check nil\n\t\tse := c.Rp.Session().(*session.ShmSession[*Member])\n\t\tif err := se.Login(member, 0); err != nil \u0026\u0026 jsr.Fail(err.Error(), 0) {\n\t\t\treturn\n\t\t}\n\t\t// Must be have a new session id after `UserSession.Login()`\n\t\tc.Rp.SetRespSid(se.Id())\n\t\tjsr.Success(nil)\n\t})\n\n\t// Bind an api with Path: signer, roles: [1]\n\tflex.TcpRouter.PushApi(router.Path(\"signer\").Append(\"roles\", 1), func(c *flex.Tcp) {\n\t\tjc := converter.JsonResponser[*flex.TcpProtocol, *Member, *Signer](c)\n\t\tsess := c.Rp.Session().(*session.ShmSession[*Member])\n\t\t// Member info can be obtained here, so there is no need to check\n\t\tmember, _ := sess.User()\n\t\t// Response the member, it can be convert to Signer struct automatically\n\t\tjc.Response(member)\n\t})\n\n\tflex.TcpRouter.SetBefore(\"*\", func(c *flex.Tcp) error {\n\t\tshadow, _ := c.Rp.CtxData(\"$shadowSession\")\n\t\trs := shadow.(*session.ShmSession[*Member])\n\t\tcloned, err := rs.CloneForRequest(c.Rp.Sid())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tse := cloned.(*session.ShmSession[*Member])\n\t\tif roles := util.MapSeqFrom[any, uint16](c.Api.ExtrasBy(\"roles\")).Map(func(i any) uint16 {\n\t\t\treturn uint16(i.(int))\n\t\t}).Collect(); len(roles) \u003e 0 {\n\t\t\t// Roles configured means need to login\n\t\t\tif member, ok := se.User(); !ok {\n\t\t\t\treturn util.Openly(401, \"need to login\")\n\t\t\t} else if !slices.Contains(roles, member.Role) {\n\t\t\t\treturn util.Openly(403, \"no permission\")\n\t\t\t} else if newer, err := session.NewAutoRenew(se).TryRenew(); err != nil {\n\t\t\t\treturn err\n\t\t\t} else if newer {\n\t\t\t\t// Logged session need to auto renew to enhance security\n\t\t\t\tc.Rp.SetRespSid(se.Id())\n\t\t\t}\n\t\t}\n\t\tc.Rp.SetSession(se)\n\t\treturn nil\n\t})\n}\n\nfunc bindClient() {\n\t// go run main.go sign\n\tproto.CliRouter.Push(\"sign\", func(c *proto.Cli) {\n\t\treader := bufio.NewReader(os.Stdin)\n\t\tsignInfo := Member{}\n\t\tfmt.Print(\"Enter username: \")\n\t\tusername, _ := reader.ReadString('\\n')\n\t\tsignInfo.Name = strings.TrimSpace(username)\n\t\tfmt.Print(\"Enter password: \")\n\t\tpassword, _ := reader.ReadString('\\n')\n\t\tsignInfo.Password = strings.TrimSpace(password)\n\t\treqBody, err := json.Marshal(signInfo)\n\t\tif err != nil \u0026\u0026 c.SetError(err) {\n\t\t\treturn\n\t\t} else if resp := request(c, \"sign\", reqBody, \"\"); resp != nil {\n\t\t\tc.Rp.SetRespSid(resp.Sid)\n\t\t\tc.WriteString(\"Signed in successfully\")\n\t\t}\n\t})\n\n\t// go run main.go signer $SESSION_ID\n\tproto.CliRouter.Push(\"signer/{sid?}\", func(c *proto.Cli) {\n\t\tsid := c.Param(\"sid\")\n\t\tif len(sid) == 0 {\n\t\t\tpanic(\"Session ID is required\")\n\t\t}\n\t\tif resp := request(c, \"signer\", nil, sid); resp == nil {\n\t\t\tc.SetError(util.Closed0(\"Request failed\"))\n\t\t} else if resp.Code == 0 {\n\t\t\tvar signer Signer\n\t\t\tif err := json.Unmarshal(resp.Body, \u0026signer); err != nil \u0026\u0026 c.SetError(err) {\n\t\t\t\treturn\n\t\t\t} else {\n\t\t\t\t// Just response the signer info if the session is logged in\n\t\t\t\tc.WriteString(fmt.Sprintf(\"Signer: %v\", signer))\n\t\t\t}\n\t\t} else {\n\t\t\tc.SetError(util.Openly(int(resp.Code), resp.Message))\n\t\t}\n\t})\n}\n\n// It's a simple example, need to mapping request id and the response callback if the server is async\nfunc request(c *proto.Cli, path string, reqBody []byte, sid string) *flex.Package {\n\tpkg := flex.NewPackage(path, reqBody, sid, -1)\n\tconn, _ := net.Dial(\"tcp\", \"127.0.0.1:\"+c.Rp.ArgOr(\"port\", \"2048\"))\n\tdefer conn.Close()\n\tif _, err := conn.Write(pkg.Serialize()); err != nil \u0026\u0026 c.SetError(err) {\n\t\treturn nil\n\t}\n\tresp, err := flex.PackageDeserialize(bufio.NewReader(conn))\n\tif err != nil \u0026\u0026 c.SetError(err) {\n\t\treturn nil\n\t}\n\treturn resp\n}\n\nvar memberId uint64 = 0\n\nvar members map[string]*Member = make(map[string]*Member)\n\ntype Member struct {\n\tId       uint64\n\tRole     uint16\n\tName     string `json:\"name\"`\n\tPassword string `json:\"password\"`\n}\n\nfunc (m Member) Uid() uint64 {\n\treturn m.Id\n}\n\ntype Signer struct {\n\tName string `json:\"name\"`\n}\n\n// Member entity converted to transfer object desensitization\nfunc (m *Signer) From(member *Member) (*Signer, error) {\n\tm = util.NewStruct[*Signer]()\n\tm.Name = member.Name\n\treturn m, nil\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fidrunk%2Fdce-go","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fidrunk%2Fdce-go","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fidrunk%2Fdce-go/lists"}