diff --git a/cmd/aqi/main.go b/cmd/aqi/main.go new file mode 100644 index 0000000..fb5fc54 --- /dev/null +++ b/cmd/aqi/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/wonli/aqi/internal/cli" +) + +func main() { + cli.Execute() +} diff --git a/go.mod b/go.mod index 5e6a93a..21a6dd9 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,13 @@ require ( github.com/hibiken/asynq v0.24.1 github.com/redis/go-redis/v9 v9.0.3 github.com/shirou/gopsutil/v3 v3.24.4 + github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 github.com/tidwall/gjson v1.17.3 go.uber.org/zap v1.27.0 golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa + golang.org/x/time v0.5.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 gorm.io/datatypes v1.2.5 @@ -48,6 +50,7 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -87,7 +90,6 @@ require ( golang.org/x/net v0.32.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.5.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 72870a7..6b7e8bd 100644 --- a/go.sum +++ b/go.sum @@ -32,6 +32,7 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -103,6 +104,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hibiken/asynq v0.24.1 h1:+5iIEAyA9K/lcSPvx3qoPtsKJeKI5u9aOIvUmSsazEw= github.com/hibiken/asynq v0.24.1/go.mod h1:u5qVeSbrnfT+vtG5Mq8ZPzQu/BmCKMHvTGb91uy9Tts= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= @@ -177,6 +180,7 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -194,6 +198,8 @@ github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNo github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= @@ -283,6 +289,7 @@ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/cli/cli.go b/internal/cli/cli.go new file mode 100644 index 0000000..ff9052b --- /dev/null +++ b/internal/cli/cli.go @@ -0,0 +1,14 @@ +package cli + +import ( + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "aqi", + Short: "AQI Project Generator", +} + +func Execute() { + cobra.CheckErr(rootCmd.Execute()) +} diff --git a/internal/cli/cli_new.go b/internal/cli/cli_new.go new file mode 100644 index 0000000..ad31d67 --- /dev/null +++ b/internal/cli/cli_new.go @@ -0,0 +1,114 @@ +package cli + +import ( + "fmt" + "html/template" + "os" + "path/filepath" + + "github.com/spf13/cobra" +) + +var packageNameFlag string + +var newCmd = &cobra.Command{ + Use: "new [project-name]", + Short: "Create new project skeleton", + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + projectName := args[0] + + packageName := projectName + if packageNameFlag != "" { + packageName = packageNameFlag + } + + createDir(projectName) + createProject(projectName, packageName) + }, +} + +func init() { + newCmd.Flags().StringVarP(&packageNameFlag, "package", "p", "", "Specify package name (optional, defaults to project name)") + rootCmd.AddCommand(newCmd) +} + +func createDir(projectName string) { + // 检查目录是否存在 + dirs := []struct { + dirName string + }{ + {"cmd"}, + {"internal/app"}, + {"internal/dbc"}, + {"internal/entity"}, + {"internal/middlewares"}, + {"internal/router"}, + } + + for _, d := range dirs { + dirPath := filepath.Join(projectName, d.dirName) + if err := os.MkdirAll(dirPath, 0755); err != nil { + fmt.Printf("Error creating directory: %v\n", err) + os.Exit(1) + } + } +} + +func createProject(name, packageName string) { + // 复制模板文件 + templates := []struct { + templatePath string + outputPath string + }{ + {"templates/default/main.go.tmpl", "main.go"}, + {"templates/default/go.mod.tmpl", "go.mod"}, + {"templates/default/makefile.tmpl", "Makefile"}, + {"templates/default/cmd/api.go.tmpl", "cmd/api.go"}, + {"templates/default/cmd/dal.go.tmpl", "cmd/dal.go"}, + {"templates/default/cmd/boot.go.tmpl", "cmd/boot.go"}, + {"templates/default/dbc/dbc.go.tmpl", "internal/dbc/dbc.go"}, + {"templates/default/middlewares/app.go.tmpl", "internal/middlewares/app.go"}, + {"templates/default/middlewares/recovery.go.tmpl", "internal/middlewares/recovery.go"}, + {"templates/default/middlewares/cors.go.tmpl", "internal/middlewares/cors.go"}, + {"templates/default/router/action.go.tmpl", "internal/router/action.go"}, + {"templates/default/router/api.go.tmpl", "internal/router/api.go"}, + } + + for _, t := range templates { + // 从嵌入的文件系统读取模板 + tmplContent, err := templateFS.ReadFile(t.templatePath) + if err != nil { + fmt.Printf("Error reading template: %v\n", err) + os.Exit(1) + } + + // 解析模板 + tmpl, err := template.New(t.outputPath).Parse(string(tmplContent)) + if err != nil { + fmt.Printf("Error parsing template: %v\n", err) + os.Exit(1) + } + + outputFile, err := os.Create(filepath.Join(name, t.outputPath)) + if err != nil { + fmt.Printf("Error creating file: %v\n", err) + os.Exit(1) + } + + data := struct { + PackageName string + }{ + PackageName: packageName, + } + + if err := tmpl.Execute(outputFile, data); err != nil { + fmt.Printf("Error executing template: %v\n", err) + os.Exit(1) + } + + _ = outputFile.Close() + } + + fmt.Printf("Successfully created project: %s\n", name) +} diff --git a/internal/cli/cli_version.go b/internal/cli/cli_version.go new file mode 100644 index 0000000..48c8996 --- /dev/null +++ b/internal/cli/cli_version.go @@ -0,0 +1,18 @@ +package cli + +import ( + "fmt" + "github.com/spf13/cobra" +) + +var verCmd = &cobra.Command{ + Use: "version", + Short: "Version of this CLI", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Version: 1.0") + }, +} + +func init() { + rootCmd.AddCommand(verCmd) +} diff --git a/internal/cli/templates.go b/internal/cli/templates.go new file mode 100644 index 0000000..fa79902 --- /dev/null +++ b/internal/cli/templates.go @@ -0,0 +1,8 @@ +package cli + +import ( + "embed" +) + +//go:embed templates/default/*.tmpl templates/default/**/*.tmpl +var templateFS embed.FS diff --git a/internal/cli/templates/default/cmd/api.go.tmpl b/internal/cli/templates/default/cmd/api.go.tmpl new file mode 100644 index 0000000..c09cfc4 --- /dev/null +++ b/internal/cli/templates/default/cmd/api.go.tmpl @@ -0,0 +1,35 @@ +package cmd + +import ( + "github.com/gin-gonic/gin" + "github.com/spf13/cobra" + "github.com/wonli/aqi" + + "{{.PackageName}}/internal/dbc" + "{{.PackageName}}/internal/router" +) + +func init() { + rootCmd.AddCommand(api) +} + +var api = &cobra.Command{ + Use: "api", + Short: "启动API", + Run: func(cmd *cobra.Command, args []string) { + app := aqi.Init( + aqi.ConfigFile(configFile), + aqi.HttpServer("Api", "apiPort"), + ) + + dbc.InitDBC() + + g := gin.Default() + + go router.Api(g) + go router.Actions(app) + + app.WithHttpServer(g) + app.Start() + }, +} diff --git a/internal/cli/templates/default/cmd/boot.go.tmpl b/internal/cli/templates/default/cmd/boot.go.tmpl new file mode 100644 index 0000000..65c6d9d --- /dev/null +++ b/internal/cli/templates/default/cmd/boot.go.tmpl @@ -0,0 +1,37 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "strings" + _ "time/tzdata" + + "github.com/spf13/cobra" +) + +var timezone string +var configFile string + +func init() { + rootCmd.PersistentFlags().StringVarP(&timezone, "tz", "t", "default", "指定时区") + rootCmd.PersistentFlags().StringVarP(&configFile, "config", "c", "config.yaml", "指定配置文件路径,默认当前路径下config.yaml") + cobra.OnInitialize(func() { + }) +} + +var runApp = filepath.Base(strings.TrimLeft(os.Args[0], "./")) +var rootCmd = &cobra.Command{ + Use: runApp, + Version: "{{.PackageName}}", + Run: func(cmd *cobra.Command, args []string) { + _ = cmd.Help() + }, +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/internal/cli/templates/default/cmd/dal.go.tmpl b/internal/cli/templates/default/cmd/dal.go.tmpl new file mode 100644 index 0000000..523b6ba --- /dev/null +++ b/internal/cli/templates/default/cmd/dal.go.tmpl @@ -0,0 +1,84 @@ +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/wonli/aqi" + "golang.org/x/text/cases" + "golang.org/x/text/language" + "gorm.io/gen" + "gorm.io/gorm" + "strings" + + "{{.PackageName}}/internal/dbc" +) + +func init() { + rootCmd.AddCommand(dalCmd) +} + +var dalCmd = &cobra.Command{ + Use: "dal", + Short: "生成数据库表对应的model和dal", + Run: func(cmd *cobra.Command, args []string) { + + aqi.Init( + aqi.ConfigFile(configFile), + ) + + dbc.InitDBC() + + g := gen.NewGenerator(gen.Config{ + OutPath: "./internal/entity/dal", + Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface, + FieldSignable: true, // 无符号整数类型字段 + FieldNullable: true, // 数据库中的字段可为空,则生成struct字段为指针类型 + FieldCoverable: true, // 如果数据库中字段有默认值,则生成指针类型的字段,以避免零值(zero-value)问题 + FieldWithIndexTag: true, // 为结构体生成gorm index tag + FieldWithTypeTag: true, // 为结构体生成gorm type tag + }) + + g.WithDataTypeMap(map[string]func(columnType gorm.ColumnType) string{ + "json": func(columnType gorm.ColumnType) string { + return "datatypes.JSON" + }, + }) + + prefixToRemove := viper.GetString("mysql.logic.prefix") + g.WithTableNameStrategy(func(tableName string) string { + // 去掉指定的前缀 + if strings.HasPrefix(tableName, prefixToRemove) { + return strings.TrimPrefix(tableName, prefixToRemove) + } + return tableName + }) + + g.WithJSONTagNameStrategy(func(columnName string) string { + parts := strings.Split(columnName, "_") + titleTransformer := cases.Title(language.English) + + for i := range parts { + parts[i] = titleTransformer.String(parts[i]) + } + + // 将首字母转换为小写 + if len(parts) > 0 { + parts[0] = strings.ToLower(parts[0][:1]) + parts[0][1:] + } + + return strings.Join(parts, "") + }) + + logic := dbc.LogicDB.Use() + g.UseDB(logic) + g.ApplyInterface(func(Filter) {}, g.GenerateAllTable()...) + + g.Execute() + }, +} + +type Filter interface { + // FilterWithColumn + // SELECT * FROM @@table WHERE @@column=@value + FilterWithColumn(column string, value string) ([]*gen.T, error) +} diff --git a/internal/cli/templates/default/dbc/dbc.go.tmpl b/internal/cli/templates/default/dbc/dbc.go.tmpl new file mode 100644 index 0000000..d4bcb2d --- /dev/null +++ b/internal/cli/templates/default/dbc/dbc.go.tmpl @@ -0,0 +1,13 @@ +package dbc + +import ( + "github.com/wonli/aqi/store" +) + +var Redis *store.RedisStore +var LogicDB *store.MySQLStore + +func InitDBC() { + LogicDB = store.DB("mysql.logic") + Redis = store.Redis("redis.store") +} diff --git a/internal/cli/templates/default/go.mod.tmpl b/internal/cli/templates/default/go.mod.tmpl new file mode 100644 index 0000000..da3af2a --- /dev/null +++ b/internal/cli/templates/default/go.mod.tmpl @@ -0,0 +1,14 @@ +module {{.PackageName}} + +go 1.24 + +require ( + github.com/gin-gonic/gin v1.10.1 + github.com/spf13/cobra v1.9.1 + github.com/spf13/viper v1.20.1 + github.com/wonli/aqi v1.3.1 + golang.org/x/text v0.25.0 + gorm.io/gen v0.3.27 + gorm.io/gorm v1.30.0 + gorm.io/plugin/dbresolver v1.6.0 +) diff --git a/internal/cli/templates/default/main.go.tmpl b/internal/cli/templates/default/main.go.tmpl new file mode 100644 index 0000000..f962708 --- /dev/null +++ b/internal/cli/templates/default/main.go.tmpl @@ -0,0 +1,7 @@ +package main + +import "{{.PackageName}}/cmd" + +func main() { + cmd.Execute() +} diff --git a/internal/cli/templates/default/makefile.tmpl b/internal/cli/templates/default/makefile.tmpl new file mode 100644 index 0000000..15ced9a --- /dev/null +++ b/internal/cli/templates/default/makefile.tmpl @@ -0,0 +1,51 @@ +APP_NAME = {{.PackageName}} +APP_PATH = . + +# 编译路径 +BUILD_PATH := ./dist + +# 编译时间 +BUILD_DATE = $(shell date +'%F %T') + +ifeq ($(shell test -d .git && echo yes),yes) +# git已初始化,可以安全执行git命令 +GIT_BRANCH = $(shell git rev-parse --abbrev-ref HEAD) +GIT_COMMIT = $(shell git rev-list --count HEAD) +GIT_REVISION = $(shell git rev-parse --short HEAD) +GIT_COMMITAT = $(shell git --no-pager log -1 --format="%at") +else +# git未初始化,相关变量设为默认值 +GIT_BRANCH = - +GIT_COMMIT = - +GIT_REVISION = - +GIT_COMMITAT = - +endif + +# support包名称 +FLAGS_PKG = github.com/wonli/aqi +LDFLAGS = "-X '$(FLAGS_PKG).BuildDate=$(BUILD_DATE)' \ + -X '$(FLAGS_PKG).Branch=$(GIT_BRANCH)' \ + -X '$(FLAGS_PKG).CommitVersion=$(GIT_COMMIT)' \ + -X '$(FLAGS_PKG).Revision=$(GIT_REVISION)' \ + -extldflags '-static -s -w'" + +# 编译参数 +GO_FLAGS = -ldflags $(LDFLAGS) -trimpath -tags netgo + +# Go编译命令 1-GOOS 2-GOARCH 3-FILE EXT +define go/build + go mod tidy + GOOS=$(1) GOARCH=$(2) CGO_ENABLED=0 go build $(GO_FLAGS) -o $(BUILD_PATH)/$(APP_NAME)-$(1)-$(2)-latest$(3) ${APP_PATH} +endef + +# Generate binaries +.PHONY: build darwin linux windows + +darwin: + $(call go/build,darwin,arm64) + +linux: + $(call go/build,linux,amd64) + +windows: + $(call go/build,windows,amd64,.exe) \ No newline at end of file diff --git a/internal/cli/templates/default/middlewares/app.go.tmpl b/internal/cli/templates/default/middlewares/app.go.tmpl new file mode 100644 index 0000000..c736865 --- /dev/null +++ b/internal/cli/templates/default/middlewares/app.go.tmpl @@ -0,0 +1,42 @@ +package middlewares + +import ( + "github.com/wonli/aqi/ws" +) + +func App() ws.HandlerFunc { + return func(a *ws.Context) { + if a.Client.AppId == "" || a.Client.Platform == "" { + appId := a.Client.HttpRequest.URL.Query().Get("appId") + platform := a.Client.HttpRequest.URL.Query().Get("platform") + + //获取storeId + if platform == "" { + a.SendCode(10003, "不支持的平台") + a.Abort() + return + } + + a.Client.AppId = appId + a.Client.Platform = platform + } + + // app登录 + if a.Client.AppId == "app" && a.Client.ClientId == "" { + clientId := a.Client.HttpRequest.URL.Query().Get("clientId") + if clientId == "" { + a.SendCode(10013, "client不能为空") + a.Abort() + return + } + + a.Client.ClientId = clientId + err := a.Client.Hub.UserLogin(clientId, a.Client.AppId, a.Client) + if err != nil { + a.SendCode(10013, "登录失败") + a.Abort() + return + } + } + } +} diff --git a/internal/cli/templates/default/middlewares/cors.go.tmpl b/internal/cli/templates/default/middlewares/cors.go.tmpl new file mode 100644 index 0000000..ef3f082 --- /dev/null +++ b/internal/cli/templates/default/middlewares/cors.go.tmpl @@ -0,0 +1,21 @@ +package middlewares + +import "github.com/gin-gonic/gin" + +func GinCORS() gin.HandlerFunc { + return func(c *gin.Context) { + // 设置响应头中的Access-Control-Allow-Origin为"*",允许所有域名的跨域请求。 + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") + c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length") + c.Writer.Header().Set("Access-Control-Allow-Credentials", "false") + + // 如果请求方法是OPTIONS,直接返回200,不继续后续的处理逻辑。 + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(200) + } else { + c.Next() + } + } +} diff --git a/internal/cli/templates/default/middlewares/recovery.go.tmpl b/internal/cli/templates/default/middlewares/recovery.go.tmpl new file mode 100644 index 0000000..da86b2c --- /dev/null +++ b/internal/cli/templates/default/middlewares/recovery.go.tmpl @@ -0,0 +1,24 @@ +package middlewares + +import ( + "github.com/wonli/aqi/logger" + "github.com/wonli/aqi/ws" + "runtime/debug" +) + +func Recovery() ws.HandlerFunc { + return func(a *ws.Context) { + defer func() { + if err := recover(); err != nil { + // 获取 panic 发生的堆栈跟踪 + stack := debug.Stack() + logger.SugarLog.Errorf("Panic happened: %s \n %s\n", err, stack) + + a.SendCode(30, "服务维护中") + a.Abort() + } + }() + + a.Next() + } +} diff --git a/internal/cli/templates/default/router/action.go.tmpl b/internal/cli/templates/default/router/action.go.tmpl new file mode 100644 index 0000000..cddf2ab --- /dev/null +++ b/internal/cli/templates/default/router/action.go.tmpl @@ -0,0 +1,15 @@ +package router + +import ( + "github.com/wonli/aqi" + "github.com/wonli/aqi/ws" + + "{{.PackageName}}/internal/middlewares" +) + +func Actions(e *aqi.AppConfig) { + app := ws.NewRouter().Use(middlewares.Recovery(), middlewares.App()) + app.Add("hi", func(a *ws.Context) { + a.Send(a.Params) + }) +} diff --git a/internal/cli/templates/default/router/api.go.tmpl b/internal/cli/templates/default/router/api.go.tmpl new file mode 100644 index 0000000..0f39a38 --- /dev/null +++ b/internal/cli/templates/default/router/api.go.tmpl @@ -0,0 +1,22 @@ +package router + +import ( + "github.com/gin-gonic/gin" + "github.com/wonli/aqi/ws" + "time" + + "{{.PackageName}}/internal/middlewares" +) + +func Api(g *gin.Engine) { + g.Use(middlewares.GinCORS()) + g.GET("/ok", func(c *gin.Context) { + c.JSON(200, gin.H{ + "t": time.Now().Unix(), + }) + }) + + g.GET("/ws", func(c *gin.Context) { + ws.HttpHandler(c.Writer, c.Request) + }) +}