reactor sql generation

This commit is contained in:
anqiansong
2020-08-12 14:09:49 +08:00
parent f226ffb57c
commit c519345924
19 changed files with 497 additions and 33 deletions

View File

@@ -0,0 +1,97 @@
package builderx
import (
"fmt"
"reflect"
"github.com/go-xorm/builder"
)
const dbTag = "db"
func NewEq(in interface{}) builder.Eq {
return builder.Eq(ToMap(in))
}
func NewGt(in interface{}) builder.Gt {
return builder.Gt(ToMap(in))
}
func ToMap(in interface{}) map[string]interface{} {
out := make(map[string]interface{})
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// we only accept structs
if v.Kind() != reflect.Struct {
panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get(dbTag); tagv != "" {
// set key of map to value in struct field
val := v.Field(i)
zero := reflect.Zero(val.Type()).Interface()
current := val.Interface()
if reflect.DeepEqual(current, zero) {
continue
}
out[tagv] = current
}
}
return out
}
func FieldNames(in interface{}) []string {
out := make([]string, 0)
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// we only accept structs
if v.Kind() != reflect.Struct {
panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get(dbTag); tagv != "" {
out = append(out, tagv)
} else {
out = append(out, fi.Name)
}
}
return out
}
func FieldNamesAlias(in interface{}, alias string) []string {
out := make([]string, 0)
v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
// we only accept structs
if v.Kind() != reflect.Struct {
panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
tagName := ""
if tagv := fi.Tag.Get(dbTag); tagv != "" {
tagName = tagv
} else {
tagName = fi.Name
}
if len(alias) > 0 {
tagName = alias + "." + tagName
}
out = append(out, tagName)
}
return out
}

View File

@@ -0,0 +1,101 @@
package builderx
import (
"fmt"
"testing"
"github.com/go-xorm/builder"
"github.com/stretchr/testify/assert"
)
type (
User struct {
// 自增id
Id string `db:"id" json:"id,omitempty"`
// 姓名
UserName string `db:"user_name" json:"userName,omitempty"`
// 1男,2女
Sex int `db:"sex" json:"sex,omitempty"`
Uuid string `db:"uuid" uuid:"uuid,omitempty"`
Age int `db:"age" json:"age"`
}
)
var userFields = FieldNames(User{})
func TestFieldNames(t *testing.T) {
var u User
out := FieldNames(&u)
fmt.Println(out)
actual := []string{"id", "user_name", "sex", "uuid", "age"}
assert.Equal(t, out, actual)
}
func TestNewEq(t *testing.T) {
u := &User{
Id: "123456",
UserName: "wahaha",
}
out := NewEq(u)
fmt.Println(out)
actual := builder.Eq{"id": "123456", "user_name": "wahaha"}
assert.Equal(t, out, actual)
}
// @see https://github.com/go-xorm/builder
func TestBuilderSql(t *testing.T) {
u := &User{
Id: "123123",
}
fields := FieldNames(u)
eq := NewEq(u)
sql, args, err := builder.Select(fields...).From("user").Where(eq).ToSQL()
fmt.Println(sql, args, err)
actualSql := "SELECT id,user_name,sex,uuid,age FROM user WHERE id=?"
actualArgs := []interface{}{"123123"}
assert.Equal(t, sql, actualSql)
assert.Equal(t, args, actualArgs)
}
func TestBuildSqlDefaultValue(t *testing.T) {
var eq = builder.Eq{}
eq["age"] = 0
eq["user_name"] = ""
sql, args, err := builder.Select(userFields...).From("user").Where(eq).ToSQL()
fmt.Println(sql, args, err)
actualSql := "SELECT id,user_name,sex,uuid,age FROM user WHERE age=? AND user_name=?"
actualArgs := []interface{}{0, ""}
assert.Equal(t, sql, actualSql)
assert.Equal(t, args, actualArgs)
}
func TestBuilderSqlIn(t *testing.T) {
u := &User{
Age: 18,
}
gtU := NewGt(u)
in := builder.In("id", []string{"1", "2", "3"})
sql, args, err := builder.Select(userFields...).From("user").Where(in).And(gtU).ToSQL()
fmt.Println(sql, args, err)
actualSql := "SELECT id,user_name,sex,uuid,age FROM user WHERE id IN (?,?,?) AND age>?"
actualArgs := []interface{}{"1", "2", "3", 18}
assert.Equal(t, sql, actualSql)
assert.Equal(t, args, actualArgs)
}
func TestBuildSqlLike(t *testing.T) {
like := builder.Like{"name", "wang"}
sql, args, err := builder.Select(userFields...).From("user").Where(like).ToSQL()
fmt.Println(sql, args, err)
actualSql := "SELECT id,user_name,sex,uuid,age FROM user WHERE name LIKE ?"
actualArgs := []interface{}{"%wang%"}
assert.Equal(t, sql, actualSql)
assert.Equal(t, args, actualArgs)
}

