Merge branch 'develop'

This commit is contained in:
John Guo
2022-06-21 19:08:36 +08:00
33 changed files with 381 additions and 168 deletions

1
.gitignore vendored
View File

@ -15,4 +15,5 @@ cbuild
**/.DS_Store
.test/
cmd/gf/main
cmd/gf/gf
go.work

View File

@ -11,6 +11,10 @@ var (
type cGen struct {
g.Meta `name:"gen" brief:"{cGenBrief}" dc:"{cGenDc}"`
cGenDao
cGenPb
cGenPbEntity
cGenService
}
const (

View File

@ -138,6 +138,7 @@ func init() {
}
type (
cGenDao struct{}
cGenDaoInput struct {
g.Meta `name:"dao" config:"{cGenDaoConfig}" usage:"{cGenDaoUsage}" brief:"{cGenDaoBrief}" eg:"{cGenDaoEg}" ad:"{cGenDaoAd}"`
Path string `name:"path" short:"p" brief:"{cGenDaoBriefPath}" d:"internal"`
@ -170,7 +171,7 @@ type (
}
)
func (c cGen) Dao(ctx context.Context, in cGenDaoInput) (out *cGenDaoOutput, err error) {
func (c cGenDao) Dao(ctx context.Context, in cGenDaoInput) (out *cGenDaoOutput, err error) {
if g.Cfg().Available(ctx) {
v := g.Cfg().MustGet(ctx, cGenDaoConfig)
if v.IsSlice() {

View File

@ -13,13 +13,14 @@ import (
)
type (
cGenPb struct{}
cGenPbInput struct {
g.Meta `name:"pb" brief:"parse proto files and generate protobuf go files"`
}
cGenPbOutput struct{}
)
func (c cGen) Pb(ctx context.Context, in cGenPbInput) (out *cGenPbOutput, err error) {
func (c cGenPb) Pb(ctx context.Context, in cGenPbInput) (out *cGenPbOutput, err error) {
// Necessary check.
if gproc.SearchBinary("protoc") == "" {
mlog.Fatalf(`command "protoc" not found in your environment, please install protoc first to proceed this command`)

View File

@ -19,6 +19,29 @@ import (
"github.com/olekukonko/tablewriter"
)
type (
cGenPbEntity struct{}
cGenPbEntityInput struct {
g.Meta `name:"pbentity" config:"{cGenPbEntityConfig}" brief:"{cGenPbEntityBrief}" eg:"{cGenPbEntityEg}" ad:"{cGenPbEntityAd}"`
Path string `name:"path" short:"p" brief:"{cGenPbEntityBriefPath}"`
Package string `name:"package" short:"k" brief:"{cGenPbEntityBriefPackage}"`
Link string `name:"link" short:"l" brief:"{cGenPbEntityBriefLink}"`
Tables string `name:"tables" short:"t" brief:"{cGenPbEntityBriefTables}"`
Prefix string `name:"prefix" short:"f" brief:"{cGenPbEntityBriefPrefix}"`
RemovePrefix string `name:"removePrefix" short:"r" brief:"{cGenPbEntityBriefRemovePrefix}"`
NameCase string `name:"nameCase" short:"n" brief:"{cGenPbEntityBriefNameCase}" d:"Camel"`
JsonCase string `name:"jsonCase" short:"j" brief:"{cGenPbEntityBriefJsonCase}" d:"CamelLower"`
Option string `name:"option" short:"o" brief:"{cGenPbEntityBriefOption}"`
}
cGenPbEntityOutput struct{}
cGenPbEntityInternalInput struct {
cGenPbEntityInput
TableName string // TableName specifies the table name of the table.
NewTableName string // NewTableName specifies the prefix-stripped name of the table.
}
)
const (
cGenPbEntityConfig = `gfcli.gen.pbentity`
cGenPbEntityBrief = `generate entity message files in protobuf3 format`
@ -83,28 +106,6 @@ set it to "none" to ignore json tag generating.
`
)
type (
cGenPbEntityInput struct {
g.Meta `name:"pbentity" config:"{cGenPbEntityConfig}" brief:"{cGenPbEntityBrief}" eg:"{cGenPbEntityEg}" ad:"{cGenPbEntityAd}"`
Path string `name:"path" short:"p" brief:"{cGenPbEntityBriefPath}"`
Package string `name:"package" short:"k" brief:"{cGenPbEntityBriefPackage}"`
Link string `name:"link" short:"l" brief:"{cGenPbEntityBriefLink}"`
Tables string `name:"tables" short:"t" brief:"{cGenPbEntityBriefTables}"`
Prefix string `name:"prefix" short:"f" brief:"{cGenPbEntityBriefPrefix}"`
RemovePrefix string `name:"removePrefix" short:"r" brief:"{cGenPbEntityBriefRemovePrefix}"`
NameCase string `name:"nameCase" short:"n" brief:"{cGenPbEntityBriefNameCase}" d:"Camel"`
JsonCase string `name:"jsonCase" short:"j" brief:"{cGenPbEntityBriefJsonCase}" d:"CamelLower"`
Option string `name:"option" short:"o" brief:"{cGenPbEntityBriefOption}"`
}
cGenPbEntityOutput struct{}
cGenPbEntityInternalInput struct {
cGenPbEntityInput
TableName string // TableName specifies the table name of the table.
NewTableName string // NewTableName specifies the prefix-stripped name of the table.
}
)
func init() {
gtag.Sets(g.MapStrStr{
`cGenPbEntityConfig`: cGenPbEntityConfig,
@ -124,7 +125,7 @@ func init() {
})
}
func (c cGen) PbEntity(ctx context.Context, in cGenPbEntityInput) (out *cGenPbEntityOutput, err error) {
func (c cGenPbEntity) PbEntity(ctx context.Context, in cGenPbEntityInput) (out *cGenPbEntityOutput, err error) {
var (
config = g.Cfg()
)

View File

@ -18,15 +18,16 @@ import (
)
type (
cGenService struct{}
cGenServiceInput struct {
g.Meta `name:"service" config:"gfcli.gen.service" brief:"parse struct and associated functions from packages to generate service go file"`
SrcFolder string `short:"s" name:"srcFolder" brief:"source folder path to be parsed. default: internal/logic" d:"internal/logic"`
DstFolder string `short:"d" name:"dstFolder" brief:"destination folder path storing automatically generated go files. default: internal/service" d:"internal/service"`
WatchFile string `short:"w" name:"watchFile" brief:"used in file watcher, it generates service go files only if given file is under srcFolder"`
StPattern string `short:"a" name:"stPattern" brief:"regular expression matching struct name for generating service. default: s([A-Z]\\\w+)" d:"s([A-Z]\\w+)"`
Packages string `short:"p" name:"packages" brief:"produce go files only for given source packages, multiple packages joined with char ','"`
ImportPrefix string `short:"i" name:"importPrefix" brief:"custom import prefix to calculate import path for generated importing go file of logic"`
OverWrite bool `short:"o" name:"overwrite" brief:"overwrite service go files that already exist in generating folder. default: true" d:"true" orphan:"true"`
SrcFolder string `short:"s" name:"srcFolder" brief:"source folder path to be parsed. default: internal/logic" d:"internal/logic"`
DstFolder string `short:"d" name:"dstFolder" brief:"destination folder path storing automatically generated go files. default: internal/service" d:"internal/service"`
WatchFile string `short:"w" name:"watchFile" brief:"used in file watcher, it generates service go files only if given file is under srcFolder"`
StPattern string `short:"a" name:"stPattern" brief:"regular expression matching struct name for generating service. default: s([A-Z]\\\\w+)" d:"s([A-Z]\\w+)"`
Packages []string `short:"p" name:"packages" brief:"produce go files only for given source packages"`
ImportPrefix string `short:"i" name:"importPrefix" brief:"custom import prefix to calculate import path for generated importing go file of logic"`
OverWrite bool `short:"o" name:"overwrite" brief:"overwrite service go files that already exist in generating folder. default: true" d:"true" orphan:"true"`
}
cGenServiceOutput struct{}
)
@ -35,7 +36,7 @@ const (
genServiceFileLockSeconds = 10
)
func (c cGen) Service(ctx context.Context, in cGenServiceInput) (out *cGenServiceOutput, err error) {
func (c cGenService) Service(ctx context.Context, in cGenServiceInput) (out *cGenServiceOutput, err error) {
// File lock to avoid multiple processes.
var (
flockFilePath = gfile.Temp("gf.cli.gen.service.lock")
@ -101,13 +102,12 @@ func (c cGen) Service(ctx context.Context, in cGenServiceInput) (out *cGenServic
}
var (
isDirty bool
files []string
fileContent string
matches [][]string
importSrcPackages []string
inputPackages = gstr.SplitAndTrim(in.Packages, ",")
dstPackageName = gstr.ToLower(gfile.Basename(in.DstFolder))
isDirty bool
files []string
fileContent string
initImportSrcPackages []string
inputPackages = in.Packages
dstPackageName = gstr.ToLower(gfile.Basename(in.DstFolder))
)
srcFolders, err := gfile.ScanDir(in.SrcFolder, "*", false)
if err != nil {
@ -125,56 +125,25 @@ func (c cGen) Service(ctx context.Context, in cGenServiceInput) (out *cGenServic
}
var (
// StructName => FunctionDefinitions
srcPkgInterfaceMap = make(map[string]*garray.StrArray)
srcPkgInterfaceFuncArray *garray.StrArray
ok bool
srcPkgInterfaceMap = make(map[string]*garray.StrArray)
srcImportedPackages = garray.NewSortedStrArray().SetUnique(true)
ok bool
)
for _, file := range files {
fileContent = gfile.GetContents(file)
matches, err = gregex.MatchAllString(`func \((.+?)\) ([\s\S]+?) {`, fileContent)
// Calculate imported packages of source go files.
err = c.calculateImportedPackages(fileContent, srcImportedPackages)
if err != nil {
return nil, err
}
for _, match := range matches {
var (
structName string
structMatch []string
funcReceiver = gstr.Trim(match[1])
receiverArray = gstr.SplitAndTrim(funcReceiver, " ")
functionHead = gstr.Trim(gstr.Replace(match[2], "\n", ""))
)
if len(receiverArray) > 1 {
structName = receiverArray[1]
} else {
structName = receiverArray[0]
}
structName = gstr.Trim(structName, "*")
// Xxx(\n ctx context.Context, req *v1.XxxReq,\n) -> Xxx(ctx context.Context, req *v1.XxxReq)
functionHead = gstr.Replace(functionHead, `,)`, `)`)
functionHead, _ = gregex.ReplaceString(`\(\s+`, `(`, functionHead)
functionHead, _ = gregex.ReplaceString(`\s{2,}`, ` `, functionHead)
if !gstr.IsLetterUpper(functionHead[0]) {
continue
}
if structMatch, err = gregex.MatchString(in.StPattern, structName); err != nil {
return nil, err
}
if len(structMatch) < 1 {
continue
}
structName = gstr.CaseCamel(structMatch[1])
if srcPkgInterfaceFuncArray, ok = srcPkgInterfaceMap[structName]; !ok {
srcPkgInterfaceMap[structName] = garray.NewStrArray()
srcPkgInterfaceFuncArray = srcPkgInterfaceMap[structName]
}
// Remove package name calls of `dstPackageName` in produced codes.
functionHead, _ = gregex.ReplaceString(fmt.Sprintf(`\*{0,1}%s\.`, dstPackageName), ``, functionHead)
srcPkgInterfaceFuncArray.Append(functionHead)
// Calculate functions and interfaces for service generating.
err = c.calculateInterfaceFunctions(in, fileContent, srcPkgInterfaceMap, dstPackageName)
if err != nil {
return nil, err
}
}
importSrcPackages = append(
importSrcPackages,
initImportSrcPackages = append(
initImportSrcPackages,
fmt.Sprintf(`%s/%s`, in.ImportPrefix, gfile.Basename(srcFolder)),
)
// Ignore source packages if input packages given.
@ -186,7 +155,7 @@ func (c cGen) Service(ctx context.Context, in cGenServiceInput) (out *cGenServic
continue
}
// Generating go files for service.
if ok, err = c.generateServiceFiles(in, srcPkgInterfaceMap, dstPackageName); err != nil {
if ok, err = c.generateServiceFiles(in, srcPkgInterfaceMap, srcImportedPackages.Slice(), dstPackageName); err != nil {
return
}
if ok {
@ -196,8 +165,8 @@ func (c cGen) Service(ctx context.Context, in cGenServiceInput) (out *cGenServic
if isDirty {
// Generate initialization go file.
if len(importSrcPackages) > 0 {
if err = c.generateInitializationFile(in, importSrcPackages); err != nil {
if len(initImportSrcPackages) > 0 {
if err = c.generateInitializationFile(in, initImportSrcPackages); err != nil {
return
}
}
@ -218,13 +187,86 @@ func (c cGen) Service(ctx context.Context, in cGenServiceInput) (out *cGenServic
return
}
func (c cGen) generateServiceFiles(
in cGenServiceInput, srcPkgInterfaceMap map[string]*garray.StrArray, dstPackageName string,
func (c cGenService) calculateImportedPackages(fileContent string, srcImportedPackages *garray.SortedStrArray) (err error) {
var match []string
match, err = gregex.MatchString(`\s+import\s+\(([\s\S]+?)\)`, fileContent)
if err != nil {
return err
}
if len(match) < 2 {
return nil
}
importPart := gstr.Trim(match[1])
srcImportedPackages.Append(gstr.SplitAndTrim(importPart, "\n")...)
return nil
}
func (c cGenService) calculateInterfaceFunctions(
in cGenServiceInput, fileContent string, srcPkgInterfaceMap map[string]*garray.StrArray, dstPackageName string,
) (err error) {
var (
ok bool
matches [][]string
srcPkgInterfaceFuncArray *garray.StrArray
)
matches, err = gregex.MatchAllString(`func \((.+?)\) ([\s\S]+?) {`, fileContent)
if err != nil {
return err
}
for _, match := range matches {
var (
structName string
structMatch []string
funcReceiver = gstr.Trim(match[1])
receiverArray = gstr.SplitAndTrim(funcReceiver, " ")
functionHead = gstr.Trim(gstr.Replace(match[2], "\n", ""))
)
if len(receiverArray) > 1 {
structName = receiverArray[1]
} else {
structName = receiverArray[0]
}
structName = gstr.Trim(structName, "*")
// Xxx(\n ctx context.Context, req *v1.XxxReq,\n) -> Xxx(ctx context.Context, req *v1.XxxReq)
functionHead = gstr.Replace(functionHead, `,)`, `)`)
functionHead, _ = gregex.ReplaceString(`\(\s+`, `(`, functionHead)
functionHead, _ = gregex.ReplaceString(`\s{2,}`, ` `, functionHead)
if !gstr.IsLetterUpper(functionHead[0]) {
continue
}
if structMatch, err = gregex.MatchString(in.StPattern, structName); err != nil {
return err
}
if len(structMatch) < 1 {
continue
}
structName = gstr.CaseCamel(structMatch[1])
if srcPkgInterfaceFuncArray, ok = srcPkgInterfaceMap[structName]; !ok {
srcPkgInterfaceMap[structName] = garray.NewStrArray()
srcPkgInterfaceFuncArray = srcPkgInterfaceMap[structName]
}
// Remove package name calls of `dstPackageName` in produced codes.
functionHead, _ = gregex.ReplaceString(fmt.Sprintf(`\*{0,1}%s\.`, dstPackageName), ``, functionHead)
srcPkgInterfaceFuncArray.Append(functionHead)
}
return nil
}
func (c cGenService) generateServiceFiles(
in cGenServiceInput,
srcPkgInterfaceMap map[string]*garray.StrArray,
srcImportedPackages []string,
dstPackageName string,
) (ok bool, err error) {
srcImportedPackagesContent := fmt.Sprintf(
"import (\n%s\n)", gstr.Join(srcImportedPackages, "\n"),
)
for structName, funcArray := range srcPkgInterfaceMap {
var (
filePath = gfile.Join(in.DstFolder, gstr.ToLower(structName)+".go")
generatedContent = gstr.ReplaceByMap(consts.TemplateGenServiceContent, g.MapStrStr{
"{Imports}": srcImportedPackagesContent,
"{StructName}": structName,
"{PackageName}": dstPackageName,
"{FuncDefinition}": funcArray.Join("\n\t"),
@ -250,7 +292,7 @@ func (c cGen) generateServiceFiles(
}
// isToGenerateServiceGoFile checks and returns whether the service content dirty.
func (c cGen) isToGenerateServiceGoFile(filePath string, funcArray *garray.StrArray) bool {
func (c cGenService) isToGenerateServiceGoFile(filePath string, funcArray *garray.StrArray) bool {
if !utils.IsFileDoNotEdit(filePath) {
mlog.Debugf(`ignore file as it is manually maintained: %s`, filePath)
return false
@ -280,7 +322,7 @@ func (c cGen) isToGenerateServiceGoFile(filePath string, funcArray *garray.StrAr
return false
}
func (c cGen) generateInitializationFile(in cGenServiceInput, importSrcPackages []string) (err error) {
func (c cGenService) generateInitializationFile(in cGenServiceInput, importSrcPackages []string) (err error) {
var (
srcPackageName = gstr.ToLower(gfile.Basename(in.SrcFolder))
srcFilePath = gfile.Join(in.SrcFolder, srcPackageName+".go")
@ -306,7 +348,7 @@ func (c cGen) generateInitializationFile(in cGenServiceInput, importSrcPackages
return nil
}
func (c cGen) replaceGeneratedServiceContentGFV2(in cGenServiceInput) (err error) {
func (c cGenService) replaceGeneratedServiceContentGFV2(in cGenServiceInput) (err error) {
return gfile.ReplaceDirFunc(func(path, content string) string {
if gstr.Contains(content, `"github.com/gogf/gf`) && !gstr.Contains(content, `"github.com/gogf/gf/v2`) {
content = gstr.Replace(content, `"github.com/gogf/gf"`, `"github.com/gogf/gf/v2"`)

View File

@ -10,6 +10,7 @@ import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/os/gproc"
"github.com/gogf/gf/v2/os/gres"
"github.com/gogf/gf/v2/util/gtag"
)
@ -48,7 +49,8 @@ func init() {
type cInitInput struct {
g.Meta `name:"init"`
Name string `name:"NAME" arg:"true" v:"required" brief:"{cInitNameBrief}"`
Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"`
Mono bool `name:"mono" short:"m" brief:"initialize a mono-repo instead a single-repo" orphan:"true"`
Update bool `name:"update" short:"u" brief:"update to the latest goframe version" orphan:"true"`
}
type cInitOutput struct{}
@ -89,6 +91,18 @@ func (c cInit) Index(ctx context.Context, in cInitInput) (out *cInitOutput, err
return
}
// Update the GoFrame version.
if in.Update {
mlog.Print("update goframe...")
updateCommand := `go get -u github.com/gogf/gf/v2@latest`
if in.Name != "." {
updateCommand = fmt.Sprintf(`cd %s && %s`, in.Name, updateCommand)
}
if err = gproc.ShellRun(updateCommand); err != nil {
mlog.Fatal(err)
}
}
mlog.Print("initialization done! ")
if !in.Mono {
enjoyCommand := `gf run main.go`

View File

@ -7,6 +7,8 @@ const TemplateGenServiceContent = `
package {PackageName}
{Imports}
type I{StructName} interface {
{FuncDefinition}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -331,9 +331,6 @@ var (
// in the field name as it conflicts with "db.table.field" pattern in SOME situations.
regularFieldNameWithoutDotRegPattern = `^[\w\-]+$`
// tableFieldsMap caches the table information retrieved from database.
tableFieldsMap = gmap.New(true)
// allDryRun sets dry-run feature for all database connections.
// It is commonly used for command options for convenience.
allDryRun = false
@ -399,8 +396,7 @@ func doNewByNode(node ConfigNode, group string) (db DB, err error) {
config: &node,
}
if v, ok := driverMap[node.Type]; ok {
c.db, err = v.New(c, &node)
if err != nil {
if c.db, err = v.New(c, &node); err != nil {
return nil, err
}
return c.db, nil

View File

@ -13,6 +13,8 @@ import (
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
)
// Config is the configuration management object.
@ -72,6 +74,12 @@ func SetConfig(config Config) {
defer instances.Clear()
configs.Lock()
defer configs.Unlock()
for k, nodes := range config {
for i, node := range nodes {
nodes[i] = parseConfigNode(node)
}
config[k] = nodes
}
configs.config = config
}
@ -80,6 +88,9 @@ func SetConfigGroup(group string, nodes ConfigGroup) {
defer instances.Clear()
configs.Lock()
defer configs.Unlock()
for i, node := range nodes {
nodes[i] = parseConfigNode(node)
}
configs.config[group] = nodes
}
@ -88,7 +99,19 @@ func AddConfigNode(group string, node ConfigNode) {
defer instances.Clear()
configs.Lock()
defer configs.Unlock()
configs.config[group] = append(configs.config[group], node)
configs.config[group] = append(configs.config[group], parseConfigNode(node))
}
// parseConfigNode parses `Link` configuration syntax.
func parseConfigNode(node ConfigNode) ConfigNode {
if node.Link != "" && node.Type == "" {
match, _ := gregex.MatchString(`([a-z]+):(.+)`, node.Link)
if len(match) == 3 {
node.Type = gstr.Trim(match[1])
node.Link = gstr.Trim(match[2])
}
}
return node
}
// AddDefaultConfigNode adds one node configuration to configuration of default group.

View File

@ -168,9 +168,13 @@ func Test_Error(t *testing.T) {
_, err = conn.Do(ctx, "Subscribe", "gf")
t.AssertNil(err)
time.Sleep(time.Second)
_, err = redis.Do(ctx, "PUBLISH", "gf", "test")
t.AssertNil(err)
time.Sleep(time.Second)
v, err = conn.Receive(ctx)
t.AssertNil(err)
t.Assert(v.Val().(*gredis.Subscription).Channel, "gf")

View File

@ -16,8 +16,6 @@ import (
"github.com/gogf/gf/v2/internal/consts"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/text/gregex"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gutil"
)
@ -173,13 +171,5 @@ func parseDBConfigNode(value interface{}) *gdb.ConfigNode {
if _, v := gutil.MapPossibleItemByKey(nodeMap, "Link"); v != nil {
node.Link = gconv.String(v)
}
// Parse `Link` configuration syntax.
if node.Link != "" && node.Type == "" {
match, _ := gregex.MatchString(`([a-z]+):(.+)`, node.Link)
if len(match) == 3 {
node.Type = gstr.Trim(match[1])
node.Link = gstr.Trim(match[2])
}
}
return node
}

View File

@ -83,6 +83,9 @@ func Server(name ...interface{}) *ghttp.Server {
ctx,
fmt.Sprintf(`%s.%s.%s`, configNodeName, instanceName, consts.ConfigNodeNameLogger),
).Map()
if len(serverLoggerConfigMap) == 0 && len(serverConfigMap) > 0 {
serverLoggerConfigMap = gconv.Map(serverConfigMap[consts.ConfigNodeNameLogger])
}
if len(serverLoggerConfigMap) > 0 {
if err = server.Logger().SetConfigWithMap(serverLoggerConfigMap); err != nil {
panic(err)

View File

@ -18,6 +18,7 @@ import (
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gmeta"
"github.com/gogf/gf/v2/util/gutil"
)
// DoRequestObj does HTTP request using standard request/response object.
@ -81,13 +82,14 @@ func (c *Client) DoRequestObj(ctx context.Context, req, res interface{}) error {
// /user/{name} -> /order/john
func (c *Client) handlePathForObjRequest(path string, req interface{}) string {
if gstr.Contains(path, "{") {
requestParamsMap := gconv.MapStrStr(req)
requestParamsMap := gconv.Map(req)
if len(requestParamsMap) > 0 {
path, _ = gregex.ReplaceStringFuncMatch(`\{(\w+)\}`, path, func(match []string) string {
if v, ok := requestParamsMap[match[1]]; ok {
return v
foundKey, foundValue := gutil.MapPossibleItemByKey(requestParamsMap, match[1])
if foundKey != "" {
return gconv.String(foundValue)
}
return match[1]
return match[0]
})
}
}

View File

@ -7,6 +7,7 @@
package gclient
import (
"bytes"
"io/ioutil"
"net/http"
@ -69,6 +70,13 @@ func (r *Response) ReadAllString() string {
return string(r.ReadAll())
}
// SetBodyContent overwrites response content with custom one.
func (r *Response) SetBodyContent(content []byte) {
buffer := bytes.NewBuffer(content)
r.Body = ioutil.NopCloser(buffer)
r.ContentLength = int64(buffer.Len())
}
// Close closes the response when it will never be used.
func (r *Response) Close() error {
if r == nil || r.Response == nil {

View File

@ -27,7 +27,8 @@ func Test_Client_DoRequestObj(t *testing.T) {
Id int
}
type UserQueryReq struct {
g.Meta `path:"/user" method:"get"`
g.Meta `path:"/user/{id}" method:"get"`
Id int
}
type UserQueryRes struct {
Id int
@ -36,8 +37,8 @@ func Test_Client_DoRequestObj(t *testing.T) {
p, _ := gtcp.GetFreePort()
s := g.Server(p)
s.Group("/user", func(group *ghttp.RouterGroup) {
group.GET("/", func(r *ghttp.Request) {
r.Response.WriteJson(g.Map{"id": 1, "name": "john"})
group.GET("/{id}", func(r *ghttp.Request) {
r.Response.WriteJson(g.Map{"id": r.Get("id").Int(), "name": "john"})
})
group.POST("/", func(r *ghttp.Request) {
r.Response.WriteJson(g.Map{"id": r.Get("Id")})
@ -68,7 +69,9 @@ func Test_Client_DoRequestObj(t *testing.T) {
client := g.Client().SetPrefix(url).ContentJson()
var (
queryRes *UserQueryRes
queryReq = UserQueryReq{}
queryReq = UserQueryReq{
Id: 1,
}
)
err := client.DoRequestObj(ctx, queryReq, &queryRes)
t.AssertNil(err)

View File

@ -640,3 +640,27 @@ func TestClient_RequestVar(t *testing.T) {
t.AssertNE(users, nil)
})
}
func TestClient_SetBodyContent(t *testing.T) {
p, _ := gtcp.GetFreePort()
s := g.Server(p)
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("hello")
})
s.SetPort(p)
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
time.Sleep(100 * time.Millisecond)
gtest.C(t, func(t *gtest.T) {
c := g.Client()
c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", p))
res, err := c.Get(ctx, "/")
t.AssertNil(err)
defer res.Close()
t.Assert(res.ReadAllString(), "hello")
res.SetBodyContent([]byte("world"))
t.Assert(res.ReadAllString(), "world")
})
}

View File

@ -117,21 +117,22 @@ const (
)
const (
supportedHttpMethods = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
defaultMethod = "ALL"
exceptionExit = "exit"
exceptionExitAll = "exit_all"
exceptionExitHook = "exit_hook"
routeCacheDuration = time.Hour
ctxKeyForRequest = "gHttpRequestObject"
contentTypeXml = "text/xml"
contentTypeHtml = "text/html"
contentTypeJson = "application/json"
swaggerUIPackedPath = "/goframe/swaggerui"
responseTraceIDHeader = "Trace-ID"
specialMethodNameInit = "Init"
specialMethodNameShut = "Shut"
specialMethodNameIndex = "Index"
supportedHttpMethods = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE"
defaultMethod = "ALL"
exceptionExit = "exit"
exceptionExitAll = "exit_all"
exceptionExitHook = "exit_hook"
routeCacheDuration = time.Hour
ctxKeyForRequest = "gHttpRequestObject"
contentTypeXml = "text/xml"
contentTypeHtml = "text/html"
contentTypeJson = "application/json"
swaggerUIPackedPath = "/goframe/swaggerui"
responseTraceIDHeader = "Trace-ID"
specialMethodNameInit = "Init"
specialMethodNameShut = "Shut"
specialMethodNameIndex = "Index"
gracefulShutdownTimeout = 5 * time.Second
)
var (

View File

@ -215,7 +215,9 @@ func (s *gracefulServer) shutdown(ctx context.Context) {
if s.status == ServerStatusStopped {
return
}
if err := s.httpServer.Shutdown(context.Background()); err != nil {
timeoutCtx, cancelFunc := context.WithTimeout(ctx, gracefulShutdownTimeout)
defer cancelFunc()
if err := s.httpServer.Shutdown(timeoutCtx); err != nil {
s.server.Logger().Errorf(
ctx,
"%d: %s server [%s] shutdown error: %v",

View File

@ -196,7 +196,13 @@ func GetFreePort() (port int, err error) {
)
}
port = l.Addr().(*net.TCPAddr).Port
err = l.Close()
if err = l.Close(); err != nil {
err = gerror.Wrapf(
err,
`close listening failed for network "%s", address "%s", port "%d"`,
network, resolvedAddr.String(), port,
)
}
return
}

View File

@ -24,9 +24,10 @@ const (
)
const (
helpOptionName = "help"
helpOptionNameShort = "h"
maxLineChars = 120
helpOptionName = "help"
helpOptionNameShort = "h"
maxLineChars = 120
tracingInstrumentName = "github.com/gogf/gf/v2/os/gcmd.Command"
)
// Init does custom initialization.

View File

@ -11,12 +11,19 @@ import (
"bytes"
"context"
"fmt"
"io"
"os"
"github.com/gogf/gf/v2/text/gstr"
)
// Print prints help info to stdout for current command.
func (c *Command) Print() {
c.PrintTo(os.Stdout)
}
// PrintTo prints help info to custom io.Writer.
func (c *Command) PrintTo(writer io.Writer) {
var (
prefix = gstr.Repeat(" ", 4)
buffer = bytes.NewBuffer(nil)
@ -191,7 +198,7 @@ func (c *Command) Print() {
}
content := buffer.String()
content = gstr.Replace(content, "\t", " ")
fmt.Println(content)
_, _ = writer.Write([]byte(content))
}
type printLineBriefInput struct {

View File

@ -8,16 +8,24 @@
package gcmd
import (
"bytes"
"context"
"fmt"
"os"
"github.com/gogf/gf/v2"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/net/gtrace"
"github.com/gogf/gf/v2/os/gcfg"
"github.com/gogf/gf/v2/os/genv"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/text/gstr"
"github.com/gogf/gf/v2/util/gconv"
"github.com/gogf/gf/v2/util/gutil"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)
// Run calls custom function that bound to this command.
@ -34,18 +42,23 @@ func (c *Command) RunWithValue(ctx context.Context) (value interface{}) {
var (
code = gerror.Code(err)
detail = code.Detail()
buffer = bytes.NewBuffer(nil)
)
if code.Code() == gcode.CodeNotFound.Code() {
fmt.Printf("ERROR: %s\n", gstr.Trim(err.Error()))
buffer.WriteString(fmt.Sprintf("ERROR: %s\n", gstr.Trim(err.Error())))
if lastCmd, ok := detail.(*Command); ok {
lastCmd.Print()
lastCmd.PrintTo(buffer)
} else {
c.Print()
c.PrintTo(buffer)
}
} else {
fmt.Printf("%+v\n", err)
buffer.WriteString(fmt.Sprintf("%+v\n", err))
}
os.Exit(1)
if gtrace.GetTraceID(ctx) == "" {
fmt.Println(buffer.String())
os.Exit(1)
}
glog.Stack(false).Fatal(ctx, buffer.String())
}
return value
}
@ -107,6 +120,24 @@ func (c *Command) doRun(ctx context.Context, parser *Parser) (value interface{},
}
return nil, c.defaultHelpFunc(ctx, parser)
}
// OpenTelemetry for command.
var (
span trace.Span
tr = otel.GetTracerProvider().Tracer(
tracingInstrumentName,
trace.WithInstrumentationVersion(gf.VERSION),
)
)
ctx, span = tr.Start(
otel.GetTextMapPropagator().Extract(
ctx,
propagation.MapCarrier(genv.Map()),
),
gstr.Join(os.Args, " "),
trace.WithSpanKind(trace.SpanKindServer),
)
defer span.End()
span.SetAttributes(gtrace.CommonLabels()...)
// Reparse the arguments for current command configuration.
parser, err = c.reParse(ctx, parser)
if err != nil {
@ -126,7 +157,7 @@ func (c *Command) doRun(ctx context.Context, parser *Parser) (value interface{},
return nil, c.defaultHelpFunc(ctx, parser)
}
// reParse re-parses the arguments using option configuration of current command.
// reParse parses the arguments using option configuration of current command.
func (c *Command) reParse(ctx context.Context, parser *Parser) (*Parser, error) {
if len(c.Arguments) == 0 {
return parser, nil

View File

@ -9,8 +9,12 @@ package gctx
import (
"context"
"os"
"strings"
"github.com/gogf/gf/v2/net/gtrace"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
)
type (
@ -18,13 +22,36 @@ type (
StrKey string // StrKey is a type for warps basic type string as context key.
)
var (
// processCtx is the context initialized from process environment.
processCtx context.Context
)
func init() {
// All environment key-value pairs.
m := make(map[string]string)
i := 0
for _, s := range os.Environ() {
i = strings.IndexByte(s, '=')
m[s[0:i]] = s[i+1:]
}
// OpenTelemetry from environments.
processCtx = otel.GetTextMapPropagator().Extract(
context.Background(),
propagation.MapCarrier(m),
)
}
// New creates and returns a context which contains context id.
func New() context.Context {
return WithCtx(context.Background())
return WithCtx(processCtx)
}
// WithCtx creates and returns a context containing context id upon given parent context `ctx`.
func WithCtx(ctx context.Context) context.Context {
if CtxId(ctx) != "" {
return ctx
}
if gtrace.IsUsingDefaultProvider() {
var span *gtrace.Span
ctx, span = gtrace.NewSpan(ctx, "gctx.WithCtx")

View File

@ -10,6 +10,8 @@ import (
"context"
"time"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/command"
"github.com/gogf/gf/v2/internal/intlog"
"github.com/gogf/gf/v2/os/gcache"
@ -17,22 +19,28 @@ import (
)
const (
defaultCacheExpire = "1m" // defaultCacheExpire is the expire time for file content caching in seconds.
defaultCacheDuration = "1m" // defaultCacheExpire is the expire time for file content caching in seconds.
commandEnvKeyForCache = "gf.gfile.cache" // commandEnvKeyForCache is the configuration key for command argument or environment configuring cache expire duration.
)
var (
// Default expire time for file content caching.
cacheExpire = getCacheExpire()
cacheDuration = getCacheDuration()
// internalCache is the memory cache for internal usage.
internalCache = gcache.New()
)
func getCacheExpire() time.Duration {
d, err := time.ParseDuration(command.GetOptWithEnv(commandEnvKeyForCache, defaultCacheExpire))
func getCacheDuration() time.Duration {
cacheDurationConfigured := command.GetOptWithEnv(commandEnvKeyForCache, defaultCacheDuration)
d, err := time.ParseDuration(cacheDurationConfigured)
if err != nil {
panic(err)
panic(gerror.WrapCodef(
gcode.CodeInvalidConfiguration,
err,
`error parsing string "%s" to time duration`,
cacheDurationConfigured,
))
}
return d
}
@ -50,7 +58,7 @@ func GetContentsWithCache(path string, duration ...time.Duration) string {
func GetBytesWithCache(path string, duration ...time.Duration) []byte {
var (
ctx = context.Background()
expire = cacheExpire
expire = cacheDuration
cacheKey = commandEnvKeyForCache + path
)

View File

@ -120,7 +120,7 @@ func (w *Watcher) eventLoop() {
}
// Calling the callbacks in order.
for _, v := range callbacks {
for _, callback := range callbacks {
go func(callback *Callback) {
defer func() {
if err := recover(); err != nil {
@ -133,7 +133,7 @@ func (w *Watcher) eventLoop() {
}
}()
callback.Func(event)
}(v)
}(callback)
}
} else {
break

View File

@ -8,8 +8,9 @@
package glog
import (
"github.com/gogf/gf/v2/os/gcmd"
"github.com/gogf/gf/v2/internal/command"
"github.com/gogf/gf/v2/os/grpool"
"github.com/gogf/gf/v2/util/gconv"
)
const (
@ -30,7 +31,7 @@ var (
)
func init() {
defaultDebug = gcmd.GetOptWithEnv(commandEnvKeyForDebug, true).Bool()
defaultDebug = gconv.Bool(command.GetOptWithEnv(commandEnvKeyForDebug, "true"))
SetDebug(defaultDebug)
}

View File

@ -14,15 +14,15 @@ import (
// HandlerOutputJson is the structure outputting logging content as single json.
type HandlerOutputJson struct {
Time string // Formatted time string, like "2016-01-09 12:00:00".
TraceId string // Trace id, only available if tracing is enabled.
CtxStr string // The retrieved context value string from context, only available if Config.CtxKeys configured.
Level string // Formatted level string, like "DEBU", "ERRO", etc. Eg: ERRO
CallerFunc string // The source function name that calls logging, only available if F_CALLER_FN set.
CallerPath string // The source file path and its line number that calls logging, only available if F_FILE_SHORT or F_FILE_LONG set.
Prefix string // Custom prefix string for logging content.
Content string // Content is the main logging content, containing error stack string produced by logger.
Stack string // Stack string produced by logger, only available if Config.StStatus configured.
Time string `json:""` // Formatted time string, like "2016-01-09 12:00:00".
TraceId string `json:",omitempty"` // Trace id, only available if tracing is enabled.
CtxStr string `json:",omitempty"` // The retrieved context value string from context, only available if Config.CtxKeys configured.
Level string `json:""` // Formatted level string, like "DEBU", "ERRO", etc. Eg: ERRO
CallerFunc string `json:",omitempty"` // The source function name that calls logging, only available if F_CALLER_FN set.
CallerPath string `json:",omitempty"` // The source file path and its line number that calls logging, only available if F_FILE_SHORT or F_FILE_LONG set.
Prefix string `json:",omitempty"` // Custom prefix string for logging content.
Content string `json:""` // Content is the main logging content, containing error stack string produced by logger.
Stack string `json:",omitempty"` // Stack string produced by logger, only available if Config.StStatus configured.
}
// HandlerJson is a handler for output logging content as a single json string.
@ -43,5 +43,6 @@ func HandlerJson(ctx context.Context, in *HandlerInput) {
panic(err)
}
in.Buffer.Write(jsonBytes)
in.Buffer.Write([]byte("\n"))
in.Next(ctx)
}

View File

@ -62,7 +62,7 @@ func ParseTag(tag string) map[string]string {
tag = tag[i+1:]
value, err := strconv.Unquote(quotedValue)
if err != nil {
panic(err)
panic(gerror.WrapCodef(gcode.CodeInvalidParameter, err, `error parsing tag "%s"`, tag))
}
data[key] = gtag.Parse(value)
}

View File

@ -25,6 +25,8 @@ import (
"time"
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/internal/command"
)
@ -58,9 +60,13 @@ var (
)
func getDefaultInterval() time.Duration {
n, err := strconv.Atoi(command.GetOptWithEnv(commandEnvKeyForInterval, defaultTimerInterval))
interval := command.GetOptWithEnv(commandEnvKeyForInterval, defaultTimerInterval)
n, err := strconv.Atoi(interval)
if err != nil {
panic(err)
panic(gerror.WrapCodef(
gcode.CodeInvalidConfiguration, err, `error converting string "%s" to int number`,
interval,
))
}
return time.Duration(n) * time.Millisecond
}

View File

@ -8,6 +8,9 @@ package grand
import (
"crypto/rand"
"github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror"
)
const (
@ -33,7 +36,7 @@ func asyncProducingRandomBufferBytesLoop() {
for {
buffer := make([]byte, 1024)
if n, err := rand.Read(buffer); err != nil {
panic(err)
panic(gerror.WrapCode(gcode.CodeInternalError, err, `error reading random buffer from system`))
} else {
// The random buffer from system is very expensive,
// so fully reuse the random buffer by changing