support database postgresql and tidb

This commit is contained in:
zhuyasen 2024-02-08 16:35:33 +08:00
parent 948b3f0b36
commit a56a20263a
83 changed files with 2050 additions and 725 deletions

8
.github/RELEASE.md vendored
View File

@ -1,4 +1,8 @@
## Change log
- Add auto test scripts.
- Repack gorm package.
- Added generate database initialization code command.
- Added support for postgresql, tidb generation code.
- Modify the Sponge UI interface.
- Adjust database configuration in configuration.
- Added generate k8s configmap script command.
- The generated code version matches the local sponge version.

View File

@ -169,7 +169,7 @@ image-build-rpc-test:
.PHONY: patch
# patch some dependent code, such as types.proto, mysql initialization code. e.g. make patch TYPE=types-pb , make patch TYPE=mysql-init
# patch some dependent code, such as types.proto, mysql initialization code. e.g. make patch TYPE=types-pb , make patch TYPE=init-mysql, make patch TYPE=init-postgresql
patch:
@bash scripts/patch.sh $(TYPE)

View File

@ -301,7 +301,7 @@ message ListUserExampleByIDsReply {
message ListUserExampleByLastIDRequest {
uint64 lastID = 1 [(tagger.tags) = "form:\"lastID\""]; // last id
uint32 limit = 2 [(validate.rules).uint32.gt = 0, (tagger.tags) = "form:\"limit\""]; // page size
uint32 limit = 2 [(validate.rules).uint32.gt = 0, (tagger.tags) = "form:\"limit\""]; // limit size per page
string sort = 3 [(tagger.tags) = "form:\"sort\""]; // sort by column name of table, default is -id, the - sign indicates descending order.
}

View File

@ -20,9 +20,9 @@ func Close(servers []app.IServer) []app.Close {
closes = append(closes, s.Stop)
}
// close mysql
// close database
closes = append(closes, func() error {
return model.CloseMysql()
return model.CloseDB()
})
// close redis

View File

@ -45,8 +45,8 @@ func InitApp() {
logger.Info("init logger succeeded")
// initializing database
model.InitMysql()
logger.Info("init mysql succeeded")
model.InitDB()
logger.Infof("init %s succeeded", cfg.Database.Driver)
model.InitCache(cfg.App.CacheType)
// initializing tracing

View File

@ -20,9 +20,9 @@ func Close(servers []app.IServer) []app.Close {
closes = append(closes, s.Stop)
}
// close mysql
// close database
//closes = append(closes, func() error {
// return model.CloseMysql()
// return model.CloseDB()
//})
// close redis

View File

@ -46,8 +46,8 @@ func InitApp() {
logger.Info("init logger succeeded")
// initializing database
//model.InitMysql()
//logger.Info("init mysql succeeded")
//model.InitDB()
//logger.Infof("init %s succeeded", cfg.Database.Driver)
//model.InitCache(cfg.App.CacheType)
// initializing tracing

View File

@ -20,9 +20,9 @@ func Close(servers []app.IServer) []app.Close {
closes = append(closes, s.Stop)
}
// close mysql
// close database
closes = append(closes, func() error {
return model.CloseMysql()
return model.CloseDB()
})
// close redis

View File

@ -45,8 +45,8 @@ func InitApp() {
logger.Info("init logger succeeded")
// initializing database
model.InitMysql()
logger.Info("init mysql succeeded")
model.InitDB()
logger.Infof("init %s succeeded", cfg.Database.Driver)
model.InitCache(cfg.App.CacheType)
// initializing tracing

View File

@ -20,9 +20,9 @@ func Close(servers []app.IServer) []app.Close {
closes = append(closes, s.Stop)
}
// close mysql
// close database
//closes = append(closes, func() error {
// return model.CloseMysql()
// return model.CloseDB()
//})
// close redis

View File

@ -46,8 +46,8 @@ func InitApp() {
logger.Info("init logger succeeded")
// initializing database
//model.InitMysql()
//logger.Info("init mysql succeeded")
//model.InitDB()
//logger.Infof("init %s succeeded", cfg.Database.Driver)
//model.InitCache(cfg.App.CacheType)
// initializing tracing

View File

@ -20,9 +20,9 @@ func Close(servers []app.IServer) []app.Close {
closes = append(closes, s.Stop)
}
// close mysql
// close database
closes = append(closes, func() error {
return model.CloseMysql()
return model.CloseDB()
})
// close redis

View File

@ -45,8 +45,8 @@ func InitApp() {
logger.Info("init logger succeeded")
// initializing database
model.InitMysql()
logger.Info("init mysql succeeded")
model.InitDB()
logger.Infof("init %s succeeded", cfg.Database.Driver)
model.InitCache(cfg.App.CacheType)
// initializing tracing

View File

@ -45,7 +45,17 @@ Examples:
}
var err error
outPath, err = runCacheCommand(moduleName, cacheName, prefixKey, keyName, keyType, valueName, valueType, outPath)
var g = &stringCacheGenerator{
moduleName: moduleName,
cacheName: cacheName,
prefixKey: prefixKey,
keyName: keyName,
keyType: keyType,
valueName: valueName,
valueType: valueType,
outPath: outPath,
}
outPath, err = g.generateCode()
if err != nil {
return err
}
@ -78,18 +88,28 @@ using help:
return cmd
}
func runCacheCommand(moduleName string, cacheName string, prefixKey string, keyName string,
keyType string, valueName string, valueType string, outPath string) (string, error) {
type stringCacheGenerator struct {
moduleName string
cacheName string
prefixKey string
keyName string
keyType string
valueName string
valueType string
outPath string
}
func (g *stringCacheGenerator) generateCode() (string, error) {
subTplName := "cache"
r := Replacers[TplNameSponge]
if r == nil {
return "", errors.New("replacer is nil")
}
if prefixKey == "" || prefixKey == ":" {
prefixKey = cacheName + ":"
} else if prefixKey[len(prefixKey)-1] != ':' {
prefixKey += ":"
if g.prefixKey == "" || g.prefixKey == ":" {
g.prefixKey = g.cacheName + ":"
} else if g.prefixKey[len(g.prefixKey)-1] != ':' {
g.prefixKey += ":"
}
// setting up template information
@ -106,9 +126,9 @@ func runCacheCommand(moduleName string, cacheName string, prefixKey string, keyN
r.SetSubDirsAndFiles(subDirs)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := addCacheFields(moduleName, r, cacheName, prefixKey, keyName, keyType, valueName, valueType)
fields := g.addFields(r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, subTplName)
_ = r.SetOutputDir(g.outPath, subTplName)
if err := r.SaveFiles(); err != nil {
return "", err
}
@ -116,22 +136,21 @@ func runCacheCommand(moduleName string, cacheName string, prefixKey string, keyN
return r.GetOutputDir(), nil
}
func addCacheFields(moduleName string, r replacer.Replacer, cacheName string, prefixKey string, keyName string,
keyType string, valueName string, valueType string) []replacer.Field {
func (g *stringCacheGenerator) addFields(r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
fields = append(fields, deleteFieldsMark(r, cacheFile, startMark, endMark)...)
// match the case where the value type is a pointer
if valueType[0] == '*' {
if g.valueType[0] == '*' {
fields = append(fields, []replacer.Field{
{
Old: "var valueNameExample valueTypeExample",
New: fmt.Sprintf("%s := &%s{}", valueName, valueType[1:]),
New: fmt.Sprintf("%s := &%s{}", g.valueName, g.valueType[1:]),
IsCaseSensitive: false,
},
{
Old: "&valueNameExample",
New: valueName,
New: g.valueName,
IsCaseSensitive: false,
},
}...)
@ -140,36 +159,36 @@ func addCacheFields(moduleName string, r replacer.Replacer, cacheName string, pr
fields = append(fields, []replacer.Field{
{
Old: "github.com/zhufuyi/sponge/internal/model",
New: moduleName + "/internal/model",
New: g.moduleName + "/internal/model",
},
{
Old: "cacheNameExample",
New: cacheName,
New: g.cacheName,
IsCaseSensitive: true,
},
{
Old: "prefixKeyExample:",
New: prefixKey,
New: g.prefixKey,
IsCaseSensitive: false,
},
{
Old: "keyNameExample",
New: keyName,
New: g.keyName,
IsCaseSensitive: false,
},
{
Old: "keyTypeExample",
New: keyType,
New: g.keyType,
IsCaseSensitive: false,
},
{
Old: "valueNameExample",
New: valueName,
New: g.valueName,
IsCaseSensitive: false,
},
{
Old: "valueTypeExample",
New: valueType,
New: g.valueType,
IsCaseSensitive: false,
},
}...)

View File

@ -2,6 +2,7 @@
package generate
import (
"bufio"
"bytes"
"errors"
"fmt"
@ -19,12 +20,22 @@ import (
const (
// TplNameSponge name of the template
TplNameSponge = "sponge"
// DBDriverMysql mysql driver
DBDriverMysql = "mysql"
// DBDriverPostgresql postgresql driver
DBDriverPostgresql = "postgresql"
// DBDriverTidb tidb driver
DBDriverTidb = "tidb"
)
var (
modelFile = "model/userExample.go"
modelFileMark = "// todo generate model code to here"
modelInitDBFile = "model/init.go"
modelInitDBFileMark = "// todo generate initialisation database code here"
cacheFile = "cache/cacheNameExample.go"
daoFile = "dao/userExample.go"
@ -35,6 +46,10 @@ var (
handlerFileMark = "// todo generate the request and response struct to here"
handlerTestFile = "handler/userExample_test.go"
handlerLogicFile = "handler/userExample_logic.go"
serviceLogicFile = "service/userExample.go"
embedTimeMark = "// todo generate the conversion createdAt and updatedAt code here"
httpFile = "server/http.go"
protoFile = "v1/userExample.proto"
@ -69,8 +84,16 @@ var (
protoShellFileGRPCMark = "# todo generate grpc files here"
protoShellFileMark = "# todo generate api template code command here"
appConfigFile = "configs/serverNameExample.yml"
appConfigFileMark = "# todo generate http or rpc server configuration here"
appConfigFile = "configs/serverNameExample.yml"
appConfigFileMark = "# todo generate http or rpc server configuration here"
appConfigFileMark2 = "# todo generate the database configuration here"
deploymentConfigFile = "kubernetes/serverNameExample-configmap.yml"
deploymentConfigFileMark = "# todo generate the database configuration for deployment here"
spongeTemplateVersionMark = "// todo generate the local sponge template code version here"
configmapFileMark = "# todo generate server configuration code here"
readmeFile = "sponge/README.md"
makeFile = "sponge/Makefile"
@ -87,6 +110,13 @@ var (
selfPackageName = "github.com/zhufuyi/sponge"
)
var (
ModelInitDBFile = modelInitDBFile
ModelInitDBFileMark = modelInitDBFileMark
StartMark = startMark
EndMark = endMark
)
func symbolConvert(str string, additionalChar ...string) []byte {
char := ""
if len(additionalChar) > 0 {
@ -154,6 +184,11 @@ func deleteFieldsMark(r replacer.Replacer, filename string, startMark []byte, en
return fields
}
// DeleteCodeMark delete code mark fragment
func DeleteCodeMark(r replacer.Replacer, filename string, startMark []byte, endMark []byte) []replacer.Field {
return deleteFieldsMark(r, filename, startMark, endMark)
}
func deleteAllFieldsMark(r replacer.Replacer, filename string, startMark []byte, endMark []byte) []replacer.Field {
var fields []replacer.Field
@ -336,3 +371,114 @@ func replacePackage(data []byte, moduleName string, serverName string) []byte {
return data
}
func getDBConfigCode(dbDriver string, forDeployment ...bool) string {
isDeployment := false
if len(forDeployment) > 0 {
isDeployment = forDeployment[0]
}
dbConfigCode := ""
switch strings.ToLower(dbDriver) {
case DBDriverMysql, DBDriverTidb:
if isDeployment {
dbConfigCode = mysqlConfigForDeploymentCode
} else {
dbConfigCode = mysqlConfigCode
}
case DBDriverPostgresql:
if isDeployment {
dbConfigCode = postgresqlConfigForDeploymentCode
} else {
dbConfigCode = postgresqlConfigCode
}
default:
panic("getDBConfigCode error, unsupported database driver: " + dbDriver)
}
return dbConfigCode
}
func getInitDBCode(dbDriver string) string {
initDBCode := ""
switch strings.ToLower(dbDriver) {
case DBDriverMysql, DBDriverTidb:
initDBCode = modelInitDBFileMysqlCode
case DBDriverPostgresql:
initDBCode = modelInitDBFilePostgresqlCode
default:
panic("getInitDBCode error, unsupported database driver: " + dbDriver)
}
return initDBCode
}
// GetInitDataBaseCode get init db code
func GetInitDataBaseCode(dbDriver string) string {
return getInitDBCode(dbDriver)
}
func getLocalSpongeTemplateVersion() string {
dir, err := os.UserHomeDir()
if err != nil {
fmt.Println("os.UserHomeDir error:", err)
return ""
}
versionFile := dir + "/.sponge/.github/version"
data, err := os.ReadFile(versionFile)
if err != nil {
fmt.Printf("read file %s error: %v\n", versionFile, err)
return ""
}
v := string(data)
if v == "" {
return ""
}
return fmt.Sprintf("github.com/zhufuyi/sponge %s", v)
}
func getEmbedTimeCode(isEmbed bool) string {
if isEmbed {
return embedTimeCode
}
return ""
}
func convertYamlConfig(configFile string) (string, error) {
f, err := os.Open(configFile)
if err != nil {
return "", err
}
defer f.Close() //nolint
scanner := bufio.NewScanner(f)
modifiedLines := []string{}
for scanner.Scan() {
line := scanner.Text()
modifiedLine := " " + line
modifiedLines = append(modifiedLines, modifiedLine)
}
if err := scanner.Err(); err != nil {
return "", err
}
return strings.Join(modifiedLines, "\n"), nil
}
func generateConfigmap(serverName string, outPath string) error {
configFile := fmt.Sprintf(outPath+"/configs/%s.yml", serverName)
configmapFile := fmt.Sprintf(outPath+"/deployments/kubernetes/%s-configmap.yml", serverName)
configFileData, err := convertYamlConfig(configFile)
if err != nil {
return err
}
configmapFileData, err := os.ReadFile(configmapFile)
if err != nil {
return err
}
data := strings.ReplaceAll(string(configmapFileData), configmapFileMark, configFileData)
return os.WriteFile(configmapFile, []byte(data), 0666)
}

View File

@ -0,0 +1,126 @@
package generate
import (
"errors"
"fmt"
"github.com/zhufuyi/sponge/pkg/replacer"
"github.com/huandu/xstrings"
"github.com/spf13/cobra"
)
// ConfigmapCommand generate k8s configmap command
func ConfigmapCommand() *cobra.Command {
var (
serverName = ""
projectName = ""
configFile = ""
outPath = ""
)
cmd := &cobra.Command{
Use: "configmap",
Short: "Generate k8s configmap",
Long: `generate k8s configmap.
Examples:
# generate k8s configmap
sponge configmap --server-name=yourServerName --project-name=yourProjectName --config-file=yourConfigFile.yml
# generate grpc connection code and specify the server directory, Note: code generation will be canceled when the latest generated file already exists.
sponge configmap --server-name=yourServerName --project-name=yourProjectName --config-file=yourConfigFile.yml --out=./yourServerDir
`,
SilenceErrors: true,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
content, err := convertYamlConfig(configFile)
if err != nil {
return err
}
g := copyConfigGenerator{
serverName: serverName,
projectName: projectName,
content: content,
outPath: outPath,
}
outPath, err = g.generateCode()
if err != nil {
return err
}
fmt.Printf("\ngenerate \"configmap\" code successfully, out = %s\n", outPath)
return nil
},
}
cmd.Flags().StringVarP(&serverName, "server-name", "s", "", "server name")
_ = cmd.MarkFlagRequired("server-name")
cmd.Flags().StringVarP(&projectName, "project-name", "p", "", "project name")
_ = cmd.MarkFlagRequired("project-name")
cmd.Flags().StringVarP(&configFile, "config-file", "f", "", "server config file")
cmd.Flags().StringVarP(&outPath, "out", "o", "", "output directory, default is ./configmap_<time>")
return cmd
}
type copyConfigGenerator struct {
serverName string
projectName string
content string
outPath string
}
func (g *copyConfigGenerator) generateCode() (string, error) {
subTplName := "configmap"
r := Replacers[TplNameSponge]
if r == nil {
return "", errors.New("replacer is nil")
}
// setting up template information
subDirs := []string{ // only the specified subdirectory is processed, if empty or no subdirectory is specified, it means all files
"deployments/kubernetes",
}
ignoreDirs := []string{} // specify the directory in the subdirectory where processing is ignored
ignoreFiles := []string{ // specify the files in the subdirectory to be ignored for processing
"projectNameExample-namespace.yml", "README.md", "serverNameExample-deployment.yml", "serverNameExample-svc.yml",
}
r.SetSubDirsAndFiles(subDirs)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := g.addFields()
r.SetReplacementFields(fields)
_ = r.SetOutputDir(g.outPath, subTplName)
if err := r.SaveFiles(); err != nil {
return "", err
}
return r.GetOutputDir(), nil
}
func (g *copyConfigGenerator) addFields() []replacer.Field {
var fields []replacer.Field
fields = append(fields, []replacer.Field{
{
Old: configmapFileMark,
New: g.content,
},
{
Old: "serverNameExample",
New: g.serverName,
IsCaseSensitive: true,
},
{
Old: "server-name-example",
New: xstrings.ToKebabCase(g.serverName), // snake_case to kebab_case
},
{
Old: "project-name-example",
New: g.projectName,
},
}...)
return fields
}

View File

@ -74,7 +74,13 @@ Examples:
isIncludeInitDB = false
}
outPath, err = runGenDaoCommand(moduleName, isIncludeInitDB, codes, outPath)
g := &daoGenerator{
moduleName: moduleName,
isIncludeInitDB: isIncludeInitDB,
codes: codes,
outPath: outPath,
}
outPath, err = g.generateCode()
if err != nil {
return err
}
@ -90,9 +96,10 @@ using help:
},
}
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql")
cmd.Flags().StringVarP(&moduleName, "module-name", "m", "", "module-name is the name of the module in the go.mod file")
//_ = cmd.MarkFlagRequired("module-name")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "db content addr, e.g. user:password@(host:port)/database")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database")
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
_ = cmd.MarkFlagRequired("db-table")
@ -105,7 +112,14 @@ using help:
return cmd
}
func runGenDaoCommand(moduleName string, isIncludeInitDB bool, codes map[string]string, outPath string) (string, error) {
type daoGenerator struct {
moduleName string
isIncludeInitDB bool
codes map[string]string
outPath string
}
func (g *daoGenerator) generateCode() (string, error) {
subTplName := "dao"
r := Replacers[TplNameSponge]
if r == nil {
@ -121,7 +135,7 @@ func runGenDaoCommand(moduleName string, isIncludeInitDB bool, codes map[string]
"init.go", "init_test.go", // internal/model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", // internal/cache
}
if isIncludeInitDB {
if g.isIncludeInitDB {
ignoreFiles = []string{
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", // internal/cache
}
@ -130,9 +144,9 @@ func runGenDaoCommand(moduleName string, isIncludeInitDB bool, codes map[string]
r.SetSubDirsAndFiles(subDirs)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := addDAOFields(moduleName, r, codes)
fields := g.addFields(r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, subTplName)
_ = r.SetOutputDir(g.outPath, subTplName)
if err := r.SaveFiles(); err != nil {
return "", err
}
@ -141,38 +155,42 @@ func runGenDaoCommand(moduleName string, isIncludeInitDB bool, codes map[string]
}
// set fields
func addDAOFields(moduleName string, r replacer.Replacer, codes map[string]string) []replacer.Field {
func (g *daoGenerator) addFields(r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
fields = append(fields, deleteFieldsMark(r, modelFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoTestFile, startMark, endMark)...)
fields = append(fields, []replacer.Field{
{
{ // replace the contents of the model/userExample.go file
Old: modelFileMark,
New: codes[parser.CodeTypeModel],
New: g.codes[parser.CodeTypeModel],
},
{
Old: daoFileMark,
New: codes[parser.CodeTypeDAO],
New: g.codes[parser.CodeTypeDAO],
},
{
Old: selfPackageName + "/" + r.GetSourcePath(),
New: moduleName,
New: g.moduleName,
},
{
Old: "github.com/zhufuyi/sponge",
New: moduleName,
New: g.moduleName,
},
{
Old: moduleName + "/pkg",
Old: g.moduleName + "/pkg",
New: "github.com/zhufuyi/sponge/pkg",
},
{
Old: "UserExample",
New: codes[parser.TableName],
New: g.codes[parser.TableName],
IsCaseSensitive: true,
},
{
Old: "github.com/zhufuyi/sponge/pkg/ggorm",
New: "user/pkg/ggorm",
},
}...)
return fields

View File

@ -77,7 +77,14 @@ Examples:
return err
}
outPath, err = runGenHandlerPbCommand(moduleName, serverName, codes, outPath)
g := &handlerPbGenerator{
moduleName: moduleName,
serverName: serverName,
isEmbed: sqlArgs.IsEmbed,
codes: codes,
outPath: outPath,
}
outPath, err = g.generateCode()
if err != nil {
return err
}
@ -96,11 +103,12 @@ using help:
},
}
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql")
cmd.Flags().StringVarP(&moduleName, "module-name", "m", "", "module-name is the name of the module in the go.mod file")
//_ = cmd.MarkFlagRequired("module-name")
cmd.Flags().StringVarP(&serverName, "server-name", "s", "", "server name")
//_ = cmd.MarkFlagRequired("server-name")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "db content addr, e.g. user:password@(host:port)/database")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database")
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
_ = cmd.MarkFlagRequired("db-table")
@ -112,15 +120,23 @@ using help:
return cmd
}
func runGenHandlerPbCommand(moduleName string, serverName string, codes map[string]string, outPath string) (string, error) {
type handlerPbGenerator struct {
moduleName string
serverName string
isEmbed bool
codes map[string]string
outPath string
}
func (g *handlerPbGenerator) generateCode() (string, error) {
subTplName := "handler-pb"
r := Replacers[TplNameSponge]
if r == nil {
return "", errors.New("replacer is nil")
}
if serverName == "" {
serverName = moduleName
if g.serverName == "" {
g.serverName = g.moduleName
}
// setting up template information
@ -138,9 +154,9 @@ func runGenHandlerPbCommand(moduleName string, serverName string, codes map[stri
r.SetSubDirsAndFiles(subDirs)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := addHandlerPbFields(moduleName, serverName, r, codes)
fields := g.addFields(r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, subTplName)
_ = r.SetOutputDir(g.outPath, subTplName)
if err := r.SaveFiles(); err != nil {
return "", err
}
@ -148,54 +164,59 @@ func runGenHandlerPbCommand(moduleName string, serverName string, codes map[stri
return r.GetOutputDir(), nil
}
func addHandlerPbFields(moduleName string, serverName string, r replacer.Replacer, codes map[string]string) []replacer.Field {
func (g *handlerPbGenerator) addFields(r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
fields = append(fields, deleteFieldsMark(r, modelFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoTestFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, handlerLogicFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, protoFile, startMark, endMark)...)
fields = append(fields, []replacer.Field{
{ // replace the contents of the model/userExample.go file
Old: modelFileMark,
New: codes[parser.CodeTypeModel],
New: g.codes[parser.CodeTypeModel],
},
{ // replace the contents of the dao/userExample.go file
Old: daoFileMark,
New: codes[parser.CodeTypeDAO],
New: g.codes[parser.CodeTypeDAO],
},
{ // replace the contents of the handler/userExample_logic.go file
Old: embedTimeMark,
New: getEmbedTimeCode(g.isEmbed),
},
{ // replace the contents of the v1/userExample.proto file
Old: protoFileMark,
New: codes[parser.CodeTypeProto],
New: g.codes[parser.CodeTypeProto],
},
{
Old: selfPackageName + "/" + r.GetSourcePath(),
New: moduleName,
New: g.moduleName,
},
{
Old: "github.com/zhufuyi/sponge",
New: moduleName,
New: g.moduleName,
},
// replace directory name
{
Old: strings.Join([]string{"api", "serverNameExample", "v1"}, gofile.GetPathDelimiter()),
New: strings.Join([]string{"api", serverName, "v1"}, gofile.GetPathDelimiter()),
New: strings.Join([]string{"api", g.serverName, "v1"}, gofile.GetPathDelimiter()),
},
{
Old: "api/serverNameExample/v1",
New: fmt.Sprintf("api/%s/v1", serverName),
New: fmt.Sprintf("api/%s/v1", g.serverName),
},
// Note: protobuf package no "-" signs allowed
{
Old: "api.serverNameExample.v1",
New: fmt.Sprintf("api.%s.v1", serverName),
New: fmt.Sprintf("api.%s.v1", g.serverName),
},
{
Old: "userExampleNO = 1",
New: fmt.Sprintf("userExampleNO = %d", rand.Intn(100)),
},
{
Old: moduleName + "/pkg",
Old: g.moduleName + "/pkg",
New: "github.com/zhufuyi/sponge/pkg",
},
{
@ -209,13 +230,17 @@ func addHandlerPbFields(moduleName string, serverName string, r replacer.Replace
},
{
Old: "serverNameExample",
New: serverName,
New: g.serverName,
},
{
Old: "UserExample",
New: codes[parser.TableName],
New: g.codes[parser.TableName],
IsCaseSensitive: true,
},
{
Old: "github.com/zhufuyi/sponge/pkg/ggorm",
New: "user/pkg/ggorm",
},
}...)
return fields

View File

@ -67,7 +67,12 @@ Examples:
return err
}
outPath, err = runGenHandlerCommand(moduleName, codes, outPath)
g := &handlerGenerator{
moduleName: moduleName,
codes: codes,
outPath: outPath,
}
outPath, err = g.generateCode()
if err != nil {
return err
}
@ -78,7 +83,7 @@ using help:
1. move the folder "internal" to your project code folder.
2. open a terminal and execute the command: make docs
3. compile and run service: make run
4. visit http://localhost:8080/swagger/index.html to your browser, and test the CRUD api interface.
4. visit http://localhost:8080/swagger/index.html in your browser, and test the CRUD api interface.
`)
fmt.Printf("generate \"handler\" code successfully, out = %s\n", outPath)
@ -86,9 +91,10 @@ using help:
},
}
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql")
cmd.Flags().StringVarP(&moduleName, "module-name", "m", "", "module-name is the name of the module in the go.mod file")
//_ = cmd.MarkFlagRequired("module-name")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "db content addr, e.g. user:password@(host:port)/database")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database")
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
_ = cmd.MarkFlagRequired("db-table")
@ -100,7 +106,13 @@ using help:
return cmd
}
func runGenHandlerCommand(moduleName string, codes map[string]string, outPath string) (string, error) {
type handlerGenerator struct {
moduleName string
codes map[string]string
outPath string
}
func (g *handlerGenerator) generateCode() (string, error) {
subTplName := "handler"
r := Replacers[TplNameSponge]
if r == nil {
@ -123,9 +135,9 @@ func runGenHandlerCommand(moduleName string, codes map[string]string, outPath st
r.SetSubDirsAndFiles(subDirs)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := addHandlerFields(moduleName, r, codes)
fields := g.addFields(r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, subTplName)
_ = r.SetOutputDir(g.outPath, subTplName)
if err := r.SaveFiles(); err != nil {
return "", err
}
@ -133,7 +145,7 @@ func runGenHandlerCommand(moduleName string, codes map[string]string, outPath st
return r.GetOutputDir(), nil
}
func addHandlerFields(moduleName string, r replacer.Replacer, codes map[string]string) []replacer.Field {
func (g *handlerGenerator) addFields(r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
fields = append(fields, deleteFieldsMark(r, modelFile, startMark, endMark)...)
@ -144,37 +156,41 @@ func addHandlerFields(moduleName string, r replacer.Replacer, codes map[string]s
fields = append(fields, []replacer.Field{
{ // replace the contents of the model/userExample.go file
Old: modelFileMark,
New: codes[parser.CodeTypeModel],
New: g.codes[parser.CodeTypeModel],
},
{ // replace the contents of the dao/userExample.go file
Old: daoFileMark,
New: codes[parser.CodeTypeDAO],
New: g.codes[parser.CodeTypeDAO],
},
{ // replace the contents of the handler/userExample.go file
Old: handlerFileMark,
New: adjustmentOfIDType(codes[parser.CodeTypeHandler]),
New: adjustmentOfIDType(g.codes[parser.CodeTypeHandler]),
},
{
Old: selfPackageName + "/" + r.GetSourcePath(),
New: moduleName,
New: g.moduleName,
},
{
Old: "github.com/zhufuyi/sponge",
New: moduleName,
New: g.moduleName,
},
{
Old: "userExampleNO = 1",
New: fmt.Sprintf("userExampleNO = %d", rand.Intn(100)),
},
{
Old: moduleName + "/pkg",
Old: g.moduleName + "/pkg",
New: "github.com/zhufuyi/sponge/pkg",
},
{
Old: "UserExample",
New: codes[parser.TableName],
New: g.codes[parser.TableName],
IsCaseSensitive: true,
},
{
Old: "github.com/zhufuyi/sponge/pkg/ggorm",
New: "user/pkg/ggorm",
},
}...)
return fields

View File

@ -39,8 +39,23 @@ Examples:
SilenceErrors: true,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
var err error
projectName, serverName = convertProjectAndServerName(projectName, serverName)
return runGenHTTPPbCommand(moduleName, serverName, projectName, protobufFile, repoAddr, outPath)
g := &httpPbGenerator{
moduleName: moduleName,
serverName: serverName,
projectName: projectName,
protobufFile: protobufFile,
repoAddr: repoAddr,
outPath: outPath,
}
outPath, err = g.generateCode()
if err != nil {
return err
}
_ = generateConfigmap(serverName, outPath)
return nil
},
}
@ -59,16 +74,25 @@ Examples:
return cmd
}
func runGenHTTPPbCommand(moduleName string, serverName string, projectName string, protobufFile string, repoAddr string, outPath string) error {
protobufFiles, isImportTypes, err := parseProtobufFiles(protobufFile)
type httpPbGenerator struct {
moduleName string
serverName string
projectName string
protobufFile string
repoAddr string
outPath string
}
func (g *httpPbGenerator) generateCode() (string, error) {
protobufFiles, isImportTypes, err := parseProtobufFiles(g.protobufFile)
if err != nil {
return err
return "", err
}
subTplName := "http-pb"
r := Replacers[TplNameSponge]
if r == nil {
return errors.New("replacer is nil")
return "", errors.New("replacer is nil")
}
// setting up template information
@ -97,15 +121,15 @@ func runGenHTTPPbCommand(moduleName string, serverName string, projectName strin
r.SetSubDirsAndFiles(subDirs, subFiles...)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := addHTTPPbFields(moduleName, serverName, projectName, repoAddr, r)
fields := g.addFields(r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, serverName+"_"+subTplName)
_ = r.SetOutputDir(g.outPath, g.serverName+"_"+subTplName)
if err = r.SaveFiles(); err != nil {
return err
return "", err
}
_ = saveProtobufFiles(moduleName, serverName, r.GetOutputDir(), protobufFiles)
_ = saveGenInfo(moduleName, serverName, r.GetOutputDir())
_ = saveProtobufFiles(g.moduleName, g.serverName, r.GetOutputDir(), protobufFiles)
_ = saveGenInfo(g.moduleName, g.serverName, r.GetOutputDir())
_ = saveEmptySwaggerJSON(r.GetOutputDir())
fmt.Printf(`
@ -116,15 +140,15 @@ using help:
4. visit http://localhost:8080/apis/swagger/index.html in your browser, and test api interface.
`)
fmt.Printf("generate %s's web service code successfully, out = %s\n", serverName, r.GetOutputDir())
return nil
outpath := r.GetOutputDir()
fmt.Printf("generate %s's web service code successfully, out = %s\n", g.serverName, outpath)
return outpath, nil
}
func addHTTPPbFields(moduleName string, serverName string, projectName string, repoAddr string,
r replacer.Replacer) []replacer.Field {
func (g *httpPbGenerator) addFields(r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
repoHost, _ := parseImageRepoAddr(repoAddr)
repoHost, _ := parseImageRepoAddr(g.repoAddr)
fields = append(fields, deleteFieldsMark(r, httpFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, dockerFile, wellStartMark, wellEndMark)...)
@ -137,9 +161,22 @@ func addHTTPPbFields(moduleName string, serverName string, projectName string, r
fields = append(fields, deleteAllFieldsMark(r, makeFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, gitIgnoreFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteAllFieldsMark(r, protoShellFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, appConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, replaceFileContentMark(r, readmeFile, "## "+serverName)...)
fields = append(fields, deleteAllFieldsMark(r, appConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, deploymentConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, replaceFileContentMark(r, readmeFile, "## "+g.serverName)...)
fields = append(fields, []replacer.Field{
{ // replace the configuration of the *.yml file
Old: appConfigFileMark,
New: httpServerConfigCode,
},
{ // replace the configuration of the *.yml file
Old: appConfigFileMark2,
New: getDBConfigCode(DBDriverMysql),
},
{ // replace the contents of the model/init.go file
Old: modelInitDBFileMark,
New: getInitDBCode(DBDriverMysql), // default is mysql
},
{ // replace the contents of the Dockerfile file
Old: dockerFileMark,
New: dockerFileHTTPCode,
@ -160,6 +197,10 @@ func addHTTPPbFields(moduleName string, serverName string, projectName string, r
Old: dockerComposeFileMark,
New: dockerComposeFileHTTPCode,
},
{ // replace the contents of the *-configmap.yml file
Old: deploymentConfigFileMark,
New: getDBConfigCode(DBDriverMysql, true),
},
{ // replace the contents of the *-deployment.yml file
Old: k8sDeploymentFileMark,
New: k8sDeploymentFileHTTPCode,
@ -168,10 +209,6 @@ func addHTTPPbFields(moduleName string, serverName string, projectName string, r
Old: k8sServiceFileMark,
New: k8sServiceFileHTTPCode,
},
{ // replace the configuration of the *.yml file
Old: appConfigFileMark,
New: httpServerConfigCode,
},
{ // replace the contents of the proto.sh file
Old: protoShellFileGRPCMark,
New: "",
@ -182,33 +219,41 @@ func addHTTPPbFields(moduleName string, serverName string, projectName string, r
},
{
Old: "github.com/zhufuyi/sponge",
New: moduleName,
New: g.moduleName,
},
{
Old: moduleName + "/pkg",
Old: g.moduleName + "/pkg",
New: "github.com/zhufuyi/sponge/pkg",
},
{ // replace the sponge version of the go.mod file
Old: spongeTemplateVersionMark,
New: getLocalSpongeTemplateVersion(),
},
{
Old: "sponge api docs",
New: serverName + " api docs",
New: g.serverName + " api docs",
},
{
Old: "serverNameExample",
New: serverName,
New: g.serverName,
},
// docker image and k8s deployment script replacement
{
Old: "server-name-example",
New: xstrings.ToKebabCase(serverName), // convert to kebab-case format
New: xstrings.ToKebabCase(g.serverName), // convert to kebab-case format
},
// docker image and k8s deployment script replacement
{
Old: "project-name-example",
New: projectName,
New: g.projectName,
},
{
Old: "projectNameExample",
New: g.projectName,
},
{
Old: "repo-addr-example",
New: repoAddr,
New: g.repoAddr,
},
{
Old: "image-repo-host",

View File

@ -72,7 +72,17 @@ Examples:
if err != nil {
return err
}
outPath, err = runGenHTTPCommand(moduleName, serverName, projectName, repoAddr, sqlArgs.DBDsn, codes, outPath)
g := &httpGenerator{
moduleName: moduleName,
serverName: serverName,
projectName: projectName,
repoAddr: repoAddr,
dbDSN: sqlArgs.DBDsn,
dbDriver: sqlArgs.DBDriver,
codes: codes,
outPath: outPath,
}
outPath, err = g.generateCode()
if err != nil {
return err
}
@ -88,7 +98,12 @@ Examples:
return err
}
outPath, err = runGenHandlerCommand(moduleName, codes, outPath)
hg := &handlerGenerator{
moduleName: moduleName,
codes: codes,
outPath: outPath,
}
outPath, err = hg.generateCode()
if err != nil {
return err
}
@ -102,17 +117,20 @@ using help:
`)
fmt.Printf("generate %s's web service code successfully, out = %s\n", serverName, outPath)
_ = generateConfigmap(serverName, outPath)
return nil
},
}
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql")
cmd.Flags().StringVarP(&moduleName, "module-name", "m", "", "module-name is the name of the module in the go.mod file")
_ = cmd.MarkFlagRequired("module-name")
cmd.Flags().StringVarP(&serverName, "server-name", "s", "", "server name")
_ = cmd.MarkFlagRequired("server-name")
cmd.Flags().StringVarP(&projectName, "project-name", "p", "", "project name")
_ = cmd.MarkFlagRequired("project-name")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "db content addr, e.g. user:password@(host:port)/database")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database")
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
_ = cmd.MarkFlagRequired("db-table")
@ -124,8 +142,18 @@ using help:
return cmd
}
func runGenHTTPCommand(moduleName string, serverName string, projectName string, repoAddr string,
dbDSN string, codes map[string]string, outPath string) (string, error) {
type httpGenerator struct {
moduleName string
serverName string
projectName string
repoAddr string
dbDSN string
dbDriver string
codes map[string]string
outPath string
}
func (g *httpGenerator) generateCode() (string, error) {
subTplName := "http"
r := Replacers[TplNameSponge]
if r == nil {
@ -157,24 +185,24 @@ func runGenHTTPCommand(moduleName string, serverName string, projectName string,
r.SetSubDirsAndFiles(subDirs, subFiles...)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := addHTTPFields(moduleName, serverName, projectName, repoAddr, r, dbDSN, codes)
fields := g.addFields(r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, serverName+"_"+subTplName)
_ = r.SetOutputDir(g.outPath, g.serverName+"_"+subTplName)
if err := r.SaveFiles(); err != nil {
return "", err
}
_ = saveGenInfo(moduleName, serverName, r.GetOutputDir())
_ = saveGenInfo(g.moduleName, g.serverName, r.GetOutputDir())
return r.GetOutputDir(), nil
}
func addHTTPFields(moduleName string, serverName string, projectName string, repoAddr string,
r replacer.Replacer, dbDSN string, codes map[string]string) []replacer.Field {
func (g *httpGenerator) addFields(r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
repoHost, _ := parseImageRepoAddr(repoAddr)
repoHost, _ := parseImageRepoAddr(g.repoAddr)
fields = append(fields, deleteFieldsMark(r, modelFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, modelInitDBFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoTestFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, handlerFile, startMark, endMark)...)
@ -190,20 +218,33 @@ func addHTTPFields(moduleName string, serverName string, projectName string, rep
//fields = append(fields, deleteAllFieldsMark(r, makeFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, gitIgnoreFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteAllFieldsMark(r, protoShellFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, appConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, replaceFileContentMark(r, readmeFile, "## "+serverName)...)
fields = append(fields, deleteAllFieldsMark(r, appConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, deploymentConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, replaceFileContentMark(r, readmeFile, "## "+g.serverName)...)
fields = append(fields, []replacer.Field{
{ // replace the configuration of the *.yml file
Old: appConfigFileMark,
New: httpServerConfigCode,
},
{ // replace the configuration of the *.yml file
Old: appConfigFileMark2,
New: getDBConfigCode(g.dbDriver),
},
{ // replace the contents of the model/userExample.go file
Old: modelFileMark,
New: codes[parser.CodeTypeModel],
New: g.codes[parser.CodeTypeModel],
},
{ // replace the contents of the model/init.go file
Old: modelInitDBFileMark,
New: getInitDBCode(g.dbDriver),
},
{ // replace the contents of the dao/userExample.go file
Old: daoFileMark,
New: codes[parser.CodeTypeDAO],
New: g.codes[parser.CodeTypeDAO],
},
{ // replace the contents of the handler/userExample.go file
Old: handlerFileMark,
New: adjustmentOfIDType(codes[parser.CodeTypeHandler]),
New: adjustmentOfIDType(g.codes[parser.CodeTypeHandler]),
},
{ // replace the contents of the Dockerfile file
Old: dockerFileMark,
@ -225,6 +266,10 @@ func addHTTPFields(moduleName string, serverName string, projectName string, rep
Old: dockerComposeFileMark,
New: dockerComposeFileHTTPCode,
},
{ // replace the contents of the *-configmap.yml file
Old: deploymentConfigFileMark,
New: getDBConfigCode(g.dbDriver, true),
},
{ // replace the contents of the *-deployment.yml file
Old: k8sDeploymentFileMark,
New: k8sDeploymentFileHTTPCode,
@ -233,14 +278,9 @@ func addHTTPFields(moduleName string, serverName string, projectName string, rep
Old: k8sServiceFileMark,
New: k8sServiceFileHTTPCode,
},
{ // replace the configuration of the *.yml file
Old: appConfigFileMark,
New: httpServerConfigCode,
},
// replace github.com/zhufuyi/sponge/templates/sponge
{
{ // replace github.com/zhufuyi/sponge/templates/sponge
Old: selfPackageName + "/" + r.GetSourcePath(),
New: moduleName,
New: g.moduleName,
},
{
Old: protoShellFileGRPCMark,
@ -252,15 +292,19 @@ func addHTTPFields(moduleName string, serverName string, projectName string, rep
},
{
Old: "github.com/zhufuyi/sponge",
New: moduleName,
New: g.moduleName,
},
{
Old: moduleName + "/pkg",
Old: g.moduleName + "/pkg",
New: "github.com/zhufuyi/sponge/pkg",
},
{ // replace the sponge version of the go.mod file
Old: spongeTemplateVersionMark,
New: getLocalSpongeTemplateVersion(),
},
{
Old: "sponge api docs",
New: serverName + " api docs",
New: g.serverName + " api docs",
},
{
Old: "userExampleNO = 1",
@ -268,21 +312,25 @@ func addHTTPFields(moduleName string, serverName string, projectName string, rep
},
{
Old: "serverNameExample",
New: serverName,
New: g.serverName,
},
// docker image and k8s deployment script replacement
{
Old: "server-name-example",
New: xstrings.ToKebabCase(serverName), // snake_case to kebab_case
New: xstrings.ToKebabCase(g.serverName), // snake_case to kebab_case
},
// docker image and k8s deployment script replacement
{
Old: "project-name-example",
New: projectName,
New: g.projectName,
},
{
Old: "projectNameExample",
New: g.projectName,
},
{
Old: "repo-addr-example",
New: repoAddr,
New: g.repoAddr,
},
{
Old: "image-repo-host",
@ -302,7 +350,11 @@ func addHTTPFields(moduleName string, serverName string, projectName string, rep
},
{
Old: "root:123456@(192.168.3.37:3306)/account",
New: dbDSN,
New: g.dbDSN,
},
{
Old: "root:123456@192.168.3.37:5432/account",
New: g.dbDSN,
},
{
Old: "Makefile-for-http",
@ -310,9 +362,13 @@ func addHTTPFields(moduleName string, serverName string, projectName string, rep
},
{
Old: "UserExample",
New: codes[parser.TableName],
New: g.codes[parser.TableName],
IsCaseSensitive: true,
},
{
Old: "github.com/zhufuyi/sponge/pkg/ggorm",
New: "user/pkg/ggorm",
},
}...)
return fields

View File

@ -58,7 +58,11 @@ Examples:
return err
}
outPath, err = runGenModelCommand(codes, outPath)
g := &modelGenerator{
codes: codes,
outPath: outPath,
}
outPath, err = g.generateCode()
if err != nil {
return err
}
@ -74,7 +78,8 @@ using help:
},
}
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "db content addr, e.g. user:password@(host:port)/database")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database")
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
_ = cmd.MarkFlagRequired("db-table")
@ -85,7 +90,12 @@ using help:
return cmd
}
func runGenModelCommand(codes map[string]string, outPath string) (string, error) {
type modelGenerator struct {
codes map[string]string
outPath string
}
func (g *modelGenerator) generateCode() (string, error) {
subTplName := "model"
r := Replacers[TplNameSponge]
if r == nil {
@ -102,9 +112,9 @@ func runGenModelCommand(codes map[string]string, outPath string) (string, error)
r.SetSubDirsAndFiles(subDirs)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := addModelFields(r, codes)
fields := g.addFields(r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, subTplName)
_ = r.SetOutputDir(g.outPath, subTplName)
if err := r.SaveFiles(); err != nil {
return "", err
}
@ -112,20 +122,24 @@ func runGenModelCommand(codes map[string]string, outPath string) (string, error)
return r.GetOutputDir(), nil
}
func addModelFields(r replacer.Replacer, codes map[string]string) []replacer.Field {
func (g *modelGenerator) addFields(r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
fields = append(fields, deleteFieldsMark(r, modelFile, startMark, endMark)...)
fields = append(fields, []replacer.Field{
{ // replace the contents of the model/userExample.go file
Old: modelFileMark,
New: codes[parser.CodeTypeModel],
New: g.codes[parser.CodeTypeModel],
},
{
Old: "UserExample",
New: codes[parser.TableName],
New: g.codes[parser.TableName],
IsCaseSensitive: true,
},
{
Old: "github.com/zhufuyi/sponge/pkg/ggorm",
New: "user/pkg/ggorm",
},
}...)
return fields

View File

@ -13,8 +13,8 @@ import (
"github.com/spf13/cobra"
)
// ProtoBufCommand generate protobuf code
func ProtoBufCommand() *cobra.Command {
// ProtobufCommand generate protobuf code
func ProtobufCommand() *cobra.Command {
var (
moduleName string // module name for go.mod
serverName string // server name
@ -73,7 +73,13 @@ Examples:
return err
}
outPath, err = runGenProtoCommand(moduleName, serverName, codes, outPath)
g := &protobufGenerator{
moduleName: moduleName,
serverName: serverName,
codes: codes,
outPath: outPath,
}
outPath, err = g.generateCode()
if err != nil {
return err
}
@ -90,11 +96,12 @@ using help:
},
}
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql")
cmd.Flags().StringVarP(&moduleName, "module-name", "m", "", "module-name is the name of the module in the go.mod file")
//_ = cmd.MarkFlagRequired("module-name")
cmd.Flags().StringVarP(&serverName, "server-name", "s", "", "server name")
//_ = cmd.MarkFlagRequired("server-name")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "db content addr, e.g. user:password@(host:port)/database")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database")
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
_ = cmd.MarkFlagRequired("db-table")
@ -106,15 +113,22 @@ using help:
return cmd
}
func runGenProtoCommand(moduleName string, serverName string, codes map[string]string, outPath string) (string, error) {
type protobufGenerator struct {
moduleName string
serverName string
codes map[string]string
outPath string
}
func (g *protobufGenerator) generateCode() (string, error) {
subTplName := "protobuf"
r := Replacers[TplNameSponge]
if r == nil {
return "", errors.New("replacer is nil")
}
if serverName == "" {
serverName = moduleName
if g.serverName == "" {
g.serverName = g.moduleName
}
// setting up template information
@ -129,9 +143,9 @@ func runGenProtoCommand(moduleName string, serverName string, codes map[string]s
r.SetSubDirsAndFiles(subDirs)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := addProtoFields(moduleName, serverName, r, codes)
fields := g.addFields(r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, subTplName)
_ = r.SetOutputDir(g.outPath, subTplName)
if err := r.SaveFiles(); err != nil {
return "", err
}
@ -139,40 +153,40 @@ func runGenProtoCommand(moduleName string, serverName string, codes map[string]s
return r.GetOutputDir(), nil
}
func addProtoFields(moduleName string, serverName string, r replacer.Replacer, codes map[string]string) []replacer.Field {
func (g *protobufGenerator) addFields(r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
fields = append(fields, deleteFieldsMark(r, protoFile, startMark, endMark)...)
fields = append(fields, []replacer.Field{
{ // replace the contents of the v1/userExample.proto file
Old: protoFileMark,
New: codes[parser.CodeTypeProto],
New: g.codes[parser.CodeTypeProto],
},
{
Old: "github.com/zhufuyi/sponge",
New: moduleName,
New: g.moduleName,
},
// replace directory name
{
Old: strings.Join([]string{"api", "serverNameExample", "v1"}, gofile.GetPathDelimiter()),
New: strings.Join([]string{"api", serverName, "v1"}, gofile.GetPathDelimiter()),
New: strings.Join([]string{"api", g.serverName, "v1"}, gofile.GetPathDelimiter()),
},
{
Old: "api/serverNameExample/v1",
New: fmt.Sprintf("api/%s/v1", serverName),
New: fmt.Sprintf("api/%s/v1", g.serverName),
},
// Note: protobuf package no "-" signs allowed
{
Old: "api.serverNameExample.v1",
New: fmt.Sprintf("api.%s.v1", serverName),
New: fmt.Sprintf("api.%s.v1", g.serverName),
},
{
Old: "serverNameExample",
New: serverName,
New: g.serverName,
},
{
Old: "UserExample",
New: codes[parser.TableName],
New: g.codes[parser.TableName],
IsCaseSensitive: true,
},
}...)

View File

@ -10,12 +10,12 @@ import (
"github.com/spf13/cobra"
)
// RPCConnectionCommand generate grpc connection code
func RPCConnectionCommand() *cobra.Command {
// GRPCConnectionCommand generate grpc connection code
func GRPCConnectionCommand() *cobra.Command {
var (
moduleName string // module name for go.mod
outPath string // output directory
rpcServerNames string // grpc service names
moduleName string // module name for go.mod
outPath string // output directory
grpcServerNames string // grpc service names
)
cmd := &cobra.Command{
@ -25,7 +25,7 @@ func RPCConnectionCommand() *cobra.Command {
Examples:
# generate grpc connection code
sponge micro rpc-conn --module-name=yourModuleName --rpc-server-name=user
sponge micro rpc-conn --module-name=yourModuleName --rpc-server-name=yourGrpcName
# generate grpc connection code with multiple names.
sponge micro rpc-conn --module-name=yourModuleName --rpc-server-name=name1,name2
@ -43,14 +43,19 @@ Examples:
return errors.New(`required flag(s) "module-name" not set, use "sponge micro rpc-conn -h" for help`)
}
rpcNames := strings.Split(rpcServerNames, ",")
for _, rpcName := range rpcNames {
if rpcName == "" {
grpcNames := strings.Split(grpcServerNames, ",")
for _, grpcName := range grpcNames {
if grpcName == "" {
continue
}
var err error
outPath, err = runGenRPCConnectionCommand(moduleName, rpcName, outPath)
var g = &grpcConnectionGenerator{
moduleName: moduleName,
grpcName: grpcName,
outPath: outPath,
}
outPath, err = g.generateCode()
if err != nil {
return err
}
@ -67,7 +72,7 @@ using help:
}
cmd.Flags().StringVarP(&moduleName, "module-name", "m", "", "module-name is the name of the module in the go.mod file")
cmd.Flags().StringVarP(&rpcServerNames, "rpc-server-name", "r", "", "rpc service name, multiple names separated by commas")
cmd.Flags().StringVarP(&grpcServerNames, "rpc-server-name", "r", "", "rpc service name, multiple names separated by commas")
_ = cmd.MarkFlagRequired("rpc-server-name")
cmd.Flags().StringVarP(&outPath, "out", "o", "", "output directory, default is ./rpc-conn_<time>,"+
" if you specify the directory where the web or microservice generated by sponge, the module-name flag can be ignored")
@ -75,7 +80,13 @@ using help:
return cmd
}
func runGenRPCConnectionCommand(moduleName string, rpcName string, outPath string) (string, error) {
type grpcConnectionGenerator struct {
moduleName string
grpcName string
outPath string
}
func (g *grpcConnectionGenerator) generateCode() (string, error) {
subTplName := "rpc-conn"
r := Replacers[TplNameSponge]
if r == nil {
@ -94,9 +105,9 @@ func runGenRPCConnectionCommand(moduleName string, rpcName string, outPath strin
r.SetSubDirsAndFiles(subDirs)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := addRPCConnectionFields(moduleName, rpcName)
fields := g.addFields()
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, subTplName)
_ = r.SetOutputDir(g.outPath, subTplName)
if err := r.SaveFiles(); err != nil {
return "", err
}
@ -104,21 +115,21 @@ func runGenRPCConnectionCommand(moduleName string, rpcName string, outPath strin
return r.GetOutputDir(), nil
}
func addRPCConnectionFields(moduleName string, serverName string) []replacer.Field {
func (g *grpcConnectionGenerator) addFields() []replacer.Field {
var fields []replacer.Field
fields = append(fields, []replacer.Field{
{
Old: "github.com/zhufuyi/sponge/configs",
New: moduleName + "/configs",
New: g.moduleName + "/configs",
},
{
Old: "github.com/zhufuyi/sponge/internal/config",
New: moduleName + "/internal/config",
New: g.moduleName + "/internal/config",
},
{
Old: "serverNameExample",
New: serverName,
New: g.grpcName,
IsCaseSensitive: true,
},
}...)

View File

@ -40,7 +40,21 @@ Examples:
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
projectName, serverName = convertProjectAndServerName(projectName, serverName)
return runGenRPCGwCommand(moduleName, serverName, projectName, protobufFile, repoAddr, outPath)
g := &rpcGwPbGenerator{
moduleName: moduleName,
serverName: serverName,
projectName: projectName,
protobufFile: protobufFile,
repoAddr: repoAddr,
outPath: outPath,
}
err := g.generateCode()
if err != nil {
return err
}
_ = generateConfigmap(serverName, outPath)
return nil
},
}
@ -59,8 +73,17 @@ Examples:
return cmd
}
func runGenRPCGwCommand(moduleName string, serverName string, projectName string, protobufFile string, repoAddr string, outPath string) error {
protobufFiles, isImportTypes, err := parseProtobufFiles(protobufFile)
type rpcGwPbGenerator struct {
moduleName string
serverName string
projectName string
protobufFile string
repoAddr string
outPath string
}
func (g *rpcGwPbGenerator) generateCode() error {
protobufFiles, isImportTypes, err := parseProtobufFiles(g.protobufFile)
if err != nil {
return err
}
@ -97,15 +120,15 @@ func runGenRPCGwCommand(moduleName string, serverName string, projectName string
r.SetSubDirsAndFiles(subDirs, subFiles...)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := addRPCGwFields(moduleName, serverName, projectName, repoAddr, r)
fields := g.addFields(r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, serverName+"_"+subTplName)
_ = r.SetOutputDir(g.outPath, g.serverName+"_"+subTplName)
if err = r.SaveFiles(); err != nil {
return err
}
_ = saveProtobufFiles(moduleName, serverName, r.GetOutputDir(), protobufFiles)
_ = saveGenInfo(moduleName, serverName, r.GetOutputDir())
_ = saveProtobufFiles(g.moduleName, g.serverName, r.GetOutputDir(), protobufFiles)
_ = saveGenInfo(g.moduleName, g.serverName, r.GetOutputDir())
_ = saveEmptySwaggerJSON(r.GetOutputDir())
fmt.Printf(`
@ -116,16 +139,15 @@ using help:
4. visit http://localhost:8080/apis/swagger/index.html in your browser, and test api interface.
`)
fmt.Printf("generate %s's grpc gateway service code successfully, out = %s\n", serverName, r.GetOutputDir())
fmt.Printf("generate %s's grpc gateway service code successfully, out = %s\n", g.serverName, r.GetOutputDir())
return nil
}
func addRPCGwFields(moduleName string, serverName string, projectName string, repoAddr string,
r replacer.Replacer) []replacer.Field {
func (g *rpcGwPbGenerator) addFields(r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
repoHost, _ := parseImageRepoAddr(repoAddr)
repoHost, _ := parseImageRepoAddr(g.repoAddr)
fields = append(fields, deleteFieldsMark(r, httpFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, dockerFile, wellStartMark, wellEndMark)...)
@ -138,9 +160,26 @@ func addRPCGwFields(moduleName string, serverName string, projectName string, re
fields = append(fields, deleteAllFieldsMark(r, makeFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, gitIgnoreFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteAllFieldsMark(r, protoShellFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, appConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, replaceFileContentMark(r, readmeFile, "## "+serverName)...)
fields = append(fields, deleteAllFieldsMark(r, appConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, deploymentConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, replaceFileContentMark(r, readmeFile, "## "+g.serverName)...)
fields = append(fields, []replacer.Field{
{ // replace the configuration of the *.yml file
Old: appConfigFileMark,
New: rpcGwServerConfigCode,
},
{ // replace the configuration of the *.yml file
Old: appConfigFileMark2,
New: getDBConfigCode(DBDriverMysql),
},
{ // replace the configuration of the *.yml file
Old: appConfigFileMark,
New: "",
},
{ // replace the contents of the model/init.go file
Old: modelInitDBFileMark,
New: getInitDBCode(DBDriverMysql), // default is mysql
},
{ // replace the contents of the Dockerfile file
Old: dockerFileMark,
New: dockerFileHTTPCode,
@ -161,6 +200,10 @@ func addRPCGwFields(moduleName string, serverName string, projectName string, re
Old: dockerComposeFileMark,
New: dockerComposeFileHTTPCode,
},
{ // replace the contents of the *-configmap.yml file
Old: deploymentConfigFileMark,
New: getDBConfigCode(DBDriverMysql, true),
},
{ // replace the contents of the *-deployment.yml file
Old: k8sDeploymentFileMark,
New: k8sDeploymentFileHTTPCode,
@ -169,10 +212,6 @@ func addRPCGwFields(moduleName string, serverName string, projectName string, re
Old: k8sServiceFileMark,
New: k8sServiceFileHTTPCode,
},
{ // replace the configuration of the *.yml file
Old: appConfigFileMark,
New: rpcGwServerConfigCode,
},
{ // replace the contents of the proto.sh file
Old: protoShellFileGRPCMark,
New: protoShellGRPCMark,
@ -183,34 +222,42 @@ func addRPCGwFields(moduleName string, serverName string, projectName string, re
},
{
Old: "github.com/zhufuyi/sponge",
New: moduleName,
New: g.moduleName,
},
{
Old: moduleName + "/pkg",
Old: g.moduleName + "/pkg",
New: "github.com/zhufuyi/sponge/pkg",
},
{ // replace the sponge version of the go.mod file
Old: spongeTemplateVersionMark,
New: getLocalSpongeTemplateVersion(),
},
{
Old: "sponge api docs",
New: serverName + " api docs",
New: g.serverName + " api docs",
},
{
Old: "serverNameExample",
New: serverName,
New: g.serverName,
IsCaseSensitive: true,
},
// docker image and k8s deployment script replacement
{
Old: "server-name-example",
New: xstrings.ToKebabCase(serverName), // snake_case to kebab_case
New: xstrings.ToKebabCase(g.serverName), // snake_case to kebab_case
},
// docker image and k8s deployment script replacement
{
Old: "project-name-example",
New: projectName,
New: g.projectName,
},
{
Old: "projectNameExample",
New: g.projectName,
},
{
Old: "repo-addr-example",
New: repoAddr,
New: g.repoAddr,
},
{
Old: "image-repo-host",

View File

@ -40,7 +40,21 @@ Examples:
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
projectName, serverName = convertProjectAndServerName(projectName, serverName)
return runGenRPCPbCommand(moduleName, serverName, projectName, protobufFile, repoAddr, outPath)
g := &rpcPbGenerator{
moduleName: moduleName,
serverName: serverName,
projectName: projectName,
protobufFile: protobufFile,
repoAddr: repoAddr,
outPath: outPath,
}
err := g.generateCode()
if err != nil {
return err
}
_ = generateConfigmap(serverName, outPath)
return nil
},
}
@ -58,8 +72,17 @@ Examples:
return cmd
}
func runGenRPCPbCommand(moduleName string, serverName string, projectName string, protobufFile string, repoAddr string, outPath string) error {
protobufFiles, isImportTypes, err := parseProtobufFiles(protobufFile)
type rpcPbGenerator struct {
moduleName string
serverName string
projectName string
protobufFile string
repoAddr string
outPath string
}
func (g *rpcPbGenerator) generateCode() error {
protobufFiles, isImportTypes, err := parseProtobufFiles(g.protobufFile)
if err != nil {
return err
}
@ -95,15 +118,15 @@ func runGenRPCPbCommand(moduleName string, serverName string, projectName string
r.SetSubDirsAndFiles(subDirs, subFiles...)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := addRPCPbFields(moduleName, serverName, projectName, repoAddr, r)
fields := g.addFields(r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, serverName+"_"+subTplName)
_ = r.SetOutputDir(g.outPath, g.serverName+"_"+subTplName)
if err = r.SaveFiles(); err != nil {
return err
}
_ = saveProtobufFiles(moduleName, serverName, r.GetOutputDir(), protobufFiles)
_ = saveGenInfo(moduleName, serverName, r.GetOutputDir())
_ = saveProtobufFiles(g.moduleName, g.serverName, r.GetOutputDir(), protobufFiles)
_ = saveGenInfo(g.moduleName, g.serverName, r.GetOutputDir())
fmt.Printf(`
using help:
@ -113,14 +136,14 @@ using help:
4. open the file "internal/service/xxx_client_test.go" using Goland or VS Code, testing the grpc methods.
`)
fmt.Printf("generate %s's grpc service code successfully, out = %s\n", serverName, r.GetOutputDir())
fmt.Printf("generate %s's grpc service code successfully, out = %s\n", g.serverName, r.GetOutputDir())
return nil
}
func addRPCPbFields(moduleName string, serverName string, projectName string, repoAddr string, r replacer.Replacer) []replacer.Field {
func (g *rpcPbGenerator) addFields(r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
repoHost, _ := parseImageRepoAddr(repoAddr)
repoHost, _ := parseImageRepoAddr(g.repoAddr)
fields = append(fields, deleteFieldsMark(r, dockerFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, dockerFileBuild, wellStartMark, wellEndMark)...)
@ -132,9 +155,22 @@ func addRPCPbFields(moduleName string, serverName string, projectName string, re
fields = append(fields, deleteAllFieldsMark(r, makeFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, gitIgnoreFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteAllFieldsMark(r, protoShellFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, appConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, replaceFileContentMark(r, readmeFile, "## "+serverName)...)
fields = append(fields, deleteAllFieldsMark(r, appConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, deploymentConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, replaceFileContentMark(r, readmeFile, "## "+g.serverName)...)
fields = append(fields, []replacer.Field{
{ // replace the configuration of the *.yml file
Old: appConfigFileMark,
New: rpcServerConfigCode,
},
{ // replace the configuration of the *.yml file
Old: appConfigFileMark2,
New: getDBConfigCode(DBDriverMysql),
},
{ // replace the contents of the model/init.go file
Old: modelInitDBFileMark,
New: getInitDBCode(DBDriverMysql), // default is mysql
},
{ // replace the contents of the Dockerfile file
Old: dockerFileMark,
New: dockerFileGrpcCode,
@ -155,6 +191,10 @@ func addRPCPbFields(moduleName string, serverName string, projectName string, re
Old: dockerComposeFileMark,
New: dockerComposeFileGrpcCode,
},
{ // replace the contents of the *-configmap.yml file
Old: deploymentConfigFileMark,
New: getDBConfigCode(DBDriverMysql, true),
},
{ // replace the contents of the *-deployment.yml file
Old: k8sDeploymentFileMark,
New: k8sDeploymentFileGrpcCode,
@ -163,10 +203,6 @@ func addRPCPbFields(moduleName string, serverName string, projectName string, re
Old: k8sServiceFileMark,
New: k8sServiceFileGrpcCode,
},
{ // replace the configuration of the *.yml file
Old: appConfigFileMark,
New: rpcServerConfigCode,
},
{ // replace the contents of the proto.sh file
Old: protoShellFileGRPCMark,
New: protoShellGRPCMark,
@ -177,33 +213,41 @@ func addRPCPbFields(moduleName string, serverName string, projectName string, re
},
{
Old: "github.com/zhufuyi/sponge",
New: moduleName,
New: g.moduleName,
},
{
Old: moduleName + "/pkg",
Old: g.moduleName + "/pkg",
New: "github.com/zhufuyi/sponge/pkg",
},
{ // replace the sponge version of the go.mod file
Old: spongeTemplateVersionMark,
New: getLocalSpongeTemplateVersion(),
},
{
Old: "sponge api docs",
New: serverName + " api docs",
New: g.serverName + " api docs",
},
{
Old: "serverNameExample",
New: serverName,
New: g.serverName,
},
// docker image and k8s deployment script replacement
{
Old: "server-name-example",
New: xstrings.ToKebabCase(serverName), // snake_case to kebab_case
New: xstrings.ToKebabCase(g.serverName), // snake_case to kebab_case
},
// docker image and k8s deployment script replacement
{
Old: "project-name-example",
New: projectName,
New: g.projectName,
},
{
Old: "projectNameExample",
New: g.projectName,
},
{
Old: "repo-addr-example",
New: repoAddr,
New: g.repoAddr,
},
{
Old: "image-repo-host",

View File

@ -73,7 +73,18 @@ Examples:
if err != nil {
return err
}
outPath, err = runGenRPCCommand(moduleName, serverName, projectName, repoAddr, sqlArgs.DBDsn, codes, outPath)
g := &rpcGenerator{
moduleName: moduleName,
serverName: serverName,
projectName: projectName,
repoAddr: repoAddr,
dbDSN: sqlArgs.DBDsn,
dbDriver: sqlArgs.DBDriver,
isEmbed: sqlArgs.IsEmbed,
codes: codes,
outPath: outPath,
}
outPath, err = g.generateCode()
if err != nil {
return err
}
@ -89,7 +100,14 @@ Examples:
return err
}
outPath, err = runGenServiceCommand(moduleName, serverName, codes, outPath)
sg := &serviceGenerator{
moduleName: moduleName,
serverName: serverName,
isEmbed: sqlArgs.IsEmbed,
codes: codes,
outPath: outPath,
}
outPath, err = sg.generateCode()
if err != nil {
return err
}
@ -103,17 +121,20 @@ using help:
`)
fmt.Printf("generate %s's grpc service code successfully, out = %s\n", serverName, outPath)
_ = generateConfigmap(serverName, outPath)
return nil
},
}
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql")
cmd.Flags().StringVarP(&moduleName, "module-name", "m", "", "module-name is the name of the module in the go.mod file")
_ = cmd.MarkFlagRequired("module-name")
cmd.Flags().StringVarP(&serverName, "server-name", "s", "", "server name")
_ = cmd.MarkFlagRequired("server-name")
cmd.Flags().StringVarP(&projectName, "project-name", "p", "", "project name")
_ = cmd.MarkFlagRequired("project-name")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "db content addr, e.g. user:password@(host:port)/database")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database")
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
_ = cmd.MarkFlagRequired("db-table")
@ -125,8 +146,19 @@ using help:
return cmd
}
func runGenRPCCommand(moduleName string, serverName string, projectName string, repoAddr string,
dbDSN string, codes map[string]string, outPath string) (string, error) {
type rpcGenerator struct {
moduleName string
serverName string
projectName string
repoAddr string
dbDSN string
dbDriver string
isEmbed bool
codes map[string]string
outPath string
}
func (g *rpcGenerator) generateCode() (string, error) {
subTplName := "rpc"
r := Replacers[TplNameSponge]
if r == nil {
@ -158,27 +190,28 @@ func runGenRPCCommand(moduleName string, serverName string, projectName string,
r.SetSubDirsAndFiles(subDirs, subFiles...)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := addRPCFields(moduleName, serverName, projectName, repoAddr, r, dbDSN, codes)
fields := g.addFields(r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, serverName+"_"+subTplName)
_ = r.SetOutputDir(g.outPath, g.serverName+"_"+subTplName)
if err := r.SaveFiles(); err != nil {
return "", err
}
_ = saveGenInfo(moduleName, serverName, r.GetOutputDir())
_ = saveGenInfo(g.moduleName, g.serverName, r.GetOutputDir())
return r.GetOutputDir(), nil
}
func addRPCFields(moduleName string, serverName string, projectName string, repoAddr string,
r replacer.Replacer, dbDSN string, codes map[string]string) []replacer.Field {
func (g *rpcGenerator) addFields(r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
repoHost, _ := parseImageRepoAddr(repoAddr)
repoHost, _ := parseImageRepoAddr(g.repoAddr)
fields = append(fields, deleteFieldsMark(r, modelFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, modelInitDBFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoTestFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, protoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, serviceLogicFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, serviceClientFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, serviceTestFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, dockerFile, wellStartMark, wellEndMark)...)
@ -191,20 +224,37 @@ func addRPCFields(moduleName string, serverName string, projectName string, repo
fields = append(fields, deleteAllFieldsMark(r, makeFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, gitIgnoreFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteAllFieldsMark(r, protoShellFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, appConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, replaceFileContentMark(r, readmeFile, "## "+serverName)...)
fields = append(fields, deleteAllFieldsMark(r, appConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, deploymentConfigFile, wellStartMark, wellEndMark)...)
fields = append(fields, replaceFileContentMark(r, readmeFile, "## "+g.serverName)...)
fields = append(fields, []replacer.Field{
{ // replace the configuration of the *.yml file
Old: appConfigFileMark,
New: rpcServerConfigCode,
},
{ // replace the configuration of the *.yml file
Old: appConfigFileMark2,
New: getDBConfigCode(g.dbDriver),
},
{ // replace the contents of the model/userExample.go file
Old: modelFileMark,
New: codes[parser.CodeTypeModel],
New: g.codes[parser.CodeTypeModel],
},
{ // replace the contents of the model/init.go file
Old: modelInitDBFileMark,
New: getInitDBCode(g.dbDriver),
},
{ // replace the contents of the dao/userExample.go file
Old: daoFileMark,
New: codes[parser.CodeTypeDAO],
New: g.codes[parser.CodeTypeDAO],
},
{ // replace the contents of the handler/userExample_logic.go file
Old: embedTimeMark,
New: getEmbedTimeCode(g.isEmbed),
},
{ // replace the contents of the v1/userExample.proto file
Old: protoFileMark,
New: codes[parser.CodeTypeProto],
New: g.codes[parser.CodeTypeProto],
},
{ // replace the contents of the proto.sh file
Old: protoShellFileGRPCMark,
@ -216,7 +266,7 @@ func addRPCFields(moduleName string, serverName string, projectName string, repo
},
{ // replace the contents of the service/userExample_client_test.go file
Old: serviceFileMark,
New: adjustmentOfIDType(codes[parser.CodeTypeService]),
New: adjustmentOfIDType(g.codes[parser.CodeTypeService]),
},
{ // replace the contents of the Dockerfile file
Old: dockerFileMark,
@ -238,6 +288,10 @@ func addRPCFields(moduleName string, serverName string, projectName string, repo
Old: dockerComposeFileMark,
New: dockerComposeFileGrpcCode,
},
{ // replace the contents of the *-configmap.yml file
Old: deploymentConfigFileMark,
New: getDBConfigCode(g.dbDriver, true),
},
{ // replace the contents of the *-deployment.yml file
Old: k8sDeploymentFileMark,
New: k8sDeploymentFileGrpcCode,
@ -246,39 +300,38 @@ func addRPCFields(moduleName string, serverName string, projectName string, repo
Old: k8sServiceFileMark,
New: k8sServiceFileGrpcCode,
},
{ // replace the configuration of the *.yml file
Old: appConfigFileMark,
New: rpcServerConfigCode,
},
// replace github.com/zhufuyi/sponge/templates/sponge
{
{ // replace github.com/zhufuyi/sponge/templates/sponge
Old: selfPackageName + "/" + r.GetSourcePath(),
New: moduleName,
New: g.moduleName,
},
// replace directory name
{
Old: strings.Join([]string{"api", "userExample", "v1"}, gofile.GetPathDelimiter()),
New: strings.Join([]string{"api", serverName, "v1"}, gofile.GetPathDelimiter()),
New: strings.Join([]string{"api", g.serverName, "v1"}, gofile.GetPathDelimiter()),
},
{
Old: "github.com/zhufuyi/sponge",
New: moduleName,
New: g.moduleName,
},
{
Old: moduleName + "/pkg",
Old: g.moduleName + "/pkg",
New: "github.com/zhufuyi/sponge/pkg",
},
{ // replace the sponge version of the go.mod file
Old: spongeTemplateVersionMark,
New: getLocalSpongeTemplateVersion(),
},
{
Old: "api/userExample/v1",
New: fmt.Sprintf("api/%s/v1", serverName),
New: fmt.Sprintf("api/%s/v1", g.serverName),
},
{
Old: "api.userExample.v1",
New: fmt.Sprintf("api.%s.v1", serverName), // protobuf package no "-" signs allowed
New: fmt.Sprintf("api.%s.v1", g.serverName), // protobuf package no "-" signs allowed
},
{
Old: "sponge api docs",
New: serverName + " api docs",
New: g.serverName + " api docs",
},
{
Old: "_userExampleNO = 2",
@ -286,21 +339,25 @@ func addRPCFields(moduleName string, serverName string, projectName string, repo
},
{
Old: "serverNameExample",
New: serverName,
New: g.serverName,
},
// docker image and k8s deployment script replacement
{
Old: "server-name-example",
New: xstrings.ToKebabCase(serverName), // snake_case to kebab_case
New: xstrings.ToKebabCase(g.serverName), // snake_case to kebab_case
},
// docker image and k8s deployment script replacement
{
Old: "project-name-example",
New: projectName,
New: g.projectName,
},
{
Old: "projectNameExample",
New: g.projectName,
},
{
Old: "repo-addr-example",
New: repoAddr,
New: g.repoAddr,
},
{
Old: "image-repo-host",
@ -316,13 +373,21 @@ func addRPCFields(moduleName string, serverName string, projectName string, repo
},
{
Old: "root:123456@(192.168.3.37:3306)/account",
New: dbDSN,
New: g.dbDSN,
},
{
Old: "root:123456@192.168.3.37:5432/account",
New: g.dbDSN,
},
{
Old: "UserExample",
New: codes[parser.TableName],
New: g.codes[parser.TableName],
IsCaseSensitive: true,
},
{
Old: "github.com/zhufuyi/sponge/pkg/ggorm",
New: "user/pkg/ggorm",
},
}...)
return fields

View File

@ -76,7 +76,14 @@ Examples:
return err
}
outPath, err = runGenServiceCommand(moduleName, serverName, codes, outPath)
g := &serviceGenerator{
moduleName: moduleName,
serverName: serverName,
isEmbed: sqlArgs.IsEmbed,
codes: codes,
outPath: outPath,
}
outPath, err = g.generateCode()
if err != nil {
return err
}
@ -95,11 +102,12 @@ using help:
},
}
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql")
cmd.Flags().StringVarP(&moduleName, "module-name", "m", "", "module-name is the name of the module in the go.mod file")
//_ = cmd.MarkFlagRequired("module-name")
cmd.Flags().StringVarP(&serverName, "server-name", "s", "", "server name")
//_ = cmd.MarkFlagRequired("server-name")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "db content addr, e.g. user:password@(host:port)/database")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database")
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
_ = cmd.MarkFlagRequired("db-table")
@ -111,15 +119,23 @@ using help:
return cmd
}
func runGenServiceCommand(moduleName string, serverName string, codes map[string]string, outPath string) (string, error) {
type serviceGenerator struct {
moduleName string
serverName string
isEmbed bool
codes map[string]string
outPath string
}
func (g *serviceGenerator) generateCode() (string, error) {
subTplName := "service"
r := Replacers[TplNameSponge]
if r == nil {
return "", errors.New("replacer is nil")
}
if serverName == "" {
serverName = moduleName
if g.serverName == "" {
g.serverName = g.moduleName
}
// setting up template information
@ -137,9 +153,9 @@ func runGenServiceCommand(moduleName string, serverName string, codes map[string
r.SetSubDirsAndFiles(subDirs)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := addServiceFields(moduleName, serverName, r, codes)
fields := g.addFields(r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(outPath, subTplName)
_ = r.SetOutputDir(g.outPath, subTplName)
if err := r.SaveFiles(); err != nil {
return "", err
}
@ -147,71 +163,80 @@ func runGenServiceCommand(moduleName string, serverName string, codes map[string
return r.GetOutputDir(), nil
}
func addServiceFields(moduleName string, serverName string, r replacer.Replacer, codes map[string]string) []replacer.Field {
func (g *serviceGenerator) addFields(r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
fields = append(fields, deleteFieldsMark(r, modelFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoTestFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, serviceLogicFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, protoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, serviceClientFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, serviceTestFile, startMark, endMark)...)
fields = append(fields, []replacer.Field{
{ // replace the contents of the model/userExample.go file
Old: modelFileMark,
New: codes[parser.CodeTypeModel],
New: g.codes[parser.CodeTypeModel],
},
{ // replace the contents of the dao/userExample.go file
Old: daoFileMark,
New: codes[parser.CodeTypeDAO],
New: g.codes[parser.CodeTypeDAO],
},
{ // replace the contents of the handler/userExample_logic.go file
Old: embedTimeMark,
New: getEmbedTimeCode(g.isEmbed),
},
{ // replace the contents of the v1/userExample.proto file
Old: protoFileMark,
New: codes[parser.CodeTypeProto],
New: g.codes[parser.CodeTypeProto],
},
{ // replace the contents of the service/userExample_client_test.go file
Old: serviceFileMark,
New: adjustmentOfIDType(codes[parser.CodeTypeService]),
New: adjustmentOfIDType(g.codes[parser.CodeTypeService]),
},
{
Old: selfPackageName + "/" + r.GetSourcePath(),
New: moduleName,
New: g.moduleName,
},
{
Old: "github.com/zhufuyi/sponge",
New: moduleName,
New: g.moduleName,
},
// replace directory name
{
Old: strings.Join([]string{"api", "serverNameExample", "v1"}, gofile.GetPathDelimiter()),
New: strings.Join([]string{"api", serverName, "v1"}, gofile.GetPathDelimiter()),
New: strings.Join([]string{"api", g.serverName, "v1"}, gofile.GetPathDelimiter()),
},
{
Old: "api/serverNameExample/v1",
New: fmt.Sprintf("api/%s/v1", serverName),
New: fmt.Sprintf("api/%s/v1", g.serverName),
},
// Note: protobuf package no "-" signs allowed
{
Old: "api.serverNameExample.v1",
New: fmt.Sprintf("api.%s.v1", serverName),
New: fmt.Sprintf("api.%s.v1", g.serverName),
},
{
Old: "_userExampleNO = 2",
New: fmt.Sprintf("_userExampleNO = %d", rand.Intn(100)),
},
{
Old: moduleName + "/pkg",
Old: g.moduleName + "/pkg",
New: "github.com/zhufuyi/sponge/pkg",
},
{
Old: "serverNameExample",
New: serverName,
New: g.serverName,
},
{
Old: "UserExample",
New: codes[parser.TableName],
New: g.codes[parser.TableName],
IsCaseSensitive: true,
},
{
Old: "github.com/zhufuyi/sponge/pkg/ggorm",
New: "user/pkg/ggorm",
},
}...)
return fields

View File

@ -218,7 +218,7 @@ func Show(hiddenFields ...string) string {
func Get() *Config {
if config == nil {
panic("config is nil")
panic("config is nil, please call config.Init() first")
}
return config
}
@ -346,77 +346,254 @@ func NewCenter(configFile string) (*Center, error) {
httpServerConfigCode = `# http server settings
http:
port: 8080 # listen port
readTimeout: 3 # read timeout, unit(second)
writeTimeout: 60 # write timeout, unit(second), if enableHTTPProfile is true, it needs to be greater than 60s, the default value for pprof to do profiling is 60s`
port: 8080 # listen port
readTimeout: 5 # read timeout, unit(second)
writeTimeout: 5 # write timeout, unit(second), if enableHTTPProfile is true, it needs to be greater than 60s, the default value for pprof to do profiling is 60s`
rpcServerConfigCode = `# grpc service settings
rpcServerConfigCode = `# grpc server settings
grpc:
port: 8282 # listen port
httpPort: 8283 # profile and metrics ports
readTimeout: 5 # read timeout, unit(second)
writeTimeout: 5 # write timeout, unit(second)
enableToken: false # whether to enable server-side token authentication, default appID=grpc, appKey=123456
port: 8282 # listen port
httpPort: 8283 # profile and metrics ports
readTimeout: 5 # read timeout, unit(second)
writeTimeout: 5 # write timeout, unit(second)
enableToken: false # whether to enable server-side token authentication, default appID=grpc, appKey=123456
# serverSecure parameter setting
# if type="", it means no secure connection, no need to fill in any parameters
# if type="one-way", it means server-side certification, only the fields "certFile" and "keyFile" should be filled in
# if type="one-way", it means server-side certification, only the fields 'certFile' and 'keyFile' should be filled in
# if type="two-way", it means both client and server side certification, fill in all fields
serverSecure:
type: "" # secures type, "", "one-way", "two-way"
certFile: "" # server side cert file, absolute path
keyFile: "" # server side key file, absolute path
caFile: "" # ca certificate file, valid only in "two-way", absolute path
type: "" # secures type, "", "one-way", "two-way"
caFile: "" # ca certificate file, valid only in "two-way", absolute path
certFile: "" # server side cert file, absolute path
keyFile: "" # server side key file, absolute path
# grpc client settings, support for setting up multiple grpc clients
# grpc client-side settings, support for setting up multiple grpc clients.
grpcClient:
- name: "your-rpc-server-name" # grpc service name, used for service discovery
host: "127.0.0.1" # grpc service address, used for direct connection
port: 8282 # grpc service port
registryDiscoveryType: "" # registration and discovery types: consul, etcd, nacos, if empty, connecting to server using host and port
enableLoadBalance: false # whether to turn on the load balancer
- name: "serverNameExample" # grpc service name, used for service discovery
host: "127.0.0.1" # grpc service address, used for direct connection
port: 8282 # grpc service port
registryDiscoveryType: "" # registration and discovery types: consul, etcd, nacos, if empty, connecting to server using host and port
enableLoadBalance: true # whether to turn on the load balancer
# clientSecure parameter setting
# if type="", it means no secure connection, no need to fill in any parameters
# if type="one-way", it means server-side certification, only the fields "serverName" and "certFile" should be filled in
# if type="one-way", it means server-side certification, only the fields 'serverName' and 'certFile' should be filled in
# if type="two-way", it means both client and server side certification, fill in all fields
clientSecure:
type: "" # secures type, "", "one-way", "two-way"
serverName: "" # server name, e.g. *.foo.com
serverName: "" # server name, e.g. *.foo.com
caFile: "" # client side ca file, valid only in "two-way", absolute path
certFile: "" # client side cert file, absolute path, if secureType="one-way", fill in server side cert file here
keyFile: "" # client side key file, valid only in "two-way", absolute path
keyFile: "" # client side key file, valid only in "two-way", absolute path
clientToken:
enable: false # whether to enable token authentication
appID: "" # app id
appKey: "" # app key`
enable: false # whether to enable token authentication
appID: "" # app id
appKey: "" # app key`
rpcGwServerConfigCode = `# http server settings
http:
port: 8080 # listen port
readTimeout: 3 # read timeout, unit(second)
writeTimeout: 60 # write timeout, unit(second), if enableHTTPProfile is true, it needs to be greater than 60s, the default value for pprof to do profiling is 60s
port: 8080 # listen port
readTimeout: 5 # read timeout, unit(second)
writeTimeout: 5 # write timeout, unit(second), if enableHTTPProfile is true, it needs to be greater than 60s, the default value for pprof to do profiling is 60s
# grpc client settings, support for setting up multiple grpc clients
# grpc client-side settings, support for setting up multiple grpc clients.
grpcClient:
- name: "your-rpc-server-name" # grpc service name, used for service discovery
host: "127.0.0.1" # grpc service address, used for direct connection
port: 8282 # grpc service port
registryDiscoveryType: "" # registration and discovery types: consul, etcd, nacos, if empty, connecting to server using host and port
enableLoadBalance: false # whether to turn on the load balancer
- name: "serverNameExample" # grpc service name, used for service discovery
host: "127.0.0.1" # grpc service address, used for direct connection
port: 8282 # grpc service port
registryDiscoveryType: "" # registration and discovery types: consul, etcd, nacos, if empty, connecting to server using host and port
enableLoadBalance: true # whether to turn on the load balancer
# clientSecure parameter setting
# if type="", it means no secure connection, no need to fill in any parameters
# if type="one-way", it means server-side certification, only the fields "serverName" and "certFile" should be filled in
# if type="one-way", it means server-side certification, only the fields 'serverName' and 'certFile' should be filled in
# if type="two-way", it means both client and server side certification, fill in all fields
clientSecure:
type: "" # secures type, "", "one-way", "two-way"
serverName: "" # server name, e.g. *.foo.com
serverName: "" # server name, e.g. *.foo.com
caFile: "" # client side ca file, valid only in "two-way", absolute path
certFile: "" # client side cert file, absolute path, if secureType="one-way", fill in server side cert file here
keyFile: "" # client side key file, valid only in "two-way", absolute path
keyFile: "" # client side key file, valid only in "two-way", absolute path
clientToken:
enable: false # whether to enable token authentication
appID: "" # app id
appKey: "" # app key`
enable: false # whether to enable token authentication
appID: "" # app id
appKey: "" # app key`
mysqlConfigCode = `# database setting
database:
driver: "mysql" # database driver, currently support mysql, postgres, tidb
# mysql settings
mysql:
# dsn format, <user>:<pass>@(127.0.0.1:3306)/<db>?[k=v& ......]
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
enableLog: true # whether to turn on printing of all logs
maxIdleConns: 10 # set the maximum number of connections in the idle connection pool
maxOpenConns: 100 # set the maximum number of open database connections
connMaxLifetime: 30 # sets the maximum time for which the connection can be reused, in minutes
#slavesDsn: # sets slaves mysql dsn, array type
# - "your slave dsn 1"
# - "your slave dsn 2"
#mastersDsn: # sets masters mysql dsn, array type, non-required field, if there is only one master, there is no need to set the mastersDsn field, the default dsn field is mysql master.
# - "your master dsn`
mysqlConfigForDeploymentCode = ` # database setting
database:
driver: "mysql" # database driver, currently support mysql, postgres, tidb
# mysql settings
mysql:
# dsn format, <user>:<pass>@(127.0.0.1:3306)/<db>?[k=v& ......]
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
enableLog: true # whether to turn on printing of all logs
slowThreshold: 0 # if greater than 0, only print logs with a time greater than the threshold, with a higher priority than enableLog, in (ms)
maxIdleConns: 3 # set the maximum number of connections in the idle connection pool
maxOpenConns: 100 # set the maximum number of open database connections
connMaxLifetime: 30 # sets the maximum time for which the connection can be reused, in minutes
#slavesDsn: # sets slaves mysql dsn, array type
# - "your slave dsn 1"
# - "your slave dsn 2"
#mastersDsn: # sets masters mysql dsn, array type, non-required field, if there is only one master, the default dsn field is mysql master.
# - "your master dsn"`
postgresqlConfigCode = `database:
driver: "postgresql" # database driver, currently support mysql, postgres, tidb
# postgresql settings
postgresql:
# dsn format, <user>:<pass>@127.0.0.1:5432/<db>?[k=v& ......]
dsn: "root:123456@192.168.3.37:5432/account?sslmode=disable"
enableLog: true # whether to turn on printing of all logs
maxIdleConns: 10 # set the maximum number of connections in the idle connection pool
maxOpenConns: 100 # set the maximum number of open database connections
connMaxLifetime: 30 # sets the maximum time for which the connection can be reused, in minutes`
postgresqlConfigForDeploymentCode = ` # database setting
database:
driver: "postgresql" # database driver, currently support mysql, postgres, tidb
# postgresql settings
postgresql:
# dsn format, <user>:<pass>@127.0.0.1:5432/<db>?[k=v& ......]
dsn: "root:123456@192.168.3.37:5432/account?sslmode=disable"
enableLog: true # whether to turn on printing of all logs
maxIdleConns: 10 # set the maximum number of connections in the idle connection pool
maxOpenConns: 100 # set the maximum number of open database connections
connMaxLifetime: 30 # sets the maximum time for which the connection can be reused, in minutes`
modelInitDBFileMysqlCode = `// InitDB connect database
func InitDB() {
switch strings.ToLower(config.Get().Database.Driver) {
case "mysql", "tidb":
InitMysql()
default:
panic("unsupported database driver: " + config.Get().Database.Driver)
}
}
// GetDB get db
func GetDB() *gorm.DB {
if db == nil {
once1.Do(func() {
switch strings.ToLower(config.Get().Database.Driver) {
case "mysql", "tidb":
InitMysql()
default:
panic("unsupported database driver: " + config.Get().Database.Driver)
}
})
}
return db
}
// InitMysql connect mysql
func InitMysql() {
opts := []ggorm.Option{
ggorm.WithMaxIdleConns(config.Get().Database.Mysql.MaxIdleConns),
ggorm.WithMaxOpenConns(config.Get().Database.Mysql.MaxOpenConns),
ggorm.WithConnMaxLifetime(time.Duration(config.Get().Database.Mysql.ConnMaxLifetime) * time.Minute),
}
if config.Get().Database.Mysql.EnableLog {
opts = append(opts,
ggorm.WithLogging(logger.Get()),
ggorm.WithLogRequestIDKey("request_id"),
)
}
if config.Get().App.EnableTrace {
opts = append(opts, ggorm.WithEnableTrace())
}
// setting mysql slave and master dsn addresses,
// if there is no read/write separation, you can comment out the following piece of code
opts = append(opts, ggorm.WithRWSeparation(
config.Get().Database.Mysql.SlavesDsn,
config.Get().Database.Mysql.MastersDsn...,
))
// add custom gorm plugin
//opts = append(opts, ggorm.WithGormPlugin(yourPlugin))
var dsn = utils.AdaptiveMysqlDsn(config.Get().Database.Mysql.Dsn)
var err error
db, err = ggorm.InitMysql(dsn, opts...)
if err != nil {
panic("ggorm.InitMysql error: " + err.Error())
}
}`
modelInitDBFilePostgresqlCode = `// InitDB connect database
func InitDB() {
switch strings.ToLower(config.Get().Database.Driver) {
case "postgresql":
InitPostgresql()
default:
panic("unsupported database driver: " + config.Get().Database.Driver)
}
}
// GetDB get db
func GetDB() *gorm.DB {
if db == nil {
once1.Do(func() {
switch strings.ToLower(config.Get().Database.Driver) {
case "postgresql":
InitPostgresql()
default:
panic("unsupported database driver: " + config.Get().Database.Driver)
}
})
}
return db
}
// InitPostgresql connect postgresql
func InitPostgresql() {
opts := []ggorm.Option{
ggorm.WithMaxIdleConns(config.Get().Database.Postgresql.MaxIdleConns),
ggorm.WithMaxOpenConns(config.Get().Database.Postgresql.MaxOpenConns),
ggorm.WithConnMaxLifetime(time.Duration(config.Get().Database.Postgresql.ConnMaxLifetime) * time.Minute),
}
if config.Get().Database.Postgresql.EnableLog {
opts = append(opts,
ggorm.WithLogging(logger.Get()),
ggorm.WithLogRequestIDKey("request_id"),
)
}
if config.Get().App.EnableTrace {
opts = append(opts, ggorm.WithEnableTrace())
}
// add custom gorm plugin
//opts = append(opts, ggorm.WithGormPlugin(yourPlugin))
var dsn = utils.AdaptivePostgresqlDsn(config.Get().Database.Postgresql.Dsn)
var err error
db, err = ggorm.InitPostgresql(dsn, opts...)
if err != nil {
panic("ggorm.InitPostgresql error: " + err.Error())
}
}`
embedTimeCode = ` value.CreatedAt = record.CreatedAt.Unix()
value.UpdatedAt = record.UpdatedAt.Unix()`
)

View File

@ -17,7 +17,7 @@ func GenMicroCommand() *cobra.Command {
}
cmd.AddCommand(
generate.ProtoBufCommand(),
generate.ProtobufCommand(),
generate.ModelCommand("micro"),
generate.DaoCommand("micro"),
generate.CacheCommand("micro"),
@ -25,7 +25,7 @@ func GenMicroCommand() *cobra.Command {
generate.RPCCommand(),
generate.RPCGwPbCommand(),
generate.RPCPbCommand(),
generate.RPCConnectionCommand(),
generate.GRPCConnectionCommand(),
generate.ConvertSwagJSONCommand("micro"),
)

View File

@ -18,6 +18,7 @@ func PatchCommand() *cobra.Command {
cmd.AddCommand(
patch.DeleteJSONOmitemptyCommand(),
patch.GenerateDBInitCommand(),
patch.GenMysqlInitCommand(),
patch.GenTypesPbCommand(),
patch.CopyProtoCommand(),

View File

@ -0,0 +1,24 @@
package patch
import (
"os"
"strings"
)
// get moduleName and serverName from directory
func getNamesFromOutDir(dir string) (moduleName string, serverName string) {
if dir == "" {
return "", ""
}
data, err := os.ReadFile(dir + "/docs/gen.info")
if err != nil {
return "", ""
}
ms := strings.Split(string(data), ",")
if len(ms) != 2 {
return "", ""
}
return ms[0], ms[1]
}

View File

@ -0,0 +1,148 @@
package patch
import (
"errors"
"fmt"
"strings"
"github.com/zhufuyi/sponge/cmd/sponge/commands/generate"
"github.com/zhufuyi/sponge/pkg/gofile"
"github.com/zhufuyi/sponge/pkg/replacer"
"github.com/spf13/cobra"
)
// GenerateDBInitCommand generate database initialization code
func GenerateDBInitCommand() *cobra.Command {
var (
moduleName string // go.mod module name
dbDriver string // database driver e.g. mysql, postgresql, tidb
outPath string // output directory
targetFile = "internal/model/init.go"
)
cmd := &cobra.Command{
Use: "gen-db-init",
Short: "Generate database initialization code",
Long: `generate database initialization code
Examples:
# generate mysql initialization code.
sponge patch gen-db-init --module-name=yourModuleName --db-driver=mysql
# generate database initialization code, and specify the server directory, Note: code generation will be canceled when the latest generated file already exists.
sponge patch gen-db-init --db-driver=mysql --out=./yourServerDir
`,
SilenceErrors: true,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
mdName, _ := getNamesFromOutDir(outPath)
if mdName != "" {
moduleName = mdName
} else if moduleName == "" {
return fmt.Errorf(`required flag(s) "module-name" not set, use "sponge patch gen-mysql-init -h" for help`)
}
var isEmpty bool
if outPath == "" {
isEmpty = true
} else {
isEmpty = false
if gofile.IsExists(targetFile) {
fmt.Printf("'%s' already exists, no need to generate it.\n", targetFile)
return nil
}
}
g := &dbInitGenerator{
moduleName: moduleName,
dbDriver: dbDriver,
outPath: outPath,
}
var err error
outPath, err = g.generateCode()
if err != nil {
return err
}
if isEmpty {
fmt.Printf(`
using help:
move the folder "internal" to your project code folder.
`)
}
if gofile.IsWindows() {
targetFile = "\\" + strings.ReplaceAll(targetFile, "/", "\\")
} else {
targetFile = "/" + targetFile
}
fmt.Printf("generate \"%s-init\" codes successfully, out = %s\n", dbDriver, outPath+targetFile)
return nil
},
}
cmd.Flags().StringVarP(&dbDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql, tidb")
cmd.Flags().StringVarP(&moduleName, "module-name", "m", "", "module-name is the name of the module in the 'go.mod' file")
cmd.Flags().StringVarP(&outPath, "out", "o", "", "output directory, default is ./mysql-init_<time>, "+
"if you specify the directory where the web or microservice generated by sponge, the module-name flag can be ignored")
return cmd
}
type dbInitGenerator struct {
moduleName string
dbDriver string
outPath string
}
func (g *dbInitGenerator) generateCode() (string, error) {
subTplName := g.dbDriver + "-init"
r := generate.Replacers[generate.TplNameSponge]
if r == nil {
return "", errors.New("replacer is nil")
}
// setting up template information
subDirs := []string{"internal/model"} // only the specified subdirectory is processed, if empty or no subdirectory is specified, it means all files
ignoreDirs := []string{} // specify the directory in the subdirectory where processing is ignored
ignoreFiles := []string{ // specify the files in the subdirectory to be ignored for processing
"userExample.go", "init_test.go",
}
r.SetSubDirsAndFiles(subDirs)
r.SetIgnoreSubDirs(ignoreDirs...)
r.SetIgnoreSubFiles(ignoreFiles...)
fields := g.addFields(r)
r.SetReplacementFields(fields)
_ = r.SetOutputDir(g.outPath, subTplName)
if err := r.SaveFiles(); err != nil {
return "", err
}
return r.GetOutputDir(), nil
}
func (g *dbInitGenerator) addFields(r replacer.Replacer) []replacer.Field {
var fields []replacer.Field
fields = append(fields, generate.DeleteCodeMark(r, generate.ModelInitDBFile, generate.StartMark, generate.EndMark)...)
fields = append(fields, []replacer.Field{
{
Old: "github.com/zhufuyi/sponge/internal",
New: g.moduleName + "/internal",
IsCaseSensitive: false,
},
{
Old: "github.com/zhufuyi/sponge/configs",
New: g.moduleName + "/configs",
IsCaseSensitive: false,
},
{ // replace the contents of the model/init.go file
Old: generate.ModelInitDBFileMark,
New: generate.GetInitDataBaseCode(g.dbDriver),
},
}...)
return fields
}

View File

@ -3,7 +3,6 @@ package patch
import (
"errors"
"fmt"
"os"
"strings"
"github.com/zhufuyi/sponge/cmd/sponge/commands/generate"
@ -129,21 +128,3 @@ func addMysqlAndRedisInitFields(moduleName string) []replacer.Field {
return fields
}
// get moduleName and serverName from directory
func getNamesFromOutDir(dir string) (moduleName string, serverName string) {
if dir == "" {
return "", ""
}
data, err := os.ReadFile(dir + "/docs/gen.info")
if err != nil {
return "", ""
}
ms := strings.Split(string(data), ",")
if len(ms) != 2 {
return "", ""
}
return ms[0], ms[1]
}

View File

@ -19,8 +19,8 @@ var (
func NewRootCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "sponge",
Long: `Sponge is a powerful golang productivity tool that integrates automatic code generation,
web and microservice framework, basic development framework.
Long: `Sponge is a powerful golang basic development framework that integrates automatic code generation,
web and microservice framework.
repo: https://github.com/zhufuyi/sponge
docs: https://go-sponge.com`,
SilenceErrors: true,
@ -35,6 +35,7 @@ docs: https://go-sponge.com`,
GenWebCommand(),
GenMicroCommand(),
generate.ConfigCommand(),
generate.ConfigmapCommand(),
OpenUICommand(),
MergeCommand(),
PatchCommand(),

View File

@ -14,11 +14,12 @@ import (
"github.com/zhufuyi/sponge/internal/ecode"
"github.com/zhufuyi/sponge/pkg/errcode"
"github.com/zhufuyi/sponge/pkg/ggorm"
"github.com/zhufuyi/sponge/pkg/gin/response"
"github.com/zhufuyi/sponge/pkg/gobash"
"github.com/zhufuyi/sponge/pkg/gofile"
"github.com/zhufuyi/sponge/pkg/krand"
"github.com/zhufuyi/sponge/pkg/mysql"
"github.com/zhufuyi/sponge/pkg/utils"
"github.com/gin-gonic/gin"
)
@ -28,8 +29,15 @@ var (
saveDir = fmt.Sprintf("%s/.%s", getSpongeDir(), recordDirName)
)
type mysqlForm struct {
Dsn string `json:"dsn" binding:"required"`
const (
dbDriverMysql = "mysql"
dbDriverPostgresql = "postgresql"
dbDriverTidb = "tidb"
)
type dbInfoForm struct {
Dsn string `json:"dsn" binding:"required"`
DbDriver string `json:"dbDriver"`
}
type kv struct {
@ -37,23 +45,47 @@ type kv struct {
Value string `json:"value"`
}
// ListDbDrivers list db drivers
func ListDbDrivers(c *gin.Context) {
dbDrivers := []string{
dbDriverMysql,
dbDriverPostgresql,
dbDriverTidb,
}
data := []kv{}
for _, driver := range dbDrivers {
data = append(data, kv{
Label: driver,
Value: driver,
})
}
response.Success(c, data)
}
// ListTables list tables
func ListTables(c *gin.Context) {
form := &mysqlForm{}
form := &dbInfoForm{}
err := c.ShouldBindJSON(form)
if err != nil {
response.Error(c, ecode.InvalidParams.WithDetails(err.Error()))
return
}
db, err := mysql.Init(form.Dsn)
if err != nil {
response.Error(c, ecode.InternalServerError.WithDetails(err.Error()))
var tables []string
switch strings.ToLower(form.DbDriver) {
case dbDriverMysql, dbDriverTidb:
tables, err = getMysqlTables(form.Dsn)
case dbDriverPostgresql:
tables, err = getPostgresqlTables(form.Dsn)
case "":
response.Error(c, ecode.InternalServerError.WithDetails("database type is empty"))
return
default:
response.Error(c, ecode.InternalServerError.WithDetails("unsupported database driver: "+form.DbDriver))
return
}
var tables []string
err = db.Raw("show tables").Scan(&tables).Error
if err != nil {
response.Error(c, ecode.InternalServerError.WithDetails(err.Error()))
return
@ -340,3 +372,37 @@ func getSpongeDir() string {
return dir
}
func getMysqlTables(dsn string) ([]string, error) {
dsn = utils.AdaptiveMysqlDsn(dsn)
db, err := ggorm.InitMysql(dsn)
if err != nil {
return nil, err
}
defer ggorm.CloseDB(db) //nolint
var tables []string
err = db.Raw("show tables").Scan(&tables).Error
if err != nil {
return nil, err
}
return tables, nil
}
func getPostgresqlTables(dsn string) ([]string, error) {
dsn = utils.AdaptivePostgresqlDsn(dsn)
db, err := ggorm.InitPostgresql(dsn)
if err != nil {
return nil, err
}
defer ggorm.CloseDB(db) //nolint
var tables []string
err = db.Raw("SELECT table_name FROM information_schema.tables WHERE table_schema = ?", "public").Scan(&tables).Error
if err != nil {
return nil, err
}
return tables, nil
}

View File

@ -61,6 +61,7 @@ func NewRouter(spongeAddr string, isLog bool) *gin.Engine {
apiV1.POST("/generate", GenerateCode)
apiV1.POST("/uploadFiles", UploadFiles)
apiV1.POST("/listTables", ListTables)
apiV1.GET("/listDrivers", ListDbDrivers)
apiV1.GET("/record/:path", GetRecord)
return r

View File

@ -25,6 +25,7 @@ type parameters struct {
RepoAddr string `json:"repoAddr"`
ProtobufFile string `json:"-"`
YamlFile string `json:"-"`
DbDriver string `json:"dbDriver"`
Dsn string `json:"dsn"`
TableName string `json:"tableName"`
Embed bool `json:"embed"`
@ -34,7 +35,7 @@ type parameters struct {
type record struct {
mux *sync.Mutex
HostRecord map[string]*parameters
HostRecord map[string]*parameters // [ip + "-" + commandType]:parameters
}
func initRecord() {
@ -114,6 +115,8 @@ func parseCommandArgs(args []string) *parameters {
switch ss[0] {
case "--db-dsn":
params.Dsn = val
case "--db-driver":
params.DbDriver = val
case "--db-table":
params.TableName = val
case "--embed":

View File

@ -1,2 +1,2 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>sponge generate code</title><link rel=icon type=image/png sizes=32x32 href="/static/favicon.png?v=1.0"><script type=text/javascript src=/static/appConfig.js async></script><link href=/static/css/app.d073a336e9e3cecd4c7996f962e12160.css rel=stylesheet></head><body style="margin: 0px; padding: 0px;"><style>.el-tooltip__popper {box-shadow: 3px 3px 10px 5px #d3d3d6;border-width: 0px !important;}
.el-tooltip__popper[x-placement^="top"] .popper__arrow:after {border-top-color: #dcdfe6 !important;}</style><div id=app></div><script type=text/javascript src=/static/js/manifest.2ae2e69a05c33dfc65f8.js></script><script type=text/javascript src=/static/js/vendor.f99d8d10a7d35fb68864.js></script><script type=text/javascript src=/static/js/app.9b5672cd500e42cf6eae.js></script></body></html>
<!DOCTYPE html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>sponge generate code</title><link rel=icon type=image/png sizes=32x32 href="/static/favicon.png?v=1.0"><script type=text/javascript src=/static/appConfig.js async></script><link href=/static/css/app.f965da2e3508ddc9ca932590fe4d1582.css rel=stylesheet></head><body style="margin: 0px; padding: 0px;"><style>.el-tooltip__popper {box-shadow: 3px 3px 10px 5px #d3d3d6;border-width: 0px !important;}
.el-tooltip__popper[x-placement^="top"] .popper__arrow:after {border-top-color: #dcdfe6 !important;}</style><div id=app></div><script type=text/javascript src=/static/js/manifest.2ae2e69a05c33dfc65f8.js></script><script type=text/javascript src=/static/js/vendor.d8cdb3748af43d2ae51a.js></script><script type=text/javascript src=/static/js/app.050e4b824a5634db92e7.js></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"version":3,"sources":["webpack:///webpack/bootstrap 12581c31f03a32ab06d7"],"names":["parentJsonpFunction","window","chunkIds","moreModules","executeModules","moduleId","chunkId","result","i","resolves","length","installedChunks","push","Object","prototype","hasOwnProperty","call","modules","shift","__webpack_require__","s","installedModules","2","exports","module","l","m","c","d","name","getter","o","defineProperty","configurable","enumerable","get","n","__esModule","object","property","p","oe","err","console","error"],"mappings":"aACA,IAAAA,EAAAC,OAAA,aACAA,OAAA,sBAAAC,EAAAC,EAAAC,GAIA,IADA,IAAAC,EAAAC,EAAAC,EAAAC,EAAA,EAAAC,KACQD,EAAAN,EAAAQ,OAAoBF,IAC5BF,EAAAJ,EAAAM,GACAG,EAAAL,IACAG,EAAAG,KAAAD,EAAAL,GAAA,IAEAK,EAAAL,GAAA,EAEA,IAAAD,KAAAF,EACAU,OAAAC,UAAAC,eAAAC,KAAAb,EAAAE,KACAY,EAAAZ,GAAAF,EAAAE,IAIA,IADAL,KAAAE,EAAAC,EAAAC,GACAK,EAAAC,QACAD,EAAAS,OAAAT,GAEA,GAAAL,EACA,IAAAI,EAAA,EAAYA,EAAAJ,EAAAM,OAA2BF,IACvCD,EAAAY,IAAAC,EAAAhB,EAAAI,IAGA,OAAAD,GAIA,IAAAc,KAGAV,GACAW,EAAA,GAIA,SAAAH,EAAAd,GAGA,GAAAgB,EAAAhB,GACA,OAAAgB,EAAAhB,GAAAkB,QAGA,IAAAC,EAAAH,EAAAhB,IACAG,EAAAH,EACAoB,GAAA,EACAF,YAUA,OANAN,EAAAZ,GAAAW,KAAAQ,EAAAD,QAAAC,IAAAD,QAAAJ,GAGAK,EAAAC,GAAA,EAGAD,EAAAD,QAKAJ,EAAAO,EAAAT,EAGAE,EAAAQ,EAAAN,EAGAF,EAAAS,EAAA,SAAAL,EAAAM,EAAAC,GACAX,EAAAY,EAAAR,EAAAM,IACAhB,OAAAmB,eAAAT,EAAAM,GACAI,cAAA,EACAC,YAAA,EACAC,IAAAL,KAMAX,EAAAiB,EAAA,SAAAZ,GACA,IAAAM,EAAAN,KAAAa,WACA,WAA2B,OAAAb,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAL,EAAAS,EAAAE,EAAA,IAAAA,GACAA,GAIAX,EAAAY,EAAA,SAAAO,EAAAC,GAAsD,OAAA1B,OAAAC,UAAAC,eAAAC,KAAAsB,EAAAC,IAGtDpB,EAAAqB,EAAA,IAGArB,EAAAsB,GAAA,SAAAC,GAA8D,MAApBC,QAAAC,MAAAF,GAAoBA","file":"static/js/manifest.2ae2e69a05c33dfc65f8.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [], result;\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n \t\tif(executeModules) {\n \t\t\tfor(i=0; i < executeModules.length; i++) {\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = executeModules[i]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// objects to store loaded and loading chunks\n \tvar installedChunks = {\n \t\t2: 0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 12581c31f03a32ab06d7"],"sourceRoot":""}
{"version":3,"sources":["webpack:///webpack/bootstrap 3ba9677dd8fa41dfdceb"],"names":["parentJsonpFunction","window","chunkIds","moreModules","executeModules","moduleId","chunkId","result","i","resolves","length","installedChunks","push","Object","prototype","hasOwnProperty","call","modules","shift","__webpack_require__","s","installedModules","2","exports","module","l","m","c","d","name","getter","o","defineProperty","configurable","enumerable","get","n","__esModule","object","property","p","oe","err","console","error"],"mappings":"aACA,IAAAA,EAAAC,OAAA,aACAA,OAAA,sBAAAC,EAAAC,EAAAC,GAIA,IADA,IAAAC,EAAAC,EAAAC,EAAAC,EAAA,EAAAC,KACQD,EAAAN,EAAAQ,OAAoBF,IAC5BF,EAAAJ,EAAAM,GACAG,EAAAL,IACAG,EAAAG,KAAAD,EAAAL,GAAA,IAEAK,EAAAL,GAAA,EAEA,IAAAD,KAAAF,EACAU,OAAAC,UAAAC,eAAAC,KAAAb,EAAAE,KACAY,EAAAZ,GAAAF,EAAAE,IAIA,IADAL,KAAAE,EAAAC,EAAAC,GACAK,EAAAC,QACAD,EAAAS,OAAAT,GAEA,GAAAL,EACA,IAAAI,EAAA,EAAYA,EAAAJ,EAAAM,OAA2BF,IACvCD,EAAAY,IAAAC,EAAAhB,EAAAI,IAGA,OAAAD,GAIA,IAAAc,KAGAV,GACAW,EAAA,GAIA,SAAAH,EAAAd,GAGA,GAAAgB,EAAAhB,GACA,OAAAgB,EAAAhB,GAAAkB,QAGA,IAAAC,EAAAH,EAAAhB,IACAG,EAAAH,EACAoB,GAAA,EACAF,YAUA,OANAN,EAAAZ,GAAAW,KAAAQ,EAAAD,QAAAC,IAAAD,QAAAJ,GAGAK,EAAAC,GAAA,EAGAD,EAAAD,QAKAJ,EAAAO,EAAAT,EAGAE,EAAAQ,EAAAN,EAGAF,EAAAS,EAAA,SAAAL,EAAAM,EAAAC,GACAX,EAAAY,EAAAR,EAAAM,IACAhB,OAAAmB,eAAAT,EAAAM,GACAI,cAAA,EACAC,YAAA,EACAC,IAAAL,KAMAX,EAAAiB,EAAA,SAAAZ,GACA,IAAAM,EAAAN,KAAAa,WACA,WAA2B,OAAAb,EAAA,SAC3B,WAAiC,OAAAA,GAEjC,OADAL,EAAAS,EAAAE,EAAA,IAAAA,GACAA,GAIAX,EAAAY,EAAA,SAAAO,EAAAC,GAAsD,OAAA1B,OAAAC,UAAAC,eAAAC,KAAAsB,EAAAC,IAGtDpB,EAAAqB,EAAA,IAGArB,EAAAsB,GAAA,SAAAC,GAA8D,MAApBC,QAAAC,MAAAF,GAAoBA","file":"static/js/manifest.2ae2e69a05c33dfc65f8.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tvar parentJsonpFunction = window[\"webpackJsonp\"];\n \twindow[\"webpackJsonp\"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [], result;\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n \t\tif(executeModules) {\n \t\t\tfor(i=0; i < executeModules.length; i++) {\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = executeModules[i]);\n \t\t\t}\n \t\t}\n \t\treturn result;\n \t};\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// objects to store loaded and loading chunks\n \tvar installedChunks = {\n \t\t2: 0\n \t};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, {\n \t\t\t\tconfigurable: false,\n \t\t\t\tenumerable: true,\n \t\t\t\tget: getter\n \t\t\t});\n \t\t}\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"/\";\n\n \t// on error function for async loading\n \t__webpack_require__.oe = function(err) { console.error(err); throw err; };\n\n\n\n// WEBPACK FOOTER //\n// webpack/bootstrap 3ba9677dd8fa41dfdceb"],"sourceRoot":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,55 +2,54 @@
# app settings
app:
name: "serverNameExample" # server name
env: "dev" # runtime environment, dev: development environment, prod: production environment, test: test environment
name: "serverNameExample" # server name
env: "dev" # runtime environment, dev: development environment, prod: production environment, test: test environment
version: "v0.0.0"
host: "127.0.0.1" # domain or ip, for service registration
enableStat: true # whether to turn on printing statistics, true:enable, false:disable
enableMetrics: true # whether to turn on indicator collection, true:enable, false:disable
enableHTTPProfile: false # whether to turn on performance analysis, true:enable, false:disable
enableLimit: false # whether to turn on rate limiting (adaptive), true:on, false:off
host: "127.0.0.1" # domain or ip, for service registration
enableStat: true # whether to turn on printing statistics, true:enable, false:disable
enableMetrics: true # whether to turn on indicator collection, true:enable, false:disable
enableHTTPProfile: false # whether to turn on performance analysis, true:enable, false:disable
enableLimit: false # whether to turn on rate limiting (adaptive), true:on, false:off
enableCircuitBreaker: false # whether to turn on circuit breaker(adaptive), true:on, false:off
enableTrace: false # whether to turn on trace, true:enable, false:disable, if true jaeger configuration must be set
enableTrace: false # whether to turn on trace, true:enable, false:disable, if true jaeger configuration must be set
tracingSamplingRate: 1.0 # tracing sampling rate, between 0 and 1, 0 means no sampling, 1 means sampling all links
registryDiscoveryType: "" # registry and discovery types: consul, etcd, nacos, if empty, registration and discovery are not used
cacheType: "" # cache type, if empty, the cache is not used, Support for "memory" and "redis", if set to redis, must set redis configuration
registryDiscoveryType: "" # registry and discovery types: consul, etcd, nacos, if empty, registration and discovery are not used
cacheType: "" # cache type, if empty, the cache is not used, support for "memory" and "redis", if set to redis, must set redis configuration
# todo generate http or rpc server configuration here
# delete the templates code start
# http server settings
http:
port: 8080 # listen port
readTimeout: 5 # read timeout, unit(second)
writeTimeout: 5 # write timeout, unit(second), if enableHTTPProfile is true, it needs to be greater than 60s, the default value for pprof to do profiling is 60s
port: 8080 # listen port
readTimeout: 5 # read timeout, unit(second)
writeTimeout: 5 # write timeout, unit(second), if enableHTTPProfile is true, it needs to be greater than 60s, the default value for pprof to do profiling is 60s
# grpc server settings
grpc:
port: 8282 # listen port
httpPort: 8283 # profile and metrics ports
readTimeout: 5 # read timeout, unit(second)
writeTimeout: 5 # write timeout, unit(second)
enableToken: false # whether to enable server-side token authentication, default appID=grpc, appKey=123456
port: 8282 # listen port
httpPort: 8283 # profile and metrics ports
readTimeout: 5 # read timeout, unit(second)
writeTimeout: 5 # write timeout, unit(second)
enableToken: false # whether to enable server-side token authentication, default appID=grpc, appKey=123456
# serverSecure parameter setting
# if type="", it means no secure connection, no need to fill in any parameters
# if type="one-way", it means server-side certification, only the fields 'certFile' and 'keyFile' should be filled in
# if type="two-way", it means both client and server side certification, fill in all fields
serverSecure:
type: "" # secures type, "", "one-way", "two-way"
caFile: "" # ca certificate file, valid only in "two-way", absolute path
certFile: "" # server side cert file, absolute path
keyFile: "" # server side key file, absolute path
type: "" # secures type, "", "one-way", "two-way"
caFile: "" # ca certificate file, valid only in "two-way", absolute path
certFile: "" # server side cert file, absolute path
keyFile: "" # server side key file, absolute path
# grpc client-side settings, support for setting up multiple grpc clients.
grpcClient:
- name: "serverNameExample" # grpc service name, used for service discovery
host: "127.0.0.1" # grpc service address, used for direct connection
port: 8282 # grpc service port
registryDiscoveryType: "" # registration and discovery types: consul, etcd, nacos, if empty, connecting to server using host and port
- name: "serverNameExample" # grpc service name, used for service discovery
host: "127.0.0.1" # grpc service address, used for direct connection
port: 8282 # grpc service port
registryDiscoveryType: "" # registration and discovery types: consul, etcd, nacos, if empty, connecting to server using host and port
enableLoadBalance: true # whether to turn on the load balancer
# clientSecure parameter setting
# if type="", it means no secure connection, no need to fill in any parameters
@ -58,51 +57,67 @@ grpcClient:
# if type="two-way", it means both client and server side certification, fill in all fields
clientSecure:
type: "" # secures type, "", "one-way", "two-way"
serverName: "" # server name, e.g. *.foo.com
serverName: "" # server name, e.g. *.foo.com
caFile: "" # client side ca file, valid only in "two-way", absolute path
certFile: "" # client side cert file, absolute path, if secureType="one-way", fill in server side cert file here
keyFile: "" # client side key file, valid only in "two-way", absolute path
keyFile: "" # client side key file, valid only in "two-way", absolute path
clientToken:
enable: false # whether to enable token authentication
appID: "" # app id
appKey: "" # app key
enable: false # whether to enable token authentication
appID: "" # app id
appKey: "" # app key
# delete the templates code end
# logger settings
logger:
level: "info" # output log levels debug, info, warn, error, default is debug
format: "console" # output format, console or json, default is console
isSave: false # false:output to terminal, true:output to file, default is false
#logFileConfig: # Effective when isSave=true
#filename: "out.log" # File name (default is out.log)
#maxSize: 20 # Maximum file size (MB, default is 10MB)
format: "console" # output format, console or json, default is console
isSave: false # false:output to terminal, true:output to file, default is false
#logFileConfig: # Effective when isSave=true
#filename: "out.log" # File name (default is out.log)
#maxSize: 20 # Maximum file size (MB, default is 10MB)
#maxBackups: 50 # Maximum number of old files to retain (default is 100)
#maxAge: 15 # Maximum number of days to retain old files (default is 30 days)
#maxAge: 15 # Maximum number of days to retain old files (default is 30 days)
#isCompression: true # Whether to compress/archive old files (default is false)
# mysql settings
mysql:
# dsn format, <user>:<pass>@(127.0.0.1:3306)/<db>?[k=v& ......]
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
enableLog: true # whether to turn on printing of all logs
maxIdleConns: 3 # set the maximum number of connections in the idle connection pool
maxOpenConns: 100 # set the maximum number of open database connections
connMaxLifetime: 30 # sets the maximum time for which the connection can be reused, in minutes
#slavesDsn: # sets slaves mysql dsn, array type
# - "your slave dsn 1"
# - "your slave dsn 2"
#mastersDsn: # sets masters mysql dsn, array type, non-required field, if there is only one master, there is no need to set the mastersDsn field, the default dsn field is mysql master.
# - "your master dsn"
# todo generate the database configuration here
# delete the templates code start
# database setting
database:
driver: "mysql" # database driver, currently support mysql, postgres, tidb
# mysql settings
mysql:
# dsn format, <user>:<pass>@(127.0.0.1:3306)/<db>?[k=v& ......]
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
enableLog: true # whether to turn on printing of all logs
maxIdleConns: 10 # set the maximum number of connections in the idle connection pool
maxOpenConns: 100 # set the maximum number of open database connections
connMaxLifetime: 30 # sets the maximum time for which the connection can be reused, in minutes
#slavesDsn: # sets slaves mysql dsn, array type
# - "your slave dsn 1"
# - "your slave dsn 2"
#mastersDsn: # sets masters mysql dsn, array type, non-required field, if there is only one master, there is no need to set the mastersDsn field, the default dsn field is mysql master.
# - "your master dsn"
# postgresql settings
postgresql:
# dsn format, <user>:<pass>@127.0.0.1:5432/<db>?[k=v& ......]
dsn: "root:123456@192.168.3.37:5432/account?sslmode=disable"
enableLog: true # whether to turn on printing of all logs
maxIdleConns: 10 # set the maximum number of connections in the idle connection pool
maxOpenConns: 100 # set the maximum number of open database connections
connMaxLifetime: 30 # sets the maximum time for which the connection can be reused, in minutes
# delete the templates code end
# redis settings
redis:
# dsn format, [user]:<pass>@127.0.0.1:6379/[db], the default user is default, redis version 6.0 and above only supports user.
dsn: "default:123456@192.168.3.37:6379/0"
dialTimeout: 10 # connection timeout, unit(second)
readTimeout: 2 # read timeout, unit(second)
writeTimeout: 2 # write timeout, unit(second)
dialTimeout: 10 # connection timeout, unit(second)
readTimeout: 2 # read timeout, unit(second)
writeTimeout: 2 # write timeout, unit(second)
# jaeger settings
@ -125,4 +140,4 @@ etcd:
nacosRd:
ipAddr: "192.168.3.37"
port: 8848
namespaceID: "3454d2b5-2455-4d0e-bf6d-e033b086bb4c" # namespace id
namespaceID: "3454d2b5-2455-4d0e-bf6d-e033b086bb4c" # namespace id

View File

@ -5,115 +5,4 @@ metadata:
namespace: project-name-example
data:
serverNameExample.yml: |-
# app settings
app:
name: "serverNameExample" # server name
env: "dev" # runtime environment, dev: development environment, prod: production environment, test: test environment
version: "v0.0.0"
host: "127.0.0.1" # domain or ip, for service registration
enablePprof: true # whether to turn on performance analysis, true:enable, false:disable
enableStat: true # whether to turn on printing statistics, true:enable, false:disable
enableMetrics: true # whether to turn on indicator collection, true:enable, false:disable
enableLimit: false # whether to turn on rate limiting (adaptive), true:on, false:off
enableCircuitBreaker: false # whether to turn on circuit breaker(adaptive), true:on, false:off
enableTrace: false # whether to turn on enable trace, true:enable, false:disable, if true jaeger configuration must be set
tracingSamplingRate: 1.0 # tracing sampling rate, between 0 and 1, 0 means no sampling, 1 means sampling all links
registryDiscoveryType: "" # registry and discovery types: consul, etcd, nacos, if empty, registration and discovery are not used
cacheType: "memory" # cache type, memory, redis, if set to redis, must set redis configuration
# http server settings
http:
port: 8080 # listening port
readTimeout: 3 # read timeout, unit(second)
writeTimeout: 60 # write timeout, unit(second), if enablePprof is true, it needs to be greater than 60s, the default value for pprof to do profiling is 60s
# grpc server settings
grpc:
port: 8282 # listening port
httpPort: 8283 # get pprof and monitor indicator ports
readTimeout: 5 # read timeout, unit(second)
writeTimeout: 5 # write timeout, unit(second)
enableToken: false # whether to enable server-side token authentication, default appID=grpc, appKey=123456
# serverSecure parameter setting
# if type="", it means no secure connection, no need to fill in any parameters
# if type="one-way", it means server-side certification, only the fields 'certFile' and 'keyFile' should be filled in
# if type="two-way", it means both client and server side certification, fill in all fields
serverSecure:
type: "" # secures type, "", "one-way", "two-way"
certFile: "" # server side cert file, absolute path
keyFile: "" # server side key file, absolute path
caFile: "" # ca certificate file, valid only in "two-way", absolute path
# grpc client settings, multiple rpc services can be set up
grpcClient:
- name: "your-rpc-server-name" # rpc server name, used for service discovery
host: "127.0.0.1" # rpc service address, used for direct connection
port: 8282 # rpc service port
registryDiscoveryType: "" # registration and discovery types: consul, etcd, nacos, if empty, registration and discovery are not used
enableLoadBalance: false # whether to turn on the load balancer
# clientSecure parameter setting
# if type="", it means no secure connection, no need to fill in any parameters
# if type="one-way", it means server-side certification, only the fields 'serverName' and 'certFile' should be filled in
# if type="two-way", it means both client and server side certification, fill in all fields
clientSecure:
type: "" # secures type, "", "one-way", "two-way"
serverName: "" # server name, e.g. *.foo.com
caFile: "" # client side ca file, valid only in "two-way", absolute path
certFile: "" # client side cert file, absolute path, if secureType="one-way", fill in server side cert file here
keyFile: "" # client side key file, valid only in "two-way", absolute path
clientToken:
enable: false # whether to enable token authentication
appID: "" # app id
appKey: "" # app key
# logger settings
logger:
level: "info" # output log levels debug, info, warn, error, default is debug
format: "console" # output format, console or json, default is console
isSave: false # false:output to terminal, true:output to file, default is false
# mysql settings
mysql:
# dsn format, <user>:<pass>@(127.0.0.1:3306)/<db>?[k=v& ......]
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
enableLog: true # whether to turn on printing of all logs
slowThreshold: 0 # if greater than 0, only print logs with a time greater than the threshold, with a higher priority than enableLog, in (ms)
maxIdleConns: 3 # set the maximum number of connections in the idle connection pool
maxOpenConns: 100 # set the maximum number of open database connections
connMaxLifetime: 30 # sets the maximum time for which the connection can be reused, in minutes
# redis settings
redis:
# dsn format, [user]:<pass>@]127.0.0.1:6379/[db], the default user is default
dsn: "default:123456@192.168.3.37:6379/0"
dialTimeout: 10 # connection timeout, unit(second)
readTimeout: 2 # read timeout, unit(second)
writeTimeout: 2 # write timeout, unit(second)
# jaeger settings
jaeger:
agentHost: "192.168.3.37"
agentPort: 6831
# consul settings
consul:
addr: "192.168.3.37:8500"
# etcd settings
etcd:
addrs: ["192.168.3.37:2379"]
# nacos settings, used in service registration discovery
nacosRd:
ipAddr: "192.168.3.37"
port: 8848
namespaceID: "3454d2b5-2455-4d0e-bf6d-e033b086bb4c" # namespace id
# todo generate server configuration code here

View File

@ -134,7 +134,7 @@ const docTemplate = `{
"parameters": [
{
"type": "integer",
"description": "last id, default is MaxInt64",
"description": "last id, default is MaxInt32",
"name": "lastID",
"in": "query",
"required": true

View File

@ -130,7 +130,7 @@
"parameters": [
{
"type": "integer",
"description": "last id, default is MaxInt64",
"description": "last id, default is MaxInt32",
"name": "lastID",
"in": "query",
"required": true

View File

@ -415,7 +415,7 @@ paths:
- application/json
description: list of userExamples by last id and limit
parameters:
- description: last id, default is MaxInt64
- description: last id, default is MaxInt32
in: query
name: lastID
required: true

View File

@ -1,5 +1,5 @@
// Package config is a package that parses configuration files into structures, and supports
// parsing yaml, json and toml files., code generated by https://github.com/zhufuyi/sponge.
// code generated by https://github.com/zhufuyi/sponge
package config
import (
@ -31,13 +31,13 @@ func Set(conf *Config) {
type Config struct {
App App `yaml:"app" json:"app"`
Consul Consul `yaml:"consul" json:"consul"`
Database Database `yaml:"database" json:"database"`
Etcd Etcd `yaml:"etcd" json:"etcd"`
Grpc Grpc `yaml:"grpc" json:"grpc"`
GrpcClient []GrpcClient `yaml:"grpcClient" json:"grpcClient"`
HTTP HTTP `yaml:"http" json:"http"`
Jaeger Jaeger `yaml:"jaeger" json:"jaeger"`
Logger Logger `yaml:"logger" json:"logger"`
Mysql Mysql `yaml:"mysql" json:"mysql"`
NacosRd NacosRd `yaml:"nacosRd" json:"nacosRd"`
Redis Redis `yaml:"redis" json:"redis"`
}
@ -110,7 +110,14 @@ type Mysql struct {
MaxIdleConns int `yaml:"maxIdleConns" json:"maxIdleConns"`
MaxOpenConns int `yaml:"maxOpenConns" json:"maxOpenConns"`
SlavesDsn []string `yaml:"slavesDsn" json:"slavesDsn"`
SlowThreshold int `yaml:"slowThreshold" json:"slowThreshold"`
}
type Postgresql struct {
ConnMaxLifetime int `yaml:"connMaxLifetime" json:"connMaxLifetime"`
Dsn string `yaml:"dsn" json:"dsn"`
EnableLog bool `yaml:"enableLog" json:"enableLog"`
MaxIdleConns int `yaml:"maxIdleConns" json:"maxIdleConns"`
MaxOpenConns int `yaml:"maxOpenConns" json:"maxOpenConns"`
}
type Redis struct {
@ -120,6 +127,12 @@ type Redis struct {
WriteTimeout int `yaml:"writeTimeout" json:"writeTimeout"`
}
type Database struct {
Driver string `yaml:"driver" json:"driver"`
Mysql Mysql `yaml:"mysql" json:"mysql"`
Postgresql Postgresql `yaml:"postgresql" json:"postgresql"`
}
type Grpc struct {
EnableToken bool `yaml:"enableToken" json:"enableToken"`
HTTPPort int `yaml:"httpPort" json:"httpPort"`
@ -145,4 +158,4 @@ type HTTP struct {
Port int `yaml:"port" json:"port"`
ReadTimeout int `yaml:"readTimeout" json:"readTimeout"`
WriteTimeout int `yaml:"writeTimeout" json:"writeTimeout"`
}
}

View File

@ -25,4 +25,4 @@ type Nacos struct {
NamespaceID string `yaml:"namespaceID" json:"namespaceID"`
Port int `yaml:"port" json:"port"`
Scheme string `yaml:"scheme" json:"scheme"`
}
}

View File

@ -10,7 +10,7 @@ import (
"github.com/zhufuyi/sponge/internal/model"
cacheBase "github.com/zhufuyi/sponge/pkg/cache"
"github.com/zhufuyi/sponge/pkg/mysql/query"
"github.com/zhufuyi/sponge/pkg/ggorm/query"
"github.com/zhufuyi/sponge/pkg/utils"
"golang.org/x/sync/singleflight"

View File

@ -8,8 +8,8 @@ import (
"github.com/zhufuyi/sponge/internal/cache"
"github.com/zhufuyi/sponge/internal/model"
"github.com/zhufuyi/sponge/pkg/ggorm/query"
"github.com/zhufuyi/sponge/pkg/gotest"
"github.com/zhufuyi/sponge/pkg/mysql/query"
"github.com/zhufuyi/sponge/pkg/utils"
"github.com/DATA-DOG/go-sqlmock"

View File

@ -13,7 +13,6 @@ import (
"github.com/zhufuyi/sponge/pkg/gin/middleware"
"github.com/zhufuyi/sponge/pkg/gin/response"
"github.com/zhufuyi/sponge/pkg/logger"
"github.com/zhufuyi/sponge/pkg/mysql/query"
"github.com/zhufuyi/sponge/pkg/utils"
"github.com/gin-gonic/gin"
@ -204,7 +203,7 @@ func (h *userExampleHandler) GetByID(c *gin.Context) {
ctx := middleware.WrapCtx(c)
userExample, err := h.iDao.GetByID(ctx, id)
if err != nil {
if errors.Is(err, query.ErrNotFound) {
if errors.Is(err, model.ErrRecordNotFound) {
logger.Warn("GetByID not found", logger.Err(err), logger.Any("id", id), middleware.GCtxRequestIDField(c))
response.Error(c, ecode.NotFound)
} else {
@ -252,7 +251,7 @@ func (h *userExampleHandler) GetByCondition(c *gin.Context) {
ctx := middleware.WrapCtx(c)
userExample, err := h.iDao.GetByCondition(ctx, &form.Conditions)
if err != nil {
if errors.Is(err, query.ErrNotFound) {
if errors.Is(err, model.ErrRecordNotFound) {
logger.Warn("GetByCondition not found", logger.Err(err), logger.Any("form", form), middleware.GCtxRequestIDField(c))
response.Error(c, ecode.NotFound)
} else {
@ -323,7 +322,7 @@ func (h *userExampleHandler) ListByIDs(c *gin.Context) {
// @Tags userExample
// @accept json
// @Produce json
// @Param lastID query int true "last id, default is MaxInt64"
// @Param lastID query int true "last id, default is MaxInt32"
// @Param limit query int false "size in each page" default(10)
// @Param sort query string false "sort by column name of table, and the "-" sign before column name indicates reverse order" default(-id)
// @Success 200 {object} types.ListUserExamplesRespond{}
@ -331,7 +330,7 @@ func (h *userExampleHandler) ListByIDs(c *gin.Context) {
func (h *userExampleHandler) ListByLastID(c *gin.Context) {
lastID := utils.StrToUint64(c.Query("lastID"))
if lastID == 0 {
lastID = math.MaxInt64
lastID = math.MaxInt32
}
limit := utils.StrToInt(c.Query("limit"))
if limit == 0 {

View File

@ -11,9 +11,9 @@ import (
"github.com/zhufuyi/sponge/internal/ecode"
"github.com/zhufuyi/sponge/internal/model"
"github.com/zhufuyi/sponge/pkg/ggorm/query"
"github.com/zhufuyi/sponge/pkg/gin/middleware"
"github.com/zhufuyi/sponge/pkg/logger"
"github.com/zhufuyi/sponge/pkg/mysql/query"
"github.com/jinzhu/copier"
)
@ -125,7 +125,7 @@ func (h *userExamplePbHandler) GetByID(ctx context.Context, req *serverNameExamp
record, err := h.userExampleDao.GetByID(ctx, req.Id)
if err != nil {
if errors.Is(err, query.ErrNotFound) {
if errors.Is(err, model.ErrRecordNotFound) {
logger.Warn("GetByID error", logger.Err(err), logger.Any("id", req.Id), middleware.CtxRequestIDField(ctx))
return nil, ecode.NotFound.Err()
}
@ -166,7 +166,7 @@ func (h *userExamplePbHandler) GetByCondition(ctx context.Context, req *serverNa
record, err := h.userExampleDao.GetByCondition(ctx, conditions)
if err != nil {
if errors.Is(err, query.ErrNotFound) {
if errors.Is(err, model.ErrRecordNotFound) {
logger.Warn("GetByID error", logger.Err(err), logger.Any("req", req), middleware.CtxRequestIDField(ctx))
return nil, ecode.NotFound.Err()
}
@ -293,7 +293,10 @@ func convertUserExamplePb(record *model.UserExample) (*serverNameExampleV1.UserE
return nil, err
}
value.Id = record.ID
// todo generate the conversion createdAt and updatedAt code here
// delete the templates code start
value.CreatedAt = record.CreatedAt.Unix()
value.UpdatedAt = record.UpdatedAt.Unix()
// delete the templates code end
return value, nil
}

View File

@ -10,9 +10,9 @@ import (
"github.com/zhufuyi/sponge/internal/model"
"github.com/zhufuyi/sponge/internal/types"
"github.com/zhufuyi/sponge/pkg/ggorm/query"
"github.com/zhufuyi/sponge/pkg/gohttp"
"github.com/zhufuyi/sponge/pkg/gotest"
"github.com/zhufuyi/sponge/pkg/mysql/query"
"github.com/zhufuyi/sponge/pkg/utils"
"github.com/DATA-DOG/go-sqlmock"

View File

@ -2,15 +2,15 @@
package model
import (
"context"
"database/sql"
"strings"
"sync"
"time"
"github.com/zhufuyi/sponge/internal/config"
"github.com/zhufuyi/sponge/pkg/ggorm"
"github.com/zhufuyi/sponge/pkg/goredis"
"github.com/zhufuyi/sponge/pkg/logger"
"github.com/zhufuyi/sponge/pkg/mysql"
"github.com/zhufuyi/sponge/pkg/utils"
"github.com/go-redis/redis/v8"
"gorm.io/gorm"
@ -35,84 +35,6 @@ var (
once3 sync.Once
)
// InitMysql connect mysql
func InitMysql() {
opts := []mysql.Option{
mysql.WithMaxIdleConns(config.Get().Mysql.MaxIdleConns),
mysql.WithMaxOpenConns(config.Get().Mysql.MaxOpenConns),
mysql.WithConnMaxLifetime(time.Duration(config.Get().Mysql.ConnMaxLifetime) * time.Minute),
}
if config.Get().Mysql.EnableLog {
opts = append(opts,
mysql.WithLogging(logger.Get()),
mysql.WithLogRequestIDKey("request_id"),
)
}
if config.Get().App.EnableTrace {
opts = append(opts, mysql.WithEnableTrace())
}
// setting mysql slave and master dsn addresses,
// if there is no read/write separation, you can comment out the following piece of code
opts = append(opts, mysql.WithRWSeparation(
config.Get().Mysql.SlavesDsn,
config.Get().Mysql.MastersDsn...,
))
// add custom gorm plugin
//opts = append(opts, mysql.WithGormPlugin(yourPlugin))
var err error
db, err = mysql.Init(config.Get().Mysql.Dsn, opts...)
if err != nil {
panic("mysql.Init error: " + err.Error())
}
}
// GetDB get db
func GetDB() *gorm.DB {
if db == nil {
once1.Do(func() {
InitMysql()
})
}
return db
}
// CloseMysql close mysql
func CloseMysql() error {
if db == nil {
return nil
}
sqlDB, err := db.DB()
if err != nil {
return err
}
checkInUse(sqlDB, time.Second*5)
return sqlDB.Close()
}
func checkInUse(sqlDB *sql.DB, duration time.Duration) {
ctx, _ := context.WithTimeout(context.Background(), duration) //nolint
for {
select {
case <-time.After(time.Millisecond * 500):
if v := sqlDB.Stats().InUse; v == 0 {
return
}
case <-ctx.Done():
return
}
}
}
// ------------------------------------------------------------------------------------------
// CacheType cache type
type CacheType struct {
CType string // cache type memory or redis
@ -183,3 +105,110 @@ func CloseRedis() error {
return nil
}
// ------------------------------------------------------------------------------------------
// todo generate initialisation database code here
// delete the templates code start
// InitDB connect database
func InitDB() {
switch strings.ToLower(config.Get().Database.Driver) {
case "mysql", "tidb":
InitMysql()
case "postgres", "postgresql":
InitPostgresql()
default:
panic("unsupported database driver: " + config.Get().Database.Driver)
}
}
// GetDB get db
func GetDB() *gorm.DB {
if db == nil {
once1.Do(func() {
switch strings.ToLower(config.Get().Database.Driver) {
case "mysql", "tidb":
InitMysql()
case "postgres", "postgresql":
InitPostgresql()
default:
panic("unsupported database driver: " + config.Get().Database.Driver)
}
})
}
return db
}
// InitMysql connect mysql
func InitMysql() {
opts := []ggorm.Option{
ggorm.WithMaxIdleConns(config.Get().Database.Mysql.MaxIdleConns),
ggorm.WithMaxOpenConns(config.Get().Database.Mysql.MaxOpenConns),
ggorm.WithConnMaxLifetime(time.Duration(config.Get().Database.Mysql.ConnMaxLifetime) * time.Minute),
}
if config.Get().Database.Mysql.EnableLog {
opts = append(opts,
ggorm.WithLogging(logger.Get()),
ggorm.WithLogRequestIDKey("request_id"),
)
}
if config.Get().App.EnableTrace {
opts = append(opts, ggorm.WithEnableTrace())
}
// setting mysql slave and master dsn addresses,
// if there is no read/write separation, you can comment out the following piece of code
opts = append(opts, ggorm.WithRWSeparation(
config.Get().Database.Mysql.SlavesDsn,
config.Get().Database.Mysql.MastersDsn...,
))
// add custom gorm plugin
//opts = append(opts, ggorm.WithGormPlugin(yourPlugin))
var dsn = utils.AdaptiveMysqlDsn(config.Get().Database.Mysql.Dsn)
var err error
db, err = ggorm.InitMysql(dsn, opts...)
if err != nil {
panic("ggorm.InitMysql error: " + err.Error())
}
}
// InitPostgresql connect postgresql
func InitPostgresql() {
opts := []ggorm.Option{
ggorm.WithMaxIdleConns(config.Get().Database.Postgresql.MaxIdleConns),
ggorm.WithMaxOpenConns(config.Get().Database.Postgresql.MaxOpenConns),
ggorm.WithConnMaxLifetime(time.Duration(config.Get().Database.Postgresql.ConnMaxLifetime) * time.Minute),
}
if config.Get().Database.Postgresql.EnableLog {
opts = append(opts,
ggorm.WithLogging(logger.Get()),
ggorm.WithLogRequestIDKey("request_id"),
)
}
if config.Get().App.EnableTrace {
opts = append(opts, ggorm.WithEnableTrace())
}
// add custom gorm plugin
//opts = append(opts, ggorm.WithGormPlugin(yourPlugin))
var dsn = utils.AdaptivePostgresqlDsn(config.Get().Database.Postgresql.Dsn)
var err error
db, err = ggorm.InitPostgresql(dsn, opts...)
if err != nil {
panic("ggorm.InitPostgresql error: " + err.Error())
}
}
// delete the templates code end
// CloseDB close db
func CloseDB() error {
return ggorm.CloseDB(db)
}

View File

@ -21,10 +21,10 @@ func TestInitMysql(t *testing.T) {
}
config.Get().App.EnableTrace = true
config.Get().Mysql.EnableLog = true
config.Get().Database.Mysql.EnableLog = true
time.Sleep(time.Millisecond * 10)
err = CloseMysql()
err = CloseDB()
assert.NoError(t, err)
utils.SafeRunWithTimeout(time.Second*2, func(cancel context.CancelFunc) {
@ -41,20 +41,20 @@ func TestInitMysqlError(t *testing.T) {
}
// change config error test
config.Get().Mysql.Dsn = "root:123456@(127.0.0.1:3306)/test"
config.Get().Database.Mysql.Dsn = "root:123456@(127.0.0.1:3306)/test"
utils.SafeRunWithTimeout(time.Second*2, func(cancel context.CancelFunc) {
_ = CloseMysql()
_ = CloseDB()
InitMysql()
assert.NotNil(t, db)
cancel()
})
}
func TestCloseMysql(t *testing.T) {
func TestCloseDB(t *testing.T) {
defer func() { recover() }()
db = &gorm.DB{}
err := CloseMysql()
err := CloseDB()
assert.NoError(t, err)
}

View File

@ -4,12 +4,12 @@
package model
import (
"github.com/zhufuyi/sponge/pkg/mysql"
"github.com/zhufuyi/sponge/pkg/ggorm"
)
// UserExample object fields mapping table
type UserExample struct {
mysql.Model `gorm:"embedded"`
ggorm.Model `gorm:"embedded"`
Name string `gorm:"column:name;NOT NULL" json:"name"` // username
Password string `gorm:"column:password;NOT NULL" json:"password"` // password

View File

@ -12,9 +12,9 @@ import (
"github.com/zhufuyi/sponge/internal/ecode"
"github.com/zhufuyi/sponge/internal/model"
"github.com/zhufuyi/sponge/pkg/ggorm/query"
"github.com/zhufuyi/sponge/pkg/grpc/interceptor"
"github.com/zhufuyi/sponge/pkg/logger"
"github.com/zhufuyi/sponge/pkg/mysql/query"
"github.com/jinzhu/copier"
"google.golang.org/grpc"
@ -140,7 +140,7 @@ func (s *userExample) GetByID(ctx context.Context, req *serverNameExampleV1.GetU
record, err := s.iDao.GetByID(ctx, req.Id)
if err != nil {
if errors.Is(err, query.ErrNotFound) {
if errors.Is(err, model.ErrRecordNotFound) {
logger.Warn("GetByID error", logger.Err(err), logger.Any("id", req.Id), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusNotFound.Err()
}
@ -180,7 +180,7 @@ func (s *userExample) GetByCondition(ctx context.Context, req *serverNameExample
record, err := s.iDao.GetByCondition(ctx, conditions)
if err != nil {
if errors.Is(err, query.ErrNotFound) {
if errors.Is(err, model.ErrRecordNotFound) {
logger.Warn("GetByCondition error", logger.Err(err), logger.Any("req", req), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusNotFound.Err()
}
@ -237,7 +237,7 @@ func (s *userExample) ListByLastID(ctx context.Context, req *serverNameExampleV1
return nil, ecode.StatusInvalidParams.Err()
}
if req.LastID == 0 {
req.LastID = math.MaxInt64
req.LastID = math.MaxInt32
}
if req.Limit == 0 {
req.Limit = 10
@ -313,7 +313,10 @@ func convertUserExample(record *model.UserExample) (*serverNameExampleV1.UserExa
return nil, err
}
value.Id = record.ID
// todo generate the conversion createdAt and updatedAt code here
// delete the templates code start
value.CreatedAt = record.CreatedAt.Unix()
value.UpdatedAt = record.UpdatedAt.Unix()
// delete the templates code end
return value, nil
}

View File

@ -134,8 +134,8 @@ func Test_service_userExample_methods(t *testing.T) {
// todo type in the parameters to test
req := &serverNameExampleV1.ListUserExampleByLastIDRequest{
LastID: 0,
Limit: 5,
Sort: "-age",
Limit: 10,
Sort: "",
}
return cli.ListByLastID(ctx, req)
},

View File

@ -4,7 +4,7 @@ package types
import (
"time"
"github.com/zhufuyi/sponge/pkg/mysql/query"
"github.com/zhufuyi/sponge/pkg/ggorm/query"
)
var _ time.Time

View File

@ -5,11 +5,12 @@ app:
version: "v0.0.0"
password: "123456"
database:
driver: "mysql"
# mysql settings
mysql:
# dsn format, <user>:<pass>@(127.0.0.1:3306)/<db>?[k=v& ......]
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
mysql:
# dsn format, <user>:<pass>@(127.0.0.1:3306)/<db>?[k=v& ......]
dsn: "root:123456@(192.168.3.37:3306)/account?parseTime=true&loc=Local&charset=utf8,utf8mb4"
# redis settings
redis:

View File

@ -17,6 +17,7 @@ type Responser interface {
Error(ctx *gin.Context, err error) bool
}
// NewResponse creates a new responser
// Deprecated: NewResponse use NewResponser instead
func NewResponse(isFromRPC bool) Responser {
return &defaultResponse{isFromRPC, make(map[int]*Error), make(map[int]*RPCStatus)}

View File

@ -2,10 +2,12 @@
package ggorm
import (
"context"
"database/sql"
"fmt"
"log"
"os"
"time"
"github.com/uptrace/opentelemetry-go-extra/otelgorm"
mysqlDriver "gorm.io/driver/mysql"
@ -68,7 +70,6 @@ func InitPostgresql(dsn string, opts ...Option) (*gorm.DB, error) {
o.apply(opts...)
db, err := gorm.Open(postgres.Open(dsn), gormConfig(o))
if err != nil {
return nil, err
}
@ -115,6 +116,36 @@ func InitTidb(dsn string, opts ...Option) (*gorm.DB, error) {
// panic("not implemented")
//}
// CloseDB close gorm db
func CloseDB(db *gorm.DB) error {
if db == nil {
return nil
}
sqlDB, err := db.DB()
if err != nil {
return err
}
checkInUse(sqlDB, time.Second*5)
return sqlDB.Close()
}
func checkInUse(sqlDB *sql.DB, duration time.Duration) {
ctx, _ := context.WithTimeout(context.Background(), duration) //nolint
for {
select {
case <-time.After(time.Millisecond * 500):
if v := sqlDB.Stats().InUse; v == 0 {
return
}
case <-ctx.Done():
return
}
}
}
// gorm setting
func gormConfig(o *options) *gorm.Config {
config := &gorm.Config{

View File

@ -1,7 +1,9 @@
package ggorm
import (
"database/sql"
"fmt"
"gorm.io/gorm"
"testing"
"time"
@ -17,6 +19,7 @@ func TestInitMysql(t *testing.T) {
t.Logf(fmt.Sprintf("connect to mysql failed, err=%v, dsn=%s", err, dsn))
return
}
defer CloseDB(db)
t.Logf("%+v", db.Name())
}
@ -28,6 +31,7 @@ func TestInitTidb(t *testing.T) {
t.Logf(fmt.Sprintf("connect to mysql failed, err=%v, dsn=%s", err, dsn))
return
}
defer CloseDB(db)
t.Logf("%+v", db.Name())
}
@ -40,6 +44,7 @@ func TestInitPostgresql(t *testing.T) {
t.Logf(fmt.Sprintf("connect to postgresql failed, err=%v, dsn=%s", err, dsn))
return
}
defer CloseDB(db)
t.Logf("%+v", db.Name())
}
@ -88,3 +93,12 @@ func TestGetTableName(t *testing.T) {
name = GetTableName("table")
assert.Empty(t, name)
}
func TestCloseDB(t *testing.T) {
sqlDB := new(sql.DB)
checkInUse(sqlDB, time.Millisecond*100)
checkInUse(sqlDB, time.Millisecond*600)
db := new(gorm.DB)
defer func() { recover() }()
CloseDB(db)
}

View File

@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/zhufuyi/sponge/pkg/mysql/query"
"github.com/zhufuyi/sponge/pkg/ggorm/query"
"github.com/DATA-DOG/go-sqlmock"
"gorm.io/gorm"

View File

@ -16,6 +16,7 @@ import (
)
// Init mysql
// Deprecated: will be moved to package pkg/gorm InitMysql
func Init(dns string, opts ...Option) (*gorm.DB, error) {
o := defaultOptions()
o.apply(opts...)

View File

@ -126,6 +126,7 @@ func (c *Column) convert() error {
}
// ConvertToPage converted to conform to gorm rules based on the page size sort parameter
// Deprecated: will be moved to package pkg/gorm/query ConvertToPage
func (p *Params) ConvertToPage() (order string, limit int, offset int) { //nolint
page := NewPage(p.Page, p.Size, p.Sort)
order = page.sort
@ -136,6 +137,7 @@ func (p *Params) ConvertToPage() (order string, limit int, offset int) { //nolin
// ConvertToGormConditions conversion to gorm-compliant parameters based on the Columns parameter
// ignore the logical type of the last column, whether it is a one-column or multi-column query
// Deprecated: will be moved to package pkg/gorm/query ConvertToGormConditions
func (p *Params) ConvertToGormConditions() (string, []interface{}, error) {
str := ""
args := []interface{}{}
@ -272,6 +274,7 @@ func (c *Conditions) CheckValid() error {
// ConvertToGorm conversion to gorm-compliant parameters based on the Columns parameter
// ignore the logical type of the last column, whether it is a one-column or multi-column query
// Deprecated: will be moved to package pkg/gorm/query ConvertToGorm
func (c *Conditions) ConvertToGorm() (string, []interface{}, error) {
p := &Params{Columns: c.Columns}
return p.ConvertToGormConditions()

View File

@ -7,11 +7,11 @@ import (
_ "github.com/go-sql-driver/mysql" //nolint
)
// GetTableInfo get table info from mysql
func GetTableInfo(dsn, tableName string) (string, error) {
// GetMysqlTableInfo get table info from mysql
func GetMysqlTableInfo(dsn, tableName string) (string, error) {
db, err := sql.Open("mysql", dsn)
if err != nil {
return "", fmt.Errorf("connect mysql error, %v", err)
return "", fmt.Errorf("GetMysqlTableInfo error, %v", err)
}
defer db.Close() //nolint
@ -34,3 +34,9 @@ func GetTableInfo(dsn, tableName string) (string, error) {
return info, nil
}
// GetTableInfo get table info from mysql
// Deprecated: replaced by GetMysqlTableInfo
func GetTableInfo(dsn, tableName string) (string, error) {
return GetMysqlTableInfo(dsn, tableName)
}

View File

@ -14,6 +14,8 @@ const (
type Option func(*options)
type options struct {
DBDriver string
FieldTypes map[string]string // name:type
Charset string
Collation string
JSONTag bool
@ -30,11 +32,31 @@ type options struct {
}
var defaultOptions = options{
NullStyle: NullInSql,
Package: "model",
DBDriver: "mysql",
FieldTypes: map[string]string{},
NullStyle: NullInSql,
Package: "model",
}
// WithCharset set charset
// WithDBDriver set db driver
func WithDBDriver(driver string) Option {
return func(o *options) {
if driver != "" {
o.DBDriver = driver
}
}
}
// WithFieldTypes set field types
func WithFieldTypes(fieldTypes map[string]string) Option {
return func(o *options) {
if fieldTypes != nil {
o.FieldTypes = fieldTypes
}
}
}
// WithCharset set charset
func WithCharset(charset string) Option {
return func(o *options) {
o.Charset = charset

View File

@ -34,6 +34,13 @@ const (
CodeTypeProto = "proto"
// CodeTypeService grpc service code
CodeTypeService = "service"
// DBDriverMysql mysql driver
DBDriverMysql = "mysql"
// DBDriverPostgresql postgresql driver
DBDriverPostgresql = "postgresql"
// DBDriverTidb tidb driver
DBDriverTidb = "tidb"
)
// Codes content
@ -199,7 +206,7 @@ const (
)
var replaceFields = map[string]string{
__mysqlModel__: "mysql.Model",
__mysqlModel__: "ggorm.Model",
__type__: "",
}
@ -299,7 +306,12 @@ func makeCode(stmt *ast.CreateTableStmt, opt options) (*codeText, error) {
gormTag.WriteString(colName)
if opt.GormType {
gormTag.WriteString(";type:")
gormTag.WriteString(col.Tp.InfoSchemaStr())
switch opt.DBDriver {
case DBDriverMysql, DBDriverTidb:
gormTag.WriteString(col.Tp.InfoSchemaStr())
case DBDriverPostgresql:
gormTag.WriteString(opt.FieldTypes[colName])
}
}
if isPrimaryKey[colName] {
gormTag.WriteString(";primary_key")
@ -431,13 +443,13 @@ func getModelStructCode(data tmplData, importPaths []string, isEmbed bool) (stri
newImportPaths = importPaths
} else {
for _, path := range importPaths {
if path == "time" {
if path == "time" { //nolint
continue
}
newImportPaths = append(newImportPaths, path)
}
}
newImportPaths = append(newImportPaths, "github.com/zhufuyi/sponge/pkg/mysql")
newImportPaths = append(newImportPaths, "github.com/zhufuyi/sponge/pkg/ggorm")
} else {
for i, field := range data.Fields {
if strings.Contains(field.GoType, "time.Time") {
@ -716,7 +728,7 @@ func mysqlToGoType(colTp *types.FieldType, style NullStyle) (name string, path s
mysql.TypeBlob, mysql.TypeTinyBlob, mysql.TypeMediumBlob, mysql.TypeLongBlob:
name = "string"
case mysql.TypeTimestamp, mysql.TypeDatetime, mysql.TypeDate:
path = "time"
path = "time" //nolint
name = "time.Time"
case mysql.TypeDecimal, mysql.TypeNewDecimal:
name = "string"

View File

@ -102,6 +102,8 @@ func Test_toCamel(t *testing.T) {
func Test_parseOption(t *testing.T) {
opts := []Option{
WithDBDriver("foo"),
WithFieldTypes(map[string]string{"foo": "bar"}),
WithCharset("foo"),
WithCollation("foo"),
WithTablePrefix("foo"),
@ -148,11 +150,6 @@ func Test_goTypeToProto(t *testing.T) {
assert.NotNil(t, v)
}
func TestGetTableInfo(t *testing.T) {
info, err := GetTableInfo("root:123456@(192.168.3.37:3306)/test", "user")
t.Log(err, info)
}
func Test_initTemplate(t *testing.T) {
initTemplate()
@ -174,3 +171,46 @@ func Test_initTemplate(t *testing.T) {
serviceStructTmplRaw = "{{if .foo}}"
initTemplate()
}
func TestGetTableInfo(t *testing.T) {
info, err := GetMysqlTableInfo("root:123456@(192.168.3.37:3306)/test", "user")
t.Log(err, info)
}
func TestGetPostgresqlTableInfo(t *testing.T) {
fields, err := GetPostgresqlTableInfo("host=192.168.3.37 port=5432 user=root password=123456 dbname=account sslmode=disable", "user_example")
t.Log(fields, err)
sql, fieldTypes := ConvertToMysqlTable("user_example", fields)
t.Log(sql, fieldTypes)
}
func TestConvertToMysqlTable(t *testing.T) {
fields := []*PGField{
{Name: "id", Type: "smallint"},
{Name: "name", Type: "character", Lengthvar: 24, Notnull: false},
{Name: "age", Type: "smallint", Notnull: true},
}
sql, tps := ConvertToMysqlTable("foobar", fields)
t.Log(sql, tps)
}
func Test_toMysqlTable(t *testing.T) {
fields := []*PGField{
{Type: "smallint"},
{Type: "bigint"},
{Type: "real"},
{Type: "decimal"},
{Type: "double precision"},
{Type: "money"},
{Type: "character", Lengthvar: 24},
{Type: "text"},
{Type: "timestamp"},
{Type: "date"},
{Type: "time"},
{Type: "interval"},
{Type: "boolean"},
}
for _, field := range fields {
t.Log(toMysqlType(field), getType(field))
}
}

View File

@ -0,0 +1,118 @@
package parser
import (
"fmt"
"strings"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type PGField struct {
Name string `gorm:"column:name;" json:"name"`
Type string `gorm:"column:type;" json:"type"`
Comment string `gorm:"column:comment;" json:"comment"`
Length int `gorm:"column:length;" json:"length"`
Lengthvar int `gorm:"column:lengthvar;" json:"lengthvar"`
Notnull bool `gorm:"column:notnull;" json:"notnull"`
}
// GetPostgresqlTableInfo get table info from postgres
func GetPostgresqlTableInfo(dsn string, tableName string) ([]*PGField, error) {
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
if err != nil {
return nil, fmt.Errorf("GetPostgresqlTableInfo error: %v", err)
}
return getPostgresqlTableFields(db, tableName)
}
func getPostgresqlTableFields(db *gorm.DB, tableName string) ([]*PGField, error) {
query := fmt.Sprintf(`SELECT a.attname AS name, t.typname AS type, a.attlen AS length, a.atttypmod AS lengthvar, a.attnotnull AS notnull, b.description AS comment
FROM pg_class c, pg_attribute a
LEFT JOIN pg_description b
ON a.attrelid = b.objoid
AND a.attnum = b.objsubid, pg_type t
WHERE c.relname = '%s'
AND a.attnum > 0
AND a.attrelid = c.oid
AND a.atttypid = t.oid
ORDER BY a.attnum;`, tableName)
var fields []*PGField
result := db.Raw(query).Scan(&fields)
if result.Error != nil {
return nil, fmt.Errorf("failed to get table fields: %v", result.Error)
}
return fields, nil
}
// ConvertToMysqlTable convert to mysql table ddl
func ConvertToMysqlTable(tableName string, fields []*PGField) (string, map[string]string) {
fieldStr := ""
pgTypeMap := make(map[string]string) // name:type
for _, field := range fields {
pgTypeMap[field.Name] = getType(field)
if field.Name == "id" {
fieldStr += fmt.Sprintf(" %s bigint unsigned primary key,\n", field.Name)
continue
}
notnullStr := "not null"
if !field.Notnull {
notnullStr = "null"
}
fieldStr += fmt.Sprintf(" %s %s %s comment '%s',\n", field.Name, toMysqlType(field), notnullStr, field.Comment)
}
fieldStr = strings.TrimSuffix(fieldStr, ",\n")
return fmt.Sprintf("CREATE TABLE %s (\n%s\n);", tableName, fieldStr), pgTypeMap
}
func toMysqlType(field *PGField) string {
switch field.Type {
// 整型
case "smallint", "integer", "smallserial", "serial", "int2", "int4":
return "int"
case "bigint", "bigserial", "int8":
return "bigint"
// 浮点数
case "real":
return "float"
case "decimal", "numeric":
return "decimal"
case "double precision":
return "double"
case "money":
return "varchar(30)"
// 字符串
case "character", "character varying", "varchar", "char", "bpchar":
if field.Lengthvar > 4 {
return fmt.Sprintf("varchar(%d)", field.Lengthvar-4)
}
case "text":
return "text"
// 日期时间
case "timestamp":
return "timestamp"
case "date":
return "date"
case "time": //nolint
return "time" //nolint
case "interval":
return "year"
case "boolean":
return "tinyint(1)"
}
return field.Type
}
func getType(field *PGField) string {
switch field.Type {
case "character", "character varying", "varchar", "char", "bpchar":
if field.Lengthvar > 4 {
return fmt.Sprintf("varchar(%d)", field.Lengthvar-4)
}
}
return field.Type
}

View File

@ -182,7 +182,7 @@ message List{{.TableName}}ByIDsReply {
message List{{.TableName}}ByLastIDRequest {
uint64 lastID = 1; // last id
uint32 limit = 2 [(validate.rules).uint32.gt = 0]; // page size
uint32 limit = 2 [(validate.rules).uint32.gt = 0]; // limit size per page
string sort = 3; // sort by column name of table, default is -id, the - sign indicates descending order.
}
@ -464,7 +464,7 @@ message List{{.TableName}}ByIDsReply {
message List{{.TableName}}ByLastIDRequest {
uint64 lastID = 1 [(tagger.tags) = "form:\"lastID\""]; // last id
uint32 limit = 2 [(validate.rules).uint32.gt = 0, (tagger.tags) = "form:\"limit\""]; // page size
uint32 limit = 2 [(validate.rules).uint32.gt = 0, (tagger.tags) = "form:\"limit\""]; // limit size per page
string sort = 3 [(tagger.tags) = "form:\"sort\""]; // sort by column name of table, default is -id, the - sign indicates descending order.
}

View File

@ -7,8 +7,10 @@ import (
"errors"
"fmt"
"os"
"strings"
"github.com/zhufuyi/sponge/pkg/sql2code/parser"
"github.com/zhufuyi/sponge/pkg/utils"
)
// Args generate code arguments
@ -17,8 +19,10 @@ type Args struct {
DDLFile string // DDL file
DBDsn string // connecting to mysql's dsn
DBTable string
DBDriver string // db driver name, such as mysql, postgres, default is mysql
DBDsn string // connecting to mysql's dsn
DBTable string // table name
fieldTypes map[string]string // field name:type
Package string // specify the package name (only valid for model types)
GormType bool // whether to display the gorm type name (only valid for model type codes)
@ -40,38 +44,64 @@ func (a *Args) checkValid() error {
if a.SQL == "" && a.DDLFile == "" && (a.DBDsn == "" && a.DBTable == "") {
return errors.New("you must specify sql or ddl file")
}
if a.DBDriver == "" {
a.DBDriver = parser.DBDriverMysql
}
return nil
}
func getSQL(args *Args) (string, error) {
func getSQL(args *Args) (string, map[string]string, error) {
if args.SQL != "" {
return args.SQL, nil
return args.SQL, nil, nil
}
sql := ""
dbDriverName := strings.ToLower(args.DBDriver)
if args.DDLFile != "" {
if dbDriverName != parser.DBDriverMysql {
return sql, nil, fmt.Errorf("not support driver %s for parsing the sql file, only mysql is supported", args.DBDriver)
}
b, err := os.ReadFile(args.DDLFile)
if err != nil {
return sql, fmt.Errorf("read %s failed, %s", args.DDLFile, err)
return sql, nil, fmt.Errorf("read %s failed, %s", args.DDLFile, err)
}
return string(b), nil
return string(b), nil, nil
} else if args.DBDsn != "" {
if args.DBTable == "" {
return sql, errors.New("miss mysql table")
return sql, nil, errors.New("miss database table")
}
sqlStr, err := parser.GetTableInfo(args.DBDsn, args.DBTable)
if err != nil {
return sql, err
switch dbDriverName {
case parser.DBDriverMysql, parser.DBDriverTidb:
dsn := utils.AdaptiveMysqlDsn(args.DBDsn)
sqlStr, err := parser.GetMysqlTableInfo(dsn, args.DBTable)
return sqlStr, nil, err
case parser.DBDriverPostgresql:
dsn := utils.AdaptivePostgresqlDsn(args.DBDsn)
fields, err := parser.GetPostgresqlTableInfo(dsn, args.DBTable)
if err != nil {
return "", nil, err
}
sqlStr, pgTypeMap := parser.ConvertToMysqlTable(args.DBTable, fields)
return sqlStr, pgTypeMap, nil
default:
return "", nil, fmt.Errorf("unsupported database driver: " + dbDriverName)
}
return sqlStr, nil
}
return sql, errors.New("no SQL input(-sql|-f|-db-dsn)")
return sql, nil, errors.New("no SQL input(-sql|-f|-db-dsn)")
}
func getOptions(args *Args) []parser.Option {
func setOptions(args *Args) []parser.Option {
var opts []parser.Option
if args.DBDriver != "" {
opts = append(opts, parser.WithDBDriver(args.DBDriver))
}
if args.fieldTypes != nil {
opts = append(opts, parser.WithFieldTypes(args.fieldTypes))
}
if args.Charset != "" {
opts = append(opts, parser.WithCharset(args.Charset))
}
@ -147,12 +177,15 @@ func Generate(args *Args) (map[string]string, error) {
return nil, err
}
sql, err := getSQL(args)
sql, fieldTypes, err := getSQL(args)
if err != nil {
return nil, err
}
if fieldTypes != nil {
args.fieldTypes = fieldTypes
}
opt := getOptions(args)
opt := setOptions(args)
return parser.ParseSQL(sql, opt...)
}

View File

@ -145,7 +145,7 @@ func Test_getOptions(t *testing.T) {
NullStyle: "sql",
}
o := getOptions(a)
o := setOptions(a)
assert.NotNil(t, o)
a.NullStyle = "ptr"
assert.NotNil(t, o)

View File

@ -2,7 +2,9 @@
patchType=$1
typesPb="types-pb"
mysqlInit="mysql-init"
initMysql="init-mysql"
initTidb="init-tidb"
initPostgresql="init-postgresql"
function checkResult() {
result=$1
@ -11,24 +13,37 @@ function checkResult() {
fi
}
function patchTypesPb() {
function generateTypesPbCode() {
sponge patch gen-types-pb --out=./
checkResult $?
}
function patchMysqlInit() {
sponge patch gen-mysql-init --out=./
function generateInitMysqlCode() {
sponge patch gen-db-init --db-driver=mysql --out=./
checkResult $?
}
if [ "X$patchType" = "X" ];then
patchTypesPb
patchMysqlInit
elif [ "$patchType" = "$typesPb" ]; then
patchTypesPb
elif [ "$patchType" = "$mysqlInit" ]; then
patchMysqlInit
function generateInitTidbCode() {
sponge patch gen-db-init --db-driver=tidb --out=./
checkResult $?
}
function generateInitPostgresqlCode() {
sponge patch gen-db-init --db-driver=postgresql --out=./
checkResult $?
}
if [ "$patchType" = "$typesPb" ]; then
generateTypesPbCode
elif [ "$patchType" = "$initMysql" ]; then
generateInitMysqlCode
elif [ "$patchType" = "$initTidb" ]; then
generateInitTidbCode
elif [ "$patchType" = "$initPostgresql" ]; then
generateInitPostgresqlCode
else
echo "TYPE should be "", $typesPb or $mysqlInit."
echo "invalid patch type: '$patchType'"
echo "supported types: $initMysql, $initTidb, $initPostgresql, $typesPb"
echo "e.g. make patch TYPE=init-mysql"
exit 1
fi