View File

@@ -13,7 +13,9 @@ func genDelete(table Table, withCache bool) (string, error) {
keySet := collection.NewSet()
keyVariableSet := collection.NewSet()
for fieldName, key := range table.CacheKey {
keySet.AddStr(key.KeyExpression)
if fieldName == table.PrimaryKey.Name.Source() {
keySet.AddStr(key.KeyExpression)
}
if fieldName != table.PrimaryKey.Name.Source() {
keySet.AddStr(key.DataKeyExpression)
}
@@ -33,7 +35,7 @@ func genDelete(table Table, withCache bool) (string, error) {
"upperStartCamelObject": camel,
"withCache": withCache,
"containsIndexCache": containsIndexCache,
"lowerStartCamelPrimaryKey": stringx.From(camel).LowerStart(),
"lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.Snake2Camel()).LowerStart(),
"dataType": table.PrimaryKey.DataType,
"keys": strings.Join(keySet.KeysStr(), "\n"),
"originalPrimaryKey": table.PrimaryKey.Name.Source(),

View File

@@ -21,13 +21,18 @@ func genFields(fields []parser.Field) (string, error) {
}
func genField(field parser.Field) (string, error) {
tag, err := genTag(field.Name.Source())
if err != nil {
return "", err
}
output, err := templatex.With("types").
Parse(template.Field).
Execute(map[string]string{
"name": field.Name.Snake2Camel(),
"type": field.DataType,
"tag": field.Name.Source(),
"comment": field.Comment,
Execute(map[string]interface{}{
"name": field.Name.Snake2Camel(),
"type": field.DataType,
"tag": tag,
"hasComment": field.Comment != "",
"comment": field.Comment,
})
if err != nil {
return "", err

View File

@@ -14,7 +14,7 @@ func genFineOneByField(table Table, withCache bool) (string, error) {
var list []string
camelTableName := table.Name.Snake2Camel()
for _, field := range table.Fields {
if field.IsPrimaryKey {
if field.IsPrimaryKey || !field.IsKey {
continue
}
camelFieldName := field.Name.Snake2Camel()
@@ -25,7 +25,7 @@ func genFineOneByField(table Table, withCache bool) (string, error) {
"withCache": withCache,
"cacheKey": table.CacheKey[field.Name.Source()].KeyExpression,
"cacheKeyVariable": table.CacheKey[field.Name.Source()].Variable,
"primaryKeyLeft": table.CacheKey[table.Name.Source()].Left,
"primaryKeyLeft": table.CacheKey[table.PrimaryKey.Name.Source()].Left,
"lowerStartCamelObject": stringx.From(camelTableName).LowerStart(),
"lowerStartCamelField": stringx.From(camelFieldName).LowerStart(),
"upperStartCamelPrimaryKey": table.PrimaryKey.Name.Snake2Camel(),

View File

@@ -104,7 +104,10 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er
if err != nil {
return "", err
}
importsCode := genImports()
importsCode, err := genImports(withCache)
if err != nil {
return "", err
}
var table Table
table.Table = in
table.CacheKey = m

View File

@@ -1,9 +1,18 @@
package gen
import (
sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
"github.com/tal-tech/go-zero/tools/goctl/util/templatex"
)
func genImports() string {
return sqltemplate.Imports
func genImports(withCache bool) (string, error) {
output, err := templatex.With("import").
Parse(template.Imports).
Execute(map[string]interface{}{
"withCache": withCache,
})
if err != nil {
return "", err
}
return output.String(), nil
}

View File

@@ -34,7 +34,7 @@ func genCacheKeys(table parser.Table) (map[string]Key, error) {
}
camelFieldName := field.Name.Snake2Camel()
lowerStartCamelFieldName := stringx.From(camelFieldName).LowerStart()
left := fmt.Sprintf("cache%s%sPrefix", camelTableName, camelFieldName)
left := fmt.Sprintf("cache%s%sPrefix", lowerStartCamelTableName, camelFieldName)
right := fmt.Sprintf("cache#%s#%s#", lowerStartCamelTableName, lowerStartCamelFieldName)
variable := fmt.Sprintf("%s%sKey", lowerStartCamelTableName, camelFieldName)
m[field.Name.Source()] = Key{
@@ -42,9 +42,9 @@ func genCacheKeys(table parser.Table) (map[string]Key, error) {
Left: left,
Right: right,
Variable: variable,
KeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("cache#user#id#%s", %s)`, variable, "%s", lowerStartCamelFieldName),
DataKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("cache#user#id#%s", data.%s)`, variable, "%s", lowerStartCamelFieldName),
RespKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("cache#user#id#%s", resp.%s)`, variable, "%s", lowerStartCamelFieldName),
KeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("cache#%s#%s#%s", %s)`, variable, lowerStartCamelTableName, lowerStartCamelFieldName, "%v", lowerStartCamelFieldName),
DataKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("cache#%s#%s#%s", data.%s)`, variable, lowerStartCamelTableName, lowerStartCamelFieldName, "%v", camelFieldName),
RespKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("cache#%s#%s#%s", resp.%s)`, variable, lowerStartCamelTableName, lowerStartCamelFieldName, "%v", camelFieldName),
}
}
return m, nil

View File

@@ -8,6 +8,14 @@ import (
"github.com/xwb1989/sqlparser"
)
const (
none = iota
primary
unique
normal
spatial
)
type (
Table struct {
Name stringx.String
@@ -26,6 +34,7 @@ type (
IsPrimaryKey bool
Comment string
}
KeyType int
)
func Parse(ddl string) (*Table, error) {
@@ -50,6 +59,7 @@ func Parse(ddl string) (*Table, error) {
columns := tableSpec.Columns
indexes := tableSpec.Indexes
keyMap := make(map[string]KeyType)
for _, index := range indexes {
info := index.Info
if info == nil {
@@ -59,13 +69,33 @@ func Parse(ddl string) (*Table, error) {
if len(index.Columns) > 1 {
return nil, errPrimaryKey
}
break
keyMap[index.Columns[0].Column.String()] = primary
continue
}
// can optimize
if len(index.Columns) > 1 {
continue
}
column := index.Columns[0]
columnName := column.Column.String()
camelColumnName := stringx.From(columnName).Snake2Camel()
// by default, createTime|updateTime findOne is not used.
if camelColumnName == "CreateTime" || camelColumnName == "UpdateTime" {
continue
}
if info.Unique {
keyMap[columnName] = unique
} else if info.Spatial {
keyMap[columnName] = spatial
} else {
keyMap[columnName] = normal
}
}
var (
fields []Field
primaryKey Primary
)
for _, column := range columns {
if column == nil {
continue
@@ -83,17 +113,18 @@ func Parse(ddl string) (*Table, error) {
field.DataBaseType = column.Type.Type
field.DataType = dataType
field.Comment = comment
// see line 1194 https://github.com/xwb1989/sqlparser/blob/master/ast.go
field.IsKey = column.Type.KeyOpt != 0
field.IsPrimaryKey = column.Type.KeyOpt == 1
fields = append(fields, field)
// see line 1195 https://github.com/xwb1989/sqlparser/blob/master/ast.go
if column.Type.KeyOpt == 1 {
primaryKey.Field = field
if column.Type.Autoincrement {
primaryKey.AutoIncrement = true
key, ok := keyMap[column.Name.String()]
if ok {
field.IsKey = true
field.IsPrimaryKey = key == primary
if field.IsPrimaryKey {
primaryKey.Field = field
if column.Type.Autoincrement {
primaryKey.AutoIncrement = true
}
}
}
fields = append(fields, field)
}
return &Table{
Name: stringx.From(tableName),

View File

@@ -1,3 +1,3 @@
package template
var Field = `{{.name}} {{.type}} {{.tag}} {{.comment}}`
var Field = `{{.name}} {{.type}} {{.tag}} {{if .hasComment}}// {{.comment}}{{end}}`

View File

@@ -55,5 +55,15 @@ func (m *{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{
default:
return nil, err
}
}
}{{else}}var resp {{.upperStartCamelObject}}
query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalField}} limit 1` + "`" + `
err := m.QueryRowNoCache(&resp, query, {{.lowerStartCamelField}})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}{{end}}
`

View File

@@ -2,13 +2,15 @@ package template
var Imports = `
import (
"database/sql"
"strings"
"database/sql"{{if .withCache}}
"fmt"
{{end}}"strings"
"time"
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/sqlc"
"github.com/tal-tech/go-zero/core/stores/sqlx"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx"
)
`

View File

@@ -4,7 +4,7 @@ var Vars = `
var (
{{.lowerStartCamelObject}}FieldNames = builderx.FieldNames(&{{.upperStartCamelObject}}{})
{{.lowerStartCamelObject}}Rows = strings.Join({{.lowerStartCamelObject}}FieldNames, ",")
{{.lowerStartCamelObject}}RowsExpectAutoSet = strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, {{.if autoIncrement}}"{{.originalPrimaryKey}}",{{end}} "create_time", "update_time"), ",")
{{.lowerStartCamelObject}}RowsExpectAutoSet = strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, {{if .autoIncrement}}"{{.originalPrimaryKey}}",{{end}} "create_time", "update_time"), ",")
{{.lowerStartCamelObject}}RowsWithPlaceHolder = strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, "{{.originalPrimaryKey}}", "create_time", "update_time"), "=?,") + "=?"
{{.cacheKeys}}

View File

@@ -0,0 +1,149 @@
package model
import (
"database/sql"
"fmt"
"strings"
"time"
"github.com/tal-tech/go-zero/core/stores/cache"
"github.com/tal-tech/go-zero/core/stores/sqlc"
"github.com/tal-tech/go-zero/core/stores/sqlx"
"github.com/tal-tech/go-zero/core/stringx"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx"
)
var (
userCamelFieldNames = builderx.FieldNames(&UserCamel{})
userCamelRows = strings.Join(userCamelFieldNames, ",")
userCamelRowsExpectAutoSet = strings.Join(stringx.Remove(userCamelFieldNames, "create_time", "update_time"), ",")
userCamelRowsWithPlaceHolder = strings.Join(stringx.Remove(userCamelFieldNames, "id", "create_time", "update_time"), "=?,") + "=?"
cacheuserCamelIdPrefix = "cache#userCamel#id#"
cacheuserCamelNamePrefix = "cache#userCamel#name#"
cacheuserCamelMobilePrefix = "cache#userCamel#mobile#"
ErrNotFound = sqlx.ErrNotFound
)
type (
UserCamelModel struct {
sqlc.CachedConn
table string
}
UserCamel struct {
Id int64 `db:"id"`
Name string `db:"name"` // 用户名称
Password string `db:"password"` // 用户密码
Mobile string `db:"mobile"` // 手机号
Gender string `db:"gender"` // 男|女|未公开
Nickname string `db:"nickname"` // 用户昵称
CreateTime time.Time `db:"createTime"`
UpdateTime time.Time `db:"updateTime"`
}
)
func NewUserCamelModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *UserCamelModel {
return &UserCamelModel{
CachedConn: sqlc.NewConn(conn, c),
table: table,
}
}
func (m *UserCamelModel) Insert(data UserCamel) error {
query := `insert into ` + m.table + `(` + userCamelRowsExpectAutoSet + `) value (?, ?, ?, ?, ?, ?)`
_, err := m.ExecNoCache(query, data.Id, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname)
return err
}
func (m *UserCamelModel) FindOne(id int64) (*UserCamel, error) {
userCamelIdKey := fmt.Sprintf("cache#userCamel#id#%v", id)
var resp UserCamel
err := m.QueryRow(&resp, userCamelIdKey, func(conn sqlx.SqlConn, v interface{}) error {
query := `select ` + userCamelRows + ` from ` + m.table + ` where id = ? limit 1`
return conn.QueryRow(v, query, id)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *UserCamelModel) FindOneByName(name string) (*UserCamel, error) {
userCamelNameKey := fmt.Sprintf("cache#userCamel#name#%v", name)
var resp UserCamel
err := m.QueryRowIndex(&resp, userCamelNameKey, func(primary interface{}) string {
return fmt.Sprintf("%s%v", cacheuserCamelIdPrefix, primary)
}, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := `select ` + userCamelRows + ` from ` + m.table + ` where name = ? limit 1`
if err := conn.QueryRow(&resp, query, name); err != nil {
return nil, err
}
return resp.Id, nil
}, func(conn sqlx.SqlConn, v, primary interface{}) error {
query := `select ` + userCamelRows + ` from ` + m.table + ` where id = ? limit 1`
return conn.QueryRow(v, query, primary)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *UserCamelModel) FindOneByMobile(mobile string) (*UserCamel, error) {
userCamelMobileKey := fmt.Sprintf("cache#userCamel#mobile#%v", mobile)
var resp UserCamel
err := m.QueryRowIndex(&resp, userCamelMobileKey, func(primary interface{}) string {
return fmt.Sprintf("%s%v", cacheuserCamelIdPrefix, primary)
}, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := `select ` + userCamelRows + ` from ` + m.table + ` where mobile = ? limit 1`
if err := conn.QueryRow(&resp, query, mobile); err != nil {
return nil, err
}
return resp.Id, nil
}, func(conn sqlx.SqlConn, v, primary interface{}) error {
query := `select ` + userCamelRows + ` from ` + m.table + ` where id = ? limit 1`
return conn.QueryRow(v, query, primary)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *UserCamelModel) Update(data UserCamel) error {
userCamelIdKey := fmt.Sprintf("cache#userCamel#id#%v", data.Id)
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `update ` + m.table + ` set ` + userCamelRowsWithPlaceHolder + ` where id = ?`
return conn.Exec(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname, data.Id)
}, userCamelIdKey)
return err
}
func (m *UserCamelModel) Delete(id int64) error {
data, err := m.FindOne(id)
if err != nil {
return err
}
userCamelIdKey := fmt.Sprintf("cache#userCamel#id#%v", id)
userCamelNameKey := fmt.Sprintf("cache#userCamel#name#%v", data.Name)
userCamelMobileKey := fmt.Sprintf("cache#userCamel#mobile#%v", data.Mobile)
_, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := `delete from ` + m.table + ` where id = ?`
return conn.Exec(query, id)
}, userCamelIdKey, userCamelNameKey, userCamelMobileKey)
return err
}

View File

@@ -0,0 +1,20 @@
package test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/gen"
)
func TestGenSqlWithCache(t *testing.T) {
generator := gen.NewDefaultGenerator("./test.sql", "./modelwithcache")
err := generator.Start(true)
assert.Nil(t, err)
}
func TestGenSqlWithoutCache(t *testing.T) {
generator := gen.NewDefaultGenerator("./test.sql", "./modelwithoutcache")
err := generator.Start(false)
assert.Nil(t, err)
}

View File

@@ -0,0 +1,30 @@
-- user_snake
CREATE TABLE `user_snake` (
`id` bigint(10) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',
`password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',
`mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',
`gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开',
`nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',
`create_time` timestamp NULL DEFAULT NULL,
`update_time` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name_index` (`name`),
KEY `mobile_index` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
-- userCamel (disable AUTO_INCREMENT)
CREATE TABLE `userCamel` (
`id` bigint(10) NOT NULL,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',
`mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',
`gender` char(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开',
`nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',
`createTime` timestamp NULL DEFAULT NULL,
`updateTime` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `nameIndex` (`name`),
KEY `mobile_index` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;