support mongodb generate code

This commit is contained in:
zhuyasen 2024-03-02 15:29:58 +08:00
parent 0ca5eaf5b1
commit e10968ae32
63 changed files with 4992 additions and 294 deletions

View File

@ -136,7 +136,7 @@ import (
{{- range .PbServices}}
// Test each method of {{.LowerName}} via the rpc client
// Test each method of {{.LowerName}} via the grpc client
func Test_service_{{.LowerName}}_methods(t *testing.T) {
conn := getRPCClientConnForTest()
cli := serverNameExampleV1.New{{.Name}}Client(conn)

View File

@ -120,6 +120,7 @@ func (g *stringCacheGenerator) generateCode() (string, error) {
ignoreFiles := []string{ // specify the files in the subdirectory to be ignored for processing
"doc.go",
"userExample.go",
"userExample.go.mgo",
"userExample_test.go",
}

View File

@ -29,6 +29,8 @@ const (
DBDriverTidb = "tidb"
// DBDriverSqlite sqlite driver
DBDriverSqlite = "sqlite"
// DBDriverMongodb mongodb driver
DBDriverMongodb = "mongodb"
)
var (
@ -41,10 +43,12 @@ var (
cacheFile = "cache/cacheNameExample.go"
daoFile = "dao/userExample.go"
daoMgoFile = "dao/userExample.go.mgo"
daoFileMark = "// todo generate the update fields code to here"
daoTestFile = "dao/userExample_test.go"
handlerFile = "types/userExample_types.go"
handlerMgoFile = "types/userExample_types.go.mgo"
handlerFileMark = "// todo generate the request and response struct to here"
handlerTestFile = "handler/userExample_test.go"
@ -57,9 +61,10 @@ var (
protoFile = "v1/userExample.proto"
protoFileMark = "// todo generate the protobuf code here"
serviceTestFile = "service/userExample_test.go"
serviceClientFile = "service/userExample_client_test.go"
serviceFileMark = "// todo generate the service struct code here"
serviceTestFile = "service/userExample_test.go"
serviceClientFile = "service/userExample_client_test.go"
serviceClientMgoFile = "service/userExample_client_test.go.mgo"
serviceFileMark = "// todo generate the service struct code here"
dockerFile = "scripts/build/Dockerfile"
dockerFileMark = "# todo generate dockerfile code for http or grpc here"
@ -138,7 +143,10 @@ func convertProjectAndServerName(projectName, serverName string) (pn string, sn
return pn, sn
}
func adjustmentOfIDType(handlerCodes string) string {
func adjustmentOfIDType(handlerCodes string, dbDriver string) string {
if dbDriver == DBDriverMongodb {
return idTypeToStr(handlerCodes)
}
return idTypeToStr(idTypeFixToUint64(handlerCodes))
}
@ -374,33 +382,6 @@ 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 getDBConfigCode(dbDriver string) string {
dbConfigCode := ""
switch strings.ToLower(dbDriver) {
@ -410,6 +391,8 @@ func getDBConfigCode(dbDriver string) string {
dbConfigCode = postgresqlConfigCode
case DBDriverSqlite:
dbConfigCode = sqliteConfigCode
case DBDriverMongodb:
dbConfigCode = mongodbConfigCode
default:
panic("getDBConfigCode error, unsupported database driver: " + dbDriver)
}
@ -425,6 +408,8 @@ func getInitDBCode(dbDriver string) string {
initDBCode = modelInitDBFilePostgresqlCode
case DBDriverSqlite:
initDBCode = modelInitDBFileSqliteCode
case DBDriverMongodb:
initDBCode = "" // do nothing
default:
panic("getInitDBCode error, unsupported database driver: " + dbDriver)
}
@ -509,3 +494,13 @@ func sqliteDSNAdaptation(dbDriver string, dsn string) string {
}
return dsn
}
func removeElement(slice []string, element string) []string {
result := make([]string, 0, len(slice)-1)
for _, s := range slice {
if s != element {
result = append(result, s)
}
}
return result
}

View File

@ -29,8 +29,8 @@ func DaoCommand(parentName string) *cobra.Command {
cmd := &cobra.Command{
Use: "dao",
Short: "Generate dao code based on mysql table",
Long: fmt.Sprintf(`generate dao code based on mysql table.
Short: "Generate dao code based on sql",
Long: fmt.Sprintf(`generate dao code based on sql.
Examples:
# generate dao code and embed gorm.model struct.
@ -61,6 +61,9 @@ Examples:
continue
}
if sqlArgs.DBDriver == DBDriverMongodb {
sqlArgs.IsEmbed = false
}
sqlArgs.DBTable = tableName
codes, err := sql2code.Generate(&sqlArgs)
if err != nil {
@ -76,6 +79,7 @@ Examples:
g := &daoGenerator{
moduleName: moduleName,
dbDriver: sqlArgs.DBDriver,
isIncludeInitDB: isIncludeInitDB,
codes: codes,
outPath: outPath,
@ -98,7 +102,7 @@ using help:
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.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, mongodb, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database. Note: if db-driver=sqlite, db-dsn must be a local sqlite db file, e.g. --db-dsn=/tmp/sponge_sqlite.db") //nolint
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
@ -114,6 +118,7 @@ using help:
type daoGenerator struct {
moduleName string
dbDriver string
isIncludeInitDB bool
codes map[string]string
outPath string
@ -131,14 +136,28 @@ func (g *daoGenerator) generateCode() (string, error) {
"internal/model", "internal/cache", "internal/dao",
}
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
"init.go", "init_test.go", // internal/model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", // internal/cache
}
if g.isIncludeInitDB {
ignoreFiles = []string{
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", // internal/cache
var ignoreFiles []string
switch strings.ToLower(g.dbDriver) {
case DBDriverMysql, DBDriverPostgresql, DBDriverTidb, DBDriverSqlite:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"init.go", "init_test.go", "init.go.mgo", // internal/model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", "cache/userExample.go.mgo", // internal/cache
"dao/userExample.go.mgo", // internal/dao
}
if g.isIncludeInitDB {
ignoreFiles = removeElement(ignoreFiles, "init.go")
}
case DBDriverMongodb:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"init.go", "init_test.go", "init.go.mgo", // internal/model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", "cache/userExample.go", "cache/userExample_test.go", // internal/cache
"dao/userExample_test.go", "dao/userExample.go", // internal/dao
}
if g.isIncludeInitDB {
ignoreFiles = removeElement(ignoreFiles, "init.go.mgo")
}
default:
return "", errors.New("unsupported db driver: " + g.dbDriver)
}
r.SetSubDirsAndFiles(subDirs)
@ -160,6 +179,7 @@ func (g *daoGenerator) addFields(r replacer.Replacer) []replacer.Field {
fields = append(fields, deleteFieldsMark(r, modelFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoMgoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoTestFile, startMark, endMark)...)
fields = append(fields, []replacer.Field{
{ // replace the contents of the model/userExample.go file
@ -182,6 +202,14 @@ func (g *daoGenerator) addFields(r replacer.Replacer) []replacer.Field {
Old: g.moduleName + "/pkg",
New: "github.com/zhufuyi/sponge/pkg",
},
{
Old: "init.go.mgo",
New: "init.go",
},
{
Old: "userExample.go.mgo",
New: "userExample.go",
},
{
Old: "UserExample",
New: g.codes[parser.TableName],

View File

@ -32,8 +32,8 @@ func HandlerPbCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "handler-pb",
Short: "Generate handler and protobuf code based on mysql table",
Long: `generate handler and protobuf code based on mysql table.
Short: "Generate handler and protobuf code based on sql",
Long: `generate handler and protobuf code based on sql.
Examples:
# generate handler and protobuf code and embed gorm.model struct.
@ -64,6 +64,9 @@ Examples:
}
serverName = convertServerName(serverName)
if sqlArgs.DBDriver == DBDriverMongodb {
sqlArgs.IsEmbed = false
}
tableNames := strings.Split(dbTables, ",")
for _, tableName := range tableNames {
@ -80,6 +83,7 @@ Examples:
g := &handlerPbGenerator{
moduleName: moduleName,
serverName: serverName,
dbDriver: sqlArgs.DBDriver,
isEmbed: sqlArgs.IsEmbed,
codes: codes,
outPath: outPath,
@ -107,7 +111,7 @@ using help:
//_ = cmd.MarkFlagRequired("module-name")
cmd.Flags().StringVarP(&serverName, "server-name", "s", "", "server name")
//_ = cmd.MarkFlagRequired("server-name")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, mongodb, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database. Note: if db-driver=sqlite, db-dsn must be a local sqlite db file, e.g. --db-dsn=/tmp/sponge_sqlite.db") //nolint
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
@ -123,6 +127,7 @@ using help:
type handlerPbGenerator struct {
moduleName string
serverName string
dbDriver string
isEmbed bool
codes map[string]string
outPath string
@ -143,12 +148,28 @@ func (g *handlerPbGenerator) generateCode() (string, error) {
subDirs := []string{"internal/model", "internal/cache", "internal/dao", "internal/ecode",
"internal/handler", "api/serverNameExample"} // 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.pb.go", "userExample.pb.validate.go", "userExample_grpc.pb.go", "userExample_router.pb.go", // api/serverNameExample
"systemCode_http.go", "systemCode_rpc.go", "userExample_rpc.go", // internal/ecode
"init.go", "init_test.go", // internal/model
"handler/userExample.go", "handler/userExample_test.go", "handler/userExample_logic_test.go", // internal/handler
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", // internal/cache
var ignoreFiles []string
switch strings.ToLower(g.dbDriver) {
case DBDriverMysql, DBDriverPostgresql, DBDriverTidb, DBDriverSqlite:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"userExample.pb.go", "userExample.pb.validate.go", "userExample_grpc.pb.go", "userExample_router.pb.go", // api/serverNameExample
"systemCode_http.go", "systemCode_rpc.go", "userExample_rpc.go", // internal/ecode
"init.go", "init_test.go", "init.go.mgo", // internal/model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", "cache/userExample.go.mgo", // internal/cache
"dao/userExample.go.mgo", // internal/dao
"handler/userExample.go", "handler/userExample_test.go", "handler/userExample_logic_test.go", "handler/userExample_logic.go.mgo", // internal/handler
}
case DBDriverMongodb:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"userExample.pb.go", "userExample.pb.validate.go", "userExample_grpc.pb.go", "userExample_router.pb.go", // api/serverNameExample
"systemCode_http.go", "systemCode_rpc.go", "userExample_rpc.go", // internal/ecode
"init.go", "init_test.go", "init.go.mgo", // internal/model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", "cache/userExample.go", "cache/userExample_test.go", // internal/cache
"dao/userExample_test.go", "dao/userExample.go", // internal/dao
"handler/userExample.go", "handler/userExample_test.go", "handler/userExample_logic_test.go", "handler/userExample_test.go", "handler/userExample_logic.go", // internal/handler
}
default:
return "", errors.New("unsupported db driver: " + g.dbDriver)
}
r.SetSubDirsAndFiles(subDirs)
@ -169,6 +190,7 @@ func (g *handlerPbGenerator) addFields(r replacer.Replacer) []replacer.Field {
fields = append(fields, deleteFieldsMark(r, modelFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoMgoFile, 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)...)
@ -219,10 +241,18 @@ func (g *handlerPbGenerator) addFields(r replacer.Replacer) []replacer.Field {
Old: g.moduleName + "/pkg",
New: "github.com/zhufuyi/sponge/pkg",
},
{
Old: "userExample_logic.go.mgo",
New: "userExample.go",
},
{
Old: "userExample_logic.go",
New: "userExample.go",
},
{
Old: "userExample.go.mgo",
New: "userExample.go",
},
{
Old: "UserExamplePb",
New: "UserExample",

View File

@ -29,8 +29,8 @@ func HandlerCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "handler",
Short: "Generate handler code based on mysql table",
Long: `generate handler code based on mysql table.
Short: "Generate handler code based on sql",
Long: `generate handler code based on sql.
Examples:
# generate handler code and embed gorm.model struct.
@ -54,6 +54,9 @@ Examples:
} else if moduleName == "" {
return errors.New(`required flag(s) "module-name" not set, use "sponge web handler -h" for help`)
}
if sqlArgs.DBDriver == DBDriverMongodb {
sqlArgs.IsEmbed = false
}
tableNames := strings.Split(dbTables, ",")
for _, tableName := range tableNames {
@ -69,6 +72,7 @@ Examples:
g := &handlerGenerator{
moduleName: moduleName,
dbDriver: sqlArgs.DBDriver,
codes: codes,
outPath: outPath,
}
@ -93,7 +97,7 @@ using help:
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.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, mongodb, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database. Note: if db-driver=sqlite, db-dsn must be a local sqlite db file, e.g. --db-dsn=/tmp/sponge_sqlite.db") //nolint
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
@ -108,6 +112,7 @@ using help:
type handlerGenerator struct {
moduleName string
dbDriver string
codes map[string]string
outPath string
}
@ -123,13 +128,30 @@ func (g *handlerGenerator) generateCode() (string, error) {
subDirs := []string{"internal/model", "internal/cache", "internal/dao",
"internal/ecode", "internal/handler", "internal/routers", "internal/types"} // 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
"systemCode_http.go", "systemCode_rpc.go", "userExample_rpc.go", // internal/ecode
"init.go", "init_test.go", // internal/model
"routers.go", "routers_test.go", "routers_pbExample.go", "routers_pbExample_test.go", "userExample_router.go", // internal/routers
"swagger_types.go", // internal/types
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", // internal/cache
"handler/userExample_logic.go", "handler/userExample_logic_test.go", // internal/handler
var ignoreFiles []string
switch strings.ToLower(g.dbDriver) {
case DBDriverMysql, DBDriverPostgresql, DBDriverTidb, DBDriverSqlite:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"systemCode_http.go", "systemCode_rpc.go", "userExample_rpc.go", // internal/ecode
"routers.go", "routers_test.go", "routers_pbExample.go", "routers_pbExample_test.go", "userExample_router.go", // internal/routers
"init.go", "init_test.go", "init.go.mgo", // internal/model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", "cache/userExample.go.mgo", // internal/cache
"dao/userExample.go.mgo", // internal/dao
"handler/userExample_logic.go", "handler/userExample_logic_test.go", "handler/userExample.go.mgo", "handler/userExample_logic.go.mgo", // internal/handler
"swagger_types.go", "userExample_types.go.mgo", // internal/types
}
case DBDriverMongodb:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"systemCode_http.go", "systemCode_rpc.go", "userExample_rpc.go", // internal/ecode
"routers.go", "routers_test.go", "routers_pbExample.go", "routers_pbExample_test.go", "userExample_router.go", // internal/routers
"init.go", "init_test.go", "init.go.mgo", // internal/model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", "cache/userExample.go", "cache/userExample_test.go", // internal/cache
"dao/userExample_test.go", "dao/userExample.go", // internal/dao
"handler/userExample_logic.go", "handler/userExample_logic_test.go", "handler/userExample.go", "handler/userExample_test.go", "handler/userExample_logic.go.mgo", // internal/handler
"swagger_types.go", "userExample_types.go", // internal/types
}
default:
return "", errors.New("unsupported db driver: " + g.dbDriver)
}
r.SetSubDirsAndFiles(subDirs)
@ -150,8 +172,10 @@ func (g *handlerGenerator) addFields(r replacer.Replacer) []replacer.Field {
fields = append(fields, deleteFieldsMark(r, modelFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoMgoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoTestFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, handlerFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, handlerMgoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, handlerTestFile, startMark, endMark)...)
fields = append(fields, []replacer.Field{
{ // replace the contents of the model/userExample.go file
@ -164,7 +188,7 @@ func (g *handlerGenerator) addFields(r replacer.Replacer) []replacer.Field {
},
{ // replace the contents of the handler/userExample.go file
Old: handlerFileMark,
New: adjustmentOfIDType(g.codes[parser.CodeTypeHandler]),
New: adjustmentOfIDType(g.codes[parser.CodeTypeHandler], g.dbDriver),
},
{
Old: selfPackageName + "/" + r.GetSourcePath(),
@ -182,6 +206,14 @@ func (g *handlerGenerator) addFields(r replacer.Replacer) []replacer.Field {
Old: g.moduleName + "/pkg",
New: "github.com/zhufuyi/sponge/pkg",
},
{
Old: "userExample_types.go.mgo",
New: "userExample_types.go",
},
{
Old: "userExample.go.mgo",
New: "userExample.go",
},
{
Old: "UserExample",
New: g.codes[parser.TableName],

View File

@ -33,8 +33,8 @@ func HTTPCommand() *cobra.Command {
//nolint
cmd := &cobra.Command{
Use: "http",
Short: "Generate web service code based on mysql table",
Long: `generate web service code based on mysql table.
Short: "Generate web service code based on sql",
Long: `generate web service code based on sql.
Examples:
# generate web service code and embed gorm.model struct.
@ -66,6 +66,9 @@ Examples:
}
projectName, serverName = convertProjectAndServerName(projectName, serverName)
if sqlArgs.DBDriver == DBDriverMongodb {
sqlArgs.IsEmbed = false
}
sqlArgs.DBTable = firstTable
codes, err := sql2code.Generate(&sqlArgs)
@ -100,6 +103,7 @@ Examples:
hg := &handlerGenerator{
moduleName: moduleName,
dbDriver: sqlArgs.DBDriver,
codes: codes,
outPath: outPath,
}
@ -129,7 +133,7 @@ using help:
_ = cmd.MarkFlagRequired("server-name")
cmd.Flags().StringVarP(&projectName, "project-name", "p", "", "project name")
_ = cmd.MarkFlagRequired("project-name")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, mongodb, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database. Note: if db-driver=sqlite, db-dsn must be a local sqlite db file, e.g. --db-dsn=/tmp/sponge_sqlite.db") //nolint
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
@ -172,15 +176,36 @@ func (g *httpGenerator) generateCode() (string, error) {
ignoreDirs := []string{ // specify the directory in the subdirectory where processing is ignored
"internal/service", "internal/rpcclient", "cmd/sponge",
}
ignoreFiles := []string{ // specify the files in the subdirectory to be ignored for processing
"swagger.json", "swagger.yaml", "apis.swagger.json", "apis.html", "apis.go", // sponge/docs
"userExample_rpc.go", "systemCode_rpc.go", // internal/ecode
"routers_pbExample.go", "routers_pbExample_test.go", "userExample_router.go", // internal/routers
"grpc.go", "grpc_option.go", "grpc_test.go", // internal/server
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", // internal/cache
"handler/userExample_logic.go", "handler/userExample_logic_test.go", // internal/handler
"scripts/image-rpc-test.sh", "scripts/patch.sh", "scripts/protoc.sh", "scripts/proto-doc.sh", // sponge/scripts
"init_test.go", // model
var ignoreFiles []string
switch strings.ToLower(g.dbDriver) {
case DBDriverMysql, DBDriverPostgresql, DBDriverTidb, DBDriverSqlite:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"swagger.json", "swagger.yaml", "apis.swagger.json", "apis.html", "apis.go", // sponge/docs
"userExample_rpc.go", "systemCode_rpc.go", // internal/ecode
"routers_pbExample.go", "routers_pbExample_test.go", "userExample_router.go", // internal/routers
"grpc.go", "grpc_option.go", "grpc_test.go", // internal/server
"scripts/image-rpc-test.sh", "scripts/patch.sh", "scripts/protoc.sh", "scripts/proto-doc.sh", // sponge/scripts
"init_test.go", "init.go.mgo", // model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", "cache/userExample.go.mgo", // internal/cache
"dao/userExample.go.mgo", // internal/dao
"handler/userExample_logic.go", "handler/userExample_logic_test.go", "handler/userExample.go.mgo", "handler/userExample_logic.go.mgo", // internal/handler
"userExample_types.go.mgo", // internal/types
}
case DBDriverMongodb:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"swagger.json", "swagger.yaml", "apis.swagger.json", "apis.html", "apis.go", // sponge/docs
"userExample_rpc.go", "systemCode_rpc.go", // internal/ecode
"routers_pbExample.go", "routers_pbExample_test.go", "userExample_router.go", // internal/routers
"grpc.go", "grpc_option.go", "grpc_test.go", // internal/server
"scripts/image-rpc-test.sh", "scripts/patch.sh", "scripts/protoc.sh", "scripts/proto-doc.sh", // sponge/scripts
"init_test.go", "init.go", // model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", "cache/userExample.go", "cache/userExample_test.go", // internal/cache
"dao/userExample_test.go", "dao/userExample.go", // internal/dao
"handler/userExample_logic.go", "handler/userExample_logic_test.go", "handler/userExample.go", "handler/userExample_test.go", "handler/userExample_logic.go.mgo", // internal/handler
"userExample_types.go", // internal/types
}
default:
return "", errors.New("unsupported db driver: " + g.dbDriver)
}
r.SetSubDirsAndFiles(subDirs, subFiles...)
@ -205,8 +230,10 @@ func (g *httpGenerator) addFields(r replacer.Replacer) []replacer.Field {
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, daoMgoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoTestFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, handlerFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, handlerMgoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, handlerTestFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, httpFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, dockerFile, wellStartMark, wellEndMark)...)
@ -245,7 +272,7 @@ func (g *httpGenerator) addFields(r replacer.Replacer) []replacer.Field {
},
{ // replace the contents of the handler/userExample.go file
Old: handlerFileMark,
New: adjustmentOfIDType(g.codes[parser.CodeTypeHandler]),
New: adjustmentOfIDType(g.codes[parser.CodeTypeHandler], g.dbDriver),
},
{ // replace the contents of the Dockerfile file
Old: dockerFileMark,
@ -353,6 +380,10 @@ func (g *httpGenerator) addFields(r replacer.Replacer) []replacer.Field {
Old: "root:123456@(192.168.3.37:3306)/account",
New: g.dbDSN,
},
{
Old: "root:123456@192.168.3.37:27017/account",
New: g.dbDSN,
},
{
Old: "root:123456@192.168.3.37:5432/account",
New: g.dbDSN,
@ -365,6 +396,18 @@ func (g *httpGenerator) addFields(r replacer.Replacer) []replacer.Field {
Old: "Makefile-for-http",
New: "Makefile",
},
{
Old: "init.go.mgo",
New: "init.go",
},
{
Old: "userExample_types.go.mgo",
New: "userExample_types.go",
},
{
Old: "userExample.go.mgo",
New: "userExample.go",
},
{
Old: "UserExample",
New: g.codes[parser.TableName],

View File

@ -27,8 +27,8 @@ func ModelCommand(parentName string) *cobra.Command {
cmd := &cobra.Command{
Use: "model",
Short: "Generate model code based on mysql table",
Long: fmt.Sprintf(`generate model code based on mysql table.
Short: "Generate model code based on sql",
Long: fmt.Sprintf(`generate model code based on sql.
Examples:
# generate model code and embed gorm.model struct.
@ -52,6 +52,9 @@ Examples:
continue
}
if sqlArgs.DBDriver == DBDriverMongodb {
sqlArgs.IsEmbed = false
}
sqlArgs.DBTable = tableName
codes, err := sql2code.Generate(&sqlArgs)
if err != nil {
@ -78,7 +81,7 @@ using help:
},
}
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, mongodb, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database. Note: if db-driver=sqlite, db-dsn must be a local sqlite db file, e.g. --db-dsn=/tmp/sponge_sqlite.db") //nolint
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
@ -106,7 +109,7 @@ func (g *modelGenerator) generateCode() (string, error) {
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
"init.go", "init_test.go",
"init.go", "init_test.go", "init.go.mgo",
}
r.SetSubDirsAndFiles(subDirs)

View File

@ -28,8 +28,8 @@ func ProtobufCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "protobuf",
Short: "Generate protobuf code based on mysql table",
Long: `generate protobuf code based on mysql table.
Short: "Generate protobuf code based on sql",
Long: `generate protobuf code based on sql.
Examples:
# generate protobuf code.
@ -60,6 +60,9 @@ Examples:
}
serverName = convertServerName(serverName)
if sqlArgs.DBDriver == DBDriverMongodb {
sqlArgs.IsEmbed = false
}
tableNames := strings.Split(dbTables, ",")
for _, tableName := range tableNames {
@ -100,7 +103,7 @@ using help:
//_ = cmd.MarkFlagRequired("module-name")
cmd.Flags().StringVarP(&serverName, "server-name", "s", "", "server name")
//_ = cmd.MarkFlagRequired("server-name")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, mongodb, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database. Note: if db-driver=sqlite, db-dsn must be a local sqlite db file, e.g. --db-dsn=/tmp/sponge_sqlite.db") //nolint
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")

View File

@ -81,6 +81,7 @@ type rpcPbGenerator struct {
outPath string
}
// nolint
func (g *rpcPbGenerator) generateCode() error {
protobufFiles, isImportTypes, err := parseProtobufFiles(g.protobufFile)
if err != nil {
@ -108,7 +109,7 @@ func (g *rpcPbGenerator) generateCode() error {
"types.pb.validate.go", "types.pb.go", // api/types
"userExample_rpc.go", "systemCode_http.go", "userExample_http.go", // internal/ecode
"http.go", "http_option.go", "http_test.go", // internal/server
"userExample.go", "userExample_client_test.go", "userExample_logic.go", "userExample_logic_test.go", "userExample_test.go", // internal/service
"userExample.go", "userExample_client_test.go", "userExample_logic.go", "userExample_logic_test.go", "userExample_test.go", "userExample.go.mgo", "userExample_client_test.go.mgo", // internal/service
}
if !isImportTypes {

View File

@ -34,8 +34,8 @@ func RPCCommand() *cobra.Command {
//nolint
cmd := &cobra.Command{
Use: "rpc",
Short: "Generate grpc service code based on mysql table",
Long: `generate grpc service code based on mysql table.
Short: "Generate grpc service code based on sql",
Long: `generate grpc service code based on sql.
Examples:
# generate grpc service code and embed gorm.model struct.
@ -67,6 +67,9 @@ Examples:
}
projectName, serverName = convertProjectAndServerName(projectName, serverName)
if sqlArgs.DBDriver == DBDriverMongodb {
sqlArgs.IsEmbed = false
}
sqlArgs.DBTable = firstTable
codes, err := sql2code.Generate(&sqlArgs)
@ -133,7 +136,7 @@ using help:
_ = cmd.MarkFlagRequired("server-name")
cmd.Flags().StringVarP(&projectName, "project-name", "p", "", "project name")
_ = cmd.MarkFlagRequired("project-name")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, mongodb, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database. Note: if db-driver=sqlite, db-dsn must be a local sqlite db file, e.g. --db-dsn=/tmp/sponge_sqlite.db") //nolint
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
@ -177,15 +180,34 @@ func (g *rpcGenerator) generateCode() (string, error) {
ignoreDirs := []string{ // specify the directory in the subdirectory where processing is ignored
"internal/handler", "internal/rpcclient", "internal/routers", "internal/types", "cmd/sponge",
}
ignoreFiles := []string{ // specify the files in the subdirectory to be ignored for processing
"types.pb.validate.go", "types.pb.go", // api/types
"userExample.pb.go", "userExample.pb.validate.go", "userExample_grpc.pb.go", "userExample_router.pb.go", // api/serverNameExample/v1
"userExample_http.go", "systemCode_http.go", // internal/ecode
"http.go", "http_option.go", "http_test.go", // internal/server
"userExample_logic.go", "userExample_logic_test.go", "service/userExample_test.go", // internal/service
"scripts/swag-docs.sh", // sponge/scripts
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", // internal/cache
"init_test.go", // model
var ignoreFiles []string
switch strings.ToLower(g.dbDriver) {
case DBDriverMysql, DBDriverPostgresql, DBDriverTidb, DBDriverSqlite:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"userExample_http.go", "systemCode_http.go", // internal/ecode
"http.go", "http_option.go", "http_test.go", // internal/server
"scripts/swag-docs.sh", // sponge/scripts
"types.pb.validate.go", "types.pb.go", // api/types
"userExample.pb.go", "userExample.pb.validate.go", "userExample_grpc.pb.go", "userExample_router.pb.go", // api/serverNameExample/v1
"init_test.go", "init.go.mgo", // model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", "cache/userExample.go.mgo", // internal/cache
"dao/userExample.go.mgo", // internal/dao
"userExample_logic.go", "userExample_logic_test.go", "service/userExample_test.go", "service/userExample.go.mgo", "service/userExample_client_test.go.mgo", // internal/service
}
case DBDriverMongodb:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"userExample_http.go", "systemCode_http.go", // internal/ecode
"http.go", "http_option.go", "http_test.go", // internal/server
"scripts/swag-docs.sh", // sponge/scripts
"types.pb.validate.go", "types.pb.go", // api/types
"userExample.pb.go", "userExample.pb.validate.go", "userExample_grpc.pb.go", "userExample_router.pb.go", // api/serverNameExample/v1
"init_test.go", "init.go", // model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", "cache/userExample.go", "cache/userExample_test.go", // internal/cache
"dao/userExample_test.go", "dao/userExample.go", // internal/dao
"userExample_logic.go", "userExample_logic_test.go", "service/userExample_test.go", "service/userExample.go", "service/userExample_client_test.go", // internal/service
}
default:
return "", errors.New("unsupported db driver: " + g.dbDriver)
}
r.SetSubDirsAndFiles(subDirs, subFiles...)
@ -210,10 +232,12 @@ func (g *rpcGenerator) addFields(r replacer.Replacer) []replacer.Field {
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, daoMgoFile, 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, serviceClientMgoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, serviceTestFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, dockerFile, wellStartMark, wellEndMark)...)
fields = append(fields, deleteFieldsMark(r, dockerFileBuild, wellStartMark, wellEndMark)...)
@ -267,7 +291,7 @@ func (g *rpcGenerator) addFields(r replacer.Replacer) []replacer.Field {
},
{ // replace the contents of the service/userExample_client_test.go file
Old: serviceFileMark,
New: adjustmentOfIDType(g.codes[parser.CodeTypeService]),
New: adjustmentOfIDType(g.codes[parser.CodeTypeService], g.dbDriver),
},
{ // replace the contents of the Dockerfile file
Old: dockerFileMark,
@ -384,6 +408,18 @@ func (g *rpcGenerator) addFields(r replacer.Replacer) []replacer.Field {
Old: "test/sql/sqlite/sponge.db",
New: sqliteDSNAdaptation(g.dbDriver, g.dbDSN),
},
{
Old: "init.go.mgo",
New: "init.go",
},
{
Old: "userExample_client_test.go.mgo",
New: "userExample_client_test.go",
},
{
Old: "userExample.go.mgo",
New: "userExample.go",
},
{
Old: "UserExample",
New: g.codes[parser.TableName],

View File

@ -31,8 +31,8 @@ func ServiceCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "service",
Short: "Generate grpc service code based on mysql table",
Long: `generate grpc service code based on mysql table.
Short: "Generate grpc service code based on sql",
Long: `generate grpc service code based on sql.
Examples:
# generate service code and embed gorm.model struct.
@ -63,6 +63,9 @@ Examples:
}
serverName = convertServerName(serverName)
if sqlArgs.DBDriver == DBDriverMongodb {
sqlArgs.IsEmbed = false
}
tableNames := strings.Split(dbTables, ",")
for _, tableName := range tableNames {
@ -79,6 +82,7 @@ Examples:
g := &serviceGenerator{
moduleName: moduleName,
serverName: serverName,
dbDriver: sqlArgs.DBDriver,
isEmbed: sqlArgs.IsEmbed,
codes: codes,
outPath: outPath,
@ -106,7 +110,7 @@ using help:
//_ = cmd.MarkFlagRequired("module-name")
cmd.Flags().StringVarP(&serverName, "server-name", "s", "", "server name")
//_ = cmd.MarkFlagRequired("server-name")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDriver, "db-driver", "k", "mysql", "database driver, support mysql, mongodb, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&sqlArgs.DBDsn, "db-dsn", "d", "", "database content address, e.g. user:password@(host:port)/database. Note: if db-driver=sqlite, db-dsn must be a local sqlite db file, e.g. --db-dsn=/tmp/sponge_sqlite.db") //nolint
_ = cmd.MarkFlagRequired("db-dsn")
cmd.Flags().StringVarP(&dbTables, "db-table", "t", "", "table name, multiple names separated by commas")
@ -122,11 +126,13 @@ using help:
type serviceGenerator struct {
moduleName string
serverName string
dbDriver string
isEmbed bool
codes map[string]string
outPath string
}
// nolint
func (g *serviceGenerator) generateCode() (string, error) {
subTplName := "service"
r := Replacers[TplNameSponge]
@ -142,12 +148,28 @@ func (g *serviceGenerator) generateCode() (string, error) {
subDirs := []string{"internal/model", "internal/cache", "internal/dao",
"internal/ecode", "internal/service", "api/serverNameExample"} // 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.pb.go", "userExample.pb.validate.go", "userExample_grpc.pb.go", "userExample_router.pb.go", // api/serverNameExample
"systemCode_http.go", "systemCode_rpc.go", "userExample_http.go", // internal/ecode
"init.go", "init_test.go", // internal/model
"service.go", "service_test.go", "userExample_logic.go", "userExample_logic_test.go", "service/userExample_test.go", // internal/service
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", // internal/cache
var ignoreFiles []string
switch strings.ToLower(g.dbDriver) {
case DBDriverMysql, DBDriverPostgresql, DBDriverTidb, DBDriverSqlite:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"systemCode_http.go", "systemCode_rpc.go", "userExample_http.go", // internal/ecode
"userExample.pb.go", "userExample.pb.validate.go", "userExample_grpc.pb.go", "userExample_router.pb.go", // api/serverNameExample/v1
"init.go", "init_test.go", "init.go.mgo", // internal/model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", "cache/userExample.go.mgo", // internal/cache
"dao/userExample.go.mgo", // internal/dao
"service.go", "service_test.go", "userExample_logic.go", "userExample_logic_test.go", "service/userExample_test.go", "service/userExample.go.mgo", "service/userExample_client_test.go.mgo", // internal/service
}
case DBDriverMongodb:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"systemCode_http.go", "systemCode_rpc.go", "userExample_http.go", // internal/ecode
"userExample.pb.go", "userExample.pb.validate.go", "userExample_grpc.pb.go", "userExample_router.pb.go", // api/serverNameExample/v1
"init.go", "init_test.go", "init.go.mgo", // internal/model
"doc.go", "cacheNameExample.go", "cacheNameExample_test.go", "cache/userExample.go", "cache/userExample_test.go", // internal/cache
"dao/userExample_test.go", "dao/userExample.go", // internal/dao
"service.go", "service_test.go", "userExample_logic.go", "userExample_logic_test.go", "service/userExample_test.go", "service/userExample.go", "service/userExample_client_test.go", // internal/service
}
default:
return "", errors.New("unsupported db driver: " + g.dbDriver)
}
r.SetSubDirsAndFiles(subDirs)
@ -168,10 +190,12 @@ func (g *serviceGenerator) addFields(r replacer.Replacer) []replacer.Field {
fields = append(fields, deleteFieldsMark(r, modelFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, daoMgoFile, 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, serviceClientMgoFile, startMark, endMark)...)
fields = append(fields, deleteFieldsMark(r, serviceTestFile, startMark, endMark)...)
fields = append(fields, []replacer.Field{
{ // replace the contents of the model/userExample.go file
@ -192,7 +216,7 @@ func (g *serviceGenerator) addFields(r replacer.Replacer) []replacer.Field {
},
{ // replace the contents of the service/userExample_client_test.go file
Old: serviceFileMark,
New: adjustmentOfIDType(g.codes[parser.CodeTypeService]),
New: adjustmentOfIDType(g.codes[parser.CodeTypeService], g.dbDriver),
},
{
Old: selfPackageName + "/" + r.GetSourcePath(),
@ -228,6 +252,14 @@ func (g *serviceGenerator) addFields(r replacer.Replacer) []replacer.Field {
Old: "serverNameExample",
New: g.serverName,
},
{
Old: "userExample_client_test.go.mgo",
New: "userExample_client_test.go",
},
{
Old: "userExample.go.mgo",
New: "userExample.go",
},
{
Old: "UserExample",
New: g.codes[parser.TableName],

View File

@ -370,7 +370,7 @@ grpc:
# grpc client-side settings, support for setting up multiple grpc clients.
grpcClient:
- name: "serverNameExample" # grpc service name, used for service discovery
- name: "your_grpc_service_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
@ -399,7 +399,7 @@ http:
# grpc client-side settings, support for setting up multiple grpc clients.
grpcClient:
- name: "serverNameExample" # grpc service name, used for service discovery
- name: "your_grpc_service_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
@ -421,10 +421,10 @@ grpcClient:
mysqlConfigCode = `# database setting
database:
driver: "mysql" # database driver, currently support mysql, postgres, tidb, sqlite
driver: "mysql" # database driver
# mysql settings
mysql:
# dsn format, <user>:<pass>@(127.0.0.1:3306)/<db>?[k=v& ......]
# dsn format, <username>:<password>@(<hostname>:<port>)/<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
@ -436,29 +436,11 @@ database:
#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, sqlite
// # 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, sqlite
driver: "postgresql" # database driver
# postgresql settings
postgresql:
# dsn format, <user>:<pass>@127.0.0.1:5432/<db>?[k=v& ......]
# dsn format, <username>:<password>@<hostname>:<port>/<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
@ -466,7 +448,7 @@ database:
connMaxLifetime: 30 # sets the maximum time for which the connection can be reused, in minutes`
sqliteConfigCode = `database:
driver: "sqlite" # database driver, currently support mysql, postgres, tidb, sqlite
driver: "sqlite" # database driver
# sqlite settings
sqlite:
dbFile: "test/sql/sqlite/sponge.db"
@ -475,17 +457,13 @@ database:
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, sqlite
// # 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`
mongodbConfigCode = `database:
driver: "mongodb" # database driver
# mongodb settings
mongodb:
# dsn format, <username>:<password>@<hostname1>:<port1>[,<hostname2>:<port2>,......]/<db>?[k=v& ......]
# parameter k=v see https://www.mongodb.com/docs/drivers/go/current/fundamentals/connections/connection-guide/#connection-options
dsn: "root:123456@192.168.3.37:27017/account?connectTimeoutMS=15000"`
modelInitDBFileMysqlCode = `// InitDB connect database
func InitDB() {
@ -493,7 +471,7 @@ func InitDB() {
case ggorm.DBDriverMysql, ggorm.DBDriverTidb:
InitMysql()
default:
panic("unsupported database driver: " + config.Get().Database.Driver)
panic("InitDB error, unsupported database driver: " + config.Get().Database.Driver)
}
}
@ -529,7 +507,7 @@ func InitMysql() {
var err error
db, err = ggorm.InitMysql(dsn, opts...)
if err != nil {
panic("ggorm.InitMysql error: " + err.Error())
panic("InitMysql error: " + err.Error())
}
}`
@ -539,7 +517,7 @@ func InitDB() {
case ggorm.DBDriverPostgresql:
InitPostgresql()
default:
panic("unsupported database driver: " + config.Get().Database.Driver)
panic("InitDB error, unsupported database driver: " + config.Get().Database.Driver)
}
}
@ -568,7 +546,7 @@ func InitPostgresql() {
var err error
db, err = ggorm.InitPostgresql(dsn, opts...)
if err != nil {
panic("ggorm.InitPostgresql error: " + err.Error())
panic("InitPostgresql error: " + err.Error())
}
}`
@ -578,7 +556,7 @@ func InitDB() {
case ggorm.DBDriverSqlite:
InitSqlite()
default:
panic("unsupported database driver: " + config.Get().Database.Driver)
panic("InitDB error, unsupported database driver: " + config.Get().Database.Driver)
}
}
@ -601,12 +579,13 @@ func InitSqlite() {
}
var err error
db, err = ggorm.InitSqlite(config.Get().Database.Sqlite.DBFile, opts...)
var dbFile = utils.AdaptiveSqlite(config.Get().Database.Sqlite.DBFile)
db, err = ggorm.InitSqlite(dbFile, opts...)
if err != nil {
panic("ggorm.InitSqlite error: " + err.Error())
panic("InitSqlite error: " + err.Error())
}
}`
embedTimeCode = ` value.CreatedAt = record.CreatedAt.Unix()
embedTimeCode = `value.CreatedAt = record.CreatedAt.Unix()
value.UpdatedAt = record.UpdatedAt.Unix()`
)

View File

@ -16,7 +16,7 @@ import (
func GenerateDBInitCommand() *cobra.Command {
var (
moduleName string // go.mod module name
dbDriver string // database driver e.g. mysql, postgresql, tidb
dbDriver string // database driver e.g. mysql, mongodb, postgresql, tidb, sqlite
outPath string // output directory
targetFile = "internal/model/init.go"
)
@ -24,13 +24,13 @@ func GenerateDBInitCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "gen-db-init",
Short: "Generate database initialization code",
Long: `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.
# generate mysql 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,
@ -40,7 +40,7 @@ Examples:
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`)
return fmt.Errorf(`required flag(s) "module-name" not set, use "sponge patch gen-db-init -h" for help`)
}
var isEmpty bool
@ -82,7 +82,7 @@ using help:
},
}
cmd.Flags().StringVarP(&dbDriver, "db-driver", "k", "mysql", "database driver, support mysql, postgresql, tidb, sqlite")
cmd.Flags().StringVarP(&dbDriver, "db-driver", "k", "mysql", "database driver, support mysql, mongodb, postgresql, tidb, sqlite")
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")
@ -97,7 +97,8 @@ type dbInitGenerator struct {
}
func (g *dbInitGenerator) generateCode() (string, error) {
subTplName := g.dbDriver + "-init"
fmt.Println(*g)
subTplName := "init-" + g.dbDriver
r := generate.Replacers[generate.TplNameSponge]
if r == nil {
return "", errors.New("replacer is nil")
@ -106,8 +107,18 @@ func (g *dbInitGenerator) generateCode() (string, error) {
// 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",
var ignoreFiles []string
switch strings.ToLower(g.dbDriver) {
case generate.DBDriverMysql, generate.DBDriverPostgresql, generate.DBDriverTidb, generate.DBDriverSqlite:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"userExample.go", "init_test.go", "init.go.mgo",
}
case generate.DBDriverMongodb:
ignoreFiles = []string{ // specify the files in the subdirectory to be ignored for processing
"userExample.go", "init_test.go", "init.go",
}
default:
return "", fmt.Errorf("unsupported database driver: %s", g.dbDriver)
}
r.SetSubDirsAndFiles(subDirs)
@ -138,6 +149,10 @@ func (g *dbInitGenerator) addFields(r replacer.Replacer) []replacer.Field {
New: g.moduleName + "/configs",
IsCaseSensitive: false,
},
{ // rename init.go.mgo --> init.go
Old: "init.go.mgo",
New: "init.go",
},
{ // replace the contents of the model/init.go file
Old: generate.ModelInitDBFileMark,
New: generate.GetInitDataBaseCode(g.dbDriver),

View File

@ -19,8 +19,7 @@ var (
func NewRootCMD() *cobra.Command {
cmd := &cobra.Command{
Use: "sponge",
Long: `Sponge is a powerful golang basic development framework that integrates automatic code generation,
web and microservice framework.
Long: `Sponge is a powerful Go development framework, it's easy to develop web and microservice projects.
repo: https://github.com/zhufuyi/sponge
docs: https://go-sponge.com`,
SilenceErrors: true,

View File

@ -128,6 +128,7 @@ func copyToTempDir(targetVersion string) (string, error) {
if err != nil {
return "", err
}
_ = executeCommand("rm", "-rf", targetDir+"/cmd/sponge")
versionNum := strings.Replace(spongeDirName, "sponge@", "", 1)
err = os.WriteFile(versionFile, []byte(versionNum), 0644)

View File

@ -1,7 +1,7 @@
// Package main sponge is a powerful tool for generating web and microservice code, a microservice
// framework based on gin and grpc encapsulation, and an open source framework for rapid application
// development. Sponge has a wealth of code generation commands, sponge generate code unified in
// the UI interface operation, it is easy to build a complete project engineering code.
// Package main sponge is a basic development framework that integrates code auto generation,
// Gin and GRPC, a microservice framework. it is easy to build a complete project from development
// to deployment, just fill in the business logic code on the generated template code, greatly improved
// development efficiency and reduced development difficulty, the use of Go can also be "low-code development".
package main
import (

View File

@ -6,22 +6,24 @@ import (
"errors"
"fmt"
"io"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"time"
"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/mgo"
"github.com/zhufuyi/sponge/pkg/utils"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/bson"
)
var (
@ -43,6 +45,7 @@ type kv struct {
func ListDbDrivers(c *gin.Context) {
dbDrivers := []string{
ggorm.DBDriverMysql,
mgo.DBDriverName,
ggorm.DBDriverPostgresql,
ggorm.DBDriverTidb,
ggorm.DBDriverSqlite,
@ -64,7 +67,7 @@ func ListTables(c *gin.Context) {
form := &dbInfoForm{}
err := c.ShouldBindJSON(form)
if err != nil {
response.Error(c, ecode.InvalidParams.WithDetails(err.Error()))
response.Error(c, errcode.InvalidParams.WithDetails(err.Error()))
return
}
@ -76,15 +79,17 @@ func ListTables(c *gin.Context) {
tables, err = getPostgresqlTables(form.Dsn)
case ggorm.DBDriverSqlite:
tables, err = getSqliteTables(form.Dsn)
case mgo.DBDriverName:
tables, err = getMongodbTables(form.Dsn)
case "":
response.Error(c, ecode.InternalServerError.WithDetails("database type is empty"))
response.Error(c, errcode.InternalServerError.WithDetails("database type is empty"))
return
default:
response.Error(c, ecode.InternalServerError.WithDetails("unsupported database driver: "+form.DbDriver))
response.Error(c, errcode.InternalServerError.WithDetails("unsupported database driver: "+form.DbDriver))
return
}
if err != nil {
response.Error(c, ecode.InternalServerError.WithDetails(err.Error()))
response.Error(c, errcode.InternalServerError.WithDetails(err.Error()))
return
}
@ -113,7 +118,7 @@ func GenerateCode(c *gin.Context) {
form := &GenerateCodeForm{}
err := c.ShouldBindJSON(form)
if err != nil {
responseErr(c, err, ecode.InvalidParams)
responseErr(c, err, errcode.InvalidParams)
return
}
@ -150,20 +155,20 @@ func handleGenerateCode(c *gin.Context, outPath string, arg string) {
_ = v
}
if result.Err != nil {
responseErr(c, result.Err, ecode.InternalServerError)
responseErr(c, result.Err, errcode.InternalServerError)
return
}
zipFile := out + ".zip"
err := CompressPathToZip(out, zipFile)
if err != nil {
responseErr(c, err, ecode.InternalServerError)
responseErr(c, err, errcode.InternalServerError)
return
}
if !gofile.IsExists(zipFile) {
err = errors.New("no found file " + zipFile)
responseErr(c, err, ecode.InternalServerError)
responseErr(c, err, errcode.InternalServerError)
return
}
@ -186,7 +191,7 @@ func handleGenerateCode(c *gin.Context, outPath string, arg string) {
func GetRecord(c *gin.Context) {
pathParam := c.Param("path")
if pathParam == "" {
response.Out(c, ecode.InvalidParams.WithDetails("path param is empty"))
response.Out(c, errcode.InvalidParams.WithDetails("path param is empty"))
return
}
@ -209,18 +214,18 @@ func responseErr(c *gin.Context, err error, ec *errcode.Error) {
func UploadFiles(c *gin.Context) {
form, err := c.MultipartForm()
if err != nil {
response.Error(c, ecode.InvalidParams.WithDetails(err.Error()))
response.Error(c, errcode.InvalidParams.WithDetails(err.Error()))
return
}
if len(form.File) == 0 {
response.Error(c, ecode.InvalidParams.WithDetails("upload file is empty"))
response.Error(c, errcode.InvalidParams.WithDetails("upload file is empty"))
return
}
//spongeArg, err := getFormValue(form.Value, "spongeArg")
//if err != nil {
// response.Error(c, ecode.InvalidParams.WithDetails("the field 'spongeArg' cannot be empty"))
// response.Error(c, errcode.InvalidParams.WithDetails("the field 'spongeArg' cannot be empty"))
// return
//}
@ -233,7 +238,7 @@ func UploadFiles(c *gin.Context) {
filename := filepath.Base(file.Filename)
fileType = path.Ext(filename)
if !checkFileType(fileType) {
response.Error(c, ecode.InvalidParams.WithDetails("only .proto or yaml files are allowed to be uploaded"))
response.Error(c, errcode.InvalidParams.WithDetails("only .proto or yaml files are allowed to be uploaded"))
return
}
@ -242,7 +247,7 @@ func UploadFiles(c *gin.Context) {
continue
}
if err = c.SaveUploadedFile(file, filePath); err != nil {
response.Error(c, ecode.InternalServerError.WithDetails(err.Error()))
response.Error(c, errcode.InternalServerError.WithDetails(err.Error()))
return
}
@ -431,3 +436,24 @@ func getSqliteTables(dbFile string) ([]string, error) {
return filteredTables, nil
}
func getMongodbTables(dsn string) ([]string, error) {
dsn = utils.AdaptiveMongodbDsn(dsn)
db, err := mgo.Init(dsn)
if err != nil {
return nil, err
}
defer mgo.Close(db) //nolint
tables, err := db.ListCollectionNames(context.Background(), bson.M{})
if err != nil {
return nil, err
}
if len(tables) == 0 {
u, _ := url.Parse(dsn)
return nil, fmt.Errorf("mongodb db %s has no tables", strings.TrimLeft(u.Path, "/"))
}
return tables, nil
}

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.7695fd620e1371d8c60dcc9e799e6a9e.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.f030f300c40167960866.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.01a6ed84310104903a9dfae0136cb164.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.18ec0c08047b3a2f8f6f.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 6ce38f5eef2f14b1055e"],"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 6ce38f5eef2f14b1055e"],"sourceRoot":""}
{"version":3,"sources":["webpack:///webpack/bootstrap 732b61e485086b18e05b"],"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 732b61e485086b18e05b"],"sourceRoot":""}

13
go.mod
View File

@ -40,6 +40,7 @@ require (
github.com/uptrace/opentelemetry-go-extra/otelgorm v0.1.15
github.com/vmihailenco/msgpack v4.0.4+incompatible
go.etcd.io/etcd/client/v3 v3.5.4
go.mongodb.org/mongo-driver v1.14.0
go.opentelemetry.io/contrib v1.9.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.34.0
go.opentelemetry.io/otel v1.9.0
@ -48,7 +49,7 @@ require (
go.opentelemetry.io/otel/sdk v1.9.0
go.opentelemetry.io/otel/trace v1.9.0
go.uber.org/zap v1.21.0
golang.org/x/crypto v0.14.0
golang.org/x/crypto v0.17.0
golang.org/x/sync v0.1.0
google.golang.org/genproto v0.0.0-20230223222841-637eb2293923
google.golang.org/grpc v1.53.0
@ -130,6 +131,7 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/juju/errors v0.0.0-20170703010042-c7d06af17c68 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
@ -145,6 +147,7 @@ require (
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
@ -164,6 +167,10 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/uptrace/opentelemetry-go-extra/otelsql v0.1.15 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
go.etcd.io/etcd/api/v3 v3.5.4 // indirect
@ -174,8 +181,8 @@ require (
golang.org/x/arch v0.3.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/oauth2 v0.5.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/appengine v1.6.7 // indirect

34
go.sum
View File

@ -428,6 +428,8 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
@ -500,6 +502,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nacos-group/nacos-sdk-go/v2 v2.1.0 h1:PxRwOzHhnK6eGGvioEGkn8s6XRXmUVuXu91i2yQcdDs=
@ -669,12 +673,21 @@ github.com/uptrace/opentelemetry-go-extra/otelsql v0.1.15/go.mod h1:aZXwJzbTHnhh
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw=
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
@ -685,6 +698,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7H
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v3 v3.5.4 h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4=
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -742,9 +757,10 @@ golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -781,6 +797,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -829,6 +846,7 @@ golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -934,13 +952,15 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -953,8 +973,9 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1018,6 +1039,7 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

148
internal/cache/userExample.go.mgo vendored Normal file
View File

@ -0,0 +1,148 @@
package cache
import (
"context"
"strings"
"time"
"github.com/zhufuyi/sponge/internal/model"
"github.com/zhufuyi/sponge/pkg/cache"
"github.com/zhufuyi/sponge/pkg/encoding"
)
const (
// cache prefix key, must end with a colon
userExampleCachePrefixKey = "userExample:"
// UserExampleExpireTime expire time
UserExampleExpireTime = 5 * time.Minute
)
var _ UserExampleCache = (*userExampleCache)(nil)
// UserExampleCache cache interface
type UserExampleCache interface {
Set(ctx context.Context, id string, data *model.UserExample, duration time.Duration) error
Get(ctx context.Context, id string) (*model.UserExample, error)
MultiGet(ctx context.Context, ids []string) (map[string]*model.UserExample, error)
MultiSet(ctx context.Context, data []*model.UserExample, duration time.Duration) error
Del(ctx context.Context, id string) error
SetCacheWithNotFound(ctx context.Context, id string) error
}
// userExampleCache define a cache struct
type userExampleCache struct {
cache cache.Cache
}
// NewUserExampleCache new a cache
func NewUserExampleCache(cacheType *model.CacheType) UserExampleCache {
jsonEncoding := encoding.JSONEncoding{}
cachePrefix := ""
cType := strings.ToLower(cacheType.CType)
switch cType {
case "redis":
c := cache.NewRedisCache(cacheType.Rdb, cachePrefix, jsonEncoding, func() interface{} {
return &model.UserExample{}
})
return &userExampleCache{cache: c}
case "memory":
c := cache.NewMemoryCache(cachePrefix, jsonEncoding, func() interface{} {
return &model.UserExample{}
})
return &userExampleCache{cache: c}
}
return nil // no cache
}
// GetUserExampleCacheKey cache key
func (c *userExampleCache) GetUserExampleCacheKey(id string) string {
return userExampleCachePrefixKey + id
}
// Set write to cache
func (c *userExampleCache) Set(ctx context.Context, id string, data *model.UserExample, duration time.Duration) error {
if data == nil || id == "" {
return nil
}
cacheKey := c.GetUserExampleCacheKey(id)
err := c.cache.Set(ctx, cacheKey, data, duration)
if err != nil {
return err
}
return nil
}
// Get cache value
func (c *userExampleCache) Get(ctx context.Context, id string) (*model.UserExample, error) {
var data *model.UserExample
cacheKey := c.GetUserExampleCacheKey(id)
err := c.cache.Get(ctx, cacheKey, &data)
if err != nil {
return nil, err
}
return data, nil
}
// MultiSet multiple set cache
func (c *userExampleCache) MultiSet(ctx context.Context, data []*model.UserExample, duration time.Duration) error {
valMap := make(map[string]interface{})
for _, v := range data {
cacheKey := c.GetUserExampleCacheKey(v.ID.Hex())
valMap[cacheKey] = v
}
err := c.cache.MultiSet(ctx, valMap, duration)
if err != nil {
return err
}
return nil
}
// MultiGet multiple get cache, return key in map is id value
func (c *userExampleCache) MultiGet(ctx context.Context, ids []string) (map[string]*model.UserExample, error) {
var keys []string
for _, v := range ids {
cacheKey := c.GetUserExampleCacheKey(v)
keys = append(keys, cacheKey)
}
itemMap := make(map[string]*model.UserExample)
err := c.cache.MultiGet(ctx, keys, itemMap)
if err != nil {
return nil, err
}
retMap := make(map[string]*model.UserExample)
for _, id := range ids {
val, ok := itemMap[c.GetUserExampleCacheKey(id)]
if ok {
retMap[id] = val
}
}
return retMap, nil
}
// Del delete cache
func (c *userExampleCache) Del(ctx context.Context, id string) error {
cacheKey := c.GetUserExampleCacheKey(id)
err := c.cache.Del(ctx, cacheKey)
if err != nil {
return err
}
return nil
}
// SetCacheWithNotFound set empty cache
func (c *userExampleCache) SetCacheWithNotFound(ctx context.Context, id string) error {
cacheKey := c.GetUserExampleCacheKey(id)
err := c.cache.SetCacheWithNotFound(ctx, cacheKey)
if err != nil {
return err
}
return nil
}

View File

@ -136,10 +136,15 @@ type Redis struct {
}
type Database struct {
Driver string `yaml:"driver" json:"driver"`
Mysql Mysql `yaml:"mysql" json:"mysql"`
Postgresql Postgresql `yaml:"postgresql" json:"postgresql"`
Sqlite Sqlite `yaml:"sqlite" json:"sqlite"`
Driver string `yaml:"driver" json:"driver"`
Mongodb Mongodb `yaml:"mongodb" json:"mongodb"`
Mysql Mysql `yaml:"mysql" json:"mysql"`
Postgresql Mysql `yaml:"postgresql" json:"postgresql"`
Sqlite Sqlite `yaml:"sqlite" json:"sqlite"`
}
type Mongodb struct {
Dsn string `yaml:"dsn" json:"dsn"`
}
type Grpc struct {
@ -167,4 +172,4 @@ type HTTP struct {
Port int `yaml:"port" json:"port"`
ReadTimeout int `yaml:"readTimeout" json:"readTimeout"`
WriteTimeout int `yaml:"writeTimeout" json:"writeTimeout"`
}
}

View File

@ -0,0 +1,418 @@
package dao
import (
"context"
"errors"
"fmt"
"time"
"github.com/zhufuyi/sponge/internal/cache"
"github.com/zhufuyi/sponge/internal/model"
cacheBase "github.com/zhufuyi/sponge/pkg/cache"
"github.com/zhufuyi/sponge/pkg/mgo"
"github.com/zhufuyi/sponge/pkg/mgo/query"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"golang.org/x/sync/singleflight"
)
var _ UserExampleDao = (*userExampleDao)(nil)
// UserExampleDao defining the dao interface
type UserExampleDao interface {
Create(ctx context.Context, record *model.UserExample) error
DeleteByID(ctx context.Context, id string) error
DeleteByIDs(ctx context.Context, ids []string) error
UpdateByID(ctx context.Context, record *model.UserExample) error
GetByID(ctx context.Context, id string) (*model.UserExample, error)
GetByCondition(ctx context.Context, condition *query.Conditions) (*model.UserExample, error)
GetByIDs(ctx context.Context, ids []string) (map[string]*model.UserExample, error)
GetByLastID(ctx context.Context, lastID string, limit int, sort string) ([]*model.UserExample, error)
GetByColumns(ctx context.Context, params *query.Params) ([]*model.UserExample, int64, error)
}
type userExampleDao struct {
collection *mongo.Collection
cache cache.UserExampleCache // if nil, the cache is not used.
sfg *singleflight.Group // if cache is nil, the sfg is not used.
}
// NewUserExampleDao creating the dao interface
func NewUserExampleDao(collection *mongo.Collection, xCache cache.UserExampleCache) UserExampleDao {
if xCache == nil {
return &userExampleDao{collection: collection}
}
return &userExampleDao{
collection: collection,
cache: xCache,
sfg: new(singleflight.Group),
}
}
func (d *userExampleDao) deleteCache(ctx context.Context, id string) error {
if d.cache != nil {
return d.cache.Del(ctx, id)
}
return nil
}
// Create a record, insert the record and the id value is written back to the table
func (d *userExampleDao) Create(ctx context.Context, record *model.UserExample) error {
if record.ID.IsZero() {
record.ID = primitive.NewObjectID()
}
if record.CreatedAt.IsZero() {
record.CreatedAt = time.Now()
record.UpdatedAt = time.Now()
}
_, err := d.collection.InsertOne(ctx, record)
_ = d.deleteCache(ctx, record.ID.Hex())
return err
}
// DeleteByID soft delete a record by id
func (d *userExampleDao) DeleteByID(ctx context.Context, id string) error {
filter := bson.M{"_id": model.ToObjectID(id)}
_, err := d.collection.UpdateOne(ctx, mgo.ExcludeDeleted(filter), mgo.EmbedDeletedAt(bson.M{}))
if err != nil {
return err
}
// delete cache
_ = d.deleteCache(ctx, id)
return nil
}
// DeleteByIDs soft delete records by batch id
func (d *userExampleDao) DeleteByIDs(ctx context.Context, ids []string) error {
oids := mgo.ConvertToObjectIDs(ids)
filter := bson.M{"_id": bson.M{"$in": oids}}
_, err := d.collection.UpdateMany(ctx, mgo.ExcludeDeleted(filter), mgo.EmbedDeletedAt(bson.M{}))
if err != nil {
return err
}
// delete cache
for _, id := range ids {
_ = d.deleteCache(ctx, id)
}
return nil
}
// UpdateByID update a record by id
func (d *userExampleDao) UpdateByID(ctx context.Context, record *model.UserExample) error {
err := d.updateDataByID(ctx, d.collection, record)
// delete cache
_ = d.deleteCache(ctx, record.ID.Hex())
return err
}
func (d *userExampleDao) updateDataByID(ctx context.Context, collection *mongo.Collection, table *model.UserExample) error {
if table.ID.IsZero() {
return errors.New("id is empty or invalid")
}
update := bson.M{}
// todo generate the update fields code to here
// delete the templates code start
if table.Name != "" {
update["name"] = table.Name
}
if table.Password != "" {
update["password"] = table.Password
}
if table.Email != "" {
update["email"] = table.Email
}
if table.Phone != "" {
update["phone"] = table.Phone
}
if table.Avatar != "" {
update["avatar"] = table.Avatar
}
if table.Age > 0 {
update["age"] = table.Age
}
if table.Gender > 0 {
update["gender"] = table.Gender
}
if table.LoginAt > 0 {
update["login_at"] = table.LoginAt
}
// delete the templates code end
filter := bson.M{"_id": table.ID}
_, err := collection.UpdateOne(ctx, mgo.ExcludeDeleted(filter), mgo.EmbedUpdatedAt(update))
return err
}
// GetByID get a record by id
func (d *userExampleDao) GetByID(ctx context.Context, id string) (*model.UserExample, error) {
oid := model.ToObjectID(id)
if oid.IsZero() {
return nil, model.ErrRecordNotFound
}
filter := bson.M{"_id": oid}
// no cache
if d.cache == nil {
record := &model.UserExample{}
err := d.collection.FindOne(ctx, mgo.ExcludeDeleted(filter)).Decode(record)
return record, err
}
// get from cache or mongodb
cacheRecord, err := d.cache.Get(ctx, id)
if err == nil {
return cacheRecord, nil
}
if errors.Is(err, model.ErrCacheNotFound) {
// for the same id, prevent high concurrent simultaneous access to mongodb
val, err, _ := d.sfg.Do(id, func() (interface{}, error) { //nolint
record := &model.UserExample{}
err = d.collection.FindOne(ctx, mgo.ExcludeDeleted(filter)).Decode(record)
if err != nil {
// if data is empty, set not found cache to prevent cache penetration, default expiration time 10 minutes
if errors.Is(err, model.ErrRecordNotFound) {
err = d.cache.SetCacheWithNotFound(ctx, id)
if err != nil {
return nil, err
}
return nil, model.ErrRecordNotFound
}
return nil, err
}
// set cache
err = d.cache.Set(ctx, id, record, cache.UserExampleExpireTime)
if err != nil {
return nil, fmt.Errorf("cache.Set error: %v, id=%d", err, id)
}
return record, nil
})
if err != nil {
return nil, err
}
record, ok := val.(*model.UserExample)
if !ok {
return nil, model.ErrRecordNotFound
}
return record, nil
} else if errors.Is(err, cacheBase.ErrPlaceholder) {
return nil, model.ErrRecordNotFound
}
// fail fast, if cache error return, don't request to db
return nil, err
}
// GetByCondition get a record by condition
// query conditions:
//
// name: column name, if value is of type objectId, the suffix :oid must be added, e.g. post_id:oid
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in
// value: column value, if exp=in, multiple values are separated by commas
// logic: logical type, defaults to and when value is null, only &(and), ||(or)
//
// example: query the id of the post under the user James
//
// condition = &query.Conditions{
// Columns: []query.Column{
// {
// Name: "user_name",
// Value: "James",
// },
// {
// Name: "post_id:oid",
// Value: "65ce48483f11aff697e30d6d",
// },
// }
func (d *userExampleDao) GetByCondition(ctx context.Context, c *query.Conditions) (*model.UserExample, error) {
filter, err := c.ConvertToMongo()
if err != nil {
return nil, err
}
record := &model.UserExample{}
err = d.collection.FindOne(ctx, mgo.ExcludeDeleted(filter)).Decode(record)
if err != nil {
return nil, err
}
return record, nil
}
// GetByIDs get records by batch id
func (d *userExampleDao) GetByIDs(ctx context.Context, ids []string) (map[string]*model.UserExample, error) {
// no cache
if d.cache == nil {
records := []*model.UserExample{}
oids := mgo.ConvertToObjectIDs(ids)
filter := bson.M{"_id": bson.M{"$in": oids}}
cursor, err := d.collection.Find(ctx, mgo.ExcludeDeleted(filter))
if err != nil {
return nil, err
}
err = cursor.All(ctx, &records)
if err != nil {
return nil, err
}
itemMap := make(map[string]*model.UserExample)
for _, record := range records {
itemMap[record.ID.Hex()] = record
}
return itemMap, nil
}
// get form cache or mongodb
itemMap, err := d.cache.MultiGet(ctx, ids)
if err != nil {
return nil, err
}
var missedIDs []string
for _, id := range ids {
_, ok := itemMap[id]
if !ok {
missedIDs = append(missedIDs, id)
continue
}
}
// get missed data
if len(missedIDs) > 0 {
// find the id of an active placeholder, i.e. an id that does not exist in mongodb
var realMissedIDs []string
for _, id := range missedIDs {
_, err = d.cache.Get(ctx, id)
if errors.Is(err, cacheBase.ErrPlaceholder) {
continue
}
realMissedIDs = append(realMissedIDs, id)
}
if len(realMissedIDs) > 0 {
missedData := []*model.UserExample{}
oids := mgo.ConvertToObjectIDs(realMissedIDs)
filter := bson.M{"_id": bson.M{"$in": oids}}
cursor, err := d.collection.Find(ctx, mgo.ExcludeDeleted(filter))
if err != nil {
return nil, err
}
err = cursor.All(ctx, &missedData)
if err != nil {
return nil, err
}
if len(missedData) > 0 {
for _, data := range missedData {
itemMap[data.ID.Hex()] = data
}
err = d.cache.MultiSet(ctx, missedData, cache.UserExampleExpireTime)
if err != nil {
return nil, err
}
} else {
for _, id := range realMissedIDs {
_ = d.cache.SetCacheWithNotFound(ctx, id)
}
}
}
}
return itemMap, nil
}
// GetByLastID get paging records by last id and limit
func (d *userExampleDao) GetByLastID(ctx context.Context, lastID string, limit int, sort string) ([]*model.UserExample, error) {
page := query.NewPage(0, limit, sort)
findOpts := new(options.FindOptions)
findOpts.SetLimit(int64(page.Size())).SetSkip(int64(page.Skip()))
findOpts.Sort = page.Sort()
records := []*model.UserExample{}
filter := bson.M{"_id": bson.M{"$lt": model.ToObjectID(lastID)}}
cursor, err := d.collection.Find(ctx, mgo.ExcludeDeleted(filter), findOpts)
if err != nil {
return nil, err
}
err = cursor.All(ctx, &records)
if err != nil {
return nil, err
}
return records, nil
}
// GetByColumns get paging records by column information,
// Note: query performance degrades when table rows are very large because of the use of offset.
//
// params includes paging parameters and query parameters
// paging parameters (required):
//
// page: page number, starting from 0
// size: lines per page
// sort: sort fields, default is id backwards, you can add - sign before the field to indicate reverse order, no - sign to indicate ascending order, multiple fields separated by comma
//
// query parameters (not required):
//
// name: column name, if value is of type objectId, the suffix :oid must be added, e.g. order_id:oid
// exp: expressions, which default is "=", support =, !=, >, >=, <, <=, like, in
// value: column value, if exp=in, multiple values are separated by commas
// logic: logical type, defaults to and when value is null, only &(and), ||(or)
//
// example: search for a male over 20 years of age
//
// params = &query.Params{
// Page: 0,
// Size: 20,
// Columns: []query.Column{
// {
// Name: "age",
// Exp: ">",
// Value: 20,
// },
// {
// Name: "gender",
// Value: "male",
// },
// }
func (d *userExampleDao) GetByColumns(ctx context.Context, params *query.Params) ([]*model.UserExample, int64, error) {
filter, err := params.ConvertToMongoFilter()
if err != nil {
return nil, 0, errors.New("query params error: " + err.Error())
}
total, err := d.collection.CountDocuments(ctx, mgo.ExcludeDeleted(filter))
if err != nil {
return nil, 0, err
}
if total == 0 {
return nil, total, nil
}
records := []*model.UserExample{}
sort, limit, skip := params.ConvertToPage()
findOpts := new(options.FindOptions)
findOpts.SetLimit(int64(limit)).SetSkip(int64(skip))
findOpts.Sort = sort
cursor, err := d.collection.Find(ctx, mgo.ExcludeDeleted(filter), findOpts)
if err != nil {
return nil, 0, err
}
err = cursor.All(ctx, &records)
if err != nil {
return nil, 0, err
}
return records, total, err
}

View File

@ -0,0 +1,414 @@
package handler
import (
"errors"
"github.com/zhufuyi/sponge/internal/cache"
"github.com/zhufuyi/sponge/internal/dao"
"github.com/zhufuyi/sponge/internal/ecode"
"github.com/zhufuyi/sponge/internal/model"
"github.com/zhufuyi/sponge/internal/types"
"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/utils"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
)
var _ UserExampleHandler = (*userExampleHandler)(nil)
// UserExampleHandler defining the handler interface
type UserExampleHandler interface {
Create(c *gin.Context)
DeleteByID(c *gin.Context)
DeleteByIDs(c *gin.Context)
UpdateByID(c *gin.Context)
GetByID(c *gin.Context)
GetByCondition(c *gin.Context)
ListByIDs(c *gin.Context)
ListByLastID(c *gin.Context)
List(c *gin.Context)
}
type userExampleHandler struct {
iDao dao.UserExampleDao
}
// NewUserExampleHandler creating the handler interface
func NewUserExampleHandler() UserExampleHandler {
collectionName := new(model.UserExample).TableName()
return &userExampleHandler{
iDao: dao.NewUserExampleDao(
model.GetDB().Collection(collectionName),
cache.NewUserExampleCache(model.GetCacheType()),
),
}
}
// Create a record
// @Summary create userExample
// @Description submit information to create userExample
// @Tags userExample
// @accept json
// @Produce json
// @Param data body types.CreateUserExampleRequest true "userExample information"
// @Success 200 {object} types.CreateUserExampleRespond{}
// @Router /api/v1/userExample [post]
func (h *userExampleHandler) Create(c *gin.Context) {
form := &types.CreateUserExampleRequest{}
err := c.ShouldBindJSON(form)
if err != nil {
logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
response.Error(c, ecode.InvalidParams)
return
}
userExample := &model.UserExample{}
err = copier.Copy(userExample, form)
if err != nil {
response.Error(c, ecode.ErrCreateUserExample)
return
}
// todo if copier.Copy cannot assign a value to a field, add it here
ctx := middleware.WrapCtx(c)
err = h.iDao.Create(ctx, userExample)
if err != nil {
logger.Error("Create error", logger.Err(err), logger.Any("form", form), middleware.GCtxRequestIDField(c))
response.Output(c, ecode.InternalServerError.ToHTTPCode())
return
}
response.Success(c, gin.H{"id": userExample.ID})
}
// DeleteByID delete a record by id
// @Summary delete userExample
// @Description delete userExample by id
// @Tags userExample
// @accept json
// @Produce json
// @Param id path string true "id"
// @Success 200 {object} types.DeleteUserExampleByIDRespond{}
// @Router /api/v1/userExample/{id} [delete]
func (h *userExampleHandler) DeleteByID(c *gin.Context) {
id := c.Param("id")
ctx := middleware.WrapCtx(c)
err := h.iDao.DeleteByID(ctx, id)
if err != nil {
logger.Error("DeleteByID error", logger.Err(err), logger.Any("id", id), middleware.GCtxRequestIDField(c))
response.Output(c, ecode.InternalServerError.ToHTTPCode())
return
}
response.Success(c)
}
// DeleteByIDs delete records by batch id
// @Summary delete userExamples
// @Description delete userExamples by batch id
// @Tags userExample
// @Param data body types.DeleteUserExamplesByIDsRequest true "id array"
// @Accept json
// @Produce json
// @Success 200 {object} types.DeleteUserExamplesByIDsRespond{}
// @Router /api/v1/userExample/delete/ids [post]
func (h *userExampleHandler) DeleteByIDs(c *gin.Context) {
form := &types.DeleteUserExamplesByIDsRequest{}
err := c.ShouldBindJSON(form)
if err != nil {
logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
response.Error(c, ecode.InvalidParams)
return
}
ctx := middleware.WrapCtx(c)
err = h.iDao.DeleteByIDs(ctx, form.IDs)
if err != nil {
logger.Error("GetByIDs error", logger.Err(err), logger.Any("form", form), middleware.GCtxRequestIDField(c))
response.Output(c, ecode.InternalServerError.ToHTTPCode())
return
}
response.Success(c)
}
// UpdateByID update information by id
// @Summary update userExample
// @Description update userExample information by id
// @Tags userExample
// @accept json
// @Produce json
// @Param id path string true "id"
// @Param data body types.UpdateUserExampleByIDRequest true "userExample information"
// @Success 200 {object} types.UpdateUserExampleByIDRespond{}
// @Router /api/v1/userExample/{id} [put]
func (h *userExampleHandler) UpdateByID(c *gin.Context) {
oid := model.ToObjectID(c.Param("id"))
if oid.IsZero() {
logger.Warn("id invalid error", middleware.GCtxRequestIDField(c))
response.Error(c, ecode.InvalidParams)
return
}
form := &types.UpdateUserExampleByIDRequest{}
err := c.ShouldBindJSON(form)
if err != nil {
logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
response.Error(c, ecode.InvalidParams)
return
}
userExample := &model.UserExample{}
err = copier.Copy(userExample, form)
if err != nil {
response.Error(c, ecode.ErrUpdateByIDUserExample)
return
}
userExample.ID = oid
// todo if copier.Copy cannot assign a value to a field, add it here
ctx := middleware.WrapCtx(c)
err = h.iDao.UpdateByID(ctx, userExample)
if err != nil {
logger.Error("UpdateByID error", logger.Err(err), logger.Any("form", form), middleware.GCtxRequestIDField(c))
response.Output(c, ecode.InternalServerError.ToHTTPCode())
return
}
response.Success(c)
}
// GetByID get a record by id
// @Summary get userExample detail
// @Description get userExample detail by id
// @Tags userExample
// @Param id path string true "id"
// @Accept json
// @Produce json
// @Success 200 {object} types.GetUserExampleByIDRespond{}
// @Router /api/v1/userExample/{id} [get]
func (h *userExampleHandler) GetByID(c *gin.Context) {
id := c.Param("id")
ctx := middleware.WrapCtx(c)
userExample, err := h.iDao.GetByID(ctx, id)
if err != nil {
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 {
logger.Error("GetByID error", logger.Err(err), logger.Any("id", id), middleware.GCtxRequestIDField(c))
response.Output(c, ecode.InternalServerError.ToHTTPCode())
}
return
}
data := &types.UserExampleObjDetail{}
err = copier.Copy(data, userExample)
if err != nil {
response.Error(c, ecode.ErrGetByIDUserExample)
return
}
data.ID = userExample.ID.Hex()
// todo if copier.Copy cannot assign a value to a field, add it here
response.Success(c, gin.H{"userExample": data})
}
// GetByCondition get a record by condition
// @Summary get userExample by condition
// @Description get userExample by condition
// @Tags userExample
// @Param data body types.Conditions true "query condition"
// @Accept json
// @Produce json
// @Success 200 {object} types.GetUserExampleByConditionRespond{}
// @Router /api/v1/userExample/condition [post]
func (h *userExampleHandler) GetByCondition(c *gin.Context) {
form := &types.GetUserExampleByConditionRequest{}
err := c.ShouldBindJSON(form)
if err != nil {
logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
response.Error(c, ecode.InvalidParams)
return
}
err = form.Conditions.CheckValid()
if err != nil {
logger.Warn("Parameters error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
response.Error(c, ecode.InvalidParams)
return
}
ctx := middleware.WrapCtx(c)
userExample, err := h.iDao.GetByCondition(ctx, &form.Conditions)
if err != nil {
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 {
logger.Error("GetByCondition error", logger.Err(err), logger.Any("form", form), middleware.GCtxRequestIDField(c))
response.Output(c, ecode.InternalServerError.ToHTTPCode())
}
return
}
data := &types.UserExampleObjDetail{}
err = copier.Copy(data, userExample)
if err != nil {
response.Error(c, ecode.ErrGetByIDUserExample)
return
}
data.ID = userExample.ID.Hex()
// todo if copier.Copy cannot assign a value to a field, add it here
response.Success(c, gin.H{"userExample": data})
}
// ListByIDs list of records by batch id
// @Summary list of userExamples by batch id
// @Description list of userExamples by batch id
// @Tags userExample
// @Param data body types.ListUserExamplesByIDsRequest true "id array"
// @Accept json
// @Produce json
// @Success 200 {object} types.ListUserExamplesByIDsRespond{}
// @Router /api/v1/userExample/list/ids [post]
func (h *userExampleHandler) ListByIDs(c *gin.Context) {
form := &types.ListUserExamplesByIDsRequest{}
err := c.ShouldBindJSON(form)
if err != nil {
logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
response.Error(c, ecode.InvalidParams)
return
}
ctx := middleware.WrapCtx(c)
userExampleMap, err := h.iDao.GetByIDs(ctx, form.IDs)
if err != nil {
logger.Error("GetByIDs error", logger.Err(err), logger.Any("form", form), middleware.GCtxRequestIDField(c))
response.Output(c, ecode.InternalServerError.ToHTTPCode())
return
}
userExamples := []*types.UserExampleObjDetail{}
for _, id := range form.IDs {
if v, ok := userExampleMap[id]; ok {
record, err := convertUserExample(v)
if err != nil {
response.Error(c, ecode.ErrListUserExample)
return
}
userExamples = append(userExamples, record)
}
}
response.Success(c, gin.H{
"userExamples": userExamples,
})
}
// ListByLastID get records by last id and limit
// @Summary list of userExamples by last id and limit
// @Description list of userExamples by last id and limit
// @Tags userExample
// @accept json
// @Produce json
// @Param lastID query string false "last id, default()"
// @Param limit query int false "size per 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{}
// @Router /api/v1/userExample/list [get]
func (h *userExampleHandler) ListByLastID(c *gin.Context) {
lastID := c.Query("lastID")
if lastID == "" {
lastID = model.MaxObjectID
}
limit := utils.StrToInt(c.Query("limit"))
if limit == 0 {
limit = 10
}
sort := c.Query("sort")
ctx := middleware.WrapCtx(c)
userExamples, err := h.iDao.GetByLastID(ctx, lastID, limit, sort)
if err != nil {
logger.Error("GetByLastID error", logger.Err(err), logger.String("latsID", lastID), logger.Int("limit", limit), middleware.GCtxRequestIDField(c))
response.Output(c, ecode.InternalServerError.ToHTTPCode())
return
}
data, err := convertUserExamples(userExamples)
if err != nil {
response.Error(c, ecode.ErrListByLastIDUserExample)
return
}
response.Success(c, gin.H{
"userExamples": data,
})
}
// List of records by query parameters
// @Summary list of userExamples by query parameters
// @Description list of userExamples by paging and conditions
// @Tags userExample
// @accept json
// @Produce json
// @Param data body types.Params true "query parameters"
// @Success 200 {object} types.ListUserExamplesRespond{}
// @Router /api/v1/userExample/list [post]
func (h *userExampleHandler) List(c *gin.Context) {
form := &types.ListUserExamplesRequest{}
err := c.ShouldBindJSON(form)
if err != nil {
logger.Warn("ShouldBindJSON error: ", logger.Err(err), middleware.GCtxRequestIDField(c))
response.Error(c, ecode.InvalidParams)
return
}
ctx := middleware.WrapCtx(c)
userExamples, total, err := h.iDao.GetByColumns(ctx, &form.Params)
if err != nil {
logger.Error("GetByColumns error", logger.Err(err), logger.Any("form", form), middleware.GCtxRequestIDField(c))
response.Output(c, ecode.InternalServerError.ToHTTPCode())
return
}
data, err := convertUserExamples(userExamples)
if err != nil {
response.Error(c, ecode.ErrListUserExample)
return
}
response.Success(c, gin.H{
"userExamples": data,
"total": total,
})
}
func convertUserExample(userExample *model.UserExample) (*types.UserExampleObjDetail, error) {
data := &types.UserExampleObjDetail{}
err := copier.Copy(data, userExample)
if err != nil {
return nil, err
}
data.ID = userExample.ID.Hex()
// todo if copier.Copy cannot assign a value to a field, add it here
return data, nil
}
func convertUserExamples(fromValues []*model.UserExample) ([]*types.UserExampleObjDetail, error) {
toValues := []*types.UserExampleObjDetail{}
for _, v := range fromValues {
data, err := convertUserExample(v)
if err != nil {
return nil, err
}
toValues = append(toValues, data)
}
return toValues, nil
}

View File

@ -297,6 +297,7 @@ func convertUserExamplePb(record *model.UserExample) (*serverNameExampleV1.UserE
return nil, err
}
value.Id = record.ID
// todo if copier.Copy cannot assign a value to a field, add it here, e.g. CreatedAt, UpdatedAt
// todo generate the conversion createdAt and updatedAt code here
// delete the templates code start
value.CreatedAt = record.CreatedAt.Unix()

View File

@ -0,0 +1,310 @@
package handler
import (
"context"
"errors"
"strings"
serverNameExampleV1 "github.com/zhufuyi/sponge/api/serverNameExample/v1"
"github.com/zhufuyi/sponge/internal/cache"
"github.com/zhufuyi/sponge/internal/dao"
"github.com/zhufuyi/sponge/internal/ecode"
"github.com/zhufuyi/sponge/internal/model"
"github.com/zhufuyi/sponge/pkg/gin/middleware"
"github.com/zhufuyi/sponge/pkg/logger"
"github.com/zhufuyi/sponge/pkg/mgo/query"
"github.com/jinzhu/copier"
)
var _ serverNameExampleV1.UserExampleLogicer = (*userExamplePbHandler)(nil)
type userExamplePbHandler struct {
userExampleDao dao.UserExampleDao
}
// NewUserExamplePbHandler create a handler
func NewUserExamplePbHandler() serverNameExampleV1.UserExampleLogicer {
collectionName := new(model.UserExample).TableName()
return &userExamplePbHandler{
userExampleDao: dao.NewUserExampleDao(
model.GetDB().Collection(collectionName),
cache.NewUserExampleCache(model.GetCacheType()),
),
}
}
// Create a record
func (h *userExamplePbHandler) Create(ctx context.Context, req *serverNameExampleV1.CreateUserExampleRequest) (*serverNameExampleV1.CreateUserExampleReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), middleware.CtxRequestIDField(ctx))
return nil, ecode.InvalidParams.Err()
}
userExample := &model.UserExample{}
err = copier.Copy(userExample, req)
if err != nil {
return nil, ecode.ErrCreateUserExample.Err()
}
// todo if copier.Copy cannot assign a value to a field, add it here
err = h.userExampleDao.Create(ctx, userExample)
if err != nil {
logger.Error("Create error", logger.Err(err), logger.Any("userExample", userExample), middleware.CtxRequestIDField(ctx))
return nil, ecode.InternalServerError.Err()
}
return &serverNameExampleV1.CreateUserExampleReply{Id: userExample.ID.Hex()}, nil
}
// DeleteByID delete a record by id
func (h *userExamplePbHandler) DeleteByID(ctx context.Context, req *serverNameExampleV1.DeleteUserExampleByIDRequest) (*serverNameExampleV1.DeleteUserExampleByIDReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), middleware.CtxRequestIDField(ctx))
return nil, ecode.InvalidParams.Err()
}
err = h.userExampleDao.DeleteByID(ctx, req.Id)
if err != nil {
logger.Warn("DeleteByID error", logger.Err(err), middleware.CtxRequestIDField(ctx))
return nil, ecode.InternalServerError.Err()
}
return &serverNameExampleV1.DeleteUserExampleByIDReply{}, nil
}
// DeleteByIDs delete records by batch id
func (h *userExamplePbHandler) DeleteByIDs(ctx context.Context, req *serverNameExampleV1.DeleteUserExampleByIDsRequest) (*serverNameExampleV1.DeleteUserExampleByIDsReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), middleware.CtxRequestIDField(ctx))
return nil, ecode.InvalidParams.Err()
}
err = h.userExampleDao.DeleteByIDs(ctx, req.Ids)
if err != nil {
logger.Warn("DeleteByIDs error", logger.Err(err), middleware.CtxRequestIDField(ctx))
return nil, ecode.InternalServerError.Err()
}
return &serverNameExampleV1.DeleteUserExampleByIDsReply{}, nil
}
// UpdateByID update a record by id
func (h *userExamplePbHandler) UpdateByID(ctx context.Context, req *serverNameExampleV1.UpdateUserExampleByIDRequest) (*serverNameExampleV1.UpdateUserExampleByIDReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), middleware.CtxRequestIDField(ctx))
return nil, ecode.InvalidParams.Err()
}
userExample := &model.UserExample{}
err = copier.Copy(userExample, req)
if err != nil {
return nil, ecode.ErrUpdateByIDUserExample.Err()
}
userExample.ID = model.ToObjectID(req.Id)
// todo if copier.Copy cannot assign a value to a field, add it here
err = h.userExampleDao.UpdateByID(ctx, userExample)
if err != nil {
logger.Error("UpdateByID error", logger.Err(err), logger.Any("userExample", userExample), middleware.CtxRequestIDField(ctx))
return nil, ecode.InternalServerError.Err()
}
return &serverNameExampleV1.UpdateUserExampleByIDReply{}, nil
}
// GetByID get a record by id
func (h *userExamplePbHandler) GetByID(ctx context.Context, req *serverNameExampleV1.GetUserExampleByIDRequest) (*serverNameExampleV1.GetUserExampleByIDReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), middleware.CtxRequestIDField(ctx))
return nil, ecode.InvalidParams.Err()
}
record, err := h.userExampleDao.GetByID(ctx, req.Id)
if err != nil {
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()
}
logger.Error("GetByID error", logger.Err(err), logger.Any("id", req.Id), middleware.CtxRequestIDField(ctx))
return nil, ecode.InternalServerError.Err()
}
data, err := convertUserExamplePb(record)
if err != nil {
logger.Warn("convertUserExample error", logger.Err(err), logger.Any("userExample", record), middleware.CtxRequestIDField(ctx))
return nil, ecode.ErrGetByIDUserExample.Err()
}
return &serverNameExampleV1.GetUserExampleByIDReply{
UserExample: data,
}, nil
}
// GetByCondition get a record by condition
func (h *userExamplePbHandler) GetByCondition(ctx context.Context, req *serverNameExampleV1.GetUserExampleByConditionRequest) (*serverNameExampleV1.GetUserExampleByConditionReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), middleware.CtxRequestIDField(ctx))
return nil, ecode.InvalidParams.Err()
}
conditions := &query.Conditions{}
for _, v := range req.Conditions.GetColumns() {
column := query.Column{}
_ = copier.Copy(&column, v)
conditions.Columns = append(conditions.Columns, column)
}
err = conditions.CheckValid()
if err != nil {
logger.Warn("Parameters error", logger.Err(err), logger.Any("conditions", conditions), middleware.CtxRequestIDField(ctx))
return nil, ecode.InvalidParams.Err()
}
record, err := h.userExampleDao.GetByCondition(ctx, conditions)
if err != nil {
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()
}
logger.Error("GetByID error", logger.Err(err), logger.Any("req", req), middleware.CtxRequestIDField(ctx))
return nil, ecode.InternalServerError.Err()
}
data, err := convertUserExamplePb(record)
if err != nil {
logger.Warn("convertUserExample error", logger.Err(err), logger.Any("userExample", record), middleware.CtxRequestIDField(ctx))
return nil, ecode.ErrGetByIDUserExample.Err()
}
return &serverNameExampleV1.GetUserExampleByConditionReply{
UserExample: data,
}, nil
}
// ListByIDs list of records by batch id
func (h *userExamplePbHandler) ListByIDs(ctx context.Context, req *serverNameExampleV1.ListUserExampleByIDsRequest) (*serverNameExampleV1.ListUserExampleByIDsReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), middleware.CtxRequestIDField(ctx))
return nil, ecode.InvalidParams.Err()
}
userExampleMap, err := h.userExampleDao.GetByIDs(ctx, req.Ids)
if err != nil {
logger.Error("GetByIDs error", logger.Err(err), logger.Any("ids", req.Ids), middleware.CtxRequestIDField(ctx))
return nil, ecode.InternalServerError.Err()
}
userExamples := []*serverNameExampleV1.UserExample{}
for _, id := range req.Ids {
if v, ok := userExampleMap[id]; ok {
record, err := convertUserExamplePb(v)
if err != nil {
logger.Warn("convertUserExample error", logger.Err(err), logger.Any("userExample", v), middleware.CtxRequestIDField(ctx))
return nil, ecode.InternalServerError.Err()
}
userExamples = append(userExamples, record)
}
}
return &serverNameExampleV1.ListUserExampleByIDsReply{
UserExamples: userExamples,
}, nil
}
// ListByLastID get records by last id
func (h *userExamplePbHandler) ListByLastID(ctx context.Context, req *serverNameExampleV1.ListUserExampleByLastIDRequest) (*serverNameExampleV1.ListUserExampleByLastIDReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), middleware.CtxRequestIDField(ctx))
return nil, ecode.InvalidParams.Err()
}
if req.LastID == "" {
req.LastID = model.MaxObjectID
}
records, err := h.userExampleDao.GetByLastID(ctx, req.LastID, int(req.Limit), req.Sort)
if err != nil {
logger.Error("GetByColumns error", logger.Err(err), logger.Any("req", req), middleware.CtxRequestIDField(ctx))
return nil, ecode.InternalServerError.Err()
}
userExamples := []*serverNameExampleV1.UserExample{}
for _, record := range records {
data, err := convertUserExamplePb(record)
if err != nil {
logger.Warn("convertUserExample error", logger.Err(err), logger.Any("id", record.ID), middleware.CtxRequestIDField(ctx))
continue
}
userExamples = append(userExamples, data)
}
return &serverNameExampleV1.ListUserExampleByLastIDReply{
UserExamples: userExamples,
}, nil
}
// List of records by query parameters
func (h *userExamplePbHandler) List(ctx context.Context, req *serverNameExampleV1.ListUserExampleRequest) (*serverNameExampleV1.ListUserExampleReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), middleware.CtxRequestIDField(ctx))
return nil, ecode.InvalidParams.Err()
}
params := &query.Params{}
err = copier.Copy(params, req.Params)
if err != nil {
return nil, ecode.ErrListUserExample.Err()
}
params.Size = int(req.Params.Limit)
// todo if copier.Copy cannot assign a value to a field, add it here
records, total, err := h.userExampleDao.GetByColumns(ctx, params)
if err != nil {
if strings.Contains(err.Error(), "query params error:") {
logger.Warn("GetByColumns error", logger.Err(err), logger.Any("params", params), middleware.CtxRequestIDField(ctx))
return nil, ecode.InvalidParams.Err()
}
logger.Error("GetByColumns error", logger.Err(err), logger.Any("params", params), middleware.CtxRequestIDField(ctx))
return nil, ecode.InternalServerError.Err()
}
userExamples := []*serverNameExampleV1.UserExample{}
for _, record := range records {
data, err := convertUserExamplePb(record)
if err != nil {
logger.Warn("convertUserExample error", logger.Err(err), logger.Any("id", record.ID), middleware.CtxRequestIDField(ctx))
continue
}
userExamples = append(userExamples, data)
}
return &serverNameExampleV1.ListUserExampleReply{
Total: total,
UserExamples: userExamples,
}, nil
}
func convertUserExamplePb(record *model.UserExample) (*serverNameExampleV1.UserExample, error) {
value := &serverNameExampleV1.UserExample{}
err := copier.Copy(value, record)
if err != nil {
return nil, err
}
value.Id = record.ID.Hex()
// todo if copier.Copy cannot assign a value to a field, add it here, e.g. CreatedAt, UpdatedAt
// 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

@ -7,6 +7,7 @@ import (
"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"
@ -121,7 +122,7 @@ func InitDB() {
case ggorm.DBDriverSqlite:
InitSqlite()
default:
panic("unsupported database driver: " + config.Get().Database.Driver)
panic("InitDB error, unsupported database driver: " + config.Get().Database.Driver)
}
}
@ -157,7 +158,7 @@ func InitMysql() {
var err error
db, err = ggorm.InitMysql(dsn, opts...)
if err != nil {
panic("ggorm.InitMysql error: " + err.Error())
panic("InitMysql error: " + err.Error())
}
}
@ -186,7 +187,7 @@ func InitPostgresql() {
var err error
db, err = ggorm.InitPostgresql(dsn, opts...)
if err != nil {
panic("ggorm.InitPostgresql error: " + err.Error())
panic("InitPostgresql error: " + err.Error())
}
}
@ -209,9 +210,10 @@ func InitSqlite() {
}
var err error
db, err = ggorm.InitSqlite(config.Get().Database.Sqlite.DBFile, opts...)
var dbFile = utils.AdaptiveSqlite(config.Get().Database.Sqlite.DBFile)
db, err = ggorm.InitSqlite(dbFile, opts...)
if err != nil {
panic("ggorm.InitSqlite error: " + err.Error())
panic("InitSqlite error: " + err.Error())
}
}

155
internal/model/init.go.mgo Normal file
View File

@ -0,0 +1,155 @@
package model
import (
"strings"
"sync"
"time"
"github.com/zhufuyi/sponge/internal/config"
"github.com/zhufuyi/sponge/pkg/goredis"
"github.com/zhufuyi/sponge/pkg/mgo"
"github.com/zhufuyi/sponge/pkg/utils"
"github.com/go-redis/redis/v8"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
)
// MaxObjectID max object id
const MaxObjectID = "fffffffffffffffffffffffe"
var (
// ErrCacheNotFound No hit cache
ErrCacheNotFound = redis.Nil
// ErrRecordNotFound no records found
ErrRecordNotFound = mongo.ErrNoDocuments
)
var (
db *mongo.Database
once1 sync.Once
redisCli *redis.Client
once2 sync.Once
cacheType *CacheType
once3 sync.Once
)
// CacheType cache type
type CacheType struct {
CType string // cache type memory or redis
Rdb *redis.Client // if CType=redis, Rdb cannot be empty
}
// InitCache initial cache
func InitCache(cType string) {
cacheType = &CacheType{
CType: cType,
}
if cType == "redis" {
cacheType.Rdb = GetRedisCli()
}
}
// GetCacheType get cacheType
func GetCacheType() *CacheType {
if cacheType == nil {
once3.Do(func() {
InitCache(config.Get().App.CacheType)
})
}
return cacheType
}
// InitRedis connect redis
func InitRedis() {
opts := []goredis.Option{
goredis.WithDialTimeout(time.Duration(config.Get().Redis.DialTimeout) * time.Second),
goredis.WithReadTimeout(time.Duration(config.Get().Redis.ReadTimeout) * time.Second),
goredis.WithWriteTimeout(time.Duration(config.Get().Redis.WriteTimeout) * time.Second),
}
if config.Get().App.EnableTrace {
opts = append(opts, goredis.WithEnableTrace())
}
var err error
redisCli, err = goredis.Init(config.Get().Redis.Dsn, opts...)
if err != nil {
panic("goredis.Init error: " + err.Error())
}
}
// GetRedisCli get redis client
func GetRedisCli() *redis.Client {
if redisCli == nil {
once2.Do(func() {
InitRedis()
})
}
return redisCli
}
// CloseRedis close redis
func CloseRedis() error {
if redisCli == nil {
return nil
}
err := redisCli.Close()
if err != nil && err.Error() != redis.ErrClosed.Error() {
return err
}
return nil
}
// ---------------------------------------------------------------------------------------
// InitDB connect database
func InitDB() {
dbDriver := config.Get().Database.Driver
switch strings.ToLower(dbDriver) {
case mgo.DBDriverName:
InitMongodb()
default:
panic("InitDB error, unsupported database driver: " + dbDriver)
}
}
// GetDB get db
func GetDB() *mongo.Database {
if db == nil {
once1.Do(func() {
InitDB()
})
}
return db
}
// CloseDB close db
func CloseDB() error {
return mgo.Close(db)
}
// InitMongodb connect mongodb
func InitMongodb() {
var err error
var dsn = utils.AdaptiveMongodbDsn(config.Get().Database.Mongodb.Dsn)
db, err = mgo.Init(dsn)
if err != nil {
panic("mgo.Init error: " + err.Error())
}
}
// ToObjectID convert string to ObjectID
func ToObjectID(id string) primitive.ObjectID {
oid, _ := primitive.ObjectIDFromHex(id)
return oid
}

View File

@ -313,6 +313,7 @@ func convertUserExample(record *model.UserExample) (*serverNameExampleV1.UserExa
return nil, err
}
value.Id = record.ID
// todo if copier.Copy cannot assign a value to a field, add it here, e.g. CreatedAt, UpdatedAt
// todo generate the conversion createdAt and updatedAt code here
// delete the templates code start
value.CreatedAt = record.CreatedAt.Unix()

View File

@ -0,0 +1,326 @@
package service
import (
"context"
"errors"
"strings"
serverNameExampleV1 "github.com/zhufuyi/sponge/api/serverNameExample/v1"
"github.com/zhufuyi/sponge/internal/cache"
"github.com/zhufuyi/sponge/internal/dao"
"github.com/zhufuyi/sponge/internal/ecode"
"github.com/zhufuyi/sponge/internal/model"
"github.com/zhufuyi/sponge/pkg/grpc/interceptor"
"github.com/zhufuyi/sponge/pkg/logger"
"github.com/zhufuyi/sponge/pkg/mgo/query"
"github.com/jinzhu/copier"
"google.golang.org/grpc"
)
func init() {
registerFns = append(registerFns, func(server *grpc.Server) {
serverNameExampleV1.RegisterUserExampleServer(server, NewUserExampleServer()) // register service to the rpc service
})
}
var _ serverNameExampleV1.UserExampleServer = (*userExample)(nil)
type userExample struct {
serverNameExampleV1.UnimplementedUserExampleServer
iDao dao.UserExampleDao
}
// NewUserExampleServer create a new service
func NewUserExampleServer() serverNameExampleV1.UserExampleServer {
collectionName := new(model.UserExample).TableName()
return &userExample{
iDao: dao.NewUserExampleDao(
model.GetDB().Collection(collectionName),
cache.NewUserExampleCache(model.GetCacheType()),
),
}
}
// Create a record
func (s *userExample) Create(ctx context.Context, req *serverNameExampleV1.CreateUserExampleRequest) (*serverNameExampleV1.CreateUserExampleReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInvalidParams.Err()
}
ctx = interceptor.WrapServerCtx(ctx)
record := &model.UserExample{}
err = copier.Copy(record, req)
if err != nil {
return nil, ecode.StatusCreateUserExample.Err()
}
// todo if copier.Copy cannot assign a value to a field, add it here
err = s.iDao.Create(ctx, record)
if err != nil {
logger.Error("Create error", logger.Err(err), logger.Any("userExample", record), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInternalServerError.ToRPCErr()
}
return &serverNameExampleV1.CreateUserExampleReply{Id: record.ID.Hex()}, nil
}
// DeleteByID delete a record by id
func (s *userExample) DeleteByID(ctx context.Context, req *serverNameExampleV1.DeleteUserExampleByIDRequest) (*serverNameExampleV1.DeleteUserExampleByIDReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInvalidParams.Err()
}
ctx = interceptor.WrapServerCtx(ctx)
err = s.iDao.DeleteByID(ctx, req.Id)
if err != nil {
logger.Error("DeleteByID error", logger.Err(err), logger.Any("id", req.Id), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInternalServerError.ToRPCErr()
}
return &serverNameExampleV1.DeleteUserExampleByIDReply{}, nil
}
// DeleteByIDs delete records by batch id
func (s *userExample) DeleteByIDs(ctx context.Context, req *serverNameExampleV1.DeleteUserExampleByIDsRequest) (*serverNameExampleV1.DeleteUserExampleByIDsReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInvalidParams.Err()
}
ctx = interceptor.WrapServerCtx(ctx)
err = s.iDao.DeleteByIDs(ctx, req.Ids)
if err != nil {
logger.Error("DeleteByID error", logger.Err(err), logger.Any("ids", req.Ids), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInternalServerError.ToRPCErr()
}
return &serverNameExampleV1.DeleteUserExampleByIDsReply{}, nil
}
// UpdateByID update a record by id
func (s *userExample) UpdateByID(ctx context.Context, req *serverNameExampleV1.UpdateUserExampleByIDRequest) (*serverNameExampleV1.UpdateUserExampleByIDReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInvalidParams.Err()
}
ctx = interceptor.WrapServerCtx(ctx)
record := &model.UserExample{}
err = copier.Copy(record, req)
if err != nil {
return nil, ecode.StatusUpdateByIDUserExample.Err()
}
record.ID = model.ToObjectID(req.Id)
// todo if copier.Copy cannot assign a value to a field, add it here
err = s.iDao.UpdateByID(ctx, record)
if err != nil {
logger.Error("UpdateByID error", logger.Err(err), logger.Any("userExample", record), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInternalServerError.ToRPCErr()
}
return &serverNameExampleV1.UpdateUserExampleByIDReply{}, nil
}
// GetByID get a record by id
func (s *userExample) GetByID(ctx context.Context, req *serverNameExampleV1.GetUserExampleByIDRequest) (*serverNameExampleV1.GetUserExampleByIDReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInvalidParams.Err()
}
ctx = interceptor.WrapServerCtx(ctx)
record, err := s.iDao.GetByID(ctx, req.Id)
if err != nil {
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()
}
logger.Error("GetByID error", logger.Err(err), logger.Any("id", req.Id), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInternalServerError.ToRPCErr()
}
data, err := convertUserExample(record)
if err != nil {
logger.Warn("convertUserExample error", logger.Err(err), logger.Any("userExample", record), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusGetByIDUserExample.Err()
}
return &serverNameExampleV1.GetUserExampleByIDReply{UserExample: data}, nil
}
// GetByCondition get a record by id
func (s *userExample) GetByCondition(ctx context.Context, req *serverNameExampleV1.GetUserExampleByConditionRequest) (*serverNameExampleV1.GetUserExampleByConditionReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInvalidParams.Err()
}
ctx = interceptor.WrapServerCtx(ctx)
conditions := &query.Conditions{}
for _, v := range req.Conditions.GetColumns() {
column := query.Column{}
_ = copier.Copy(&column, v)
conditions.Columns = append(conditions.Columns, column)
}
err = conditions.CheckValid()
if err != nil {
logger.Warn("Parameters error", logger.Err(err), logger.Any("conditions", conditions), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInvalidParams.Err()
}
record, err := s.iDao.GetByCondition(ctx, conditions)
if err != nil {
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()
}
logger.Error("GetByCondition error", logger.Err(err), logger.Any("req", req), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInternalServerError.ToRPCErr()
}
data, err := convertUserExample(record)
if err != nil {
logger.Warn("convertUserExample error", logger.Err(err), logger.Any("userExample", record), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusGetByConditionUserExample.Err()
}
return &serverNameExampleV1.GetUserExampleByConditionReply{
UserExample: data,
}, nil
}
// ListByIDs list of records by batch id
func (s *userExample) ListByIDs(ctx context.Context, req *serverNameExampleV1.ListUserExampleByIDsRequest) (*serverNameExampleV1.ListUserExampleByIDsReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInvalidParams.Err()
}
ctx = interceptor.WrapServerCtx(ctx)
userExampleMap, err := s.iDao.GetByIDs(ctx, req.Ids)
if err != nil {
logger.Error("GetByIDs error", logger.Err(err), logger.Any("ids", req.Ids), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInternalServerError.ToRPCErr()
}
userExamples := []*serverNameExampleV1.UserExample{}
for _, id := range req.Ids {
if v, ok := userExampleMap[id]; ok {
record, err := convertUserExample(v)
if err != nil {
logger.Warn("convertUserExample error", logger.Err(err), logger.Any("userExample", v), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInternalServerError.ToRPCErr()
}
userExamples = append(userExamples, record)
}
}
return &serverNameExampleV1.ListUserExampleByIDsReply{UserExamples: userExamples}, nil
}
// ListByLastID list userExample by last id
func (s *userExample) ListByLastID(ctx context.Context, req *serverNameExampleV1.ListUserExampleByLastIDRequest) (*serverNameExampleV1.ListUserExampleByLastIDReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), interceptor.CtxRequestIDField(ctx))
return nil, ecode.StatusInvalidParams.Err()
}
if req.LastID == "" {
req.LastID = model.MaxObjectID
}
if req.Limit == 0 {
req.Limit = 10
}
records, err := s.iDao.GetByLastID(ctx, req.LastID, int(req.Limit), req.Sort)
if err != nil {
logger.Error("ListByLastID error", logger.Err(err), interceptor.CtxRequestIDField(ctx))
return nil, ecode.StatusInternalServerError.ToRPCErr()
}
userExamples := []*serverNameExampleV1.UserExample{}
for _, record := range records {
data, err := convertUserExample(record)
if err != nil {
logger.Warn("convertUserExample error", logger.Err(err), logger.Any("id", record.ID), interceptor.ServerCtxRequestIDField(ctx))
continue
}
userExamples = append(userExamples, data)
}
return &serverNameExampleV1.ListUserExampleByLastIDReply{
UserExamples: userExamples,
}, nil
}
// List of records by query parameters
func (s *userExample) List(ctx context.Context, req *serverNameExampleV1.ListUserExampleRequest) (*serverNameExampleV1.ListUserExampleReply, error) {
err := req.Validate()
if err != nil {
logger.Warn("req.Validate error", logger.Err(err), logger.Any("req", req), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInvalidParams.Err()
}
ctx = interceptor.WrapServerCtx(ctx)
params := &query.Params{}
err = copier.Copy(params, req.Params)
if err != nil {
return nil, ecode.StatusListUserExample.Err()
}
params.Size = int(req.Params.Limit)
// todo if copier.Copy cannot assign a value to a field, add it here
records, total, err := s.iDao.GetByColumns(ctx, params)
if err != nil {
if strings.Contains(err.Error(), "query params error:") {
logger.Warn("GetByColumns error", logger.Err(err), logger.Any("params", params), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInvalidParams.Err()
}
logger.Error("GetByColumns error", logger.Err(err), logger.Any("params", params), interceptor.ServerCtxRequestIDField(ctx))
return nil, ecode.StatusInternalServerError.ToRPCErr()
}
userExamples := []*serverNameExampleV1.UserExample{}
for _, record := range records {
data, err := convertUserExample(record)
if err != nil {
logger.Warn("convertUserExample error", logger.Err(err), logger.Any("id", record.ID), interceptor.ServerCtxRequestIDField(ctx))
continue
}
userExamples = append(userExamples, data)
}
return &serverNameExampleV1.ListUserExampleReply{
Total: total,
UserExamples: userExamples,
}, nil
}
func convertUserExample(record *model.UserExample) (*serverNameExampleV1.UserExample, error) {
value := &serverNameExampleV1.UserExample{}
err := copier.Copy(value, record)
if err != nil {
return nil, err
}
value.Id = record.ID.Hex()
// todo if copier.Copy cannot assign a value to a field, add it here, e.g. CreatedAt, UpdatedAt
// 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

@ -0,0 +1,296 @@
package service
import (
"context"
"encoding/json"
"fmt"
"testing"
"time"
"github.com/zhufuyi/sponge/configs"
serverNameExampleV1 "github.com/zhufuyi/sponge/api/serverNameExample/v1"
"github.com/zhufuyi/sponge/api/types"
"github.com/zhufuyi/sponge/internal/config"
"github.com/zhufuyi/sponge/pkg/grpc/benchmark"
)
// Test each method of userExample via the rpc client
func Test_service_userExample_methods(t *testing.T) {
conn := getRPCClientConnForTest()
cli := serverNameExampleV1.NewUserExampleClient(conn)
ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
//ctx = interceptor.SetJwtTokenToCtx(ctx, "Bearer jwt-token-value")
tests := []struct {
name string
fn func() (interface{}, error)
wantErr bool
}{
// todo generate the service struct code here
// delete the templates code start
{
name: "Create",
fn: func() (interface{}, error) {
// todo type in the parameters to test
req := &serverNameExampleV1.CreateUserExampleRequest{
Name: "foo9",
Email: "foo9@bar.com",
Password: "f447b20a7fcbf53a5d5be013ea0b15af",
Phone: "16000000009",
Avatar: "http://internal.com/9.jpg",
Age: 19,
Gender: 2,
}
return cli.Create(ctx, req)
},
wantErr: false,
},
{
name: "UpdateByID",
fn: func() (interface{}, error) {
// todo type in the parameters to test
req := &serverNameExampleV1.UpdateUserExampleByIDRequest{
Id: "65cf5a5ad6abda94a8c24ed3",
Phone: "16000000019",
Age: 0,
}
return cli.UpdateByID(ctx, req)
},
wantErr: false,
},
// delete the templates code end
{
name: "DeleteByID",
fn: func() (interface{}, error) {
// todo type in the parameters to test
req := &serverNameExampleV1.DeleteUserExampleByIDRequest{
Id: "",
}
return cli.DeleteByID(ctx, req)
},
wantErr: false,
},
{
name: "DeleteByIDs",
fn: func() (interface{}, error) {
// todo type in the parameters to test
req := &serverNameExampleV1.DeleteUserExampleByIDsRequest{
Ids: []string{""},
}
return cli.DeleteByIDs(ctx, req)
},
wantErr: false,
},
{
name: "GetByID",
fn: func() (interface{}, error) {
// todo type in the parameters to test
req := &serverNameExampleV1.GetUserExampleByIDRequest{
Id: "",
}
return cli.GetByID(ctx, req)
},
wantErr: false,
},
{
name: "GetByCondition",
fn: func() (interface{}, error) {
// todo type in the parameters to test
req := &serverNameExampleV1.GetUserExampleByConditionRequest{
Conditions: &types.Conditions{
Columns: []*types.Column{
{
Name: "",
Value: "",
},
},
},
}
return cli.GetByCondition(ctx, req)
},
wantErr: false,
},
{
name: "ListByIDs",
fn: func() (interface{}, error) {
// todo type in the parameters to test
req := &serverNameExampleV1.ListUserExampleByIDsRequest{
Ids: []string{"65cf5b4cd6abda94a8c24ed5", "65c9ae1b1378ae7f0787a03a", "65c9ae1b1378ae7f0787a03b"},
}
return cli.ListByIDs(ctx, req)
},
wantErr: false,
},
{
name: "ListByLastID",
fn: func() (interface{}, error) {
// todo type in the parameters to test
req := &serverNameExampleV1.ListUserExampleByLastIDRequest{
LastID: "",
Limit: 10,
Sort: "",
}
return cli.ListByLastID(ctx, req)
},
wantErr: false,
},
{
name: "List",
fn: func() (interface{}, error) {
// todo type in the parameters to test
req := &serverNameExampleV1.ListUserExampleRequest{
Params: &types.Params{
Page: 0,
Limit: 10,
Sort: "",
//Columns: []*types.Column{
// {
// Name: "_id",
// //Exp: ">=",
// Value: "11",
// Logic: "",
// },
//},
},
}
return cli.List(ctx, req)
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.fn()
if (err != nil) != tt.wantErr {
t.Logf("test '%s' error = %v, wantErr %v", tt.name, err, tt.wantErr)
return
}
data, _ := json.MarshalIndent(got, "", " ")
fmt.Println(string(data))
})
}
}
// Perform a stress test on {{.LowerName}}'s method and
// copy the press test report to your browser when you are finished.
func Test_service_userExample_benchmark(t *testing.T) {
err := config.Init(configs.Path("serverNameExample.yml"))
if err != nil {
panic(err)
}
host := fmt.Sprintf("127.0.0.1:%d", config.Get().Grpc.Port)
protoFile := configs.Path("../api/serverNameExample/v1/userExample.proto")
// If third-party dependencies are missing during the press test,
// copy them to the project's third_party directory.
importPaths := []string{
configs.Path("../third_party"), // third_party directory
configs.Path(".."), // Previous level of third_party
}
tests := []struct {
name string
fn func() error
wantErr bool
}{
{
name: "GetByID",
fn: func() error {
// todo type in the parameters to test
message := &serverNameExampleV1.GetUserExampleByIDRequest{
Id: "",
}
var total uint = 1000 // total number of requests
b, err := benchmark.New(host, protoFile, "GetByID", message, total, importPaths...)
if err != nil {
return err
}
return b.Run()
},
wantErr: false,
},
{
name: "ListByIDs",
fn: func() error {
// todo type in the parameters to test
message := &serverNameExampleV1.ListUserExampleByIDsRequest{
Ids: []string{""},
}
var total uint = 1000 // total number of requests
b, err := benchmark.New(host, protoFile, "ListByIDs", message, total, importPaths...)
if err != nil {
return err
}
return b.Run()
},
wantErr: false,
},
{
name: "ListByLastID",
fn: func() error {
// todo type in the parameters to test
message := &serverNameExampleV1.ListUserExampleByLastIDRequest{
LastID: "",
Limit: 5,
Sort: "-id",
}
var total uint = 100 // total number of requests
b, err := benchmark.New(host, protoFile, "ListByLastID", message, total, importPaths...)
if err != nil {
return err
}
return b.Run()
},
wantErr: false,
},
{
name: "List",
fn: func() error {
// todo type in the parameters to test
message := &serverNameExampleV1.ListUserExampleRequest{
Params: &types.Params{
Page: 0,
Limit: 10,
Sort: "",
Columns: []*types.Column{
{
Name: "id",
Exp: ">=",
Value: "1",
Logic: "",
},
},
},
}
var total uint = 100 // total number of requests
b, err := benchmark.New(host, protoFile, "List", message, total, importPaths...)
if err != nil {
return err
}
return b.Run()
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.fn()
if (err != nil) != tt.wantErr {
t.Errorf("test '%s' error = %v, wantErr %v", tt.name, err, tt.wantErr)
return
}
})
}
}

View File

@ -1,3 +1,4 @@
// Package types define the structure of request parameters and respond results in this package
package types
// This file is public struct, only used to generate swagger documents, it is recommended

View File

@ -1,4 +1,3 @@
// Package types define the structure of request parameters and respond results in this package
package types
import (

View File

@ -0,0 +1,137 @@
package types
import (
"time"
"github.com/zhufuyi/sponge/internal/model"
"github.com/zhufuyi/sponge/pkg/mgo/query"
)
var _ time.Time
var _ = model.MaxObjectID
// Tip: suggested filling in the binding rules https://github.com/go-playground/validator in request struct fields tag.
// todo generate the request and response struct to here
// delete the templates code start
// CreateUserExampleRequest request params
type CreateUserExampleRequest struct {
Name string `json:"name" binding:"min=2"` // username
Email string `json:"email" binding:"email"` // email
Password string `json:"password" binding:"md5"` // password
Phone string `json:"phone" binding:"e164"` // phone number, e164 rules, e.g. +8612345678901
Avatar string `json:"avatar" binding:"min=5"` // avatar
Age int `json:"age" binding:"gt=0,lt=120"` // age
Gender int `json:"gender" binding:"gte=0,lte=2"` // gender, 1:Male, 2:Female, other values:unknown
}
// UpdateUserExampleByIDRequest request params
type UpdateUserExampleByIDRequest struct {
ID string `json:"id" binding:"-"` // id
Name string `json:"name" binding:""` // username
Email string `json:"email" binding:""` // email
Password string `json:"password" binding:""` // password
Phone string `json:"phone" binding:""` // phone number
Avatar string `json:"avatar" binding:""` // avatar
Age int `json:"age" binding:""` // age
Gender int `json:"gender" binding:""` // gender, 1:Male, 2:Female, other values:unknown
}
// UserExampleObjDetail detail
type UserExampleObjDetail struct {
ID string `json:"id"` // id
Name string `json:"name"` // username
Email string `json:"email"` // email
Phone string `json:"phone"` // phone number
Avatar string `json:"avatar"` // avatar
Age int `json:"age"` // age
Gender int `json:"gender"` // gender, 1:Male, 2:Female, other values:unknown
Status int `json:"status"` // account status, 1:inactive, 2:activated, 3:blocked
LoginAt int64 `json:"loginAt"` // login timestamp
CreatedAt time.Time `json:"createdAt"` // create time
UpdatedAt time.Time `json:"updatedAt"` // update time
}
// delete the templates code end
// CreateUserExampleRespond only for api docs
type CreateUserExampleRespond struct {
Code int `json:"code"` // return code
Msg string `json:"msg"` // return information description
Data struct {
ID uint64 `json:"id"` // id
} `json:"data"` // return data
}
// UpdateUserExampleByIDRespond only for api docs
type UpdateUserExampleByIDRespond struct {
Result
}
// GetUserExampleByIDRespond only for api docs
type GetUserExampleByIDRespond struct {
Code int `json:"code"` // return code
Msg string `json:"msg"` // return information description
Data struct {
UserExample UserExampleObjDetail `json:"userExample"`
} `json:"data"` // return data
}
// DeleteUserExampleByIDRespond only for api docs
type DeleteUserExampleByIDRespond struct {
Result
}
// DeleteUserExamplesByIDsRequest request params
type DeleteUserExamplesByIDsRequest struct {
IDs []string `json:"ids" binding:"min=1"` // id list
}
// DeleteUserExamplesByIDsRespond only for api docs
type DeleteUserExamplesByIDsRespond struct {
Result
}
// GetUserExampleByConditionRequest request params
type GetUserExampleByConditionRequest struct {
query.Conditions
}
// GetUserExampleByConditionRespond only for api docs
type GetUserExampleByConditionRespond struct {
Code int `json:"code"` // return code
Msg string `json:"msg"` // return information description
Data struct {
UserExample UserExampleObjDetail `json:"userExample"`
} `json:"data"` // return data
}
// ListUserExamplesByIDsRequest request params
type ListUserExamplesByIDsRequest struct {
IDs []string `json:"ids" binding:"min=1"` // id list
}
// ListUserExamplesByIDsRespond only for api docs
type ListUserExamplesByIDsRespond struct {
Code int `json:"code"` // return code
Msg string `json:"msg"` // return information description
Data struct {
UserExamples []UserExampleObjDetail `json:"userExamples"`
} `json:"data"` // return data
}
// ListUserExamplesRequest request params
type ListUserExamplesRequest struct {
query.Params
}
// ListUserExamplesRespond only for api docs
type ListUserExamplesRespond struct {
Code int `json:"code"` // return code
Msg string `json:"msg"` // return information description
Data struct {
UserExamples []UserExampleObjDetail `json:"userExamples"`
} `json:"data"` // return data
}

View File

@ -169,7 +169,7 @@ func checkInUse(sqlDB *sql.DB, duration time.Duration) {
ctx, _ := context.WithTimeout(context.Background(), duration) //nolint
for {
select {
case <-time.After(time.Millisecond * 100):
case <-time.After(time.Millisecond * 250):
if v := sqlDB.Stats().InUse; v == 0 {
return
}

View File

@ -71,10 +71,10 @@ type Params struct {
// Column query info
type Column struct {
Name string `json:"name" form:"columns"` // column name
Exp string `json:"exp" form:"columns"` // expressions, which default to = when the value is null, have =, !=, >, >=, <, <=, like, in
Value interface{} `json:"value" form:"columns"` // column value
Logic string `json:"logic" form:"columns"` // logical type, defaults to and when the value is null, with &(and), ||(or)
Name string `json:"name" form:"name"` // column name
Exp string `json:"exp" form:"exp"` // expressions, which default to = when the value is null, have =, !=, >, >=, <, <=, like, in
Value interface{} `json:"value" form:"value"` // column value
Logic string `json:"logic" form:"logic"` // logical type, defaults to and when the value is null, with &(and), ||(or)
}
func (c *Column) checkValid() error {

Binary file not shown.

19
pkg/mgo/README.md Normal file
View File

@ -0,0 +1,19 @@
## mgo
Mgo is based on the official library [mongo](https://github.com/mongodb/mongo-go-driver).
<br>
### Example of use
```go
import "github.com/zhufuyi/sponge/pkg/mgo"
var (
dsn = "mongodb://root:123456@192.168.3.37:27017"
dbName = "account"
)
db, err := mgo.Init(dsn, dbName)
defer Close(db)
```

80
pkg/mgo/model.go Normal file
View File

@ -0,0 +1,80 @@
package mgo
import (
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Model embedded structs, add `bson: ",inline"` when defining table structs
type Model struct {
ID primitive.ObjectID `bson:"_id" json:"id"`
CreatedAt time.Time `bson:"created_at" json:"createdAt"`
UpdatedAt time.Time `bson:"updated_at" json:"updatedAt"`
DeletedAt *time.Time `bson:"deleted_at,omitempty" json:"deletedAt,omitempty"`
}
// SetModelValue set model fields
func (p *Model) SetModelValue() {
now := time.Now()
if !p.ID.IsZero() {
p.ID = primitive.NewObjectID()
}
if p.CreatedAt.IsZero() {
p.CreatedAt = now
p.UpdatedAt = now
}
}
// ExcludeDeleted exclude soft deleted records
func ExcludeDeleted(filter bson.M) bson.M {
if filter == nil {
filter = bson.M{}
}
filter["deleted_at"] = bson.M{"$exists": false}
return filter
}
// EmbedUpdatedAt embed updated_at datetime column
func EmbedUpdatedAt(update bson.M) bson.M {
updateM := bson.M{}
if v, ok := update["$set"]; ok {
if m, ok2 := v.(bson.M); ok2 {
m["updated_at"] = time.Now()
updateM["$set"] = m
}
} else {
update["updated_at"] = time.Now()
updateM["$set"] = update
}
return updateM
}
// EmbedDeletedAt embed deleted_at datetime column
func EmbedDeletedAt(update bson.M) bson.M {
updateM := bson.M{}
if v, ok := update["$set"]; ok {
if m, ok2 := v.(bson.M); ok2 {
m["deleted_at"] = time.Now()
updateM["$set"] = m
}
} else {
updateM["$set"] = bson.M{"deleted_at": time.Now()}
}
return updateM
}
// ConvertToObjectIDs convert ids to objectIDs
func ConvertToObjectIDs(ids []string) []primitive.ObjectID {
oids := []primitive.ObjectID{}
for _, id := range ids {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
continue
}
oids = append(oids, oid)
}
return oids
}

67
pkg/mgo/mongo.go Normal file
View File

@ -0,0 +1,67 @@
// Package mgo is a library wrapped on go.mongodb.org/mongo-driver/mongo, with added features paging queries, etc.
package mgo
import (
"context"
"errors"
"net/url"
"strings"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
const (
// DBDriverName mongodb driver
DBDriverName = "mongodb"
)
// Init connecting to mongo
func Init(dsn string, opts ...*options.ClientOptions) (*mongo.Database, error) {
u, err := url.Parse(dsn)
if err != nil {
return nil, err
}
dbName := strings.TrimLeft(u.Path, "/")
if dbName == "" {
return nil, errors.New("database name is empty")
}
var uri string
if u.RawQuery == "" {
uri = strings.TrimRight(dsn, u.Path)
} else {
tmp := strings.TrimRight(dsn, u.RawQuery)
uri = strings.TrimRight(tmp, dbName+"?") + "?" + u.RawQuery
}
return Init2(uri, dbName, opts...)
}
// Init2 connecting to mongo using uri
func Init2(uri string, dbName string, opts ...*options.ClientOptions) (*mongo.Database, error) {
ctx := context.Background()
mongoOpts := []*options.ClientOptions{
options.Client().ApplyURI(uri),
}
mongoOpts = append(mongoOpts, opts...)
client, err := mongo.Connect(ctx, mongoOpts...)
if err != nil {
return nil, err
}
err = client.Ping(ctx, nil)
if err != nil {
return nil, err
}
db := client.Database(dbName)
return db, nil
}
// Close mongodb
func Close(db *mongo.Database) error {
return db.Client().Disconnect(context.Background())
}

90
pkg/mgo/mongo_test.go Normal file
View File

@ -0,0 +1,90 @@
package mgo
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
mgoOptions "go.mongodb.org/mongo-driver/mongo/options"
)
func TestInit(t *testing.T) {
dsns := []string{
"mongodb://root:123456@192.168.3.37:27017/account",
"mongodb://root:123456@192.168.3.37:27017/account?connectTimeoutMS=2000",
}
for _, dsn := range dsns {
timeout := time.Second * 2
opts := &mgoOptions.ClientOptions{Timeout: &timeout}
db, err := Init(dsn, opts)
if err != nil {
t.Log(err)
return
}
time.Sleep(time.Millisecond * 100)
defer Close(db)
}
}
func TestInit2(t *testing.T) {
uri := "mongodb://root:123456@192.168.3.37:27017"
dbName := "account"
timeout := time.Second * 3
opts := &mgoOptions.ClientOptions{Timeout: &timeout}
db, err := Init2(uri, dbName, opts)
if err != nil {
t.Log(err)
return
}
time.Sleep(time.Millisecond * 100)
defer Close(db)
}
func TestModel_SetModelValue(t *testing.T) {
m := new(Model)
m.SetModelValue()
assert.NotNil(t, m.ID)
assert.NotNil(t, m.CreatedAt)
assert.NotNil(t, m.UpdatedAt)
}
func TestExcludeDeleted(t *testing.T) {
filter := bson.M{"foo": "bar"}
filter = ExcludeDeleted(filter)
assert.NotNil(t, filter["deleted_at"])
filter = ExcludeDeleted(nil)
assert.NotNil(t, filter["deleted_at"])
}
func TestEmbedUpdatedAt(t *testing.T) {
update := bson.M{"$set": bson.M{"foo": "bar"}}
update = EmbedUpdatedAt(update)
m := update["$set"].(bson.M)
assert.NotNil(t, m["updated_at"])
update = bson.M{"foo": "bar"}
update = EmbedUpdatedAt(update)
m = update["$set"].(bson.M)
assert.NotNil(t, m["updated_at"])
}
func TestEmbedDeletedAt(t *testing.T) {
update := bson.M{"$set": bson.M{"foo": "bar"}}
update = EmbedDeletedAt(update)
m := update["$set"].(bson.M)
assert.NotNil(t, m["deleted_at"])
update = bson.M{"foo": "bar"}
update = EmbedDeletedAt(update)
m = update["$set"].(bson.M)
assert.NotNil(t, m["deleted_at"])
}
func TestConvertToObjectIDs(t *testing.T) {
ids := []string{"65c9ae1b1378ae7f0787a039", "invalid_id"}
oids := ConvertToObjectIDs(ids)
assert.Equal(t, len(oids), 1)
}

111
pkg/mgo/query/page.go Normal file
View File

@ -0,0 +1,111 @@
package query
import (
"strings"
"go.mongodb.org/mongo-driver/bson"
)
var defaultMaxSize = 1000
const oidName = "_id"
// SetMaxSize change the default maximum number of pages per page
func SetMaxSize(max int) {
if max < 10 {
max = 10
}
defaultMaxSize = max
}
// Page info
type Page struct {
page int // page number, starting from page 0
size int // number per page
sort bson.D // sort fields, default is id backwards, you can add - sign before the field to indicate reverse order, no - sign to indicate ascending order, multiple fields separated by comma
}
// Page get page value
func (p *Page) Page() int {
return p.page
}
// Size number per page
func (p *Page) Size() int {
return p.size
}
// Sort get sort field
func (p *Page) Sort() bson.D {
return p.sort
}
// Skip get offset value
func (p *Page) Skip() int {
return p.page * p.size
}
// DefaultPage default page, number 20 per page, sorted by id backwards
func DefaultPage(page int) *Page {
if page < 0 {
page = 0
}
return &Page{
page: page,
size: 10,
sort: bson.D{{oidName, -1}}, //nolint
}
}
// NewPage custom page, starting from page 0.
// the parameter columnNames indicates a sort field, if empty means id descending, if there are multiple column names, separated by a comma,
// a '-' sign in front of each column name indicates descending order, otherwise ascending order.
func NewPage(page int, size int, columnNames string) *Page {
if page < 0 {
page = 0
}
if size > defaultMaxSize {
size = defaultMaxSize
} else if size < 1 {
size = 10
}
return &Page{
page: page,
size: size,
sort: getSort(columnNames),
}
}
// convert to mysql sort, each column name preceded by a '-' sign, indicating descending order, otherwise ascending order, example:
//
// columnNames="name" means sort by name in ascending order,
// columnNames="-name" means sort by name descending,
// columnNames="name,age" means sort by name in ascending order, otherwise sort by age in ascending order,
// columnNames="-name,-age" means sort by name descending before sorting by age descending.
func getSort(columnNames string) bson.D {
d := bson.D{}
columnNames = strings.Replace(columnNames, " ", "", -1)
if columnNames == "" {
d = bson.D{{oidName, -1}} //nolint
return d
}
names := strings.Split(columnNames, ",")
for _, name := range names {
if name[0] == '-' && len(name) > 1 {
col := name[1:]
if col == "id" {
col = oidName
}
d = append(d, bson.E{col, -1}) //nolint
} else {
if name == "id" {
name = oidName
}
d = append(d, bson.E{name, 1}) //nolint
}
}
return d
}

View File

@ -0,0 +1,372 @@
// Package query is a library of custom condition queries, support for complex conditional paging queries.
package query
import (
"fmt"
"strings"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
const (
// Eq equal
Eq = "eq"
eqSymbol = "="
// Neq not equal
Neq = "neq"
neqSymbol = "!="
// Gt greater than
Gt = "gt"
gtSymbol = ">"
// Gte greater than or equal
Gte = "gte"
gteSymbol = ">="
// Lt less than
Lt = "lt"
ltSymbol = "<"
// Lte less than or equal
Lte = "lte"
lteSymbol = "<="
// Like fuzzy lookup
Like = "like"
// In include
In = "in"
// AND logic and
AND string = "and" //nolint
andSymbol1 = "&"
andSymbol2 = "&&"
// OR logic or
OR string = "or" //nolint
orSymbol1 = "|"
orSymbol2 = "||"
allLogicAnd = 1
allLogicOr = 2
)
var expMap = map[string]string{
Eq: eqSymbol,
eqSymbol: eqSymbol,
Neq: neqSymbol,
neqSymbol: neqSymbol,
Gt: gtSymbol,
gtSymbol: gtSymbol,
Gte: gteSymbol,
gteSymbol: gteSymbol,
Lt: ltSymbol,
ltSymbol: ltSymbol,
Lte: lteSymbol,
lteSymbol: lteSymbol,
Like: Like,
In: In,
}
var logicMap = map[string]string{
AND: andSymbol1,
andSymbol1: andSymbol1,
andSymbol2: andSymbol1,
OR: orSymbol1,
orSymbol1: orSymbol1,
orSymbol2: orSymbol1,
}
// Params query parameters
type Params struct {
Page int `json:"page" form:"page" binding:"gte=0"`
Size int `json:"size" form:"size" binding:"gt=0"`
Sort string `json:"sort,omitempty" form:"sort" binding:""`
Columns []Column `json:"columns,omitempty" form:"columns"` // not required
}
// Column query info
type Column struct {
Name string `json:"name" form:"name"` // column name
Exp string `json:"exp" form:"exp"` // expressions, which default to = when the value is null, have =, !=, >, >=, <, <=, like, in
Value interface{} `json:"value" form:"value"` // column value
Logic string `json:"logic" form:"logic"` // logical type, defaults to and when the value is null, with &(and), ||(or)
}
func (c *Column) checkValid() error {
if c.Name == "" {
return fmt.Errorf("field 'name' cannot be empty")
}
if c.Value == nil {
return fmt.Errorf("field 'value' cannot be nil")
}
return nil
}
func (c *Column) convertLogic() error {
if c.Logic == "" {
c.Logic = AND
}
if v, ok := logicMap[strings.ToLower(c.Logic)]; ok { //nolint
c.Logic = v
return nil
}
return fmt.Errorf("unknown logic type '%s'", c.Logic)
}
// converting ExpType to sql expressions and LogicType to sql using characters
func (c *Column) convert() error {
if err := c.checkValid(); err != nil {
return err
}
if c.Name == "id" || c.Name == "_id" {
if str, ok := c.Value.(string); ok {
c.Name = "_id"
c.Value, _ = primitive.ObjectIDFromHex(str)
}
} else if strings.Contains(c.Name, ":oid") {
if str, ok := c.Value.(string); ok {
c.Name = strings.Replace(c.Name, ":oid", "", 1)
c.Value, _ = primitive.ObjectIDFromHex(str)
}
}
if c.Exp == "" {
c.Exp = Eq
}
if v, ok := expMap[strings.ToLower(c.Exp)]; ok { //nolint
c.Exp = v
switch c.Exp {
//case eqSymbol:
case neqSymbol:
c.Value = bson.M{"$neq": c.Value}
case gtSymbol:
c.Value = bson.M{"$gt": c.Value}
case gteSymbol:
c.Value = bson.M{"$gte": c.Value}
case ltSymbol:
c.Value = bson.M{"$lt": c.Value}
case lteSymbol:
c.Value = bson.M{"$lte": c.Value}
case Like:
c.Value = bson.M{"$regex": fmt.Sprintf("%v", c.Value)}
case In:
val, ok := c.Value.(string)
if !ok {
return fmt.Errorf("invalid value type '%s'", c.Value)
}
values := []interface{}{}
ss := strings.Split(val, ",")
for _, s := range ss {
values = append(values, s)
}
c.Value = bson.M{"$in": values}
}
} else {
return fmt.Errorf("unknown exp type '%s'", c.Exp)
}
return c.convertLogic()
}
// ConvertToPage converted to conform to mongo rules based on the page size sort parameter
func (p *Params) ConvertToPage() (sort bson.D, limit int, skip int) { //nolint
page := NewPage(p.Page, p.Size, p.Sort)
sort = page.sort
limit = page.size
skip = page.page * page.size
return //nolint
}
// ConvertToMongoFilter conversion to mongo-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
func (p *Params) ConvertToMongoFilter() (bson.M, error) {
filter := bson.M{}
l := len(p.Columns)
switch l {
case 0:
return bson.M{}, nil
case 1: // l == 1
err := p.Columns[0].convert()
if err != nil {
return nil, err
}
filter[p.Columns[0].Name] = p.Columns[0].Value
return filter, nil
case 2: // l == 2
err := p.Columns[0].convert()
if err != nil {
return nil, err
}
err = p.Columns[1].convert()
if err != nil {
return nil, err
}
if p.Columns[0].Logic == andSymbol1 {
filter = bson.M{"$and": []bson.M{
{p.Columns[0].Name: p.Columns[0].Value},
{p.Columns[1].Name: p.Columns[1].Value}}}
} else {
filter = bson.M{"$or": []bson.M{
{p.Columns[0].Name: p.Columns[0].Value},
{p.Columns[1].Name: p.Columns[1].Value}}}
}
return filter, nil
default: // l >=3
return p.convertMultiColumns()
}
}
func (p *Params) convertMultiColumns() (bson.M, error) {
filter := bson.M{}
logicType, groupIndexes, err := checkSameLogic(p.Columns)
if err != nil {
return nil, err
}
if logicType == allLogicAnd {
for _, column := range p.Columns {
err := column.convert()
if err != nil {
return nil, err
}
if v, ok := filter["$and"]; !ok {
filter["$and"] = []bson.M{{column.Name: column.Value}}
} else {
if cols, ok1 := v.([]bson.M); ok1 {
cols = append(cols, bson.M{column.Name: column.Value})
filter["$and"] = cols
}
}
}
return filter, nil
} else if logicType == allLogicOr {
for _, column := range p.Columns {
err := column.convert()
if err != nil {
return nil, err
}
if v, ok := filter["$or"]; !ok {
filter["$or"] = []bson.M{{column.Name: column.Value}}
} else {
if cols, ok1 := v.([]bson.M); ok1 {
cols = append(cols, bson.M{column.Name: column.Value})
filter["$or"] = cols
}
}
}
return filter, nil
}
orConditions := []bson.M{}
for _, indexes := range groupIndexes {
if len(indexes) == 1 {
column := p.Columns[indexes[0]]
err := column.convert()
if err != nil {
return nil, err
}
orConditions = append(orConditions, bson.M{column.Name: column.Value})
} else {
andConditions := []bson.M{}
for _, index := range indexes {
column := p.Columns[index]
err := column.convert()
if err != nil {
return nil, err
}
andConditions = append(andConditions, bson.M{column.Name: column.Value})
}
orConditions = append(orConditions, bson.M{"$and": andConditions})
}
}
filter["$or"] = orConditions
return filter, nil
}
func checkSameLogic(columns []Column) (int, [][]int, error) {
orIndexes := []int{}
l := len(columns)
for i, column := range columns {
if i == l-1 { // ignore the logical type of the last column
break
}
err := column.convertLogic()
if err != nil {
return 0, nil, err
}
if column.Logic == orSymbol1 {
orIndexes = append(orIndexes, i)
}
}
if len(orIndexes) == 0 {
return allLogicAnd, nil, nil
} else if len(orIndexes) == l-1 {
return allLogicOr, nil, nil
}
// mix and or
groupIndexes := groupingIndex(l, orIndexes)
return 0, groupIndexes, nil
}
func groupingIndex(l int, orIndexes []int) [][]int {
groupIndexes := [][]int{}
lastIndex := 0
for _, index := range orIndexes {
group := []int{}
for i := lastIndex; i <= index; i++ {
group = append(group, i)
}
groupIndexes = append(groupIndexes, group)
if lastIndex == index {
lastIndex++
} else {
lastIndex = index
}
}
group := []int{}
for i := lastIndex + 1; i < l; i++ {
group = append(group, i)
}
groupIndexes = append(groupIndexes, group)
return groupIndexes
}
// Conditions query conditions
type Conditions struct {
Columns []Column `json:"columns" form:"columns" binding:"min=1"` // columns info
}
// CheckValid check valid
func (c *Conditions) CheckValid() error {
if len(c.Columns) == 0 {
return fmt.Errorf("field 'columns' cannot be empty")
}
for _, column := range c.Columns {
err := column.checkValid()
if err != nil {
return err
}
if column.Exp != "" {
if _, ok := expMap[column.Exp]; !ok {
return fmt.Errorf("unknown exp type '%s'", column.Exp)
}
}
if column.Logic != "" {
if _, ok := logicMap[column.Logic]; !ok {
return fmt.Errorf("unknown logic type '%s'", column.Logic)
}
}
}
return nil
}
// ConvertToMongo conversion to mongo-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
func (c *Conditions) ConvertToMongo() (bson.M, error) {
p := &Params{Columns: c.Columns}
return p.ConvertToMongoFilter()
}

View File

@ -0,0 +1,669 @@
package query
import (
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)
func TestPage(t *testing.T) {
page := DefaultPage(-1)
t.Log(page.Page(), page.Size(), page.Sort(), page.Skip())
page = NewPage(0, 20, "")
t.Log(page.Page(), page.Size(), page.Sort(), page.Skip())
SetMaxSize(1)
page = NewPage(0, 20, "_id")
t.Log(page.Page(), page.Size(), page.Sort(), page.Skip())
}
func TestParams_ConvertToPage(t *testing.T) {
p := &Params{
Page: 0,
Size: 20,
Sort: "age,-name",
}
order, limit, offset := p.ConvertToPage()
t.Logf("order=%v, limit=%d, skip=%d", order, limit, offset)
}
func TestParams_ConvertToMongoFilter(t *testing.T) {
type args struct {
columns []Column
}
tests := []struct {
name string
args args
want bson.M
wantErr bool
}{
// --------------------------- only 1 column query ------------------------------
{
name: "1 column eq",
args: args{
columns: []Column{
{
Name: "name",
Value: "ZhangSan",
},
},
},
want: bson.M{"name": "ZhangSan"},
wantErr: false,
},
{
name: "1 column neq",
args: args{
columns: []Column{
{
Name: "name",
Exp: "!=",
Value: "ZhangSan",
},
},
},
want: bson.M{"name": bson.M{"$neq": "ZhangSan"}},
wantErr: false,
},
{
name: "1 column gt",
args: args{
columns: []Column{
{
Name: "age",
Exp: ">",
Value: 20,
},
},
},
want: bson.M{"age": bson.M{"$gt": 20}},
wantErr: false,
},
{
name: "1 column gte",
args: args{
columns: []Column{
{
Name: "age",
Exp: ">=",
Value: 20,
},
},
},
want: bson.M{"age": bson.M{"$gte": 20}},
wantErr: false,
},
{
name: "1 column lt",
args: args{
columns: []Column{
{
Name: "age",
Exp: "<",
Value: 20,
},
},
},
want: bson.M{"age": bson.M{"$lt": 20}},
wantErr: false,
},
{
name: "1 column lte",
args: args{
columns: []Column{
{
Name: "age",
Exp: "<=",
Value: 20,
},
},
},
want: bson.M{"age": bson.M{"$lte": 20}},
wantErr: false,
},
{
name: "1 column like",
args: args{
columns: []Column{
{
Name: "name",
Exp: Like,
Value: "Li",
},
},
},
want: bson.M{"name": bson.M{"$regex": "Li"}},
wantErr: false,
},
{
name: "1 column IN",
args: args{
columns: []Column{
{
Name: "name",
Exp: In,
Value: "ab,cd,ef",
},
},
},
want: bson.M{"name": bson.M{"$in": []interface{}{"ab", "cd", "ef"}}},
wantErr: false,
},
// --------------------------- query 2 columns ------------------------------
{
name: "2 columns eq and",
args: args{
columns: []Column{
{
Name: "name",
Value: "ZhangSan",
},
{
Name: "gender",
Value: "male",
},
},
},
want: bson.M{"$and": []bson.M{{"name": "ZhangSan"}, {"gender": "male"}}},
wantErr: false,
},
{
name: "2 columns neq and",
args: args{
columns: []Column{
{
Name: "name",
Exp: "!=",
Value: "ZhangSan",
},
{
Name: "name",
Exp: "!=",
Value: "LiSi",
},
},
},
want: bson.M{"$and": []bson.M{{"name": bson.M{"$neq": "ZhangSan"}}, {"name": bson.M{"$neq": "LiSi"}}}},
wantErr: false,
},
{
name: "2 columns gt and",
args: args{
columns: []Column{
{
Name: "gender",
Value: "male",
},
{
Name: "age",
Exp: ">",
Value: 20,
},
},
},
want: bson.M{"$and": []bson.M{{"gender": "male"}, {"age": bson.M{"$gt": 20}}}},
wantErr: false,
},
{
name: "2 columns gte and",
args: args{
columns: []Column{
{
Name: "gender",
Value: "male",
},
{
Name: "age",
Exp: ">=",
Value: 20,
},
},
},
want: bson.M{"$and": []bson.M{{"gender": "male"}, {"age": bson.M{"$gte": 20}}}},
wantErr: false,
},
{
name: "2 columns lt and",
args: args{
columns: []Column{
{
Name: "gender",
Value: "female",
},
{
Name: "age",
Exp: "<",
Value: 20,
},
},
},
want: bson.M{"$and": []bson.M{{"gender": "female"}, {"age": bson.M{"$lt": 20}}}},
wantErr: false,
},
{
name: "2 columns lte and",
args: args{
columns: []Column{
{
Name: "gender",
Value: "female",
},
{
Name: "age",
Exp: "<=",
Value: 20,
},
},
},
want: bson.M{"$and": []bson.M{{"gender": "female"}, {"age": bson.M{"$lte": 20}}}},
wantErr: false,
},
{
name: "2 columns range and",
args: args{
columns: []Column{
{
Name: "age",
Exp: ">=",
Value: 10,
},
{
Name: "age",
Exp: "<=",
Value: 20,
},
},
},
want: bson.M{"$and": []bson.M{{"age": bson.M{"$gte": 10}}, {"age": bson.M{"$lte": 20}}}},
wantErr: false,
},
{
name: "2 columns eq or",
args: args{
columns: []Column{
{
Name: "name",
Value: "LiSi",
Logic: "||",
},
{
Name: "gender",
Value: "female",
},
},
},
want: bson.M{"$or": []bson.M{{"name": "LiSi"}, {"gender": "female"}}},
wantErr: false,
},
{
name: "2 columns neq or",
args: args{
columns: []Column{
{
Name: "name",
Value: "LiSi",
Logic: "||",
},
{
Name: "gender",
Exp: "!=",
Value: "male",
},
},
},
want: bson.M{"$or": []bson.M{{"name": "LiSi"}, {"gender": bson.M{"$neq": "male"}}}},
wantErr: false,
},
{
name: "2 columns eq and in",
args: args{
columns: []Column{
{
Name: "gender",
Value: "male",
},
{
Name: "name",
Exp: In,
Value: "LiSi,ZhangSan,WangWu",
},
},
},
want: bson.M{"$and": []bson.M{{"gender": "male"}, {"name": bson.M{"$in": []interface{}{"LiSi", "ZhangSan", "WangWu"}}}}},
wantErr: false,
},
// --------------------------- query 3 columns ------------------------------
{
name: "3 columns and",
args: args{
columns: []Column{
{
Name: "gender",
Value: "male",
},
{
Name: "name",
Value: "ZhangSan",
},
{
Name: "age",
Exp: "<",
Value: 12,
},
},
},
want: bson.M{"$and": []bson.M{{"gender": "male"}, {"name": "ZhangSan"}, {"age": bson.M{"$lt": 12}}}},
wantErr: false,
},
{
name: "3 columns or",
args: args{
columns: []Column{
{
Name: "gender",
Value: "male",
Logic: "||",
},
{
Name: "name",
Value: "ZhangSan",
Logic: "||",
},
{
Name: "age",
Exp: "<",
Value: 12,
},
},
},
want: bson.M{"$or": []bson.M{{"gender": "male"}, {"name": "ZhangSan"}, {"age": bson.M{"$lt": 12}}}},
wantErr: false,
},
{
name: "3 columns mix (or and)",
args: args{
columns: []Column{
{
Name: "gender",
Value: "male",
Logic: "and",
},
{
Name: "name",
Value: "ZhangSan",
Logic: "||",
},
{
Name: "age",
Exp: "<",
Value: 12,
},
},
},
want: bson.M{"$or": []bson.M{{"$and": []bson.M{{"gender": "male"}, {"name": "ZhangSan"}}}, {"age": bson.M{"$lt": 12}}}},
wantErr: false,
},
// --------------------------- query 4 columns ------------------------------
{
name: "4 columns mix (or and)",
args: args{
columns: []Column{
{
Name: "gender",
Value: "male",
Logic: "||",
},
{
Name: "name",
Value: "ZhangSan",
},
{
Name: "age",
Exp: "<",
Value: 12,
Logic: "||",
},
{
Name: "city",
Value: "canton",
},
},
},
want: bson.M{"$or": []bson.M{{"gender": "male"}, {"$and": []bson.M{{"name": "ZhangSan"}, {"age": bson.M{"$lt": 12}}}}, {"city": "canton"}}},
wantErr: false,
},
{
name: "convert to object id",
args: args{
columns: []Column{
{
Name: "id",
Value: "65ce48483f11aff697e30d6d",
},
{
Name: "order_id:oid",
Value: "65ce48483f11aff697e30d6d",
},
},
},
want: bson.M{"$and": []bson.M{{"_id": primitive.ObjectID{0x65, 0xce, 0x48, 0x48, 0x3f, 0x11, 0xaf, 0xf6, 0x97, 0xe3, 0xd, 0x6d}}, {"order_id": primitive.ObjectID{0x65, 0xce, 0x48, 0x48, 0x3f, 0x11, 0xaf, 0xf6, 0x97, 0xe3, 0xd, 0x6d}}}},
wantErr: false,
},
// ---------------------------- error ----------------------------------------------
{
name: "exp type err",
args: args{
columns: []Column{
{
Name: "gender",
Exp: "xxxxxx",
Value: "male",
},
},
},
want: nil,
wantErr: true,
},
{
name: "logic type err",
args: args{
columns: []Column{
{
Name: "gender",
Value: "male",
Logic: "xxxxxx",
},
},
},
want: nil,
wantErr: true,
},
{
name: "name empty",
args: args{
columns: []Column{
{
Name: "",
Value: "male",
},
},
},
want: nil,
wantErr: true,
},
{
name: "value empty",
args: args{
columns: []Column{
{
Name: "name",
Value: nil,
},
},
},
want: nil,
wantErr: true,
},
{
name: "empty",
args: args{
columns: nil,
},
want: primitive.M{},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
params := &Params{
Columns: tt.args.columns,
}
got, err := params.ConvertToMongoFilter()
if (err != nil) != tt.wantErr {
t.Errorf("ConvertToMongoFilter() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ConvertToMongoFilter() got = %#v, want = %#v", got, tt.want)
}
})
}
}
func TestConditions_ConvertToMongo(t *testing.T) {
c := Conditions{
Columns: []Column{
{
Name: "name",
Value: "ZhangSan",
},
{
Name: "gender",
Value: "male",
},
}}
got, err := c.ConvertToMongo()
if err != nil {
t.Error(err)
}
want := bson.M{"$and": []bson.M{{"name": "ZhangSan"}, {"gender": "male"}}}
if !reflect.DeepEqual(got, want) {
t.Errorf("ConvertToMongo() got = %+v, want %+v", got, want)
}
}
func TestConditions_checkValid(t *testing.T) {
// empty error
c := Conditions{}
err := c.CheckValid()
assert.Error(t, err)
// value is empty error
c = Conditions{
Columns: []Column{
{
Name: "foo",
Value: nil,
},
},
}
err = c.CheckValid()
assert.Error(t, err)
// exp error
c = Conditions{
Columns: []Column{
{
Name: "foo",
Value: "bar",
Exp: "unknown-exp",
},
},
}
err = c.CheckValid()
assert.Error(t, err)
// logic error
c = Conditions{
Columns: []Column{
{
Name: "foo",
Value: "bar",
Logic: "unknown-logic",
},
},
}
err = c.CheckValid()
assert.Error(t, err)
// success
c = Conditions{
Columns: []Column{
{
Name: "name",
Value: "ZhangSan",
},
{
Name: "gender",
Value: "male",
},
}}
err = c.CheckValid()
assert.NoError(t, err)
}
func Test_groupingIndex(t *testing.T) {
type args struct {
l int
orIndexes []int
}
tests := []struct {
name string
args args
want [][]int
}{
{
name: "4 index 1",
args: args{
l: 4,
orIndexes: []int{0, 2},
},
want: [][]int{{0}, {1, 2}, {3}},
},
{
name: "4 index 2",
args: args{
l: 4,
orIndexes: []int{1},
},
want: [][]int{{0, 1}, {2, 3}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := groupingIndex(tt.args.l, tt.args.orIndexes)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("groupingIndex got = %#v, want = %#v", got, tt.want)
}
t.Log(got)
})
}
}
func Test_getSort(t *testing.T) {
names := []string{
"", "id", "-id", "gender", "gender,id", "-gender,-id",
}
for _, name := range names {
d := getSort(name)
t.Log(d)
}
}

View File

@ -71,10 +71,10 @@ type Params struct {
// Column query info
type Column struct {
Name string `json:"name" form:"columns"` // column name
Exp string `json:"exp" form:"columns"` // expressions, which default to = when the value is null, have =, !=, >, >=, <, <=, like, in
Value interface{} `json:"value" form:"columns"` // column value
Logic string `json:"logic" form:"columns"` // logical type, defaults to and when the value is null, with &(and), ||(or)
Name string `json:"name" form:"name"` // column name
Exp string `json:"exp" form:"exp"` // expressions, which default to = when the value is null, have =, !=, >, >=, <, <=, like, in
Value interface{} `json:"value" form:"value"` // column value
Logic string `json:"logic" form:"logic"` // logical type, defaults to and when the value is null, with &(and), ||(or)
}
func (c *Column) checkValid() error {

View File

@ -0,0 +1,391 @@
package parser
import (
"context"
"fmt"
"strings"
"sync/atomic"
"time"
"github.com/zhufuyi/sponge/pkg/mgo"
"github.com/zhufuyi/sponge/pkg/utils"
"github.com/huandu/xstrings"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/mongo"
mgoOptions "go.mongodb.org/mongo-driver/mongo/options"
)
const (
goTypeOID = "primitive.ObjectID"
goTypeInt = "int"
goTypeInt64 = "int64"
goTypeFloat64 = "float64"
goTypeString = "string"
goTypeTime = "time.Time"
goTypeBool = "bool"
goTypeNil = "nil"
goTypeBytes = "[]byte"
goTypeStrings = "[]string"
goTypeInts = "[]int"
goTypeInterface = "interface{}"
goTypeSliceInterface = "[]interface{}"
// SubStructKey sub struct key
SubStructKey = "_sub_struct_"
// ProtoSubStructKey proto sub struct key
ProtoSubStructKey = "_proto_sub_struct_"
oidName = "_id"
)
var mgoTypeToGo = map[bsontype.Type]string{
bson.TypeObjectID: goTypeOID,
bson.TypeInt32: goTypeInt,
bson.TypeInt64: goTypeInt64,
bson.TypeDouble: goTypeFloat64,
bson.TypeString: goTypeString,
bson.TypeArray: goTypeSliceInterface,
bson.TypeEmbeddedDocument: goTypeInterface,
bson.TypeTimestamp: goTypeTime,
bson.TypeDateTime: goTypeTime,
bson.TypeBoolean: goTypeBool,
bson.TypeNull: goTypeNil,
bson.TypeBinary: goTypeBytes,
bson.TypeUndefined: goTypeInterface,
bson.TypeCodeWithScope: goTypeString,
bson.TypeSymbol: goTypeString,
bson.TypeRegex: goTypeString,
bson.TypeDecimal128: goTypeInterface,
bson.TypeDBPointer: goTypeInterface,
bson.TypeMinKey: goTypeInt,
bson.TypeMaxKey: goTypeInt,
bson.TypeJavaScript: goTypeString,
}
var jsonTagFormat int32 // 0: camel case, 1: snake case
// SetJSONTagSnakeCase set json tag format to snake case
func SetJSONTagSnakeCase() {
atomic.AddInt32(&jsonTagFormat, 1)
}
// SetJSONTagCamelCase set json tag format to camel case
func SetJSONTagCamelCase() {
atomic.AddInt32(&jsonTagFormat, -jsonTagFormat)
}
// MgoField mongo field
type MgoField struct {
Name string `json:"name"`
Type string `json:"type"`
Comment string `json:"comment"`
ObjectStr string `json:"objectStr"`
ProtoObjectStr string `json:"protoObjectStr"`
}
// GetMongodbTableInfo get table info from mongodb
func GetMongodbTableInfo(dsn string, tableName string) ([]*MgoField, error) {
timeout := time.Second * 5
opts := &mgoOptions.ClientOptions{Timeout: &timeout}
dsn = utils.AdaptiveMongodbDsn(dsn)
db, err := mgo.Init(dsn, opts)
if err != nil {
return nil, err
}
return getMongodbTableFields(db, tableName)
}
func getMongodbTableFields(db *mongo.Database, collectionName string) ([]*MgoField, error) {
findOpts := new(mgoOptions.FindOneOptions)
findOpts.Sort = bson.M{oidName: -1}
result := db.Collection(collectionName).FindOne(context.Background(), bson.M{}, findOpts)
raw, err := result.Raw()
if err != nil {
return nil, err
}
elements, err := raw.Elements()
if err != nil {
return nil, err
}
fields := []*MgoField{}
names := []string{}
for _, element := range elements {
name := element.Key()
if name == "deleted_at" { // filter deleted_at, used for soft delete
continue
}
names = append(names, name)
t, o, p := getTypeFromMgo(name, element)
fields = append(fields, &MgoField{
Name: name,
Type: t,
ObjectStr: o,
ProtoObjectStr: p,
})
}
return embedTimeField(names, fields), nil
}
func getTypeFromMgo(name string, element bson.RawElement) (goTypeStr string, goObjectStr string, protoObjectStr string) {
v := element.Value()
switch v.Type {
case bson.TypeEmbeddedDocument:
var br bson.Raw = v.Value
es, err := br.Elements()
if err != nil {
return goTypeInterface, "", ""
}
return parseObject(name, es)
case bson.TypeArray:
var br bson.Raw = v.Value
es, err := br.Elements()
if err != nil {
return goTypeInterface, "", ""
}
if len(es) == 0 {
return goTypeInterface, "", ""
}
t, o, p := parseArray(name, es[0])
return convertToSingular(t, o, p)
}
if goType, ok := mgoTypeToGo[v.Type]; !ok {
return goTypeInterface, "", ""
} else { //nolint
return goType, "", ""
}
}
func parseObject(name string, elements []bson.RawElement) (goTypeStr string, goObjectStr string, protoObjectStr string) {
var goObjStr string
var protoObjStr string
for num, element := range elements {
t, _, _ := getTypeFromMgo(name, element)
k := element.Key()
var jsonTag string
if jsonTagFormat == 0 {
jsonTag = toLowerFirst(xstrings.ToCamelCase(k))
} else {
jsonTag = xstrings.ToSnakeCase(k)
}
goObjStr += fmt.Sprintf(" %s %s `bson:\"%s\" json:\"%s\"`\n", xstrings.ToCamelCase(k), t, k, jsonTag)
num++
protoObjStr += fmt.Sprintf(" %s %s = %d;\n", convertToProtoFieldType(name, t), k, num)
}
return "*" + xstrings.ToCamelCase(name),
fmt.Sprintf("type %s struct {\n%s}\n", xstrings.ToCamelCase(name), goObjStr),
fmt.Sprintf("message %s {\n%s}\n", xstrings.ToCamelCase(name), protoObjStr)
}
func parseArray(name string, element bson.RawElement) (goTypeStr string, goObjectStr string, protoObjectStr string) {
t, o, p := getTypeFromMgo(name, element)
if o != "" {
return "[]" + t, o, p
}
return "[]" + t, "", ""
}
func toLowerFirst(str string) string {
if len(str) == 0 {
return str
}
return strings.ToLower(string(str[0])) + str[1:]
}
func embedTimeField(names []string, fields []*MgoField) []*MgoField {
isHaveCreatedAt, isHaveUpdatedAt := false, false
for _, name := range names {
if name == "created_at" {
isHaveCreatedAt = true
}
if name == "updated_at" {
isHaveUpdatedAt = true
}
names = append(names, name)
}
var timeFields []*MgoField
if !isHaveCreatedAt {
timeFields = append(timeFields, &MgoField{
Name: "created_at",
Type: goTypeTime,
})
}
if !isHaveUpdatedAt {
timeFields = append(timeFields, &MgoField{
Name: "updated_at",
Type: goTypeTime,
})
}
if len(timeFields) == 0 {
return fields
}
return append(fields, timeFields...)
}
// ConvertToSQLByMgoFields convert to mysql table ddl
func ConvertToSQLByMgoFields(tableName string, fields []*MgoField) (string, map[string]string) {
isHaveID := false
fieldStr := ""
srcMongoTypeMap := make(map[string]string) // name:type
objectStrs := []string{}
protoObjectStrs := []string{}
for _, field := range fields {
switch field.Type {
case goTypeInterface, goTypeSliceInterface:
srcMongoTypeMap[field.Name] = xstrings.ToCamelCase(field.Name)
default:
srcMongoTypeMap[field.Name] = field.Type
}
if field.Name == oidName {
isHaveID = true
srcMongoTypeMap["id"] = field.Type
continue
}
fieldStr += fmt.Sprintf(" %s %s,\n", field.Name, convertMongoToMysqlType(field.Type))
if field.ObjectStr != "" {
objectStrs = append(objectStrs, field.ObjectStr)
protoObjectStrs = append(protoObjectStrs, field.ProtoObjectStr)
}
}
if isHaveID {
fieldStr = " id bigint unsigned primary key,\n" + fieldStr
}
fieldStr = strings.TrimSuffix(fieldStr, ",\n")
if len(objectStrs) > 0 {
srcMongoTypeMap[SubStructKey] = strings.Join(objectStrs, "\n") + "\n"
srcMongoTypeMap[ProtoSubStructKey] = strings.Join(protoObjectStrs, "\n") + "\n"
}
return fmt.Sprintf("CREATE TABLE %s (\n%s\n);", tableName, fieldStr), srcMongoTypeMap
}
// nolint
func convertMongoToMysqlType(goType string) string {
switch goType {
case goTypeInt:
return "int"
case goTypeInt64:
return "bigint"
case goTypeFloat64:
return "double"
case goTypeString:
return "varchar(255)"
case goTypeTime:
return "timestamp" //nolint
case goTypeBool:
return "tinyint(1)"
case goTypeOID, goTypeNil, goTypeBytes, goTypeInterface, goTypeSliceInterface, goTypeInts, goTypeStrings:
return "json"
}
return "json"
}
// nolint
func convertToProtoFieldType(name string, goType string) string {
switch goType {
case "int":
return "int32"
case "uint":
return "uint32" //nolint
case "time.Time":
return "int64"
case "float32":
return "float"
case "float64":
return "double"
case goTypeInts, "[]int64":
return "repeated int64"
case "[]int32":
return "repeated int32"
case "[]byte":
return "string"
case goTypeStrings:
return "repeated string"
}
if strings.Contains(goType, "[]") {
t := strings.TrimLeft(goType, "[]")
if strings.Contains(name, t) {
return "repeated " + t
}
}
return goType
}
// MgoFieldToGoStruct convert to go struct
func MgoFieldToGoStruct(name string, fs []*MgoField) string {
var str = ""
var objStr string
for _, f := range fs {
if f.Name == oidName {
str += " ID primitive.ObjectID `bson:\"_id\" json:\"id\"`\n"
continue
}
if f.Type == goTypeInterface || f.Type == goTypeSliceInterface {
f.Type = xstrings.ToCamelCase(f.Name)
}
str += fmt.Sprintf(" %s %s `bson:\"%s\" json:\"%s\"`\n", xstrings.ToCamelCase(f.Name), f.Type, f.Name, f.Name)
if f.ObjectStr != "" {
objStr += f.ObjectStr + "\n"
}
}
return fmt.Sprintf("type %s struct {\n%s}\n\n%s\n", xstrings.ToCamelCase(name), str, objStr)
}
func toSingular(word string) string {
if strings.HasSuffix(word, "es") {
if len(word) > 2 {
return word[:len(word)-2]
}
} else if strings.HasSuffix(word, "s") {
if len(word) > 1 {
return word[:len(word)-1]
}
}
return word
}
func nameToSingular(goTypeStr string, targetObjectStr string, markStr string) string {
name := strings.ReplaceAll(goTypeStr, "[]*", "")
prefixStr := markStr + " " + name
l := len(prefixStr)
if len(targetObjectStr) <= l {
return targetObjectStr
}
if prefixStr == targetObjectStr[:l] {
targetObjectStr = toSingular(prefixStr) + " " + targetObjectStr[l:]
return targetObjectStr
}
return targetObjectStr
}
func convertToSingular(goTypeStr string, objectStr string, protoObjectStr string) (tStr string, oStr string, pObjStr string) {
if !strings.Contains(goTypeStr, "[]*") || objectStr == "" {
return goTypeStr, objectStr, protoObjectStr
}
objectStr = nameToSingular(goTypeStr, objectStr, "type")
protoObjectStr = nameToSingular(goTypeStr, protoObjectStr, "message")
goTypeStr = toSingular(goTypeStr)
return goTypeStr, objectStr, protoObjectStr
}

View File

@ -43,6 +43,8 @@ const (
DBDriverTidb = "tidb"
// DBDriverSqlite sqlite driver
DBDriverSqlite = "sqlite"
// DBDriverMongodb mongodb driver
DBDriverMongodb = "mongodb"
)
// Codes content
@ -126,12 +128,15 @@ func ParseSQL(sql string, options ...Option) (map[string]string, error) {
}
type tmplData struct {
TableName string
TName string
NameFunc bool
RawTableName string
Fields []tmplField
Comment string
TableName string
TName string
NameFunc bool
RawTableName string
Fields []tmplField
Comment string
SubStructs string // sub structs for model
ProtoSubStructs string // sub structs for protobuf
DBDriver string
}
type tmplField struct {
@ -141,9 +146,10 @@ type tmplField struct {
Tag string
Comment string
JSONName string
DBDriver string
}
// ConditionZero type of condition 0
// ConditionZero type of condition 0, used in dao template code
func (t tmplField) ConditionZero() string {
switch t.GoType {
case "int8", "int16", "int32", "int64", "int", "uint8", "uint16", "uint32", "uint64", "uint", "float64", "float32", //nolint
@ -153,12 +159,28 @@ func (t tmplField) ConditionZero() string {
return `!= ""`
case "time.Time", "*time.Time", "sql.NullTime": //nolint
return `.IsZero() == false`
case "[]byte", "[]string", "[]int", "interface{}": //nolint
return `!= nil` //nolint
case "bool": //nolint
return `!= false /*Warning: if the value itself is false, can't be updated*/`
}
if t.DBDriver == DBDriverMongodb {
if t.GoType == goTypeOID {
return `!= primitive.NilObjectID`
}
if t.GoType == "*"+t.Name {
return `!= nil`
}
if strings.Contains(t.GoType, "[]") {
return `!= nil`
}
}
return `!= ` + t.GoType
}
// GoZero type of 0
// GoZero type of 0, used in model to json template code
func (t tmplField) GoZero() string {
switch t.GoType {
case "int8", "int16", "int32", "int64", "int", "uint8", "uint16", "uint32", "uint64", "uint", "float64", "float32",
@ -168,12 +190,28 @@ func (t tmplField) GoZero() string {
return `= "string"`
case "time.Time", "*time.Time", "sql.NullTime":
return `= "0000-01-00T00:00:00.000+08:00"`
case "[]byte", "[]string", "[]int", "interface{}": //nolint
return `= nil` //nolint
case "bool": //nolint
return `= false`
}
if t.DBDriver == DBDriverMongodb {
if t.GoType == goTypeOID {
return `= primitive.NilObjectID`
}
if t.GoType == "*"+t.Name {
return `= nil`
}
if strings.Contains(t.GoType, "[]") {
return `= nil`
}
}
return `= ` + t.GoType
}
// GoTypeZero type of 0
// GoTypeZero type of 0, used in service template code
func (t tmplField) GoTypeZero() string {
switch t.GoType {
case "int8", "int16", "int32", "int64", "int", "uint8", "uint16", "uint32", "uint64", "uint", "float64", "float32",
@ -183,6 +221,22 @@ func (t tmplField) GoTypeZero() string {
return `""`
case "time.Time", "*time.Time", "sql.NullTime":
return `0 /*time.Now().Second()*/`
case "[]byte", "[]string", "[]int", "interface{}": //nolint
return `nil` //nolint
case "bool": //nolint
return `false`
}
if t.DBDriver == DBDriverMongodb {
if t.GoType == goTypeOID {
return `primitive.NilObjectID`
}
if t.GoType == "*"+t.Name {
return `nil` //nolint
}
if strings.Contains(t.GoType, "[]") {
return `nil` //nolint
}
}
return t.GoType
@ -214,6 +268,7 @@ var replaceFields = map[string]string{
const (
columnID = "id"
_columnID = "_id"
columnCreatedAt = "created_at"
columnUpdatedAt = "updated_at"
columnDeletedAt = "deleted_at"
@ -349,30 +404,56 @@ func makeCode(stmt *ast.CreateTableStmt, opt options) (*codeText, error) {
//return "", nil, errors.Errorf(" unsupport option %d\n", o.Tp)
}
}
if !isPrimaryKey[colName] && isNotNull {
gormTag.WriteString(";NOT NULL")
}
tags = append(tags, "gorm", gormTag.String())
if opt.JSONTag {
tags = append(tags, "json", jsonName)
}
field.DBDriver = opt.DBDriver
switch opt.DBDriver {
case DBDriverMongodb: // mongodb
tags = append(tags, "bson", gormTag.String())
if opt.JSONTag {
if strings.ToLower(jsonName) == "_id" {
jsonName = "id"
}
field.JSONName = jsonName
tags = append(tags, "json", jsonName)
}
field.Tag = makeTagStr(tags)
field.GoType = opt.FieldTypes[colName]
if field.GoType == "time.Time" {
importPath = append(importPath, "time")
}
field.Tag = makeTagStr(tags)
default: // gorm
if !isPrimaryKey[colName] && isNotNull {
gormTag.WriteString(";NOT NULL")
}
tags = append(tags, "gorm", gormTag.String())
// get type in golang
nullStyle := opt.NullStyle
if !canNull {
nullStyle = NullDisable
if opt.JSONTag {
tags = append(tags, "json", jsonName)
}
field.Tag = makeTagStr(tags)
// get type in golang
nullStyle := opt.NullStyle
if !canNull {
nullStyle = NullDisable
}
goType, pkg := mysqlToGoType(col.Tp, nullStyle)
if pkg != "" {
importPath = append(importPath, pkg)
}
field.GoType = goType
}
goType, pkg := mysqlToGoType(col.Tp, nullStyle)
if pkg != "" {
importPath = append(importPath, pkg)
}
field.GoType = goType
data.Fields = append(data.Fields, field)
}
if v, ok := opt.FieldTypes[SubStructKey]; ok {
data.SubStructs = v
}
if v, ok := opt.FieldTypes[ProtoSubStructKey]; ok {
data.ProtoSubStructs = v
}
data.DBDriver = opt.DBDriver
updateFieldsCode, err := getUpdateFieldsCode(data, opt.IsEmbed)
if err != nil {
@ -454,13 +535,22 @@ func getModelStructCode(data tmplData, importPaths []string, isEmbed bool) (stri
newImportPaths = append(newImportPaths, "github.com/zhufuyi/sponge/pkg/ggorm")
} else {
for i, field := range data.Fields {
if strings.Contains(field.GoType, "time.Time") {
data.Fields[i].GoType = "*time.Time"
continue
}
// force conversion of ID field to uint64 type
if field.Name == "ID" {
data.Fields[i].GoType = "uint64"
switch field.DBDriver {
case DBDriverMongodb:
if field.Name == "ID" {
data.Fields[i].GoType = goTypeOID
importPaths = append(importPaths, "go.mongodb.org/mongo-driver/bson/primitive")
}
default:
if strings.Contains(field.GoType, "time.Time") {
data.Fields[i].GoType = "*time.Time"
continue
}
// force conversion of ID field to uint64 type
if field.Name == "ID" {
data.Fields[i].GoType = "uint64"
}
}
}
newImportPaths = importPaths
@ -482,6 +572,16 @@ func getModelStructCode(data tmplData, importPaths []string, isEmbed bool) (stri
structCode = strings.ReplaceAll(structCode, __type__, replaceFields[__type__])
}
if data.SubStructs != "" {
structCode += data.SubStructs
}
if data.DBDriver == DBDriverMongodb {
structCode = strings.ReplaceAll(structCode, `bson:"column:`, `bson:"`)
structCode = strings.ReplaceAll(structCode, `;type:"`, `"`)
structCode = strings.ReplaceAll(structCode, `;type:;primary_key`, ``)
structCode = strings.ReplaceAll(structCode, `bson:"id" json:"id"`, `bson:"_id" json:"id"`)
}
return structCode, newImportPaths, nil
}
@ -507,28 +607,39 @@ func getUpdateFieldsCode(data tmplData, isEmbed bool) (string, error) {
var newFields = []tmplField{}
for _, field := range data.Fields {
falseColumns := []string{}
if isIgnoreFields(field.ColName, falseColumns...) {
if isIgnoreFields(field.ColName, falseColumns...) || field.ColName == columnID || field.ColName == _columnID {
continue
}
newFields = append(newFields, field)
}
data.Fields = newFields
builder := strings.Builder{}
err := updateFieldTmpl.Execute(&builder, data)
buf := new(bytes.Buffer)
err := updateFieldTmpl.Execute(buf, data)
if err != nil {
return "", err
}
code, err := format.Source([]byte(builder.String()))
if err != nil {
return "", err
}
return string(code), nil
return buf.String(), nil
}
func getHandlerStructCodes(data tmplData) (string, error) {
newFields := []tmplField{}
for _, field := range data.Fields {
if field.DBDriver == DBDriverMongodb { // mongodb
if field.Name == "ID" {
field.GoType = "string"
}
if "*"+field.Name == field.GoType {
field.GoType = "*model." + field.Name
}
if strings.Contains(field.GoType, "[]*") {
field.GoType = "[]*model." + strings.ReplaceAll(field.GoType, "[]*", "")
}
}
newFields = append(newFields, field)
}
data.Fields = newFields
postStructCode, err := tmplExecuteWithFilter(data, handlerCreateStructTmpl)
if err != nil {
return "", fmt.Errorf("handlerCreateStructTmpl error: %v", err)
@ -554,6 +665,11 @@ func tmplExecuteWithFilter(data tmplData, tmpl *template.Template, reservedColum
if isIgnoreFields(field.ColName, reservedColumns...) {
continue
}
if field.DBDriver == DBDriverMongodb { // mongodb
if strings.ToLower(field.Name) == "id" {
field.GoType = "string"
}
}
newFields = append(newFields, field)
}
data.Fields = newFields
@ -624,10 +740,86 @@ func getProtoFileCode(data tmplData, isWebProto bool) (string, error) {
code = strings.ReplaceAll(code, "// protoMessageDetailCode", protoMessageDetailCode)
code = strings.ReplaceAll(code, "*time.Time", "int64")
code = strings.ReplaceAll(code, "time.Time", "int64")
code = adaptedDbType(data, isWebProto, code)
return code, nil
}
const (
createTableReplyFieldCodeMark = "// createTableReplyFieldCode"
deleteTableByIDRequestFieldCodeMark = "// deleteTableByIDRequestFieldCode"
deleteTableByIDsRequestFieldCodeMark = "// deleteTableByIDsRequestFieldCode"
getTableByIDRequestFieldCodeMark = "// getTableByIDRequestFieldCode"
getTableByIDsRequestFieldCodeMark = "// getTableByIDsRequestFieldCode"
listTableByLastIDRequestFieldCodeMark = "// listTableByLastIDRequestFieldCode"
)
var grpcDefaultProtoMessageFieldCodes = map[string]string{
createTableReplyFieldCodeMark: "uint64 id = 1;",
deleteTableByIDRequestFieldCodeMark: "uint64 id = 1 [(validate.rules).uint64.gt = 0];",
deleteTableByIDsRequestFieldCodeMark: "repeated uint64 ids = 1 [(validate.rules).repeated.min_items = 1];",
getTableByIDRequestFieldCodeMark: "uint64 id = 1 [(validate.rules).uint64.gt = 0];",
getTableByIDsRequestFieldCodeMark: "repeated uint64 ids = 1 [(validate.rules).repeated.min_items = 1];",
listTableByLastIDRequestFieldCodeMark: "uint64 lastID = 1; // last id",
}
var webDefaultProtoMessageFieldCodes = map[string]string{
createTableReplyFieldCodeMark: "uint64 id = 1;",
deleteTableByIDRequestFieldCodeMark: `uint64 id =1 [(validate.rules).uint64.gt = 0, (tagger.tags) = "uri:\"id\""];`,
deleteTableByIDsRequestFieldCodeMark: "repeated uint64 ids = 1 [(validate.rules).repeated.min_items = 1];",
getTableByIDRequestFieldCodeMark: `uint64 id =1 [(validate.rules).uint64.gt = 0, (tagger.tags) = "uri:\"id\"" ];`,
getTableByIDsRequestFieldCodeMark: "repeated uint64 ids = 1 [(validate.rules).repeated.min_items = 1];",
listTableByLastIDRequestFieldCodeMark: `uint64 lastID = 1 [(tagger.tags) = "form:\"lastID\""]; // last id`,
}
var grpcProtoMessageFieldCodes = map[string]string{
createTableReplyFieldCodeMark: "string id = 1;",
deleteTableByIDRequestFieldCodeMark: "string id = 1 [(validate.rules).string.min_len = 6];",
deleteTableByIDsRequestFieldCodeMark: "repeated string ids = 1 [(validate.rules).repeated.min_items = 1];",
getTableByIDRequestFieldCodeMark: "string id = 1 [(validate.rules).string.min_len = 6];",
getTableByIDsRequestFieldCodeMark: "repeated string ids = 1 [(validate.rules).repeated.min_items = 1];",
listTableByLastIDRequestFieldCodeMark: "string lastID = 1; // last id",
}
var webProtoMessageFieldCodes = map[string]string{
createTableReplyFieldCodeMark: "string id = 1;",
deleteTableByIDRequestFieldCodeMark: `string id =1 [(validate.rules).string.min_len = 6, (tagger.tags) = "uri:\"id\""];`,
deleteTableByIDsRequestFieldCodeMark: "repeated string ids = 1 [(validate.rules).repeated.min_items = 1];",
getTableByIDRequestFieldCodeMark: `string id =1 [(validate.rules).string.min_len = 6, (tagger.tags) = "uri:\"id\"" ];`,
getTableByIDsRequestFieldCodeMark: "repeated string ids = 1 [(validate.rules).repeated.min_items = 1];",
listTableByLastIDRequestFieldCodeMark: `string lastID = 1 [(tagger.tags) = "form:\"lastID\""]; // last id`,
}
func adaptedDbType(data tmplData, isWebProto bool, code string) string {
switch data.DBDriver {
case DBDriverMongodb: // mongodb
if isWebProto {
code = replaceProtoMessageFieldCode(code, webProtoMessageFieldCodes)
} else {
code = replaceProtoMessageFieldCode(code, grpcProtoMessageFieldCodes)
}
default:
if isWebProto {
code = replaceProtoMessageFieldCode(code, webDefaultProtoMessageFieldCodes)
} else {
code = replaceProtoMessageFieldCode(code, grpcDefaultProtoMessageFieldCodes)
}
}
if data.ProtoSubStructs != "" {
code += "\n" + data.ProtoSubStructs
}
return code
}
func replaceProtoMessageFieldCode(code string, messageFields map[string]string) string {
for k, v := range messageFields {
code = strings.ReplaceAll(code, k, v)
}
return code
}
func getServiceStructCode(data tmplData) (string, error) {
builder := strings.Builder{}
err := serviceStructTmpl.Execute(&builder, data)
@ -688,6 +880,7 @@ func addCommaToJSON(modelJSONCode string) string {
return out
}
// nolint
func mysqlToGoType(colTp *types.FieldType, style NullStyle) (name string, path string) {
if style == NullInSql {
path = "database/sql"
@ -746,6 +939,7 @@ func mysqlToGoType(colTp *types.FieldType, style NullStyle) (name string, path s
return name, path
}
// nolint
func goTypeToProto(fields []tmplField) []tmplField {
var newFields []tmplField
for _, field := range fields {
@ -760,7 +954,24 @@ func goTypeToProto(fields []tmplField) []tmplField {
field.GoType = "float"
case "float64":
field.GoType = "double"
case goTypeInts, "[]int64":
field.GoType = "repeated int64"
case "[]int32":
field.GoType = "repeated int32"
case "[]byte":
field.GoType = "string"
case goTypeStrings:
field.GoType = "repeated string"
}
if field.DBDriver == DBDriverMongodb {
if field.GoType[0] == '*' {
field.GoType = field.GoType[1:]
} else if strings.Contains(field.GoType, "[]*") {
field.GoType = "repeated " + strings.ReplaceAll(field.GoType, "[]*", "")
}
}
newFields = append(newFields, field)
}
return newFields

View File

@ -11,22 +11,25 @@ import (
func TestParseSql(t *testing.T) {
sql := `CREATE TABLE t_person_info (
age INT(11) unsigned NULL,
id BIGINT(11) PRIMARY KEY AUTO_INCREMENT NOT NULL COMMENT 'id',
age INT(11) unsigned NULL,
name VARCHAR(30) NOT NULL DEFAULT 'default_name' COMMENT 'name',
created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
login_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
sex VARCHAR(2) NULL,
gender INT(8) NULL,
num INT(11) DEFAULT 3 NULL,
comment TEXT
) COMMENT="person info";`
codes, err := ParseSQL(sql, WithTablePrefix("t_"), WithJSONTag(0))
codes, err := ParseSQL(sql, WithTablePrefix("t_"), WithJSONTag(0), WithNullStyle(NullDisable))
assert.Nil(t, err)
for k, v := range codes {
assert.NotEmpty(t, k)
assert.NotEmpty(t, v)
}
t.Log(codes[CodeTypeJSON])
return
//printCode(codes)
codes, err = ParseSQL(sql, WithTablePrefix("t_"), WithJSONTag(0), WithEmbed())
assert.Nil(t, err)
@ -34,6 +37,23 @@ func TestParseSql(t *testing.T) {
assert.NotEmpty(t, k)
assert.NotEmpty(t, v)
}
//printCode(codes)
codes, err = ParseSQL(sql, WithTablePrefix("t_"), WithJSONTag(0), WithWebProto())
assert.Nil(t, err)
for k, v := range codes {
assert.NotEmpty(t, k)
assert.NotEmpty(t, v)
}
//printCode(codes)
codes, err = ParseSQL(sql, WithTablePrefix("t_"), WithJSONTag(0), WithDBDriver(DBDriverPostgresql))
assert.Nil(t, err)
for k, v := range codes {
assert.NotEmpty(t, k)
assert.NotEmpty(t, v)
}
//printCode(codes)
}
var testData = [][]string{
@ -121,7 +141,7 @@ func Test_parseOption(t *testing.T) {
}
func Test_mysqlToGoType(t *testing.T) {
testData := []*types.FieldType{
fields := []*types.FieldType{
{Tp: uint8('n')},
{Tp: mysql.TypeTiny},
{Tp: mysql.TypeLonglong},
@ -132,7 +152,7 @@ func Test_mysqlToGoType(t *testing.T) {
{Tp: mysql.TypeJSON},
}
var names []string
for _, d := range testData {
for _, d := range fields {
name1, _ := mysqlToGoType(d, NullInSql)
name2, _ := mysqlToGoType(d, NullInPointer)
names = append(names, name1, name2)
@ -141,12 +161,12 @@ func Test_mysqlToGoType(t *testing.T) {
}
func Test_goTypeToProto(t *testing.T) {
testData := []tmplField{
fields := []tmplField{
{GoType: "int"},
{GoType: "uint"},
{GoType: "time.Time"},
}
v := goTypeToProto(testData)
v := goTypeToProto(fields)
assert.NotNil(t, v)
}
@ -179,8 +199,11 @@ func TestGetMysqlTableInfo(t *testing.T) {
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)
if err != nil {
t.Log(err)
return
}
sql, fieldTypes := ConvertToSQLByPgFields("user_example", fields)
t.Log(sql, fieldTypes)
}
@ -189,13 +212,23 @@ func TestGetSqliteTableInfo(t *testing.T) {
t.Log(err, info)
}
func TestConvertToMysqlTable(t *testing.T) {
func TestGetMongodbTableInfo(t *testing.T) {
fields, err := GetMongodbTableInfo("mongodb://root:123456@192.168.3.37:27017/account", "people")
if err != nil {
t.Log(err)
return
}
sql, fieldTypes := ConvertToSQLByMgoFields("people", fields)
t.Log(sql, fieldTypes)
}
func TestConvertToSQLByPgFields(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)
sql, tps := ConvertToSQLByPgFields("foobar", fields)
t.Log(sql, tps)
}
@ -219,3 +252,155 @@ func Test_toMysqlTable(t *testing.T) {
t.Log(toMysqlType(field), getType(field))
}
}
func printCode(code map[string]string) {
for k, v := range code {
fmt.Printf("\n\n----------------- %s --------------------\n%s\n", k, v)
}
}
func Test_getMongodbTableFields(t *testing.T) {
fields := []*MgoField{
{
Name: "_id",
Type: "primitive.ObjectID",
ObjectStr: "",
ProtoObjectStr: "",
},
{
Name: "age",
Type: "int",
ObjectStr: "",
ProtoObjectStr: "",
},
{
Name: "birthday",
Type: "time.Time",
ObjectStr: "",
ProtoObjectStr: "",
},
{
Name: "home_address",
Type: "HomeAddress",
ObjectStr: "type HomeAddress struct { Street string `bson:\"street\" json:\"street\"`; City string `bson:\"city\" json:\"city\"`; State string `bson:\"state\" json:\"state\"`; Zip int `bson:\"zip\" json:\"zip\"` } ",
ProtoObjectStr: `message HomeAddress {
string street = 1;
string city = 2;
string state = 3;
int32 zip = 4;
}
`,
},
{
Name: "interests",
Type: "[]string",
ObjectStr: "",
ProtoObjectStr: "",
},
{
Name: "is_child",
Type: "bool",
ObjectStr: "",
ProtoObjectStr: "",
},
{
Name: "name",
Type: "string",
ObjectStr: "",
ProtoObjectStr: "",
},
{
Name: "numbers",
Type: "[]int",
ObjectStr: "",
ProtoObjectStr: "",
},
{
Name: "shop_addresses",
Type: "[]ShopAddress",
ObjectStr: "type ShopAddress struct { CityO string `bson:\"city_o\" json:\"cityO\"`; StateO string `bson:\"state_o\" json:\"stateO\"` }",
ProtoObjectStr: `message ShopAddress {
string city_o = 1;
string state_o = 2;
}
`,
},
{
Name: "created_at",
Type: "time.Time",
ObjectStr: "",
ProtoObjectStr: "",
},
{
Name: "updated_at",
Type: "time.Time",
ObjectStr: "",
ProtoObjectStr: "",
},
{
Name: "deleted_at",
Type: "*time.Time",
ObjectStr: "",
ProtoObjectStr: "",
},
}
goStructs := MgoFieldToGoStruct("foobar", fields)
t.Log(goStructs)
sql, fieldsMap := ConvertToSQLByMgoFields("foobar", fields)
t.Log(sql)
opts := []Option{
WithDBDriver(DBDriverMongodb),
WithFieldTypes(fieldsMap),
WithJSONTag(1),
}
codes, err := ParseSQL(sql, opts...)
if err != nil {
t.Error(err)
return
}
_ = codes
//printCode(codes)
sql, fieldsMap = ConvertToSQLByMgoFields("foobar", fields)
t.Log(sql)
opts = []Option{
WithDBDriver(DBDriverMongodb),
WithFieldTypes(fieldsMap),
WithJSONTag(1),
WithWebProto(),
}
codes, err = ParseSQL(sql, opts...)
if err != nil {
t.Error(err)
return
}
//printCode(codes)
}
func Test_toSingular(t *testing.T) {
strs := []string{
"users",
"address",
"addresses",
}
for _, str := range strs {
t.Log(str, toSingular(str))
}
}
func Test_embedTimeFields(t *testing.T) {
names := []string{"age"}
fields := embedTimeField(names, []*MgoField{})
t.Log(fields)
names = []string{
"created_at",
"updated_at",
"deleted_at",
}
fields = embedTimeField(names, []*MgoField{})
t.Log(fields)
}

View File

@ -50,8 +50,8 @@ ORDER BY a.attnum;`, tableName)
return fields, nil
}
// ConvertToMysqlTable convert to mysql table ddl
func ConvertToMysqlTable(tableName string, fields []*PGField) (string, map[string]string) {
// ConvertToSQLByPgFields convert to mysql table ddl
func ConvertToSQLByPgFields(tableName string, fields []*PGField) (string, map[string]string) {
fieldStr := ""
pgTypeMap := make(map[string]string) // name:type
for _, field := range fields {
@ -71,14 +71,13 @@ func ConvertToMysqlTable(tableName string, fields []*PGField) (string, map[strin
return fmt.Sprintf("CREATE TABLE %s (\n%s\n);", tableName, fieldStr), pgTypeMap
}
// nolint
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":
@ -87,14 +86,12 @@ func toMysqlType(field *PGField) string {
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":

View File

@ -42,7 +42,7 @@ import (
updateFieldTmpl *template.Template
updateFieldTmplRaw = `
{{- range .Fields}}
if table.{{.Name}} {{.ConditionZero}} {
if table.{{.Name}}{{.ConditionZero}} {
update["{{.ColName}}"] = table.{{.Name}}
}
{{- end}}`
@ -129,11 +129,11 @@ service {{.TName}} {
// protoMessageCreateCode
message Create{{.TableName}}Reply {
uint64 id = 1;
// createTableReplyFieldCode
}
message Delete{{.TableName}}ByIDRequest {
uint64 id = 1 [(validate.rules).uint64.gt = 0];
// deleteTableByIDRequestFieldCode
}
message Delete{{.TableName}}ByIDReply {
@ -141,7 +141,7 @@ message Delete{{.TableName}}ByIDReply {
}
message Delete{{.TableName}}ByIDsRequest {
repeated uint64 ids = 1 [(validate.rules).repeated.min_items = 1];
// deleteTableByIDsRequestFieldCode
}
message Delete{{.TableName}}ByIDsReply {
@ -157,7 +157,7 @@ message Update{{.TableName}}ByIDReply {
// protoMessageDetailCode
message Get{{.TableName}}ByIDRequest {
uint64 id = 1 [(validate.rules).uint64.gt = 0];
// getTableByIDRequestFieldCode
}
message Get{{.TableName}}ByIDReply {
@ -173,7 +173,7 @@ message Get{{.TableName}}ByConditionReply {
}
message List{{.TableName}}ByIDsRequest {
repeated uint64 ids = 1 [(validate.rules).repeated.min_items = 1];
// getTableByIDsRequestFieldCode
}
message List{{.TableName}}ByIDsReply {
@ -181,8 +181,8 @@ message List{{.TableName}}ByIDsReply {
}
message List{{.TableName}}ByLastIDRequest {
uint64 lastID = 1; // last id
uint32 limit = 2 [(validate.rules).uint32.gt = 0]; // limit size per page
// listTableByLastIDRequestFieldCode
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.
}
@ -411,11 +411,11 @@ service {{.TName}} {
// protoMessageCreateCode
message Create{{.TableName}}Reply {
uint64 id = 1;
// createTableReplyFieldCode
}
message Delete{{.TableName}}ByIDRequest {
uint64 id =1 [(validate.rules).uint64.gte = 1, (tagger.tags) = "uri:\"id\"" ];
// deleteTableByIDRequestFieldCode
}
message Delete{{.TableName}}ByIDReply {
@ -423,7 +423,7 @@ message Delete{{.TableName}}ByIDReply {
}
message Delete{{.TableName}}ByIDsRequest {
repeated uint64 ids = 1 [(validate.rules).repeated.min_items = 1];
// deleteTableByIDsRequestFieldCode
}
message Delete{{.TableName}}ByIDsReply {
@ -439,7 +439,7 @@ message Update{{.TableName}}ByIDReply {
// protoMessageDetailCode
message Get{{.TableName}}ByIDRequest {
uint64 id =1 [(validate.rules).uint64.gte = 1, (tagger.tags) = "uri:\"id\"" ];
// getTableByIDRequestFieldCode
}
message Get{{.TableName}}ByIDReply {
@ -455,7 +455,7 @@ message Get{{.TableName}}ByConditionReply {
}
message List{{.TableName}}ByIDsRequest {
repeated uint64 ids = 1 [(validate.rules).repeated.min_items = 1];
// getTableByIDsRequestFieldCode
}
message List{{.TableName}}ByIDsReply {
@ -463,8 +463,8 @@ 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\""]; // limit size per page
// listTableByLastIDRequestFieldCode
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

@ -52,6 +52,9 @@ func (a *Args) checkValid() error {
return fmt.Errorf("sqlite db file %s not found in local host", a.DBDsn)
}
}
if a.fieldTypes == nil {
a.fieldTypes = make(map[string]string)
}
return nil
}
@ -87,13 +90,21 @@ func getSQL(args *Args) (string, map[string]string, error) {
if err != nil {
return "", nil, err
}
sqlStr, pgTypeMap := parser.ConvertToMysqlTable(args.DBTable, fields)
sqlStr, pgTypeMap := parser.ConvertToSQLByPgFields(args.DBTable, fields)
return sqlStr, pgTypeMap, nil
case parser.DBDriverSqlite:
sqlStr, err := parser.GetSqliteTableInfo(args.DBDsn, args.DBTable)
return sqlStr, nil, err
case parser.DBDriverMongodb:
dsn := utils.AdaptiveMongodbDsn(args.DBDsn)
fields, err := parser.GetMongodbTableInfo(dsn, args.DBTable)
if err != nil {
return "", nil, err
}
sqlStr, mongoTypeMap := parser.ConvertToSQLByMgoFields(args.DBTable, fields)
return sqlStr, mongoTypeMap, nil
default:
return "", nil, fmt.Errorf("unsupported database driver: " + dbDriverName)
return "", nil, fmt.Errorf("getsql error, unsupported database driver: " + dbDriverName)
}
}

View File

@ -41,6 +41,21 @@ func AdaptivePostgresqlDsn(dsn string) string {
u.Hostname(), u.Port(), u.User.Username(), password, u.Path[1:], strings.Join(ss, " "))
}
// AdaptiveSqlite adaptive sqlite
func AdaptiveSqlite(dbFile string) string {
// todo convert to absolute path
return dbFile
}
// AdaptiveMongodbDsn adaptive mongodb dsn
func AdaptiveMongodbDsn(dsn string) string {
if !strings.Contains(dsn, "mongodb://") {
dsn = "mongodb://" + dsn
}
return deleteBrackets(dsn)
}
func deleteBrackets(str string) string {
start := strings.Index(str, "@(")
end := strings.LastIndex(str, ")/")

View File

@ -34,3 +34,17 @@ func TestAdaptiveMysqlDsn(t *testing.T) {
t.Log(dsn)
}
}
func TestAdaptiveMongodbDsn(t *testing.T) {
mongoDsns := []string{
"root:123456@192.168.3.37:27017/account",
"root:123456@(192.168.3.37:27017)/account?connectTimeoutMS=15000",
"mongodb://root:123456@192.168.3.37:27017/account",
"mongodb://root:123456@(192.168.3.37:27017)/account?connectTimeoutMS=15000",
}
for _, v := range mongoDsns {
dsn := AdaptiveMongodbDsn(v)
t.Log(dsn)
}
}