Repository: zeromicro/go-zero
Pull request #1591 (2022-02-28 06:12:36)

fix:goctl pg gen of insert code
refer to https://github.com/lib/pq/issues/24


Patch:
diff --git a/core/logx/durationlogger.go b/core/logx/durationlogger.go
index ab26a0a6..7108a939 100644
--- a/core/logx/durationlogger.go
+++ b/core/logx/durationlogger.go
@@ -3,6 +3,7 @@ package logx
 import (
 	"fmt"
 	"io"
+	"sync/atomic"
 	"time"
 
 	"github.com/zeromicro/go-zero/core/timex"
@@ -79,7 +80,7 @@ func (l *durationLogger) WithDuration(duration time.Duration) Logger {
 }
 
 func (l *durationLogger) write(writer io.Writer, level string, val interface{}) {
-	switch encoding {
+	switch atomic.LoadUint32(&encoding) {
 	case plainEncodingType:
 		writePlainAny(writer, level, val, l.Duration)
 	default:
diff --git a/core/logx/logs.go b/core/logx/logs.go
index 34bb1112..c0a3315a 100644
--- a/core/logx/logs.go
+++ b/core/logx/logs.go
@@ -75,7 +75,7 @@ var (
 	timeFormat   = "2006-01-02T15:04:05.000Z07:00"
 	writeConsole bool
 	logLevel     uint32
-	encoding     = jsonEncodingType
+	encoding     uint32 = jsonEncodingType
 	// use uint32 for atomic operations
 	disableStat uint32
 	infoLog     io.WriteCloser
@@ -137,9 +137,9 @@ func SetUp(c LogConf) error {
 	}
 	switch c.Encoding {
 	case plainEncoding:
-		encoding = plainEncodingType
+		atomic.StoreUint32(&encoding, plainEncodingType)
 	default:
-		encoding = jsonEncodingType
+		atomic.StoreUint32(&encoding, jsonEncodingType)
 	}
 
 	switch c.Mode {
@@ -424,7 +424,7 @@ func infoTextSync(msg string) {
 }
 
 func outputAny(writer io.Writer, level string, val interface{}) {
-	switch encoding {
+	switch atomic.LoadUint32(&encoding) {
 	case plainEncodingType:
 		writePlainAny(writer, level, val)
 	default:
@@ -438,7 +438,7 @@ func outputAny(writer io.Writer, level string, val interface{}) {
 }
 
 func outputText(writer io.Writer, level, msg string) {
-	switch encoding {
+	switch atomic.LoadUint32(&encoding) {
 	case plainEncodingType:
 		writePlainText(writer, level, msg)
 	default:
diff --git a/core/logx/tracelogger.go b/core/logx/tracelogger.go
index e2702d51..647f3dff 100644
--- a/core/logx/tracelogger.go
+++ b/core/logx/tracelogger.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"sync/atomic"
 	"time"
 
 	"github.com/zeromicro/go-zero/core/timex"
@@ -80,7 +81,7 @@ func (l *traceLogger) write(writer io.Writer, level string, val interface{}) {
 	traceID := traceIdFromContext(l.ctx)
 	spanID := spanIdFromContext(l.ctx)
 
-	switch encoding {
+	switch atomic.LoadUint32(&encoding) {
 	case plainEncodingType:
 		writePlainAny(writer, level, val, l.Duration, traceID, spanID)
 	default:
diff --git a/readme.md b/readme.md
index 3e0045d2..3aff815d 100644
--- a/readme.md
+++ b/readme.md
@@ -10,6 +10,7 @@ English | [简体中文](readme-cn.md)
 [![Release](https://img.shields.io/github/v/release/zeromicro/go-zero.svg?style=flat-square)](https://github.com/zeromicro/go-zero)
 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
 [![Discord](https://img.shields.io/discord/794530774463414292?label=chat&logo=discord)](https://discord.gg/4JQvC5A4Fe)
+<a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go&#0045;zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go&#0045;zero - A&#0032;web&#0032;&#0038;&#0032;rpc&#0032;framework&#0032;written&#0032;in&#0032;Go&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
 
 **Note: To meet the requirements of Open Source Foundation, we moved go-zero from tal-tech to zeromicro (a neutral GitHub organization).**
 
diff --git a/tools/goctl/api/dartgen/gendata.go b/tools/goctl/api/dartgen/gendata.go
index 6b33e610..f8e4d538 100644
--- a/tools/goctl/api/dartgen/gendata.go
+++ b/tools/goctl/api/dartgen/gendata.go
@@ -19,12 +19,12 @@ class {{.Name}}{
 	});
 	factory {{.Name}}.fromJson(Map<String,dynamic> m) {
 		return {{.Name}}({{range .Members}}
-			{{lowCamelCase .Name}}: {{if isDirectType .Type.Name}}m['{{tagGet .Tag "json"}}']{{else if isClassListType .Type.Name}}(m['{{tagGet .Tag "json"}}'] as List<dynamic>).map((i) => {{getCoreType .Type.Name}}.fromJson(i)){{else}}{{.Type.Name}}.fromJson(m['{{tagGet .Tag "json"}}']){{end}},{{end}}
+			{{lowCamelCase .Name}}: {{if isDirectType .Type.Name}}m['{{getPropertyFromMember .}}']{{else if isClassListType .Type.Name}}(m['{{getPropertyFromMember .}}'] as List<dynamic>).map((i) => {{getCoreType .Type.Name}}.fromJson(i)){{else}}{{.Type.Name}}.fromJson(m['{{getPropertyFromMember .}}']){{end}},{{end}}
 		);
 	}
 	Map<String,dynamic> toJson() {
 		return { {{range .Members}}
-			'{{tagGet .Tag "json"}}': {{if isDirectType .Type.Name}}{{lowCamelCase .Name}}{{else if isClassListType .Type.Name}}{{lowCamelCase .Name}}.map((i) => i.toJson()){{else}}{{lowCamelCase .Name}}.toJson(){{end}},{{end}}
+			'{{getPropertyFromMember .}}': {{if isDirectType .Type.Name}}{{lowCamelCase .Name}}{{else if isClassListType .Type.Name}}{{lowCamelCase .Name}}.map((i) => i.toJson()){{else}}{{lowCamelCase .Name}}.toJson(){{end}},{{end}}
 		};
 	}
 }
diff --git a/tools/goctl/api/dartgen/util.go b/tools/goctl/api/dartgen/util.go
index 5e5fe7e3..92cf2a82 100644
--- a/tools/goctl/api/dartgen/util.go
+++ b/tools/goctl/api/dartgen/util.go
@@ -34,18 +34,12 @@ func pathToFuncName(path string) string {
 	return util.ToLower(camel[:1]) + camel[1:]
 }
 
-func tagGet(tag, k string) string {
-	tags, err := spec.Parse(tag)
+func getPropertyFromMember(member spec.Member) string {
+	name, err := member.GetPropertyName()
 	if err != nil {
-		panic(k + " not exist")
+		panic(fmt.Sprintf("cannot get property name of %q", member.Name))
 	}
-
-	v, err := tags.Get(k)
-	if err != nil {
-		panic(k + " value not exist")
-	}
-
-	return v.Name
+	return name
 }
 
 func isDirectType(s string) bool {
diff --git a/tools/goctl/api/dartgen/vars.go b/tools/goctl/api/dartgen/vars.go
index 78f95e1f..67aafde2 100644
--- a/tools/goctl/api/dartgen/vars.go
+++ b/tools/goctl/api/dartgen/vars.go
@@ -3,12 +3,12 @@ package dartgen
 import "text/template"
 
 var funcMap = template.FuncMap{
-	"tagGet":          tagGet,
-	"isDirectType":    isDirectType,
-	"isClassListType": isClassListType,
-	"getCoreType":     getCoreType,
-	"pathToFuncName":  pathToFuncName,
-	"lowCamelCase":    lowCamelCase,
+	"getPropertyFromMember": getPropertyFromMember,
+	"isDirectType":          isDirectType,
+	"isClassListType":       isClassListType,
+	"getCoreType":           getCoreType,
+	"pathToFuncName":        pathToFuncName,
+	"lowCamelCase":          lowCamelCase,
 }
 
 const (
diff --git a/tools/goctl/go.mod b/tools/goctl/go.mod
index 5e4ffd7f..9adc3c44 100644
--- a/tools/goctl/go.mod
+++ b/tools/goctl/go.mod
@@ -12,6 +12,6 @@ require (
 	github.com/stretchr/testify v1.7.0
 	github.com/urfave/cli v1.22.5
 	github.com/zeromicro/antlr v0.0.1
-	github.com/zeromicro/ddl-parser v1.0.2
+	github.com/zeromicro/ddl-parser v1.0.3
 	github.com/zeromicro/go-zero v1.3.0
 )
diff --git a/tools/goctl/go.sum b/tools/goctl/go.sum
index 6efd385b..104774e4 100644
--- a/tools/goctl/go.sum
+++ b/tools/goctl/go.sum
@@ -356,8 +356,8 @@ github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5u
 github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
 github.com/zeromicro/antlr v0.0.1 h1:CQpIn/dc0pUjgGQ81y98s/NGOm2Hfru2NNio2I9mQgk=
 github.com/zeromicro/antlr v0.0.1/go.mod h1:nfpjEwFR6Q4xGDJMcZnCL9tEfQRgszMwu3rDz2Z+p5M=
-github.com/zeromicro/ddl-parser v1.0.2 h1:bHmxTh8+unNAQoIiCwBdzS49ho6Bb9E0/KrH1HqUwqo=
-github.com/zeromicro/ddl-parser v1.0.2/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiHrb9f5XGiYbo8=
+github.com/zeromicro/ddl-parser v1.0.3 h1:hFecpbt0oPQMhHAbqG1tz78MUepHUnOkFJp1dvRBFyc=
+github.com/zeromicro/ddl-parser v1.0.3/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiHrb9f5XGiYbo8=
 github.com/zeromicro/go-zero v1.3.0 h1:Eyn36yBtR043sm4YKmxR6eS3UA/GtZDktQ+UqIJ3Lm0=
 github.com/zeromicro/go-zero v1.3.0/go.mod h1:Hy4o1VFAt32lXaQMbaBhoFeZjA/rJqJ4PTGNdGsURcc=
 go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
diff --git a/tools/goctl/model/sql/gen/gen.go b/tools/goctl/model/sql/gen/gen.go
index 6730142d..49d95a4b 100644
--- a/tools/goctl/model/sql/gen/gen.go
+++ b/tools/goctl/model/sql/gen/gen.go
@@ -223,7 +223,7 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er
 	table.UniqueCacheKey = uniqueKey
 	table.ContainsUniqueCacheKey = len(uniqueKey) > 0
 
-	importsCode, err := genImports(withCache, in.ContainsTime(), table)
+	importsCode, err := genImports(withCache, in.ContainsTime(), table, g.isPostgreSql)
 	if err != nil {
 		return "", err
 	}
diff --git a/tools/goctl/model/sql/gen/imports.go b/tools/goctl/model/sql/gen/imports.go
index 7fe98768..2fabf48d 100644
--- a/tools/goctl/model/sql/gen/imports.go
+++ b/tools/goctl/model/sql/gen/imports.go
@@ -6,7 +6,7 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
 
-func genImports(withCache, timeImport bool, table Table) (string, error) {
+func genImports(withCache, timeImport bool, table Table, postgreSql bool) (string, error) {
 	if withCache {
 		text, err := pathx.LoadTemplate(category, importsTemplateFile, template.Imports)
 		if err != nil {
@@ -23,10 +23,18 @@ func genImports(withCache, timeImport bool, table Table) (string, error) {
 
 		return buffer.String(), nil
 	}
-
-	text, err := pathx.LoadTemplate(category, importsWithNoCacheTemplateFile, template.ImportsNoCache)
-	if err != nil {
-		return "", err
+	var text string
+	var err error
+	if postgreSql {
+		text, err = pathx.LoadTemplate(category, importsWithNoCacheTemplateFile, template.ImportsNoCachePg)
+		if err != nil {
+			return "", err
+		}
+	} else {
+		text, err = pathx.LoadTemplate(category, importsWithNoCacheTemplateFile, template.ImportsNoCache)
+		if err != nil {
+			return "", err
+		}
 	}
 
 	buffer, err := util.With("import").Parse(text).Execute(map[string]interface{}{
diff --git a/tools/goctl/model/sql/gen/insert.go b/tools/goctl/model/sql/gen/insert.go
index 46c28748..f207c636 100644
--- a/tools/goctl/model/sql/gen/insert.go
+++ b/tools/goctl/model/sql/gen/insert.go
@@ -46,9 +46,19 @@ func genInsert(table Table, withCache, postgreSql bool) (string, string, error)
 	}
 
 	camel := table.Name.ToCamel()
-	text, err := pathx.LoadTemplate(category, insertTemplateFile, template.Insert)
-	if err != nil {
-		return "", "", err
+	var text string
+	var err error
+	// if database is postgresql
+	if postgreSql {
+		text, err = pathx.LoadTemplate(category, insertTemplateFile, template.InsertPg)
+		if err != nil {
+			return "", "", err
+		}
+	} else {
+		text, err = pathx.LoadTemplate(category, insertTemplateFile, template.Insert)
+		if err != nil {
+			return "", "", err
+		}
 	}
 
 	output, err := util.With("insert").
@@ -69,9 +79,17 @@ func genInsert(table Table, withCache, postgreSql bool) (string, string, error)
 	}
 
 	// interface method
-	text, err = pathx.LoadTemplate(category, insertTemplateMethodFile, template.InsertMethod)
-	if err != nil {
-		return "", "", err
+	// if database is postgresql
+	if postgreSql {
+		text, err = pathx.LoadTemplate(category, insertTemplateMethodFile, template.InsertMethodPg)
+		if err != nil {
+			return "", "", err
+		}
+	} else {
+		text, err = pathx.LoadTemplate(category, insertTemplateMethodFile, template.InsertMethod)
+		if err != nil {
+			return "", "", err
+		}
 	}
 
 	insertMethodOutput, err := util.With("insertMethod").Parse(text).Execute(map[string]interface{}{
diff --git a/tools/goctl/model/sql/template/import.go b/tools/goctl/model/sql/template/import.go
index a52e3be1..944d7094 100644
--- a/tools/goctl/model/sql/template/import.go
+++ b/tools/goctl/model/sql/template/import.go
@@ -30,4 +30,17 @@ var (
 	"github.com/zeromicro/go-zero/core/stringx"
 )
 `
+	// pg
+	ImportsNoCachePg = `import (
+		"context"
+		"fmt"
+		"strings"
+		{{if .time}}"time"{{end}}
+	
+		"github.com/zeromicro/go-zero/core/stores/builder"
+		"github.com/zeromicro/go-zero/core/stores/sqlc"
+		"github.com/zeromicro/go-zero/core/stores/sqlx"
+		"github.com/zeromicro/go-zero/core/stringx"
+	)
+	`
 )
diff --git a/tools/goctl/model/sql/template/insert.go b/tools/goctl/model/sql/template/insert.go
index 115ea7ff..67687be9 100644
--- a/tools/goctl/model/sql/template/insert.go
+++ b/tools/goctl/model/sql/template/insert.go
@@ -15,5 +15,24 @@ func (m *default{{.upperStartCamelObject}}Model) Insert(ctx context.Context, dat
 }
 `
 
+// pg
+var InsertPg = `
+func (m *default{{.upperStartCamelObject}}Model) Insert(data *{{.upperStartCamelObject}}) (int64,error) {
+	{{if .withCache}}{{if .containsIndexCache}}{{.keys}}
+    ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
+		query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
+		return conn.Exec(query, {{.expressionValues}})
+	}, {{.keyValues}}){{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
+    ret,err:=m.ExecNoCache(query, {{.expressionValues}})
+	{{end}}{{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}}) RETURNING id", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
+	var id int64
+    err:=m.conn.QueryRow(&id, query, {{.expressionValues}}){{end}}
+	return id,err
+}
+`
+
 // InsertMethod defines an interface method template for insert code in model
 var InsertMethod = `Insert(ctx context.Context, data *{{.upperStartCamelObject}}) (sql.Result,error)`
+
+// pg
+var InsertMethodPg = `Insert(ctx context.Context, data *{{.upperStartCamelObject}}) (int64,error)`


Test patch:
diff --git a/core/collection/fifo_test.go b/core/collection/fifo_test.go
index 3e58afef..6f1acfdf 100644
--- a/core/collection/fifo_test.go
+++ b/core/collection/fifo_test.go
@@ -61,3 +61,41 @@ func TestPutMore(t *testing.T) {
 		assert.Equal(t, string(element), string(body.([]byte)))
 	}
 }
+
+func TestPutMoreWithHeaderNotZero(t *testing.T) {
+	elements := [][]byte{
+		[]byte("hello"),
+		[]byte("world"),
+		[]byte("again"),
+	}
+	queue := NewQueue(4)
+	for i := range elements {
+		queue.Put(elements[i])
+	}
+
+	// take 1
+	body, ok := queue.Take()
+	assert.True(t, ok)
+	element, ok := body.([]byte)
+	assert.True(t, ok)
+	assert.Equal(t, element, []byte("hello"))
+
+	// put more
+	queue.Put([]byte("b4"))
+	queue.Put([]byte("b5")) // will store in elements[0]
+	queue.Put([]byte("b6")) // cause expansion
+
+	results := [][]byte{
+		[]byte("world"),
+		[]byte("again"),
+		[]byte("b4"),
+		[]byte("b5"),
+		[]byte("b6"),
+	}
+
+	for _, element := range results {
+		body, ok := queue.Take()
+		assert.True(t, ok)
+		assert.Equal(t, string(element), string(body.([]byte)))
+	}
+}
diff --git a/core/logx/durationlogger_test.go b/core/logx/durationlogger_test.go
index 96eba091..4cebf448 100644
--- a/core/logx/durationlogger_test.go
+++ b/core/logx/durationlogger_test.go
@@ -3,6 +3,7 @@ package logx
 import (
 	"log"
 	"strings"
+	"sync/atomic"
 	"testing"
 	"time"
 
@@ -38,10 +39,10 @@ func TestWithDurationInfo(t *testing.T) {
 }
 
 func TestWithDurationInfoConsole(t *testing.T) {
-	old := encoding
-	encoding = plainEncodingType
+	old := atomic.LoadUint32(&encoding)
+	atomic.StoreUint32(&encoding, plainEncodingType)
 	defer func() {
-		encoding = old
+		atomic.StoreUint32(&encoding, old)
 	}()
 
 	var builder strings.Builder
diff --git a/core/logx/logs_test.go b/core/logx/logs_test.go
index 29d647c2..7ab86c52 100644
--- a/core/logx/logs_test.go
+++ b/core/logx/logs_test.go
@@ -145,10 +145,10 @@ func TestStructedLogInfoConsoleAny(t *testing.T) {
 	doTestStructedLogConsole(t, func(writer io.WriteCloser) {
 		infoLog = writer
 	}, func(v ...interface{}) {
-		old := encoding
-		encoding = plainEncodingType
+		old := atomic.LoadUint32(&encoding)
+		atomic.StoreUint32(&encoding, plainEncodingType)
 		defer func() {
-			encoding = old
+			atomic.StoreUint32(&encoding, old)
 		}()
 
 		Infov(v)
@@ -159,10 +159,10 @@ func TestStructedLogInfoConsoleAnyString(t *testing.T) {
 	doTestStructedLogConsole(t, func(writer io.WriteCloser) {
 		infoLog = writer
 	}, func(v ...interface{}) {
-		old := encoding
-		encoding = plainEncodingType
+		old := atomic.LoadUint32(&encoding)
+		atomic.StoreUint32(&encoding, plainEncodingType)
 		defer func() {
-			encoding = old
+			atomic.StoreUint32(&encoding, old)
 		}()
 
 		Infov(fmt.Sprint(v...))
@@ -173,10 +173,10 @@ func TestStructedLogInfoConsoleAnyError(t *testing.T) {
 	doTestStructedLogConsole(t, func(writer io.WriteCloser) {
 		infoLog = writer
 	}, func(v ...interface{}) {
-		old := encoding
-		encoding = plainEncodingType
+		old := atomic.LoadUint32(&encoding)
+		atomic.StoreUint32(&encoding, plainEncodingType)
 		defer func() {
-			encoding = old
+			atomic.StoreUint32(&encoding, old)
 		}()
 
 		Infov(errors.New(fmt.Sprint(v...)))
@@ -187,10 +187,10 @@ func TestStructedLogInfoConsoleAnyStringer(t *testing.T) {
 	doTestStructedLogConsole(t, func(writer io.WriteCloser) {
 		infoLog = writer
 	}, func(v ...interface{}) {
-		old := encoding
-		encoding = plainEncodingType
+		old := atomic.LoadUint32(&encoding)
+		atomic.StoreUint32(&encoding, plainEncodingType)
 		defer func() {
-			encoding = old
+			atomic.StoreUint32(&encoding, old)
 		}()
 
 		Infov(ValStringer{
@@ -203,10 +203,10 @@ func TestStructedLogInfoConsoleText(t *testing.T) {
 	doTestStructedLogConsole(t, func(writer io.WriteCloser) {
 		infoLog = writer
 	}, func(v ...interface{}) {
-		old := encoding
-		encoding = plainEncodingType
+		old := atomic.LoadUint32(&encoding)
+		atomic.StoreUint32(&encoding, plainEncodingType)
 		defer func() {
-			encoding = old
+			atomic.StoreUint32(&encoding, old)
 		}()
 
 		Info(fmt.Sprint(v...))
diff --git a/core/logx/syslog_test.go b/core/logx/syslog_test.go
index 01b285ee..d2a4fd29 100644
--- a/core/logx/syslog_test.go
+++ b/core/logx/syslog_test.go
@@ -29,9 +29,9 @@ func TestRedirector(t *testing.T) {
 }
 
 func captureOutput(f func()) string {
-	atomic.StoreUint32(&initialized, 1)
 	writer := new(mockWriter)
 	infoLog = writer
+	atomic.StoreUint32(&initialized, 1)
 
 	prevLevel := atomic.LoadUint32(&logLevel)
 	SetLevel(InfoLevel)
@@ -44,5 +44,9 @@ func captureOutput(f func()) string {
 func getContent(jsonStr string) string {
 	var entry logEntry
 	json.Unmarshal([]byte(jsonStr), &entry)
-	return entry.Content.(string)
+	val, ok := entry.Content.(string)
+	if ok {
+		return val
+	}
+	return ""
 }
diff --git a/core/logx/tracelogger_test.go b/core/logx/tracelogger_test.go
index 7da8d0dd..3fa4d2ed 100644
--- a/core/logx/tracelogger_test.go
+++ b/core/logx/tracelogger_test.go
@@ -83,10 +83,10 @@ func TestTraceInfo(t *testing.T) {
 }
 
 func TestTraceInfoConsole(t *testing.T) {
-	old := encoding
-	encoding = plainEncodingType
+	old := atomic.LoadUint32(&encoding)
+	atomic.StoreUint32(&encoding, jsonEncodingType)
 	defer func() {
-		encoding = old
+		atomic.StoreUint32(&encoding, old)
 	}()
 
 	var buf mockWriter
diff --git a/tools/goctl/api/dartgen/util_test.go b/tools/goctl/api/dartgen/util_test.go
new file mode 100644
index 00000000..68a221fd
--- /dev/null
+++ b/tools/goctl/api/dartgen/util_test.go
@@ -0,0 +1,39 @@
+package dartgen
+
+import (
+	"testing"
+
+	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
+)
+
+func Test_getPropertyFromMember(t *testing.T) {
+	tests := []struct {
+		name   string
+		member spec.Member
+		want   string
+	}{
+		{
+			name: "json tag should be ok",
+			member: spec.Member{
+				Tag:  "`json:\"foo\"`",
+				Name: "Foo",
+			},
+			want: "foo",
+		},
+		{
+			name: "form tag should be ok",
+			member: spec.Member{
+				Tag:  "`form:\"bar\"`",
+				Name: "Bar",
+			},
+			want: "bar",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := getPropertyFromMember(tt.member); got != tt.want {
+				t.Errorf("getPropertyFromMember() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}