diff --git a/README.en.md b/README.en.md index 468b907b8e0eceb781ff27309470075fa6e193fe..919a561d61b14d27f82f3d2d8ec85cf110f278c8 100644 --- a/README.en.md +++ b/README.en.md @@ -1,36 +1,81 @@ # go-light #### Description -基于go语言实现的web业务框架 + + web business framework based on go language (refer to the table below for specific functions) + +| 序号 | 功能点 | 注意事项 | +|:-----:|:--------------------:|:------------------------------:| +| one | Route matching | Support '' and ':' wildcards | +| two | Parameter binding | uriquerybodyformheader is supported | +| three | Parameter check | Mandatory check is provided by default, and user-defined check functions are supported | +| four | Access static resources | You can configure the access and proxy directories yourself | +| five | Binding middleware | Support multiple sets of binding, the priority of global > local level depends on the order of binding triggered | +| six | Built-in logging | Logs display colors based on different requests or responses | +| seven | Generating scaffold | Refer to the instructions | +| eight | Support for custom error codes (markdown parsing) | It has not been completed, and subsequent iterations will follow | + #### Software Architecture -Software architecture description + + |-- go-light + |-- .gitignore #git ignores files + |-- img.png #Gift code + |-- go.mod #Project dependency + |-- go.sum + |-- LICENSE #Open source protocol + |-- README.en.md #Documentation (English) + |-- README.md #Description Document (Chinese) + |-- cmd #A compilable package + |-- generate #Scaffolding generator + |-- internal #Core source code + |-- pkg #Public files within the project + |-- global #Global constants and enums + |-- utils #tool kit + |-- scaffold #go-light web framework scaffolding related core code + |-- templates #Scaffold form + |-- generate.go #Template generator core code + |-- web #go-light web framework related core code + |-- binding #Request parameter binding && Responder mapping && Request a parameter validator + |-- controller.go #Routing controller + |-- engine.go #Frame core engine + |-- pkg #Publicly accessible file + |-- middleware #Common middleware + |-- server #go-light The initiator of the web framework + |-- utils #Commonly used tools + |-- test #Test case + #### Installation -1. xxxx -2. xxxx -3. xxxx + 1. The windows operating system is ready + + 2. go version: 1.18 + + 3. Configure the go environment(Including agency GOPROXY=https://goproxy.cn,direct) + + 4. Installation dependency: go get -u gitee.com/lstack_light/go-light + #### Instructions -1. xxxx -2. xxxx -3. xxxx + 1. Create your own project,execute (go mod init Own project name) After that, open the command line + + 2. go get -u gitee.com/lstack_light/go-light/cmd/generate + + 3. go install gitee.com/lstack_light/go-light/cmd/generate + + 4. generate -project "Project name" -name "File name" (You can view the help command with generate help) + + 5. You can use main.go until the console log reads "Scaffolding completed" + -#### Contribution +#### Technical support -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request + Contact author:1343450971@qq.com -#### Gitee Feature +#### Send gifts -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) + Want to support the author output better new content, you can ask the author to drink a cup of Luckin, thank you ~ +![image](img.png) \ No newline at end of file diff --git a/README.md b/README.md index 1ecc45bd9699835618b9101aee2edcb67079aebd..e87cfd76c0d43649a1fcb13301837221cd5d9e9f 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,79 @@ # go-light #### 介绍 -基于go语言实现的web业务框架 -#### 软件架构 -软件架构说明 + 基于go语言实现的web业务框架(具体功能参考下面表格) + +| 序号 | 功能点 | 注意事项 | +|:---:|:--------------------:|:------------------------------:| +| 1 | 路由匹配 | 支持 '*' 以及 ':' 通配符 | +| 2 | 参数绑定 | 支持uri/query/body/form/header类型 | +| 3 | 参数校验 | 默认提供必填校验,支持自定义校验函数 | +| 4 | 访问静态资源 | 可以自己配置访问以及代理的目录 | +| 5 | 绑定中间件 | 支持绑定多套,优先级 全局>局部 同等级就依赖绑定的顺序触发 | +| 6 | 内置日志功能 | 日志根据不同请求或者不同响应展示对应的颜色 | +| 7 | 生成脚手架 | 参考使用说明 | +| 8 | 支持自定义错误码(markdown解析) | 暂未完成,后续迭代跟进 | +#### 软件架构 + + |-- go-light + |-- .gitignore #git忽略的文件 + |-- img.png #打赏码 + |-- go.mod #项目依赖 + |-- go.sum + |-- LICENSE #开源协议 + |-- README.en.md #说明文档(英文) + |-- README.md #说明文档(中文) + |-- cmd #可编译的程序包 + |-- generate #脚手架生成器 + |-- internal #核心源码 + |-- pkg #项目内公共文件 + |-- global #全局常量和枚举 + |-- utils #工具包 + |-- scaffold #go-light web框架的脚手架相关核心代码 + |-- templates #脚手架的模板 + |-- generate.go #模板生成器核心代码 + |-- web #go-light web框架的相关核心代码 + |-- binding #请求参数绑定 && 响应体映射 && 请求参数校验器 + |-- controller.go #路由控制器 + |-- engine.go #框架核心引擎 + |-- pkg #公共可访问文件 + |-- middleware #常用的中间件 + |-- server #go-light web框架的启动器 + |-- utils #常用的工具 + |-- test #测试用例 + #### 安装教程 -1. xxxx -2. xxxx -3. xxxx + 1. 准备 windows 操作系统 + + 2. go version: 1.18 + 3. 配置go的环境(包括代理 GOPROXY=https://goproxy.cn,direct) + + 4. 安装依赖: go get -u gitee.com/lstack_light/go-light + #### 使用说明 + + 1. 创建自己的项目,执行 (go mod init 自己的项目名称) 后,打开命令行 + + 2. go get -u gitee.com/lstack_light/go-light/cmd/generate + + 3. go install gitee.com/lstack_light/go-light/cmd/generate + + 4. generate -project "项目名称" -name "文件名称" (可以通过 generate help 查看帮助命令) + + 5. 等待控制台日志显示 "已搭建完成脚手架" 字样即可使用 main.go -1. xxxx -2. xxxx -3. xxxx -#### 参与贡献 +#### 技术支持 -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request + 联系作者:1343450971@qq.com -#### 特技 +#### 打赏 -1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md -2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) -3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 -4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 -5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) -6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) + 想要支持作者输出更好的新内容,可以请作者喝一杯瑞幸,谢谢~ +![image](img.png) \ No newline at end of file diff --git a/cmd/generate/main.go b/cmd/generate/main.go new file mode 100644 index 0000000000000000000000000000000000000000..fb89e5c53571cfc8798e194f511f8f297440703f --- /dev/null +++ b/cmd/generate/main.go @@ -0,0 +1,7 @@ +package main + +import "gitee.com/lstack_light/go-light/internal/pkg/utils" + +func main() { + utils.Execute() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000000000000000000000000000000000000..d51c72adad74929abaed4916f33e6ba892e386fc --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module gitee.com/lstack_light/go-light + +go 1.18 + +require github.com/logrusorgru/aurora v2.0.3+incompatible diff --git a/img.png b/img.png new file mode 100644 index 0000000000000000000000000000000000000000..922190061017a6511dffbc4f910a581857e2324e Binary files /dev/null and b/img.png differ diff --git a/internal/pkg/global/const.go b/internal/pkg/global/const.go new file mode 100644 index 0000000000000000000000000000000000000000..a625c0d2c79f656f577721ffb105fc1710f7268b --- /dev/null +++ b/internal/pkg/global/const.go @@ -0,0 +1,17 @@ +package global + +/** + @author: light + @since: 2023/8/5 + @desc: 常量 +*/ + +const ( + QuestionMarkWildcard = "?" + StarMarkWildcard = "*" + StarMarkWildcardCharacter = '*' + TheColonWildcardCharacter = ':' + TheSlashSeparator = "/" +) + +const AcceptLanguage = "Accept-Language" diff --git a/internal/pkg/global/eunm.go b/internal/pkg/global/eunm.go new file mode 100644 index 0000000000000000000000000000000000000000..fa4ac2e9029acb9428cea4835430b7fe7d8800fd --- /dev/null +++ b/internal/pkg/global/eunm.go @@ -0,0 +1,14 @@ +package global + +/** + @author: light + @since: 2023/9/3 + @desc: 枚举 +*/ + +type TemplateEventType string + +const ( + InitEvent TemplateEventType = "Init" + GenerateEvent TemplateEventType = "Generate" +) diff --git a/internal/pkg/utils/commandUtil.go b/internal/pkg/utils/commandUtil.go new file mode 100644 index 0000000000000000000000000000000000000000..b7b54c703a6c32ec52e347118f8a7948c08f065e --- /dev/null +++ b/internal/pkg/utils/commandUtil.go @@ -0,0 +1,128 @@ +package utils + +import ( + "fmt" + "gitee.com/lstack_light/go-light/internal/scaffold" + "gitee.com/lstack_light/go-light/pkg/utils" + "log" + "os" +) + +/** + @author: light + @since: 2023/9/6 + @desc: 命令行工具 +*/ + +type cmd struct { + ProjectName string + FileName string + Dir string +} + +func help() { + // 帮助命令 + fmt.Print( + "Generate a scaffolding for a web framework (Go-Light) based on the go language implementation.\n\n", + "Usage:\n", + "\tgenerate [arguments]\n\n", + "The commands are:\n", + "\t-project\tused in the specified project\n", + "\t-name\t\tname of the generated file\n\n", + "Use \"generate help\" for more information about a command.\n", + ) +} + +func (c *cmd) AddCommand() bool { + flags := make([]utils.Flag, 0) + // 注册 -project 命令 + projectFlag := utils.Flag{ + Name: "project", + Usage: "项目名称", + } + // 注册 -name 命令 + fileNameFlag := utils.Flag{ + Name: "name", + Usage: "文件名称", + } + flags = append(flags, projectFlag, fileNameFlag) + command := utils.Command{ + Flags: flags, + } + resultMap, err := command.ParseString() + if nil != err { + log.Fatal(err) + return false + } + if projectName, ok := resultMap[projectFlag.Name]; ok && 0 == len(projectName) { + fmt.Print("Error: You must specify a project name\n\n") + help() + return false + } + c.ProjectName = resultMap[projectFlag.Name] + if fileName, ok := resultMap[fileNameFlag.Name]; ok && 0 == len(fileName) { + fmt.Print("Error: You must specify the file name\n\n") + help() + return false + } + c.FileName = resultMap[fileNameFlag.Name] + return true +} + +func (c *cmd) GenerateTemplatesCommand() bool { + // 生成模板 + return scaffold.Generate(c.Dir, c.ProjectName, c.FileName) +} + +func (c *cmd) UpdateModCommand() bool { + // 更新依赖 + updateModCommand := &utils.Command{ + Order: "go mod tidy", + Dir: c.Dir, + } + execResult, execErr := updateModCommand.Exec() + if nil != execErr && 0 < len(execResult) { + log.Fatal("已搭建完成脚手架 但自动更新 go-light 依赖失败(需要手动下载依赖即可启动 main.go):", execResult) + return false + } + log.Println("已搭建完成脚手架 并自动更新 go-light 依赖成功(可以直接启动 main.go)") + return true +} + +type CommandHandler struct { + Current func() bool + Next *CommandHandler +} + +func Execute() { + // 获取当前程序运行的路径 + dir, err := os.Getwd() + if err != nil { + log.Fatal("无法获取当前程序路径:", err) + return + } + command := cmd{ + Dir: dir, + } + // 链式注册命令 + updateModHandler := CommandHandler{ + Current: command.UpdateModCommand, + } + generateTemplatesHandler := CommandHandler{ + Current: command.GenerateTemplatesCommand, + Next: &updateModHandler, + } + commandHandler := CommandHandler{ + Current: command.AddCommand, + Next: &generateTemplatesHandler, + } + // 链式执行命令 + handler := commandHandler + for handler.Current() { + next := handler.Next + if nil == next { + break + } + handler = *next + } +} diff --git a/internal/pkg/utils/convertUtil.go b/internal/pkg/utils/convertUtil.go new file mode 100644 index 0000000000000000000000000000000000000000..4d9f84f10ceae0e101143bec8e94e2e1c9346c99 --- /dev/null +++ b/internal/pkg/utils/convertUtil.go @@ -0,0 +1,165 @@ +package utils + +import ( + "errors" + "mime/multipart" + "reflect" + "strconv" +) + +func ConvertTo(kind reflect.Type, source interface{}) (interface{}, error) { + b := &base{source: source} + switch kind.Kind() { + case reflect.String: + b.ConvertToString() + case reflect.Int: + b.ConvertToInt() + case reflect.Int8: + b.ConvertToInt8() + case reflect.Int16: + b.ConvertToInt16() + case reflect.Int32: + b.ConvertToInt32() + case reflect.Int64: + b.ConvertToInt64() + case reflect.Float32: + b.ConvertToFloat32() + case reflect.Float64: + b.ConvertToFloat64() + case reflect.Bool: + b.ConvertToBool() + case reflect.Ptr: + b.ConvertToPtr(kind) + default: + return b.dest, errors.New("convert error") + } + return b.dest, nil +} + +type Convert interface { + ConvertToString() + ConvertToInt() + ConvertToInt8() + ConvertToInt16() + ConvertToInt32() + ConvertToInt64() + ConvertToFloat32() + ConvertToFloat64() + ConvertToBool() + ConvertToPtr() +} + +type base struct { + source interface{} + dest interface{} +} + +func (b *base) ConvertToString() { + if data, ok := b.source.(string); ok { + b.dest = data + } +} + +func (b *base) ConvertToInt() { + if data, ok := b.source.(int); ok { + b.dest = data + } else { + str := b.source.(string) + result, err := strconv.ParseInt(str, 10, 64) + if nil == err { + b.dest = int(result) + } + } +} + +func (b *base) ConvertToInt8() { + if data, ok := b.source.(int8); ok { + b.dest = data + } else { + str := b.source.(string) + result, err := strconv.ParseInt(str, 10, 64) + if nil == err { + b.dest = int8(result) + } + } +} + +func (b *base) ConvertToInt16() { + if data, ok := b.source.(int16); ok { + b.dest = data + } else { + str := b.source.(string) + result, err := strconv.ParseInt(str, 10, 64) + if nil == err { + b.dest = int16(result) + } + } +} + +func (b *base) ConvertToInt32() { + if data, ok := b.source.(int32); ok { + b.dest = data + } else { + str := b.source.(string) + result, err := strconv.ParseInt(str, 10, 64) + if nil == err { + b.dest = int32(result) + } + } +} + +func (b *base) ConvertToInt64() { + if data, ok := b.source.(int64); ok { + b.dest = data + } else { + str := b.source.(string) + result, err := strconv.ParseInt(str, 10, 64) + if nil == err { + b.dest = result + } + } +} + +func (b *base) ConvertToFloat32() { + if data, ok := b.source.(float32); ok { + b.dest = data + } else { + str := b.source.(string) + result, err := strconv.ParseFloat(str, 64) + if nil == err { + b.dest = float32(result) + } + } +} + +func (b *base) ConvertToFloat64() { + if data, ok := b.source.(float64); ok { + b.dest = data + } else { + str := b.source.(string) + result, err := strconv.ParseFloat(str, 64) + if nil == err { + b.dest = result + } + } +} + +func (b *base) ConvertToBool() { + if data, ok := b.source.(bool); ok { + b.dest = data + } else { + str := b.source.(string) + result, err := strconv.ParseBool(str) + if nil == err { + b.dest = result + } + } +} + +func (b *base) ConvertToPtr(kind reflect.Type) { + if kind.String() == "*multipart.FileHeader" { + if data, ok := b.source.(*multipart.FileHeader); ok { + b.dest = data + } + } +} diff --git a/internal/pkg/utils/logUtil.go b/internal/pkg/utils/logUtil.go new file mode 100644 index 0000000000000000000000000000000000000000..e193ade941cb9404425b2e37716b1f6bba07f46e --- /dev/null +++ b/internal/pkg/utils/logUtil.go @@ -0,0 +1,62 @@ +package utils + +import ( + "github.com/logrusorgru/aurora" + "log" + "net/http" + "os" + "sync" +) + +/** + @author: light + @since: 2023/6/4 + @desc: 日志工具 +*/ + +var ( + once sync.Once + logger *log.Logger +) + +func GetLogger() *log.Logger { + once.Do(func() { + if nil == logger { + // 将log输出到标准输出,并设置前缀和日志级别 + logger = log.New(os.Stdout, "[LIGHT]\t", log.Ldate|log.Ltime) + } + }) + return logger +} + +func BeautifyRequestMode(method string) interface{} { + var style interface{} + switch method { + case http.MethodGet: + style = aurora.BgBlue(method) + case http.MethodPost: + style = aurora.BgBrightMagenta(method) + case http.MethodPut: + style = aurora.BgYellow(method) + case http.MethodDelete: + style = aurora.BgRed(method) + default: + style = aurora.BgWhite(method) + } + return style +} + +func BeautifyResponseCode(code int) interface{} { + var style interface{} + switch code { + case http.StatusOK: + style = aurora.BgGreen(code) + case http.StatusNotFound: + style = aurora.BgWhite(code) + case http.StatusInternalServerError: + style = aurora.BgRed(code) + default: + style = aurora.BgBlack(code) + } + return style +} diff --git a/internal/pkg/utils/mapUtil.go b/internal/pkg/utils/mapUtil.go new file mode 100644 index 0000000000000000000000000000000000000000..d2e15b9b440f859b090a460f496ae29fc05a93fc --- /dev/null +++ b/internal/pkg/utils/mapUtil.go @@ -0,0 +1,16 @@ +package utils + +/** + @author: light + @since: 2023/8/5 + @desc: map相关工具 +*/ + +type Map map[string]interface{} + +func (m *Map) Get(key string) interface{} { + return (*m)[key] +} +func (m *Map) Delete(key string) { + delete(*m, key) +} diff --git a/internal/scaffold/generate.go b/internal/scaffold/generate.go new file mode 100644 index 0000000000000000000000000000000000000000..2dda649aa9c9a3aa41bdf4953fccce8a2aa3c893 --- /dev/null +++ b/internal/scaffold/generate.go @@ -0,0 +1,127 @@ +package scaffold + +import ( + _const "gitee.com/lstack_light/go-light/internal/pkg/global" + templates2 "gitee.com/lstack_light/go-light/internal/scaffold/templates" + "log" + "os" + "path/filepath" + "strings" +) + +/** + @author: light + @since: 2023/9/2 + @desc: 目标文件生成器 +*/ + +func Generate(dir, project, fileName string) bool { + split := strings.Split(dir, "\\") + for split[len(split)-1] != project { + split = split[:len(split)-1] + if 0 == len(split) { + log.Fatal("未找到当前项目:", project) + return false + } + } + // 生成依赖 + module, dependencyErr := generateDependency(split) + if nil != dependencyErr { + log.Fatal(dependencyErr) + return false + } + // 定义并创建文件夹 + dirs := []string{ + strings.Join(split, "/") + "/config", + strings.Join(split, "/") + "/init", + strings.Join(split, "/") + "/deploy", + strings.Join(split, "/") + "/cmd", + strings.Join(split, "/") + "/internal/model/bto", + strings.Join(split, "/") + "/internal/model/dto", + strings.Join(split, "/") + "/internal/model/vto", + strings.Join(split, "/") + "/internal/controller", + strings.Join(split, "/") + "/internal/service/impl", + strings.Join(split, "/") + "/internal/middleware", + strings.Join(split, "/") + "/internal/tool", + strings.Join(split, "/") + "/internal/pkg", + strings.Join(split, "/") + "/pkg", + strings.Join(split, "/") + "/third_party", + strings.Join(split, "/") + "/test", + } + for _, catalog := range dirs { + err := os.MkdirAll(catalog, os.ModePerm) + if err != nil { + log.Fatal(err) + return false + } + } + // 截取模块名称 + name := strings.ReplaceAll(strings.Title(fileName), " ", "") + // 构造执行器 + invoker := &templates2.Invoker{} + // 实体相关操作 + modelCommand := &templates2.Model{Receiver: &templates2.Receiver{}, TemplateParams: templates2.TemplateParams{ + OutputFile: strings.Join(split, "/") + "/internal/model/bto/" + strings.ToLower(fileName) + ".go", + TemplateName: strings.Join(split, "/") + "/internal/model/model.go.tmpl", + Module: module, + Name: name, + }} + // 业务层接口相关操作 + serviceCommand := &templates2.Service{Receiver: &templates2.Receiver{}, TemplateParams: templates2.TemplateParams{ + OutputFile: strings.Join(split, "/") + "/internal/service/" + strings.ToLower(fileName) + ".go", + TemplateName: strings.Join(split, "/") + "/internal/service/service_interface.go.tmpl", + Module: module, + Name: name, + }} + // 业务层实现相关操作 + serviceImplCommand := &templates2.ServiceImpl{Receiver: &templates2.Receiver{}, TemplateParams: templates2.TemplateParams{ + OutputFile: strings.Join(split, "/") + "/internal/service/impl/" + strings.ToLower(fileName) + ".go", + TemplateName: strings.Join(split, "/") + "/internal/service/impl/service_impl.go.tmpl", + Module: module, + Name: name, + }} + // 路由层相关操作 + routerCommand := &templates2.Router{Receiver: &templates2.Receiver{}, TemplateParams: templates2.TemplateParams{ + OutputFile: strings.Join(split, "/") + "/internal/controller/" + strings.ToLower(fileName) + ".go", + TemplateName: strings.Join(split, "/") + "/internal/controller/controller.go.tmpl", + Module: module, + Name: name, + }} + // 主函数相关操作 + mainCommand := &templates2.Main{Receiver: &templates2.Receiver{}, TemplateParams: templates2.TemplateParams{ + OutputFile: strings.Join(split, "/") + "/cmd/main.go", + TemplateName: strings.Join(split, "/") + "/cmd/main.go.tmpl", + Module: module, + Name: name, + }} + // 配置责任链 + routerCommand.Receiver.SetNext(mainCommand) + serviceImplCommand.Receiver.SetNext(routerCommand) + serviceCommand.Receiver.SetNext(serviceImplCommand) + modelCommand.Receiver.SetNext(serviceCommand) + // 设置执行器命令 + invoker.SetCommand(modelCommand) + // 执行命令 + invoker.ExecuteCommand(_const.InitEvent) + return true +} + +func generateDependency(split []string) (string, error) { + // 查找 go mod 中的 module + module := "" + mod := filepath.Join(strings.Join(split, "/"), "go.mod") + content, readErr := os.ReadFile(mod) + if readErr != nil { + log.Fatal("读取 go mod 文件失败:", readErr) + return module, readErr + } + // 解析依赖 + lines := strings.Split(string(content), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "module") { + module = strings.TrimSpace(strings.ReplaceAll(line, "module", "")) + break + } + } + return module, nil +} diff --git a/internal/scaffold/templates/main.go b/internal/scaffold/templates/main.go new file mode 100644 index 0000000000000000000000000000000000000000..9e002478afa54c299632f6ddb1cd2020b55f8536 --- /dev/null +++ b/internal/scaffold/templates/main.go @@ -0,0 +1,73 @@ +package templates + +import ( + _const "gitee.com/lstack_light/go-light/internal/pkg/global" + "log" + "os" + "text/template" +) + +/** + @author: light + @since: 2023/9/3 + @desc: 主函数模板 +*/ + +var _ Template = &Main{} + +type Main struct { + Receiver *Receiver + TemplateParams +} + +func (m *Main) Execute(eventType _const.TemplateEventType) error { + switch eventType { + case _const.InitEvent: + return m.Init() + case _const.GenerateEvent: + return m.Generate() + } + return nil +} +func (m *Main) Init() error { + mainData := []byte("package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"{{.Module}}/internal/controller\"\n\tlight \"gitee.com/lstack_light/go-light/pkg/server\"\n\t\"log\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tctx, cancel := context.WithCancel(context.Background())\n\tengine := light.InitEngine().\n\t\tSetControllers(controller.{{.Name}}Router)\n\tserver := http.Server{\n\t\tAddr: fmt.Sprintf(\":%d\", 9090),\n\t\tHandler: engine,\n\t}\n\tdefer func() {\n\t\tif err := recover(); nil != err {\n\t\t\tlog.Println(\"异常--\", err)\n\t\t\tcancel()\n\t\t\terr = server.Shutdown(ctx)\n\t\t\tif nil != err {\n\t\t\t\tlog.Fatal(\"关闭server异常\")\n\t\t\t}\n\t\t}\n\t}()\n\terr := server.ListenAndServe()\n\tif nil != err {\n\t\tpanic(\"启动服务异常:\" + err.Error())\n\t}\n}") + err := os.WriteFile(m.TemplateName, mainData, 0644) + if err != nil { + log.Fatal("创建主函数模板文件失败:", err) + return err + } + return nil +} + +func (m *Main) Generate() error { + file, createErr := os.Create(m.OutputFile) + if createErr != nil { + log.Fatal(createErr) + return createErr + } + // 读取模板文件 + tmpl, tempErr := template.ParseFiles(m.TemplateName) + if tempErr != nil { + log.Fatal("解析主函数模板失败:", tempErr) + return tempErr + } + // 定义引用变量 + type Temp struct { + Name string + Module string + } + temp := Temp{ + Name: m.Name, + Module: m.Module, + } + // 执行模板填充并写入输出文件 + err := tmpl.Execute(file, temp) + if err != nil { + log.Fatal(err) + return err + } + // 删除模板文件 + _ = os.RemoveAll(m.TemplateName) + log.Println("Go文件已生成:", m.OutputFile) + return nil +} diff --git a/internal/scaffold/templates/model.go b/internal/scaffold/templates/model.go new file mode 100644 index 0000000000000000000000000000000000000000..5b4f3f003ae4184e42ac52acf15e5fb22fea7453 --- /dev/null +++ b/internal/scaffold/templates/model.go @@ -0,0 +1,70 @@ +package templates + +import ( + "errors" + _const "gitee.com/lstack_light/go-light/internal/pkg/global" + "log" + "os" + "text/template" +) + +/** + @author: light + @since: 2023/9/3 + @desc: 实体模板 +*/ + +var _ Template = &Model{} + +type Model struct { + Receiver *Receiver + TemplateParams +} + +func (m *Model) Execute(eventType _const.TemplateEventType) error { + if _const.InitEvent != eventType { + err := errors.New("执行的事件不是初始化模板,拒绝执行!!!") + log.Fatal(err) + return err + } + err := m.Init() + if nil != err { + log.Fatal(err) + return err + } + return m.Generate() +} + +func (m *Model) Init() error { + modelData := []byte("package bto\n\ntype {{.}} struct {\n\tId int `uri:\"id\"`\n\tName string `json:\"name\"`\n}") + err := os.WriteFile(m.TemplateName, modelData, 0644) + if err != nil { + log.Fatal("创建实体模板文件失败:", err) + return err + } + return m.Receiver.next.Execute(_const.InitEvent) +} + +func (m *Model) Generate() error { + file, createErr := os.Create(m.OutputFile) + if createErr != nil { + log.Fatal(createErr) + return createErr + } + // 读取模板文件 + tmpl, tempErr := template.ParseFiles(m.TemplateName) + if tempErr != nil { + log.Fatal("解析实体模板失败:", tempErr) + return tempErr + } + // 执行模板填充并写入输出文件 + err := tmpl.Execute(file, m.Name) + if err != nil { + log.Fatal(err) + return err + } + // 删除模板文件 + _ = os.RemoveAll(m.TemplateName) + log.Println("Go文件已生成:", m.OutputFile) + return m.Receiver.next.Execute(_const.GenerateEvent) +} diff --git a/internal/scaffold/templates/router.go b/internal/scaffold/templates/router.go new file mode 100644 index 0000000000000000000000000000000000000000..e885dc5a69bc0779efff47fc1c836ea0624d653b --- /dev/null +++ b/internal/scaffold/templates/router.go @@ -0,0 +1,79 @@ +package templates + +import ( + _const "gitee.com/lstack_light/go-light/internal/pkg/global" + "log" + "os" + "strings" + "text/template" +) + +/** + @author: light + @since: 2023/9/3 + @desc: 路由模板 +*/ + +var _ Template = &Router{} + +type Router struct { + Receiver *Receiver + TemplateParams +} + +func (r *Router) Execute(eventType _const.TemplateEventType) error { + switch eventType { + case _const.InitEvent: + return r.Init() + case _const.GenerateEvent: + return r.Generate() + } + return nil +} + +func (r *Router) Init() error { + routerData := []byte("package controller\n\nimport (\n\t\"gitee.com/lstack_light/go-light/pkg/middleware\"\n\t\"gitee.com/lstack_light/go-light/pkg/server\"\n\t\"{{.Module}}/internal/service\"\n\t\"{{.Module}}/internal/service/impl\"\n)\n\ntype {{.Self}} struct {\n\tservice.{{.Name}}Service\n}\n\nvar {{.Name}}Router = server.NewController(\"/api/users\", &{{.Self}}{\n\t{{.Name}}Service: impl.{{.Name}}ServiceImpl,\n}, middleware.Logger(true))\n\nfunc ({{.SelfPrefix}} *{{.Self}}) FindAll() {\n\t{{.Name}}Router.Get(\"\", {{.SelfPrefix}}.{{.Name}}Service.FindAll)\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) FindOne() {\n\t{{.Name}}Router.Get(\"/:id\", {{.SelfPrefix}}.{{.Name}}Service.FindOne)\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) CreateOne() {\n\t{{.Name}}Router.Post(\"\", {{.SelfPrefix}}.{{.Name}}Service.CreateOne)\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) UpdateOne() {\n\t{{.Name}}Router.Put(\"/:id\", {{.SelfPrefix}}.{{.Name}}Service.UpdateOne)\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) DeleteOne() {\n\t{{.Name}}Router.Delete(\"/:id\", {{.SelfPrefix}}.{{.Name}}Service.DeleteOne)\n}") + err := os.WriteFile(r.TemplateName, routerData, 0644) + if err != nil { + log.Fatal("创建路由层模板文件失败:", err) + return err + } + return r.Receiver.next.Execute(_const.InitEvent) +} + +func (r *Router) Generate() error { + file, createErr := os.Create(r.OutputFile) + if createErr != nil { + log.Fatal(createErr) + return createErr + } + // 读取模板文件 + tmpl, tempErr := template.ParseFiles(r.TemplateName) + if tempErr != nil { + log.Fatal("解析路由层模板失败:", tempErr) + return tempErr + } + // 定义引用变量 + type Temp struct { + Name string + Module string + Self string + SelfPrefix string + } + temp := Temp{ + Name: r.Name, + Module: r.Module, + Self: r.Name + "Controller", + SelfPrefix: strings.ToLower(r.Name)[0:1], + } + // 执行模板填充并写入输出文件 + err := tmpl.Execute(file, temp) + if err != nil { + log.Fatal(err) + return err + } + // 删除模板文件 + _ = os.RemoveAll(r.TemplateName) + log.Println("Go文件已生成:", r.OutputFile) + return r.Receiver.next.Execute(_const.GenerateEvent) +} diff --git a/internal/scaffold/templates/service.go b/internal/scaffold/templates/service.go new file mode 100644 index 0000000000000000000000000000000000000000..3b3228e007b652fd641e047c951e0271debf4056 --- /dev/null +++ b/internal/scaffold/templates/service.go @@ -0,0 +1,87 @@ +package templates + +import ( + _const "gitee.com/lstack_light/go-light/internal/pkg/global" + "log" + "os" + "text/template" +) + +/** + @author: light + @since: 2023/9/3 + @desc: 业务层接口模板 +*/ + +var _ Template = &Service{} + +type Service struct { + Receiver *Receiver + TemplateParams +} + +func (s *Service) Execute(eventType _const.TemplateEventType) error { + switch eventType { + case _const.InitEvent: + return s.Init() + case _const.GenerateEvent: + return s.Generate() + } + return nil +} +func (s *Service) Init() error { + serviceData := []byte("package service\n\nimport (\n\t\"{{.Module}}/internal/model/bto\"\n\t\"gitee.com/lstack_light/go-light/pkg/server/respData\"\n)\n\ntype {{.Name}}Service interface{\n\t{{range.Methods}}\n\t{{.Name}}({{.Params}}) *respData.Response\n\t{{end}}\n}") + err := os.WriteFile(s.TemplateName, serviceData, 0644) + if err != nil { + log.Fatal("创建业务层接口模板文件失败:", err) + return err + } + return s.Receiver.next.Execute(_const.InitEvent) +} + +func (s *Service) Generate() error { + file, createErr := os.Create(s.OutputFile) + if createErr != nil { + log.Fatal(createErr) + return createErr + } + // 读取模板文件 + tmpl, tempErr := template.ParseFiles(s.TemplateName) + if tempErr != nil { + log.Fatal("解析业务层接口模板失败:", tempErr) + return tempErr + } + // 定义引用变量 + type Method struct { + Name string + Params string + } + type Temp struct { + Module string + Name string + Methods []Method + } + // 定义方法列表 + methods := []Method{ + {Name: "FindAll"}, + {Name: "FindOne", Params: "param *bto." + s.Name}, + {Name: "CreateOne", Params: "param *bto." + s.Name}, + {Name: "UpdateOne", Params: "param *bto." + s.Name}, + {Name: "DeleteOne", Params: "param *bto." + s.Name}, + } + temp := Temp{ + Methods: methods, + Module: s.Module, + Name: s.Name, + } + // 执行模板填充并写入输出文件 + err := tmpl.Execute(file, temp) + if err != nil { + log.Fatal(err) + return err + } + // 删除模板文件 + _ = os.RemoveAll(s.TemplateName) + log.Println("Go文件已生成:", s.OutputFile) + return s.Receiver.next.Execute(_const.GenerateEvent) +} diff --git a/internal/scaffold/templates/serviceImpl.go b/internal/scaffold/templates/serviceImpl.go new file mode 100644 index 0000000000000000000000000000000000000000..58a665b70eb6fa28627c3aa33ed1be6c0eff7ac1 --- /dev/null +++ b/internal/scaffold/templates/serviceImpl.go @@ -0,0 +1,79 @@ +package templates + +import ( + _const "gitee.com/lstack_light/go-light/internal/pkg/global" + "log" + "os" + "strings" + "text/template" +) + +/** + @author: light + @since: 2023/9/3 + @desc: 业务层实现模板 +*/ + +var _ Template = &ServiceImpl{} + +type ServiceImpl struct { + Receiver *Receiver + TemplateParams +} + +func (s *ServiceImpl) Execute(eventType _const.TemplateEventType) error { + switch eventType { + case _const.InitEvent: + return s.Init() + case _const.GenerateEvent: + return s.Generate() + } + return nil +} + +func (s *ServiceImpl) Init() error { + serviceImplData := []byte("package impl\n\nimport (\n\t\"gitee.com/lstack_light/go-light/pkg/server/respData\"\n\t\"strconv\"\n\t\"{{.Module}}/internal/model/bto\"\n)\n\nvar (\n\t{{.Name}}ServiceImpl = &{{.Self}}{}\n)\n\ntype {{.Self}} struct {\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) FindAll() *respData.Response {\n\tresult := struct {\n\t\tItems []bto.{{.Name}} `json:\"items\"`\n\t\tTotal int `json:\"total\"`\n\t}{\n\t\tItems: []bto.{{.Name}}{},\n\t\tTotal: 0,\n\t}\n\treturn respData.SuccessResponse(result, \"操作成功\")\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) FindOne(param *bto.{{.Name}}) *respData.Response {\n\treturn respData.SuccessResponse(param, \"操作成功\")\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) CreateOne(param *bto.{{.Name}}) *respData.Response {\n\treturn respData.SuccessResponse(param, \"操作成功\")\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) UpdateOne(param *bto.{{.Name}}) *respData.Response {\n\treturn respData.SuccessResponse(param, \"操作成功\")\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) DeleteOne(param *bto.{{.Name}}) *respData.Response {\n\treturn respData.SuccessResponse(\"已删除:\"+strconv.Itoa(param.Id), \"操作成功\")\n}") + err := os.WriteFile(s.TemplateName, serviceImplData, 0644) + if err != nil { + log.Fatal("创建业务层实现模板文件失败:", err) + return err + } + return s.Receiver.next.Execute(_const.InitEvent) +} + +func (s *ServiceImpl) Generate() error { + file, createErr := os.Create(s.OutputFile) + if createErr != nil { + log.Fatal(createErr) + return createErr + } + // 读取模板文件 + tmpl, tempErr := template.ParseFiles(s.TemplateName) + if tempErr != nil { + log.Fatal("解析业务层实现模板失败:", tempErr) + return tempErr + } + // 定义引用变量 + type Temp struct { + Name string + Module string + Self string + SelfPrefix string + } + temp := Temp{ + Name: s.Name, + Module: s.Module, + Self: strings.ToLower(s.Name) + "ServiceImpl", + SelfPrefix: strings.ToLower(s.Name)[0:1], + } + // 执行模板填充并写入输出文件 + err := tmpl.Execute(file, temp) + if err != nil { + log.Fatal(err) + return err + } + // 删除模板文件 + _ = os.RemoveAll(s.TemplateName) + log.Println("Go文件已生成:", s.OutputFile) + return s.Receiver.next.Execute(_const.GenerateEvent) +} diff --git a/internal/scaffold/templates/template.go b/internal/scaffold/templates/template.go new file mode 100644 index 0000000000000000000000000000000000000000..8dd96b8348c99ec183d3226799114b04c3f17588 --- /dev/null +++ b/internal/scaffold/templates/template.go @@ -0,0 +1,55 @@ +package templates + +import ( + _const "gitee.com/lstack_light/go-light/internal/pkg/global" + "log" +) + +/** + @author: light + @since: 2023/9/3 + @desc: 模板 +*/ + +type TemplateParams struct { + OutputFile string + TemplateName string + Module string + Name string +} + +type Template interface { + Init() error + Generate() error +} + +// Command 命令接口 +type Command interface { + Execute(eventType _const.TemplateEventType) error +} + +// Receiver 接收者 +type Receiver struct { + // 下一个执行链 + next Command + Template +} + +func (r *Receiver) SetNext(next Command) { + r.next = next +} + +// Invoker 调用者 +type Invoker struct { + command Command +} + +func (i *Invoker) SetCommand(command Command) { + i.command = command +} + +func (i *Invoker) ExecuteCommand(eventType _const.TemplateEventType) { + if err := i.command.Execute(eventType); nil != err { + log.Fatal(err) + } +} diff --git a/internal/web/binding/request.go b/internal/web/binding/request.go new file mode 100644 index 0000000000000000000000000000000000000000..f1e933fb5308cc8a625d04a8d753ad9e0992185c --- /dev/null +++ b/internal/web/binding/request.go @@ -0,0 +1,96 @@ +package binding + +import ( + "encoding/json" + "errors" + "gitee.com/lstack_light/go-light/internal/pkg/utils" + "io" + "net/http" + "reflect" + "strings" +) + +type RequestParamType string + +const ( + queryParam RequestParamType = "query" + uriParam RequestParamType = "uri" + headerParam RequestParamType = "header" + formDataParam RequestParamType = "form-data" + formFileParam RequestParamType = "form-file" +) + +var reqTypes = []RequestParamType{queryParam, uriParam, headerParam, formDataParam, formFileParam} + +type Bind struct { + *http.Request + SourceData []reflect.Value +} + +func (b *Bind) BindParams(uriMap utils.Map) error { + if nil == b.Request { + return errors.New("请求异常,无法进行参数绑定!") + } + if 0 == len(b.SourceData) { + return nil + } + var err error + if strings.HasPrefix(b.Request.Header.Get("Content-Type"), "multipart/form-data") { + err = b.Request.ParseMultipartForm(10 << 20) + if nil != err { + return err + } + } + for _, data := range b.SourceData { + source := data.Elem() + sourceType := source.Type() + fields := sourceType.NumField() + if reflect.ValueOf(b.Body).IsValid() { + newStruct := reflect.New(sourceType).Elem() + reader := b.Body.(io.Reader) + err = json.NewDecoder(reader).Decode(newStruct.Addr().Interface()) + if nil != err && err.Error() == "EOF" { + err = nil + } + if nil != err { + return err + } + source.Set(reflect.ValueOf(newStruct.Interface())) + } + for _, rt := range reqTypes { + for i := 0; i < fields; i++ { + name := sourceType.Field(i).Tag.Get(string(rt)) + if 0 < len(name) { + var value interface{} + switch rt { + case uriParam: + value = uriMap.Get(name) + case queryParam: + value = b.Request.URL.Query().Get(name) + case headerParam: + value = b.Request.Header.Get(name) + case formDataParam: + value = b.FormValue(name) + case formFileParam: + _, value, err = b.FormFile(name) + if nil != err && http.ErrMissingFile != err { + return err + } + } + if reflect.ValueOf(value).IsValid() { + if nil != err { + err = errors.New("[" + string(rt) + ":" + name + "], can't convert " + + reflect.ValueOf(value).Type().String() + " to " + sourceType.Field(i).Type.String()) + return err + } + if sourceType.Field(i).Type.Kind() != reflect.Struct { + value, err = utils.ConvertTo(sourceType.Field(i).Type, value) + } + source.Field(i).Set(reflect.ValueOf(value)) + } + } + } + } + } + return err +} diff --git a/internal/web/binding/response.go b/internal/web/binding/response.go new file mode 100644 index 0000000000000000000000000000000000000000..17bd5d580eb28880a5eaa5a0879e928004df176f --- /dev/null +++ b/internal/web/binding/response.go @@ -0,0 +1,15 @@ +package binding + +/** + @author: light + @since: 2023/4/23 + @desc: 响应格式 +*/ + +type BaseResponse struct { + HttpStatusCode int `json:"-"` + File *struct { + Name string + Content []byte + } `json:"-"` +} diff --git a/internal/web/binding/validator.go b/internal/web/binding/validator.go new file mode 100644 index 0000000000000000000000000000000000000000..376e553631b15f17e513bf54492a15ab063c444e --- /dev/null +++ b/internal/web/binding/validator.go @@ -0,0 +1,82 @@ +package binding + +import ( + "errors" + "fmt" + "reflect" +) + +/** + @author: light + @since: 2023/6/2 + @desc: 参数校验器 +*/ + +type ValidationRule struct { + Name string + Validator func(interface{}) error +} + +type validator struct { + rules map[string][]ValidationRule + args []reflect.Value +} + +func NewValidator(args []reflect.Value, rule ...ValidationRule) *validator { + validate := &validator{args: args} + if 0 == len(rule) { + rule = append(rule, new(defaultRule).ValidationRule()) + } + validate.addRule(rule...) + return validate +} + +type CustomRule interface { + ValidationRule() ValidationRule +} + +type defaultRule struct{} + +func (d *defaultRule) ValidationRule() ValidationRule { + requiredRule := ValidationRule{Name: "required", + Validator: func(data interface{}) error { + if nil == data || reflect.ValueOf(data).IsZero() { + return errors.New("is required") + } + return nil + }} + return requiredRule +} + +func (v *validator) addRule(rule ...ValidationRule) { + if v.rules == nil { + v.rules = make(map[string][]ValidationRule) + } + for _, arg := range v.args { + value := arg.Elem() + fields := value.NumField() + for i := 0; i < fields; i++ { + for _, r := range rule { + if r.Name == value.Type().Field(i).Tag.Get("validator") { + name := value.Type().Field(i).Name + v.rules[name] = append(v.rules[name], rule...) + } + } + } + } +} + +func (v *validator) Validate() error { + for _, arg := range v.args { + for fieldName, rules := range v.rules { + field := arg.Elem().FieldByName(fieldName) + for _, rule := range rules { + err := rule.Validator(field.Interface()) + if err != nil { + return fmt.Errorf("[%s] %s", fieldName, err.Error()) + } + } + } + } + return nil +} diff --git a/internal/web/controller.go b/internal/web/controller.go new file mode 100644 index 0000000000000000000000000000000000000000..df574c732997582073ff8e87d26a6f01404db818 --- /dev/null +++ b/internal/web/controller.go @@ -0,0 +1,252 @@ +package web + +import ( + _const "gitee.com/lstack_light/go-light/internal/pkg/global" + "gitee.com/lstack_light/go-light/internal/pkg/utils" + "gitee.com/lstack_light/go-light/pkg/server/respData" + commonUtils "gitee.com/lstack_light/go-light/pkg/utils" + "io/ioutil" + "net/http" + "path/filepath" + "reflect" + "strings" +) + +/** + @author: light + @since: 2023/8/5 + @desc: 路由控制器 +*/ + +// +// tree +// @Description: 路由树 +// +type tree struct { + prefix string + part string + isWild bool + handler interface{} + children []*tree +} + +func (t *tree) matchChild(part string) *tree { + for _, child := range t.children { + if child.part == part || child.isWild { + return child + } + } + return nil +} + +func (t *tree) appendTree(prefix string, parts []string, index int, handler interface{}) { + if len(parts) == index { + t.prefix, t.handler = prefix, handler + return + } + part := parts[index] + if child := t.matchChild(part); nil != child { + child.appendTree(prefix, parts, index+1, handler) + } else { + isWild := part[0] == _const.StarMarkWildcardCharacter || part[0] == _const.TheColonWildcardCharacter + t.children = append(t.children, &tree{part: part, isWild: isWild, children: make([]*tree, 0)}) + t.children[len(t.children)-1].appendTree(prefix, parts, index+1, handler) + } +} +func convertRouters(path string) []string { + parts := make([]string, 0) + for _, part := range strings.Split(path, _const.TheSlashSeparator) { + if 0 < len(part) { + parts = append(parts, part) + if part[0] == _const.StarMarkWildcardCharacter { + break + } + } + } + return parts +} +func (t *tree) MatchingRouter(path string, paramMap *utils.Map) (args []reflect.Value, method reflect.Value) { + routers := convertRouters(path) + node := t + for i, part := range routers { + if child := node.matchChild(part); child != nil { + if child.isWild { + if strings.HasPrefix(child.part, _const.StarMarkWildcard) { + (*paramMap)[child.part[0:1]] = strings.Join(routers[i:], _const.TheSlashSeparator) + node = child + break + } + if qMarkIndex := strings.Index(part, _const.QuestionMarkWildcard); qMarkIndex != -1 { + (*paramMap)[child.part[1:]] = part[:qMarkIndex] + } else { + (*paramMap)[child.part[1:]] = part + } + } + node = child + } else { + qMarkIndex := strings.Index(part, _const.QuestionMarkWildcard) + if qMarkIndex == -1 { + return + } + if i == len(routers)-1 && 1 == len(node.children) { + node = node.children[0] + } + } + } + method = reflect.ValueOf(node.handler) + args = make([]reflect.Value, 0) + numIn := method.Type().NumIn() + for j := 0; j < numIn; j++ { + param := method.Type().In(j).Elem() + args = append(args, reflect.New(param)) + } + return +} + +type methodTrees struct { + methodTree map[string]*tree + routerTable map[string][]string +} + +func (mts *methodTrees) getMethodTree(method string) *tree { + return (*mts).methodTree[method] +} + +func (mts *methodTrees) getRouterTable() map[string][]string { + return (*mts).routerTable +} + +func (mts *methodTrees) set(method, path string, handler interface{}) { + mt := mts.getMethodTree(method) + if nil == mt { + mt = &tree{part: _const.TheSlashSeparator} + } + mt.appendTree(path, convertRouters(path), 0, handler) + (*mts).methodTree[method] = mt +} + +type Controller struct { + path string + module interface{} + Router +} + +// +// addRouter +// @Description: 注册路由 +// @receiver Router 路由分组指针 +// @param method 请求方式 +// @param path 请求路由 +// @param handler 请求处理函数 +// @return Router 路由接口 +// +func (c *Controller) addRouter(method, path string, handler interface{}) { + if !commonUtils.RegexMatch("^(GET|POST|PUT|DELETE|HEAD|PATCH|OPTIONS|CONNECT|TRACE)$", method) { + panic("[" + method + "]请求方式暂不支持") + } + path = filepath.ToSlash(filepath.Join(c.path, path)) + defaultEngine := InitEngine() + defaultEngine.routerTable[method] = append(defaultEngine.routerTable[method], path) + defaultEngine.set(method, path, handler) +} + +type Router interface { + Get(path string, handler interface{}) + Post(path string, handler interface{}) + Put(path string, handler interface{}) + Delete(path string, handler interface{}) + Head(path string, handler interface{}) + Patch(path string, handler interface{}) + Options(path string, handler interface{}) + Connect(path string, handler interface{}) + Trace(path string, handler interface{}) + StaticFile(path, rootDir string) +} + +func NewController(path string, module interface{}, middleWares ...*MiddleWare) *Controller { + if !strings.HasPrefix(path, _const.TheSlashSeparator) { + panic("设置路由必须以'/'开头") + } + c := &Controller{path: path, module: module} + c.useMiddleware(middleWares...) + return c +} + +func (c *Controller) useMiddleware(middleWares ...*MiddleWare) { + initEngine := InitEngine() + for _, middleWare := range middleWares { + exits := false + for _, mw := range initEngine.MiddleWares { + if middleWare.Key == mw.Key { + exits = true + break + } + } + if !exits { + initEngine.MiddleWares = append(initEngine.MiddleWares, *middleWare) + } + initEngine.Interceptor[middleWare.Key] = append(initEngine.Interceptor[middleWare.Key], c.path) + } +} + +func (c *Controller) Get(path string, handler interface{}) { + c.addRouter(http.MethodGet, path, handler) +} + +func (c *Controller) Post(path string, handler interface{}) { + c.addRouter(http.MethodPost, path, handler) +} + +func (c *Controller) Put(path string, handler interface{}) { + c.addRouter(http.MethodPut, path, handler) +} + +func (c *Controller) Delete(path string, handler interface{}) { + c.addRouter(http.MethodDelete, path, handler) +} + +func (c *Controller) Head(path string, handler interface{}) { + c.addRouter(http.MethodHead, path, handler) +} + +func (c *Controller) Patch(path string, handler interface{}) { + c.addRouter(http.MethodPatch, path, handler) +} + +func (c *Controller) Options(path string, handler interface{}) { + c.addRouter(http.MethodOptions, path, handler) +} + +func (c *Controller) Connect(path string, handler interface{}) { + c.addRouter(http.MethodConnect, path, handler) +} + +func (c *Controller) Trace(path string, handler interface{}) { + c.addRouter(http.MethodTrace, path, handler) +} + +func (c *Controller) StaticFile(path, rootDir string) { + if !strings.HasSuffix(rootDir, _const.TheSlashSeparator) { + rootDir = rootDir + _const.TheSlashSeparator + } + s := staticFile{rootDir: rootDir} + c.addRouter(http.MethodGet, path, s.download) +} + +type File struct { + FilePath string `uri:"*"` +} + +type staticFile struct { + rootDir string +} + +func (s *staticFile) download(file *File) *respData.Response { + if strings.HasPrefix(file.FilePath, _const.TheSlashSeparator) { + file.FilePath = file.FilePath[1:] + } + path := s.rootDir + file.FilePath + content, _ := ioutil.ReadFile(path) + split := strings.Split(path, _const.TheSlashSeparator) + return respData.StaticFileResponse(split[len(split)-1], content) +} diff --git a/internal/web/engine.go b/internal/web/engine.go new file mode 100644 index 0000000000000000000000000000000000000000..d89f6cf5fd36e167cd584cbb01cd9134e3986b6e --- /dev/null +++ b/internal/web/engine.go @@ -0,0 +1,181 @@ +package web + +import ( + "bytes" + "encoding/json" + _const "gitee.com/lstack_light/go-light/internal/pkg/global" + "gitee.com/lstack_light/go-light/internal/pkg/utils" + "gitee.com/lstack_light/go-light/internal/web/binding" + "gitee.com/lstack_light/go-light/pkg/server/respData" + "github.com/logrusorgru/aurora" + "io" + "io/ioutil" + "net/http" + "reflect" + "strings" + "sync" +) + +/** + @author: light + @since: 2023/8/5 + @desc: 引擎 +*/ + +type Engine struct { + *methodTrees + MiddleWares []MiddleWare + Interceptor map[string][]string +} + +var ( + lightEngine *Engine + once sync.Once +) + +// +// InitEngine +// @Description: 初始化框架引擎 +// @return *engine 框架引擎的指针 +// +func InitEngine() *Engine { + once.Do(func() { + if nil == lightEngine { + trees := methodTrees{methodTree: make(map[string]*tree), routerTable: make(map[string][]string)} + lightEngine = &Engine{methodTrees: &trees, Interceptor: make(map[string][]string)} + } + }) + return lightEngine +} +func (e *Engine) ServeHTTP(wr http.ResponseWriter, r *http.Request) { + var resp *respData.Response + r.Response = &http.Response{} + // 查找路由 + routerTree := e.getMethodTree(r.Method) + if nil == routerTree { + r.Response.StatusCode = http.StatusNotFound + r.Response.Status = http.StatusText(http.StatusNotFound) + wr.WriteHeader(http.StatusNotFound) + return + } + paramMap := make(utils.Map) + args, handler := routerTree.MatchingRouter(r.RequestURI, ¶mMap) + if !handler.IsValid() { + r.Response.StatusCode = http.StatusNotFound + r.Response.Status = http.StatusText(http.StatusNotFound) + wr.WriteHeader(http.StatusNotFound) + return + } + // 参数绑定 + bind := &binding.Bind{Request: r, SourceData: args} + err := bind.BindParams(paramMap) + if nil != err { + r.Response.StatusCode = http.StatusInternalServerError + r.Response.Status = "参数绑定异常" + err.Error() + utils.GetLogger().Println(aurora.Yellow("参数绑定异常" + err.Error())) + return + } + // 参数校验 + err = binding.NewValidator(args).Validate() + if nil != err { + msg := "参数校验异常" + err.Error() + acceptLanguage := r.Header.Get(_const.AcceptLanguage) + if strings.Contains(acceptLanguage, "en-US") { + msg = "Parameter check exception" + err.Error() + } + resp = respData.SystemErrorResponse(msg) + } else { + // 执行处理器 + result := handler.Call(args) + resp = result[0].Interface().(*respData.Response) + // 处理文件下载 + if nil != resp.File { + if 0 == len(resp.File.Content) { + r.Response.StatusCode = http.StatusNotFound + r.Response.Status = http.StatusText(http.StatusNotFound) + wr.WriteHeader(http.StatusNotFound) + return + } + wr.Header().Set("Content-Disposition", "attachment; filename="+resp.File.Name) + if _, err = io.Copy(wr, bytes.NewReader(resp.File.Content)); nil == err { + return + } + r.Response.StatusCode = http.StatusOK + r.Response.Status = http.StatusText(http.StatusOK) + wr.WriteHeader(http.StatusOK) + return + } + } + wr.WriteHeader(resp.HttpStatusCode) + marshal, jsonErr := json.Marshal(&resp) + if nil != jsonErr { + utils.GetLogger().Println(aurora.Yellow("格式化结果异常" + jsonErr.Error())) + return + } + r.Response.StatusCode = resp.HttpStatusCode + r.Response.Status = http.StatusText(resp.HttpStatusCode) + r.Response.Body = ioutil.NopCloser(bytes.NewBuffer(marshal)) + _, err = wr.Write(marshal) + if nil != err { + utils.GetLogger().Println(aurora.Yellow("写入结果异常" + err.Error())) + } +} + +func (e *Engine) SetControllers(controllers ...*Controller) http.Handler { + for _, handler := range controllers { + result := reflect.ValueOf(handler.module) + methods := result.NumMethod() + for i := 0; i < methods; i++ { + var params []reflect.Value + numIn := result.Method(i).Type().NumIn() + for j := 0; j < numIn; j++ { + param := result.Method(i).Type().In(j).Elem() + params = append(params, reflect.New(param)) + } + result.Method(i).Call(params) + } + } + e.printSystemLogs() + handler := e.ServeHTTP + for i := len(e.MiddleWares) - 1; i >= 0; i-- { + handler = e.MiddleWares[i].CoreHandler(handler, e.Interceptor) + } + http.HandleFunc(_const.TheSlashSeparator, func(writer http.ResponseWriter, request *http.Request) { + handler(writer, request) + }) + return nil +} + +func (e *Engine) printSystemLogs() { + logger := utils.GetLogger() + table := e.getRouterTable() + for method, routers := range table { + for _, router := range routers { + logger.Println(utils.BeautifyRequestMode(method), router) + } + } +} + +type Handler func(http.ResponseWriter, *http.Request) + +type MiddleWare struct { + Key string + CoreHandler func(next Handler, interceptor map[string][]string) Handler +} + +func (e *Engine) UseMiddleware(middleWares ...*MiddleWare) *Engine { + for _, middleWare := range middleWares { + exits := false + for _, mw := range e.MiddleWares { + if middleWare.Key == mw.Key { + exits = true + break + } + } + if !exits { + e.MiddleWares = append(e.MiddleWares, *middleWare) + } + e.Interceptor[middleWare.Key] = []string{_const.TheSlashSeparator} + } + return e +} diff --git a/pkg/middleware/logger.go b/pkg/middleware/logger.go new file mode 100644 index 0000000000000000000000000000000000000000..b91fd890c94b37670549566d96e8fdaf39099711 --- /dev/null +++ b/pkg/middleware/logger.go @@ -0,0 +1,63 @@ +package middleware + +import ( + "gitee.com/lstack_light/go-light/internal/pkg/utils" + "gitee.com/lstack_light/go-light/internal/web" + "github.com/logrusorgru/aurora" + "io/ioutil" + "net/http" + "net/http/httputil" + "strings" + "time" +) + +/** + @author: light + @since: 2023/6/3 + @desc: 日志中间件 +*/ + +func Logger(body bool) *web.MiddleWare { + // 指定中间件的类型,便于控制执行的范围 + key := "logger" + handler := func(next web.Handler, interceptor map[string][]string) web.Handler { + return func(w http.ResponseWriter, r *http.Request) { + flag := false + for _, group := range interceptor[key] { + if strings.Contains(r.RequestURI, group) { + start := time.Now() + content, err := httputil.DumpRequest(r, body) + if err != nil { + utils.GetLogger().Println(aurora.Yellow(err)) + return + } + next(w, r) + resp := "null" + if nil != r.Response.Body { + // 读取响应体 + data, readErr := ioutil.ReadAll(r.Response.Body) + r.Response.Body.Close() + if nil != readErr { + utils.GetLogger().Println(aurora.Yellow(err)) + return + } + resp = string(data) + } + utils.GetLogger().Printf("%s %d %s %s\n%s", + time.Since(start), + utils.BeautifyResponseCode(r.Response.StatusCode), + utils.BeautifyRequestMode(r.Method), + strings.ReplaceAll(string(content), r.Method, ""), + "Response:"+resp, + ) + flag = true + break + } + } + if !flag { + next(w, r) + } + } + } + return &web.MiddleWare{Key: key, CoreHandler: handler} +} diff --git a/pkg/server/respData/response.go b/pkg/server/respData/response.go new file mode 100644 index 0000000000000000000000000000000000000000..33611ca57a3c27bdb95687f13eca8d69fda17316 --- /dev/null +++ b/pkg/server/respData/response.go @@ -0,0 +1,91 @@ +package respData + +import ( + "gitee.com/lstack_light/go-light/internal/web/binding" + "net/http" +) + +/** + @author: light + @since: 2023/4/23 + @desc: 响应格式 +*/ + +type Response struct { + BusinessStatusCode int `json:"statusCode"` + Message string `json:"message"` + Data interface{} `json:"data"` + binding.BaseResponse `json:"-"` +} + +func StaticFileResponse(fileName string, Content []byte) *Response { + return &Response{ + BaseResponse: struct { + HttpStatusCode int `json:"-"` + File *struct { + Name string + Content []byte + } `json:"-"` + }{ + File: &struct { + Name string + Content []byte + }{ + Name: fileName, + Content: Content, + }, + }, + } +} + +func SystemErrorResponse(message string) *Response { + return &Response{ + BaseResponse: struct { + HttpStatusCode int `json:"-"` + File *struct { + Name string + Content []byte + } `json:"-"` + }{ + HttpStatusCode: http.StatusInternalServerError, + }, + BusinessStatusCode: http.StatusInternalServerError, + Message: message, + } +} +func ErrorResponse(statusCode int, message string) *Response { + return &Response{ + BaseResponse: struct { + HttpStatusCode int `json:"-"` + File *struct { + Name string + Content []byte + } `json:"-"` + }{ + HttpStatusCode: http.StatusOK, + }, + BusinessStatusCode: statusCode, + Message: message, + } +} + +func SuccessResponse(data interface{}, message ...string) *Response { + msg := "" + if 0 < len(message) { + msg = message[0] + } + return &Response{ + BaseResponse: struct { + HttpStatusCode int `json:"-"` + File *struct { + Name string + Content []byte + } `json:"-"` + }{ + HttpStatusCode: http.StatusOK, + }, + BusinessStatusCode: 0, + Data: data, + Message: msg, + } +} diff --git a/pkg/server/server.go b/pkg/server/server.go new file mode 100644 index 0000000000000000000000000000000000000000..a65b8a3eb13eff82cf526544c1508765c307ead8 --- /dev/null +++ b/pkg/server/server.go @@ -0,0 +1,19 @@ +package server + +import ( + "gitee.com/lstack_light/go-light/internal/web" +) + +/** + @author: light + @since: 2023/9/3 + @desc: 服务 +*/ + +func InitEngine() *web.Engine { + return web.InitEngine() +} + +func NewController(path string, module interface{}, middleWares ...*web.MiddleWare) *web.Controller { + return web.NewController(path, module, middleWares...) +} diff --git a/pkg/utils/commandUtil.go b/pkg/utils/commandUtil.go new file mode 100644 index 0000000000000000000000000000000000000000..06933ce438f0384400cc22d07c235734d465d1eb --- /dev/null +++ b/pkg/utils/commandUtil.go @@ -0,0 +1,51 @@ +package utils + +import ( + "errors" + "flag" + "os/exec" +) + +/** + @author: light + @since: 2023/9/4 + @desc: 命令行工具 +*/ + +type Command struct { + Order string + Dir string + Flags []Flag +} + +type Flag struct { + Name string + DefaultValue string + Usage string +} + +func (c *Command) Exec() (string, error) { + if 0 == len(c.Dir) { + return "", errors.New("必须指定工作目录") + } + cmd := exec.Command("sh", "-c", c.Order) + cmd.Dir = c.Dir + output, err := cmd.CombinedOutput() + return string(output), err +} + +func (c *Command) ParseString() (map[string]string, error) { + resultMap := make(map[string]string) + if 0 == len(c.Flags) { + return resultMap, errors.New("未识别到命令输入") + } + cmdResult := make([]*string, 0) + for _, f := range c.Flags { + cmdResult = append(cmdResult, flag.String(f.Name, f.DefaultValue, f.Usage)) + } + flag.Parse() + for index, f := range c.Flags { + resultMap[f.Name] = *cmdResult[index] + } + return resultMap, nil +} diff --git a/pkg/utils/regexUtil.go b/pkg/utils/regexUtil.go new file mode 100644 index 0000000000000000000000000000000000000000..8ea51f504e6fa155894d2bdd29898dbaa34f7d23 --- /dev/null +++ b/pkg/utils/regexUtil.go @@ -0,0 +1,17 @@ +package utils + +import "regexp" + +/** + @author: light + @since: 2023/8/5 + @desc: 正则相关工具 +*/ + +func RegexMatch(pattern, source string) bool { + regex := regexp.MustCompile(pattern) + if regex.MatchString(source) { + return true + } + return false +} diff --git a/test/cmd/main.go.tmpl b/test/cmd/main.go.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..7a3b83395c472a8ed51357594636e950f50c33a8 --- /dev/null +++ b/test/cmd/main.go.tmpl @@ -0,0 +1,35 @@ +package test + +import ( + "context" + "fmt" + "{{.Module}}/test/controller" + light "gitee.com/lstack_light/go-light/pkg/server" + "log" + "net/http" + "testing" +) + +func TestLight(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + engine := light.InitEngine(). + SetControllers(controller.{{.Name}}Router) + server := http.Server{ + Addr: fmt.Sprintf(":%d", 9090), + Handler: engine, + } + defer func() { + if err := recover(); nil != err { + log.Println("异常--", err) + cancel() + err = server.Shutdown(ctx) + if nil != err { + log.Fatal("关闭server异常") + } + } + }() + err := server.ListenAndServe() + if nil != err { + panic("启动服务异常:" + err.Error()) + } +} \ No newline at end of file diff --git a/test/cmd/main_test.go b/test/cmd/main_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0df82c4641936cc2f8f1b3e8502d441dbbc480eb --- /dev/null +++ b/test/cmd/main_test.go @@ -0,0 +1,35 @@ +package test + +import ( + "context" + "fmt" + light "gitee.com/lstack_light/go-light/pkg/server" + "gitee.com/lstack_light/go-light/test/controller" + "log" + "net/http" + "testing" +) + +func TestLight(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + engine := light.InitEngine(). + SetControllers(controller.UserRouter) + server := http.Server{ + Addr: fmt.Sprintf(":%d", 9090), + Handler: engine, + } + defer func() { + if err := recover(); nil != err { + log.Println("异常--", err) + cancel() + err = server.Shutdown(ctx) + if nil != err { + log.Fatal("关闭server异常") + } + } + }() + err := server.ListenAndServe() + if nil != err { + panic("启动服务异常:" + err.Error()) + } +} diff --git a/test/controller/controller.go.tmpl b/test/controller/controller.go.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..df62a674189e3e49ca58c0dc3871770ac35a9c8e --- /dev/null +++ b/test/controller/controller.go.tmpl @@ -0,0 +1,36 @@ +package controller + +import ( + "gitee.com/lstack_light/go-light/pkg/middleware" + "gitee.com/lstack_light/go-light/pkg/server" + "{{.Module}}/test/service" + "{{.Module}}/test/service/impl" +) + +type {{.Self}} struct { + service.{{.Name}}Service +} + +var {{.Name}}Router = server.NewController("/api/users", &{{.Self}}{ + {{.Name}}Service: impl.{{.Name}}ServiceImpl, +}, middleware.Logger(true)) + +func ({{.SelfPrefix}} *{{.Self}}) FindAll() { + {{.Name}}Router.Get("", {{.SelfPrefix}}.{{.Name}}Service.FindAll) +} + +func ({{.SelfPrefix}} *{{.Self}}) FindOne() { + {{.Name}}Router.Get("/:id", {{.SelfPrefix}}.{{.Name}}Service.FindOne) +} + +func ({{.SelfPrefix}} *{{.Self}}) CreateOne() { + {{.Name}}Router.Post("", {{.SelfPrefix}}.{{.Name}}Service.CreateOne) +} + +func ({{.SelfPrefix}} *{{.Self}}) UpdateOne() { + {{.Name}}Router.Put("/:id", {{.SelfPrefix}}.{{.Name}}Service.UpdateOne) +} + +func ({{.SelfPrefix}} *{{.Self}}) DeleteOne() { + {{.Name}}Router.Delete("/:id", {{.SelfPrefix}}.{{.Name}}Service.DeleteOne) +} \ No newline at end of file diff --git a/test/controller/user.go b/test/controller/user.go new file mode 100644 index 0000000000000000000000000000000000000000..5e92f5b089e512f3b6a67b41529d7d7fd0e5a7bf --- /dev/null +++ b/test/controller/user.go @@ -0,0 +1,36 @@ +package controller + +import ( + "gitee.com/lstack_light/go-light/pkg/middleware" + "gitee.com/lstack_light/go-light/pkg/server" + "gitee.com/lstack_light/go-light/test/service" + "gitee.com/lstack_light/go-light/test/service/impl" +) + +type UserController struct { + service.UserService +} + +var UserRouter = server.NewController("/api/users", &UserController{ + UserService: impl.UserServiceImpl, +}, middleware.Logger(true)) + +func (u *UserController) FindAll() { + UserRouter.Get("", u.UserService.FindAll) +} + +func (u *UserController) FindOne() { + UserRouter.Get("/:id", u.UserService.FindOne) +} + +func (u *UserController) CreateOne() { + UserRouter.Post("", u.UserService.CreateOne) +} + +func (u *UserController) UpdateOne() { + UserRouter.Put("/:id", u.UserService.UpdateOne) +} + +func (u *UserController) DeleteOne() { + UserRouter.Delete("/:id", u.UserService.DeleteOne) +} diff --git a/test/create_templates_test.go b/test/create_templates_test.go new file mode 100644 index 0000000000000000000000000000000000000000..80e72974b1f0ab98827bc1f0319c9d2b95d04fc7 --- /dev/null +++ b/test/create_templates_test.go @@ -0,0 +1,271 @@ +package test + +import ( + "gitee.com/lstack_light/go-light/pkg/utils" + "log" + "os" + "strings" + "testing" + "text/template" +) + +/** + @author: light + @since: 2023/9/3 + @desc: 测试类 +*/ + +func TestCreateTemplates(t *testing.T) { + err := createTemplates( + "model/model.go.tmpl", + "service/service.go.tmpl", + "service/impl/serviceImpl.go.tmpl", + "controller/controller.go.tmpl", + "cmd/main.go.tmpl", + ) + if nil != err { + panic(err) + } +} + +func createTemplates(modelFile, serviceInterfaceFile, serviceImplFile, routerFile, mainFile string) error { + name := strings.ReplaceAll(strings.Title("user"), " ", "") + model := "gitee.com/lstack_light/go-light" + modelData := []byte("package bto\n\ntype {{.}} struct {\n\tId int `uri:\"id\"`\n\tName string `json:\"name\"`\n}") + err := os.WriteFile(modelFile, modelData, 0644) + if err != nil { + log.Fatal("创建实体模板文件失败:", err) + return err + } + err = generateModel(modelFile, name) + if err != nil { + log.Fatal("创建实体文件失败:", err) + return err + } + serviceInterfaceData := []byte("package service\n\nimport (\n\t\"{{.Module}}/test/model/bto\"\n\t\"gitee.com/lstack_light/go-light/pkg/server/respData\"\n)\n\ntype {{.Name}}Service interface{\n\t{{range.Methods}}\n\t{{.Name}}({{.Params}}) *respData.Response\n\t{{end}}\n}") + err = os.WriteFile(serviceInterfaceFile, serviceInterfaceData, 0644) + if err != nil { + log.Fatal("创建业务层接口模板文件失败:", err) + return err + } + err = generateService(serviceInterfaceFile, model, name) + if err != nil { + log.Fatal("创建业务层接口文件失败:", err) + return err + } + serviceImplData := []byte("package impl\n\nimport (\n \"gitee.com/lstack_light/go-light/pkg/server/respData\"\n \"strconv\"\n \"{{.Module}}/test/model/bto\"\n)\n\nvar (\n\t{{.Name}}ServiceImpl = &{{.Self}}{}\n)\n\ntype {{.Self}} struct {\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) FindAll() *respData.Response {\n\tresult := struct {\n\t\tItems []bto.{{.Name}} `json:\"items\"`\n\t\tTotal int `json:\"total\"`\n\t}{\n\t\tItems: []bto.{{.Name}}{},\n\t\tTotal: 0,\n\t}\n\treturn respData.SuccessResponse(result, \"操作成功\")\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) FindOne(param *bto.{{.Name}}) *respData.Response {\n\treturn respData.SuccessResponse(param, \"操作成功\")\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) CreateOne(param *bto.{{.Name}}) *respData.Response {\n\treturn respData.SuccessResponse(param, \"操作成功\")\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) UpdateOne(param *bto.{{.Name}}) *respData.Response {\n\treturn respData.SuccessResponse(param, \"操作成功\")\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) DeleteOne(param *bto.{{.Name}}) *respData.Response {\n\treturn respData.SuccessResponse(\"已删除:\"+strconv.Itoa(param.Id), \"操作成功\")\n}") + err = os.WriteFile(serviceImplFile, serviceImplData, 0644) + if err != nil { + log.Fatal("创建业务层实现模板文件失败:", err) + return err + } + err = generateServiceImpl(serviceImplFile, model, name) + if err != nil { + log.Fatal("创建业务层实现文件失败:", err) + return err + } + routerData := []byte("package controller\n\nimport (\n\t\"gitee.com/lstack_light/go-light/pkg/middleware\"\n\t\"gitee.com/lstack_light/go-light/pkg/server\"\n\t\"{{.Module}}/test/service\"\n\t\"{{.Module}}/test/service/impl\"\n)\n\ntype {{.Self}} struct {\n\tservice.{{.Name}}Service\n}\n\nvar {{.Name}}Router = server.NewController(\"/api/users\", &{{.Self}}{\n\t{{.Name}}Service: impl.{{.Name}}ServiceImpl,\n}, middleware.Logger(true))\n\nfunc ({{.SelfPrefix}} *{{.Self}}) FindAll() {\n\t{{.Name}}Router.Get(\"\", {{.SelfPrefix}}.{{.Name}}Service.FindAll)\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) FindOne() {\n\t{{.Name}}Router.Get(\"/:id\", {{.SelfPrefix}}.{{.Name}}Service.FindOne)\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) CreateOne() {\n\t{{.Name}}Router.Post(\"\", {{.SelfPrefix}}.{{.Name}}Service.CreateOne)\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) UpdateOne() {\n\t{{.Name}}Router.Put(\"/:id\", {{.SelfPrefix}}.{{.Name}}Service.UpdateOne)\n}\n\nfunc ({{.SelfPrefix}} *{{.Self}}) DeleteOne() {\n\t{{.Name}}Router.Delete(\"/:id\", {{.SelfPrefix}}.{{.Name}}Service.DeleteOne)\n}") + err = os.WriteFile(routerFile, routerData, 0644) + if err != nil { + log.Fatal("创建路由层模板文件失败:", err) + return err + } + err = generateRouter(routerFile, model, name) + if err != nil { + log.Fatal("创建路由层文件失败:", err) + return err + } + mainData := []byte("package test\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"{{.Module}}/test/controller\"\n\tlight \"gitee.com/lstack_light/go-light/pkg/server\"\n\t\"log\"\n\t\"net/http\"\n\t\"testing\"\n)\n\nfunc TestLight(t *testing.T) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tengine := light.InitEngine().\n\t\tSetControllers(controller.{{.Name}}Router)\n\tserver := http.Server{\n\t\tAddr: fmt.Sprintf(\":%d\", 9090),\n\t\tHandler: engine,\n\t}\n\tdefer func() {\n\t\tif err := recover(); nil != err {\n\t\t\tlog.Println(\"异常--\", err)\n\t\t\tcancel()\n\t\t\terr = server.Shutdown(ctx)\n\t\t\tif nil != err {\n\t\t\t\tlog.Fatal(\"关闭server异常\")\n\t\t\t}\n\t\t}\n\t}()\n\terr := server.ListenAndServe()\n\tif nil != err {\n\t\tpanic(\"启动服务异常:\" + err.Error())\n\t}\n}") + err = os.WriteFile(mainFile, mainData, 0644) + if err != nil { + log.Fatal("创建主函数模板文件失败:", err) + return err + } + err = generateMain(mainFile, model, name) + if err != nil { + log.Fatal("创建主函数文件失败:", err) + return err + } + log.Println("已完成模板的创建") + return nil +} + +func generateModel(templateFile, name string) error { + file, createErr := os.Create("model/bto/user.go") + if createErr != nil { + log.Fatal(createErr) + return createErr + } + // 读取模板文件 + tmpl, tempErr := template.ParseFiles(templateFile) + if tempErr != nil { + log.Fatal("解析实体模板失败:", tempErr) + return tempErr + } + // 执行模板填充并写入输出文件 + err := tmpl.Execute(file, name) + if err != nil { + log.Fatal(err) + return err + } + return nil +} +func generateService(templateName, module, name string) error { + file, createErr := os.Create("service/user.go") + if createErr != nil { + log.Fatal(createErr) + return createErr + } + // 读取模板文件 + tmpl, tempErr := template.ParseFiles(templateName) + if tempErr != nil { + log.Fatal("解析业务层接口模板失败:", tempErr) + return tempErr + } + type Method struct { + Name string + Params string + } + type Temp struct { + Module string + Name string + Methods []Method + } + // 定义方法列表 + methods := []Method{ + {Name: "FindAll"}, + {Name: "FindOne", Params: "param *bto." + name}, + {Name: "CreateOne", Params: "param *bto." + name}, + {Name: "UpdateOne", Params: "param *bto." + name}, + {Name: "DeleteOne", Params: "param *bto." + name}, + } + temp := Temp{ + Methods: methods, + Name: name, + Module: module, + } + // 执行模板填充并写入输出文件 + err := tmpl.Execute(file, temp) + if err != nil { + log.Fatal(err) + return err + } + return nil +} + +func generateServiceImpl(templateName, module, name string) error { + file, createErr := os.Create("service/impl/user.go") + if createErr != nil { + log.Fatal(createErr) + return createErr + } + // 读取模板文件 + tmpl, tempErr := template.ParseFiles(templateName) + if tempErr != nil { + log.Fatal("解析业务层实现模板失败:", tempErr) + return tempErr + } + type Temp struct { + Name string + Module string + Self string + SelfPrefix string + } + temp := Temp{ + Name: name, + Module: module, + Self: strings.ToLower(name) + "ServiceImpl", + SelfPrefix: strings.ToLower(name)[0:1], + } + // 执行模板填充并写入输出文件 + err := tmpl.Execute(file, temp) + if err != nil { + log.Fatal(err) + return err + } + return nil +} + +func generateRouter(templateName, module, name string) error { + file, createErr := os.Create("controller/user.go") + if createErr != nil { + log.Fatal(createErr) + return createErr + } + // 读取模板文件 + tmpl, tempErr := template.ParseFiles(templateName) + if tempErr != nil { + log.Fatal("解析路由层模板失败:", tempErr) + return tempErr + } + // 定义引用变量 + type Temp struct { + Name string + Module string + Self string + SelfPrefix string + } + temp := Temp{ + Name: name, + Module: module, + Self: name + "Controller", + SelfPrefix: strings.ToLower(name)[0:1], + } + // 执行模板填充并写入输出文件 + err := tmpl.Execute(file, temp) + if err != nil { + log.Fatal(err) + return err + } + return nil +} + +func generateMain(templateName, module, name string) error { + file, createErr := os.Create("cmd/main_test.go") + if createErr != nil { + log.Fatal(createErr) + return createErr + } + // 读取模板文件 + tmpl, tempErr := template.ParseFiles(templateName) + if tempErr != nil { + log.Fatal("解析主函数模板失败:", tempErr) + return tempErr + } + // 定义引用变量 + type Temp struct { + Name string + Module string + } + temp := Temp{ + Name: name, + Module: module, + } + // 执行模板填充并写入输出文件 + err := tmpl.Execute(file, temp) + if err != nil { + log.Fatal(err) + return err + } + return nil +} + +func TestName(t *testing.T) { + // 获取当前程序运行的路径 + dir, err := os.Getwd() + if err != nil { + t.Fatal("无法获取当前程序路径:", err) + return + } + // 更新依赖 + command := &utils.Command{ + Order: "go get -u gitee.com/lstack_light/go-light", + Dir: dir, + } + result, execErr := command.Exec() + if nil != execErr || (!strings.Contains(result, "go: upgraded") && 0 < len(result)) { + t.Fatal("更新 go-light 依赖失败:", result) + return + } + t.Log(result) +} diff --git a/test/model/bto/user.go b/test/model/bto/user.go new file mode 100644 index 0000000000000000000000000000000000000000..7c0647c34e80442b69d702865c05a28f4f375d62 --- /dev/null +++ b/test/model/bto/user.go @@ -0,0 +1,6 @@ +package bto + +type User struct { + Id int `uri:"id"` + Name string `json:"name"` +} diff --git a/test/model/model.go.tmpl b/test/model/model.go.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..47cf56f39498bfad7ccad11a4552a1cecbc0c817 --- /dev/null +++ b/test/model/model.go.tmpl @@ -0,0 +1,6 @@ +package bto + +type {{.}} struct { + Id int `uri:"id"` + Name string `json:"name"` +} \ No newline at end of file diff --git a/test/service/impl/serviceImpl.go.tmpl b/test/service/impl/serviceImpl.go.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..bbd9ad88ce9d31d1c7e9592ad23a8be088501a88 --- /dev/null +++ b/test/service/impl/serviceImpl.go.tmpl @@ -0,0 +1,41 @@ +package impl + +import ( + "gitee.com/lstack_light/go-light/pkg/server/respData" + "strconv" + "{{.Module}}/test/model/bto" +) + +var ( + {{.Name}}ServiceImpl = &{{.Self}}{} +) + +type {{.Self}} struct { +} + +func ({{.SelfPrefix}} *{{.Self}}) FindAll() *respData.Response { + result := struct { + Items []bto.{{.Name}} `json:"items"` + Total int `json:"total"` + }{ + Items: []bto.{{.Name}}{}, + Total: 0, + } + return respData.SuccessResponse(result, "操作成功") +} + +func ({{.SelfPrefix}} *{{.Self}}) FindOne(param *bto.{{.Name}}) *respData.Response { + return respData.SuccessResponse(param, "操作成功") +} + +func ({{.SelfPrefix}} *{{.Self}}) CreateOne(param *bto.{{.Name}}) *respData.Response { + return respData.SuccessResponse(param, "操作成功") +} + +func ({{.SelfPrefix}} *{{.Self}}) UpdateOne(param *bto.{{.Name}}) *respData.Response { + return respData.SuccessResponse(param, "操作成功") +} + +func ({{.SelfPrefix}} *{{.Self}}) DeleteOne(param *bto.{{.Name}}) *respData.Response { + return respData.SuccessResponse("已删除:"+strconv.Itoa(param.Id), "操作成功") +} \ No newline at end of file diff --git a/test/service/impl/user.go b/test/service/impl/user.go new file mode 100644 index 0000000000000000000000000000000000000000..03d067add1f74d619abaa52d59ec236ed1649c5b --- /dev/null +++ b/test/service/impl/user.go @@ -0,0 +1,41 @@ +package impl + +import ( + "gitee.com/lstack_light/go-light/pkg/server/respData" + "gitee.com/lstack_light/go-light/test/model/bto" + "strconv" +) + +var ( + UserServiceImpl = &userServiceImpl{} +) + +type userServiceImpl struct { +} + +func (u *userServiceImpl) FindAll() *respData.Response { + result := struct { + Items []bto.User `json:"items"` + Total int `json:"total"` + }{ + Items: []bto.User{}, + Total: 0, + } + return respData.SuccessResponse(result, "操作成功") +} + +func (u *userServiceImpl) FindOne(param *bto.User) *respData.Response { + return respData.SuccessResponse(param, "操作成功") +} + +func (u *userServiceImpl) CreateOne(param *bto.User) *respData.Response { + return respData.SuccessResponse(param, "操作成功") +} + +func (u *userServiceImpl) UpdateOne(param *bto.User) *respData.Response { + return respData.SuccessResponse(param, "操作成功") +} + +func (u *userServiceImpl) DeleteOne(param *bto.User) *respData.Response { + return respData.SuccessResponse("已删除:"+strconv.Itoa(param.Id), "操作成功") +} diff --git a/test/service/service.go.tmpl b/test/service/service.go.tmpl new file mode 100644 index 0000000000000000000000000000000000000000..9fbff259d32b9edd1d6a7283f6e43702e732b743 --- /dev/null +++ b/test/service/service.go.tmpl @@ -0,0 +1,12 @@ +package service + +import ( + "{{.Module}}/test/model/bto" + "gitee.com/lstack_light/go-light/pkg/server/respData" +) + +type {{.Name}}Service interface{ + {{range.Methods}} + {{.Name}}({{.Params}}) *respData.Response + {{end}} +} \ No newline at end of file diff --git a/test/service/user.go b/test/service/user.go new file mode 100644 index 0000000000000000000000000000000000000000..771bcea0bed66714fc94dcd78bb08a0980172df0 --- /dev/null +++ b/test/service/user.go @@ -0,0 +1,18 @@ +package service + +import ( + "gitee.com/lstack_light/go-light/pkg/server/respData" + "gitee.com/lstack_light/go-light/test/model/bto" +) + +type UserService interface { + FindAll() *respData.Response + + FindOne(param *bto.User) *respData.Response + + CreateOne(param *bto.User) *respData.Response + + UpdateOne(param *bto.User) *respData.Response + + DeleteOne(param *bto.User) *respData.Response +}