package autocode import ( "fmt" "slices" "strings" "text/template" ) // AutoCodeField 自动代码字段(用于模板函数) type AutoCodeField struct { FieldName string `json:"fieldName"` FieldDesc string `json:"fieldDesc"` FieldType string `json:"fieldType"` FieldJson string `json:"fieldJson"` DataTypeLong string `json:"dataTypeLong"` Comment string `json:"comment"` ColumnName string `json:"columnName"` FieldSearchType string `json:"fieldSearchType"` FieldSearchHide bool `json:"fieldSearchHide"` DictType string `json:"dictType"` Form bool `json:"form"` Table bool `json:"table"` Desc bool `json:"desc"` Excel bool `json:"excel"` Require bool `json:"require"` DefaultValue string `json:"defaultValue"` ErrorText string `json:"errorText"` Clearable bool `json:"clearable"` Sort bool `json:"sort"` PrimaryKey bool `json:"primaryKey"` DataSource *DataSource `json:"dataSource"` CheckDataSource bool `json:"checkDataSource"` FieldIndexType string `json:"fieldIndexType"` } // DataSource 数据源 type DataSource struct { DBName string `json:"dbName"` Table string `json:"table"` Label string `json:"label"` Value string `json:"value"` Association int `json:"association"` HasDeletedAt bool `json:"hasDeletedAt"` } // CustomMethod 自定义查询方法 // 用于在 GORM Gen 生成的代码中添加自定义查询方法 type CustomMethod struct { Name string `json:"name"` // 方法名称 (如: FindByName, GetActiveUsers) Description string `json:"description"` // 方法描述 ReturnType string `json:"returnType"` // 返回类型: single(单个), list(列表), count(计数), exists(存在检查) Params []*CustomMethodParam `json:"params"` // 方法参数 Conditions []*CustomCondition `json:"conditions"` // 查询条件 OrderBy string `json:"orderBy"` // 排序字段 (如: created_at DESC) Limit int `json:"limit"` // 限制数量 (0表示不限制) } // CustomMethodParam 自定义方法参数 type CustomMethodParam struct { Name string `json:"name"` // 参数名称 Type string `json:"type"` // 参数类型 (string, int, uint, bool, time.Time等) FieldName string `json:"fieldName"` // 对应的字段名 (用于生成查询条件) } // CustomCondition 自定义查询条件 type CustomCondition struct { FieldName string `json:"fieldName"` // 字段名 Operator string `json:"operator"` // 操作符: eq, neq, gt, gte, lt, lte, like, in, between, isNull, isNotNull ParamName string `json:"paramName"` // 参数名 (对应 CustomMethodParam.Name) } // ToPascalCase 将字符串转换为 PascalCase 格式 // 支持 snake_case、kebab-case 和普通字符串 // 例如: "article_status" -> "ArticleStatus", "user-role" -> "UserRole" func ToPascalCase(s string) string { if s == "" { return s } // 将下划线和连字符替换为空格,然后使用 Title 转换 s = strings.ReplaceAll(s, "_", " ") s = strings.ReplaceAll(s, "-", " ") // 使用 strings.Title 转换每个单词首字母大写 s = strings.Title(s) // 移除空格 return strings.ReplaceAll(s, " ", "") } // GetTemplateFuncMap 返回模板函数映射 func GetTemplateFuncMap() template.FuncMap { return template.FuncMap{ "title": strings.Title, "toPascalCase": ToPascalCase, "GenerateField": GenerateField, "GenerateModelField": GenerateModelField, "GenerateSearchField": GenerateSearchField, "GenerateSearchConditions": GenerateSearchConditions, "GenerateGormGenSearchConditions": GenerateGormGenSearchConditions, "GenerateSearchFormItem": GenerateSearchFormItem, "GenerateTableColumn": GenerateTableColumn, "GenerateFormItem": GenerateFormItem, "GenerateDescriptionItem": GenerateDescriptionItem, "GenerateDefaultFormValue": GenerateDefaultFormValue, // React/TypeScript 相关函数 "GenerateTSType": GenerateTSType, "GenerateReactSearchFormItem": GenerateReactSearchFormItem, "GenerateReactTableColumn": GenerateReactTableColumn, "GenerateReactProTableColumn": GenerateReactProTableColumn, "GenerateReactFormItem": GenerateReactFormItem, "GenerateReactDescriptionItem": GenerateReactDescriptionItem, "GenerateReactDefaultValue": GenerateReactDefaultValue, // 自定义查询方法相关函数 "GenerateCustomMethodInterface": GenerateCustomMethodInterface, "GenerateCustomMethodImpl": GenerateCustomMethodImpl, } } // GenerateField 渲染Model中的字段 func GenerateField(field AutoCodeField) string { gormTag := `` if field.FieldIndexType != "" { gormTag += field.FieldIndexType + ";" } if field.PrimaryKey { gormTag += "primarykey;" } if field.DefaultValue != "" { gormTag += fmt.Sprintf("default:%s;", field.DefaultValue) } if field.Comment != "" { gormTag += fmt.Sprintf("comment:%s;", field.Comment) } gormTag += "column:" + field.ColumnName + ";" if field.DataTypeLong != "" && field.FieldType != "enum" && field.FieldType != "int" { gormTag += fmt.Sprintf("size:%s;", field.DataTypeLong) } requireTag := ` binding:"required"` + "`" var result string switch field.FieldType { case "enum": result = fmt.Sprintf(`%s string `+"`"+`json:"%s" form:"%s" gorm:"%stype:enum(%s);"`+"`", field.FieldName, field.FieldJson, field.FieldJson, gormTag, field.DataTypeLong) case "picture", "video": tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`, field.FieldJson, field.FieldJson, gormTag) result = fmt.Sprintf(`%s string `+"`"+`%s`+"`"+``, field.FieldName, tagContent) case "file", "pictures", "array": tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`, field.FieldJson, field.FieldJson, gormTag) result = fmt.Sprintf(`%s datatypes.JSON `+"`"+`%s swaggertype:"array,object"`+"`"+``, field.FieldName, tagContent) case "richtext": tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s`, field.FieldJson, field.FieldJson, gormTag) result = fmt.Sprintf(`%s *string `+"`"+`%stype:text;"`+"`"+``, field.FieldName, tagContent) case "json": tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`, field.FieldJson, field.FieldJson, gormTag) result = fmt.Sprintf(`%s datatypes.JSON `+"`"+`%s swaggertype:"object"`+"`"+``, field.FieldName, tagContent) default: tagContent := fmt.Sprintf(`json:"%s" form:"%s" gorm:"%s"`, field.FieldJson, field.FieldJson, gormTag) var fieldType string if field.FieldType == "int" { switch field.DataTypeLong { case "1", "2", "3": fieldType = "int8" case "4", "5": fieldType = "int16" case "6", "7", "8", "9", "10": fieldType = "int32" default: fieldType = "int64" } } else { fieldType = field.FieldType } result = fmt.Sprintf(`%s *%s `+"`"+`%s`+"`"+``, field.FieldName, fieldType, tagContent) } if field.Require { result = result[0:len(result)-1] + requireTag } if field.FieldDesc != "" { result += fmt.Sprintf(" //%s", field.FieldDesc) } return result } // GenerateModelField 生成 GORM Gen 风格的模型字段定义 // 用于生成与 GORM Gen 兼容的模型结构体字段 func GenerateModelField(field AutoCodeField) string { // 构建 GORM 标签 var gormParts []string // column 标签 gormParts = append(gormParts, fmt.Sprintf("column:%s", field.ColumnName)) // type 标签 - 根据字段类型生成数据库类型 dbType := getDBType(field) if dbType != "" { gormParts = append(gormParts, fmt.Sprintf("type:%s", dbType)) } // index 标签 if field.FieldIndexType != "" { gormParts = append(gormParts, field.FieldIndexType) } // default 标签 if field.DefaultValue != "" { gormParts = append(gormParts, fmt.Sprintf("default:%s", field.DefaultValue)) } // comment 标签 comment := field.Comment if comment == "" { comment = field.FieldDesc } if comment != "" { gormParts = append(gormParts, fmt.Sprintf("comment:%s", comment)) } gormTag := strings.Join(gormParts, ";") // 获取 Go 类型 goType := getModelGoType(field) // 构建完整的字段定义 var result string if comment != "" { result = fmt.Sprintf(`%s %s `+"`"+`gorm:"%s" json:"%s"`+"`"+` // %s`, field.FieldName, goType, gormTag, field.FieldJson, comment) } else { result = fmt.Sprintf(`%s %s `+"`"+`gorm:"%s" json:"%s"`+"`", field.FieldName, goType, gormTag, field.FieldJson) } return result } // getDBType 根据字段类型获取数据库类型 func getDBType(field AutoCodeField) string { switch field.FieldType { case "string": if field.DataTypeLong != "" { return fmt.Sprintf("varchar(%s)", field.DataTypeLong) } return "varchar(191)" case "int": switch field.DataTypeLong { case "1", "2", "3": return "tinyint" case "4", "5": return "smallint" case "6", "7", "8", "9", "10": return "int" default: return "bigint(20)" } case "int64": return "bigint(20)" case "uint": return "bigint(20) unsigned" case "float64": return "double" case "bool": return "tinyint(1)" case "time.Time": return "datetime(3)" case "enum": if field.DataTypeLong != "" { return fmt.Sprintf("enum(%s)", field.DataTypeLong) } return "varchar(50)" case "richtext": return "text" case "json", "file", "pictures", "array": return "json" case "picture", "video": return "varchar(500)" default: return "" } } // getModelGoType 根据字段类型获取 Go 类型(用于 GORM Gen 模型) func getModelGoType(field AutoCodeField) string { switch field.FieldType { case "string", "picture", "video": return "string" case "int": switch field.DataTypeLong { case "1", "2", "3": return "int8" case "4", "5": return "int16" case "6", "7", "8", "9", "10": return "int32" default: return "int64" } case "int64": return "int64" case "uint": return "int64" // GORM Gen 使用 int64 表示 unsigned case "float64": return "float64" case "bool": return "bool" case "time.Time": return "time.Time" case "enum": return "string" case "richtext": return "string" case "json", "file", "pictures", "array": return "datatypes.JSON" default: return "string" } } // GenerateSearchConditions 格式化搜索条件语句 func GenerateSearchConditions(fields []*AutoCodeField) string { var conditions []string for _, field := range fields { if field.FieldSearchType == "" { continue } var condition string if slices.Contains([]string{"enum", "pictures", "picture", "video", "json", "richtext", "array"}, field.FieldType) { if field.FieldType == "enum" { if field.FieldSearchType == "LIKE" { condition = fmt.Sprintf(` if info.%s != "" { db = db.Where("%s LIKE ?", "%%"+ info.%s+"%%") }`, field.FieldName, field.ColumnName, field.FieldName) } else { condition = fmt.Sprintf(` if info.%s != "" { db = db.Where("%s %s ?", info.%s) }`, field.FieldName, field.ColumnName, field.FieldSearchType, field.FieldName) } } else { condition = fmt.Sprintf(` if info.%s != "" { // TODO 数据类型为复杂类型,请根据业务需求自行实现复杂类型的查询业务 }`, field.FieldName) } } else if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" { if field.FieldType == "time.Time" { condition = fmt.Sprintf(` if len(info.%sRange) == 2 { db = db.Where("%s %s ? AND ? ", info.%sRange[0], info.%sRange[1]) }`, field.FieldName, field.ColumnName, field.FieldSearchType, field.FieldName, field.FieldName) } else { condition = fmt.Sprintf(` if info.Start%s != nil && info.End%s != nil { db = db.Where("%s %s ? AND ? ", *info.Start%s, *info.End%s) }`, field.FieldName, field.FieldName, field.ColumnName, field.FieldSearchType, field.FieldName, field.FieldName) } } else if field.FieldSearchType == "IN" || field.FieldSearchType == "NOT IN" { // IN 和 NOT IN 需要传递数组 condition = fmt.Sprintf(` if info.%s != nil && len(info.%s) > 0 { db = db.Where("%s %s ?", info.%s) }`, field.FieldName, field.FieldName, field.ColumnName, field.FieldSearchType, field.FieldName) } else { nullCheck := "info." + field.FieldName + " != nil" if field.FieldType == "string" { condition = fmt.Sprintf(` if %s && *info.%s != "" {`, nullCheck, field.FieldName) } else { condition = fmt.Sprintf(` if %s {`, nullCheck) } if field.FieldSearchType == "LIKE" { condition += fmt.Sprintf(` db = db.Where("%s LIKE ?", "%%"+ *info.%s+"%%") }`, field.ColumnName, field.FieldName) } else { condition += fmt.Sprintf(` db = db.Where("%s %s ?", *info.%s) }`, field.ColumnName, field.FieldSearchType, field.FieldName) } } conditions = append(conditions, condition) } return strings.Join(conditions, "") } // GenerateGormGenSearchConditions 生成 GORM Gen 风格的搜索条件语句 // 使用类型安全的 GORM Gen 查询语法,如 q.Where(t.FieldName.Like(...)) func GenerateGormGenSearchConditions(fields []*AutoCodeField, structName string) string { var conditions []string for _, field := range fields { if field.FieldSearchType == "" { continue } var condition string // 复杂类型处理 if slices.Contains([]string{"enum", "pictures", "picture", "video", "json", "richtext", "array"}, field.FieldType) { if field.FieldType == "enum" { if field.FieldSearchType == "LIKE" { condition = fmt.Sprintf(` if req.%s != "" { q = q.Where(t.%s.Like("%%" + req.%s + "%%")) }`, field.FieldName, field.FieldName, field.FieldName) } else { condition = fmt.Sprintf(` if req.%s != "" { q = q.Where(t.%s.Eq(req.%s)) }`, field.FieldName, field.FieldName, field.FieldName) } } else { condition = fmt.Sprintf(` if req.%s != "" { // TODO 数据类型为复杂类型,请根据业务需求自行实现复杂类型的查询业务 }`, field.FieldName) } } else if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" { // BETWEEN 查询 if field.FieldType == "time.Time" { condition = fmt.Sprintf(` if len(req.%sRange) == 2 { q = q.Where(t.%s.Between(req.%sRange[0], req.%sRange[1])) }`, field.FieldName, field.FieldName, field.FieldName, field.FieldName) } else { condition = fmt.Sprintf(` if req.Start%s != nil && req.End%s != nil { q = q.Where(t.%s.Between(*req.Start%s, *req.End%s)) }`, field.FieldName, field.FieldName, field.FieldName, field.FieldName, field.FieldName) } } else { // 普通查询 nullCheck := "req." + field.FieldName + " != nil" if field.FieldType == "string" { condition = fmt.Sprintf(` if %s && *req.%s != "" {`, nullCheck, field.FieldName) } else { condition = fmt.Sprintf(` if %s {`, nullCheck) } // 根据搜索类型生成不同的查询方法 switch field.FieldSearchType { case "LIKE": condition += fmt.Sprintf(` q = q.Where(t.%s.Like("%%" + *req.%s + "%%")) }`, field.FieldName, field.FieldName) case "=", "EQ": if field.FieldType == "string" { condition += fmt.Sprintf(` q = q.Where(t.%s.Eq(*req.%s)) }`, field.FieldName, field.FieldName) } else { condition += fmt.Sprintf(` q = q.Where(t.%s.Eq(*req.%s)) }`, field.FieldName, field.FieldName) } case ">", "GT": condition += fmt.Sprintf(` q = q.Where(t.%s.Gt(*req.%s)) }`, field.FieldName, field.FieldName) case ">=", "GTE": condition += fmt.Sprintf(` q = q.Where(t.%s.Gte(*req.%s)) }`, field.FieldName, field.FieldName) case "<", "LT": condition += fmt.Sprintf(` q = q.Where(t.%s.Lt(*req.%s)) }`, field.FieldName, field.FieldName) case "<=", "LTE": condition += fmt.Sprintf(` q = q.Where(t.%s.Lte(*req.%s)) }`, field.FieldName, field.FieldName) case "<>", "!=", "NEQ": condition += fmt.Sprintf(` q = q.Where(t.%s.Neq(*req.%s)) }`, field.FieldName, field.FieldName) case "IN": condition += fmt.Sprintf(` q = q.Where(t.%s.In(*req.%s...)) }`, field.FieldName, field.FieldName) case "NOT IN": condition += fmt.Sprintf(` q = q.Where(t.%s.NotIn(*req.%s...)) }`, field.FieldName, field.FieldName) default: // 默认使用 Eq condition += fmt.Sprintf(` q = q.Where(t.%s.Eq(*req.%s)) }`, field.FieldName, field.FieldName) } } conditions = append(conditions, condition) } return strings.Join(conditions, "") } // GenerateSearchFormItem 格式化前端搜索条件 func GenerateSearchFormItem(field AutoCodeField) string { result := fmt.Sprintf(` `, field.FieldDesc, field.FieldJson) if field.FieldType == "bool" { result += fmt.Sprintf(` `, field.FieldJson) result += ` ` result += ` ` result += ` ` } else if field.DictType != "" { multipleAttr := "" if field.FieldType == "array" { multipleAttr = "multiple " } result += fmt.Sprintf(` `, field.FieldJson, field.FieldDesc, field.DictType, field.Clearable, multipleAttr) } else if field.CheckDataSource { multipleAttr := "" if field.DataSource.Association == 2 { multipleAttr = "multiple " } result += fmt.Sprintf(` `, multipleAttr, field.FieldJson, field.FieldDesc, field.Clearable) result += fmt.Sprintf(` `, field.FieldJson) result += ` ` } else if field.FieldType == "float64" || field.FieldType == "int" { if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" { result += fmt.Sprintf(` `, field.FieldName) result += ` — ` result += fmt.Sprintf(` `, field.FieldName) } else { result += fmt.Sprintf(` `, field.FieldJson) } } else if field.FieldType == "time.Time" { if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" { result += ` ` result += fmt.Sprintf(``, field.FieldJson) } else { result += fmt.Sprintf(``, field.FieldJson) } } else { result += fmt.Sprintf(` `, field.FieldJson) } result += `` return result } // GenerateTableColumn 生成表格列HTML func GenerateTableColumn(field AutoCodeField) string { sortAttr := "" if field.Sort { sortAttr = " sortable" } if field.CheckDataSource { result := fmt.Sprintf(` `, sortAttr, field.FieldDesc, field.FieldJson) result += ` ` result += `` return result } else if field.DictType != "" { result := fmt.Sprintf(` `, sortAttr, field.FieldDesc, field.FieldJson) result += ` ` result += `` return result } else if field.FieldType == "bool" { result := fmt.Sprintf(` `, sortAttr, field.FieldDesc, field.FieldJson) result += fmt.Sprintf(` `, field.FieldJson) result += `` return result } else if field.FieldType == "time.Time" { result := fmt.Sprintf(` `, sortAttr, field.FieldDesc, field.FieldJson) result += fmt.Sprintf(` `, field.FieldJson) result += `` return result } else if field.FieldType == "picture" { result := fmt.Sprintf(` `, field.FieldDesc, field.FieldJson) result += ` ` result += `` return result } else if field.FieldType == "pictures" { result := fmt.Sprintf(` `, field.FieldDesc, field.FieldJson) result += ` ` result += `` return result } else if field.FieldType == "video" { result := fmt.Sprintf(` `, field.FieldDesc, field.FieldJson) result += ` ` result += `` return result } else if field.FieldType == "richtext" { result := fmt.Sprintf(` `, field.FieldDesc, field.FieldJson) result += ` ` result += `` return result } else if field.FieldType == "file" { result := fmt.Sprintf(` `, field.FieldDesc, field.FieldJson) result += ` ` result += `` return result } else if field.FieldType == "json" { result := fmt.Sprintf(` `, field.FieldDesc, field.FieldJson) result += ` ` result += `` return result } else if field.FieldType == "array" { result := fmt.Sprintf(` `, field.FieldDesc, field.FieldJson) result += ` ` result += `` return result } return fmt.Sprintf(` `, sortAttr, field.FieldDesc, field.FieldJson) } // GenerateFormItem 生成表单项 func GenerateFormItem(field AutoCodeField) string { result := fmt.Sprintf(` `, field.FieldDesc, field.FieldJson) if field.CheckDataSource { multipleAttr := "" if field.DataSource.Association == 2 { multipleAttr = " multiple" } result += fmt.Sprintf(` `, multipleAttr, field.FieldJson, field.FieldDesc, field.Clearable) result += fmt.Sprintf(` `, field.FieldJson) result += ` ` } else { switch field.FieldType { case "bool": result += fmt.Sprintf(` `, field.FieldJson) case "string": if field.DictType != "" { result += fmt.Sprintf(` `, field.FieldJson, field.FieldDesc, field.DictType, field.Clearable) } else { result += fmt.Sprintf(` `, field.FieldJson, field.Clearable, field.FieldDesc) } case "richtext": result += fmt.Sprintf(` `, field.FieldJson) case "json": result += fmt.Sprintf(` // 此字段为json结构,可以前端自行控制展示和数据绑定模式 需绑定json的key为 formData.%s 后端会按照json的类型进行存取 `, field.FieldJson) result += fmt.Sprintf(` {{ formData.%s }} `, field.FieldJson) case "array": if field.DictType != "" { result += fmt.Sprintf(` `, field.FieldJson, field.FieldDesc, field.Clearable) result += fmt.Sprintf(` `, field.DictType) result += ` ` } else { result += fmt.Sprintf(` `, field.FieldJson) } case "int": result += fmt.Sprintf(` `, field.FieldJson, field.Clearable, field.FieldDesc) case "time.Time": result += fmt.Sprintf(` `, field.FieldJson, field.Clearable) case "float64": result += fmt.Sprintf(` `, field.FieldJson, field.Clearable) case "enum": result += fmt.Sprintf(` `, field.FieldJson, field.FieldDesc, field.Clearable) result += fmt.Sprintf(` `, field.DataTypeLong) result += ` ` case "picture": result += fmt.Sprintf(` `, field.FieldJson) case "pictures": result += fmt.Sprintf(` `, field.FieldJson) case "video": result += fmt.Sprintf(` `, field.FieldJson) case "file": result += fmt.Sprintf(` `, field.FieldJson) } } result += `` return result } // GenerateDescriptionItem 生成描述项 func GenerateDescriptionItem(field AutoCodeField) string { result := fmt.Sprintf(` `, field.FieldDesc) if field.CheckDataSource { result += ` ` } else if field.FieldType != "picture" && field.FieldType != "pictures" && field.FieldType != "file" && field.FieldType != "array" && field.FieldType != "richtext" { result += fmt.Sprintf(` {{ detailForm.%s }} `, field.FieldJson) } else { switch field.FieldType { case "picture": result += fmt.Sprintf(` `, field.FieldJson, field.FieldJson) case "array": result += fmt.Sprintf(` `, field.FieldJson) case "pictures": result += fmt.Sprintf(` `, field.FieldJson, field.FieldJson) case "richtext": result += fmt.Sprintf(` `, field.FieldJson) case "file": result += fmt.Sprintf(`
`, field.FieldJson) result += ` ` result += ` ` result += ` {{ item.name }} ` result += ` ` result += `
` } } result += `
` return result } // GenerateDefaultFormValue 生成默认表单值 func GenerateDefaultFormValue(field AutoCodeField) string { var defaultValue string switch field.FieldType { case "bool": defaultValue = "false" case "string", "richtext": defaultValue = "''" case "int": if field.DataSource != nil { defaultValue = "undefined" } else { defaultValue = "0" } case "time.Time": defaultValue = "new Date()" case "float64": defaultValue = "0" case "picture", "video": defaultValue = "\"\"" case "pictures", "file", "array": defaultValue = "[]" case "json": defaultValue = "{}" default: defaultValue = "null" } return fmt.Sprintf(`%s: %s,`, field.FieldJson, defaultValue) } // GenerateSearchField 生成搜索字段 func GenerateSearchField(field AutoCodeField) string { if field.FieldSearchType == "" { return "" } var result string if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" { if field.FieldType == "time.Time" { result = fmt.Sprintf("%sRange []time.Time `json:\"%sRange\" form:\"%sRange[]\"`", field.FieldName, field.FieldJson, field.FieldJson) } else { startField := fmt.Sprintf("Start%s *%s `json:\"start%s\" form:\"start%s\"`", field.FieldName, field.FieldType, field.FieldName, field.FieldName) endField := fmt.Sprintf("End%s *%s `json:\"end%s\" form:\"end%s\"`", field.FieldName, field.FieldType, field.FieldName, field.FieldName) result = startField + "\n" + endField } } else if field.FieldSearchType == "IN" || field.FieldSearchType == "NOT IN" { // IN 和 NOT IN 需要数组类型 result = fmt.Sprintf("%s []%s `json:\"%s\" form:\"%s\"`", field.FieldName, field.FieldType, field.FieldJson, field.FieldJson) } else { if field.FieldType == "enum" || field.FieldType == "picture" || field.FieldType == "pictures" || field.FieldType == "video" || field.FieldType == "json" || field.FieldType == "richtext" || field.FieldType == "array" || field.FieldType == "file" { result = fmt.Sprintf("%s string `json:\"%s\" form:\"%s\"` ", field.FieldName, field.FieldJson, field.FieldJson) } else { result = fmt.Sprintf("%s *%s `json:\"%s\" form:\"%s\"` ", field.FieldName, field.FieldType, field.FieldJson, field.FieldJson) } } return result } // ==================== React/TypeScript 模板函数 ==================== // GenerateTSType Go类型转TypeScript类型 func GenerateTSType(goType string) string { typeMap := map[string]string{ "int": "number", "int8": "number", "int16": "number", "int32": "number", "int64": "number", "uint": "number", "uint8": "number", "uint16": "number", "uint32": "number", "uint64": "number", "float32": "number", "float64": "number", "string": "string", "bool": "boolean", "time.Time": "string", "datatypes.JSON": "Record", "json": "Record", "picture": "string", "pictures": "string[]", "video": "string", "file": "Array<{ name: string; url: string; uid: string }>", "array": "any[]", "richtext": "string", "enum": "string", } if tsType, ok := typeMap[goType]; ok { return tsType } // 处理指针类型 if strings.HasPrefix(goType, "*") { return GenerateTSType(goType[1:]) } return "any" } // GenerateReactSearchFormItem 生成React搜索表单项 func GenerateReactSearchFormItem(field AutoCodeField) string { var result string switch { case field.FieldType == "bool": result = fmt.Sprintf(` `, field.FieldJson, field.FieldDesc) case field.DictType != "": result = fmt.Sprintf(` `, field.FieldJson, field.FieldDesc, field.FieldDesc, field.FieldJson) case field.FieldType == "time.Time": if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" { result = fmt.Sprintf(` `, field.FieldJson, field.FieldDesc) } else { result = fmt.Sprintf(` `, field.FieldJson, field.FieldDesc) } case field.FieldType == "int" || field.FieldType == "float64": if field.FieldSearchType == "BETWEEN" || field.FieldSearchType == "NOT BETWEEN" { result = fmt.Sprintf(` `, field.FieldDesc, field.FieldName, field.FieldName) } else { result = fmt.Sprintf(` `, field.FieldJson, field.FieldDesc, field.FieldDesc) } default: result = fmt.Sprintf(` `, field.FieldJson, field.FieldDesc, field.FieldDesc) } return result } // GenerateReactTableColumn 生成React表格列配置 func GenerateReactTableColumn(field AutoCodeField) string { sortable := "" if field.Sort { sortable = "\n\t\t\tsorter: true," } switch { case field.CheckDataSource: if field.DataSource != nil && field.DataSource.Association == 2 { return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', key: '%s', width: 120,%s render: (val) => val?.map((v: any) => {dataSource['%s']?.find((d: any) => d.value === v)?.label || v}), },`, field.FieldDesc, field.FieldJson, field.FieldJson, sortable, field.FieldJson) } return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', key: '%s', width: 120,%s render: (val) => dataSource['%s']?.find((d: any) => d.value === val)?.label || val, },`, field.FieldDesc, field.FieldJson, field.FieldJson, sortable, field.FieldJson) case field.DictType != "": if field.FieldType == "array" { return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', key: '%s', width: 120,%s render: (val) => val?.map((v: any) => {%sOptions?.find((d: any) => d.value === v)?.label || v}), },`, field.FieldDesc, field.FieldJson, field.FieldJson, sortable, field.DictType) } return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', key: '%s', width: 120,%s render: (val) => %sOptions?.find((d: any) => d.value === val)?.label || val, },`, field.FieldDesc, field.FieldJson, field.FieldJson, sortable, field.DictType) case field.FieldType == "bool": return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', key: '%s', width: 80,%s render: (val) => val ? '是' : '否', },`, field.FieldDesc, field.FieldJson, field.FieldJson, sortable) case field.FieldType == "time.Time": return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', key: '%s', width: 180,%s render: (val) => val ? dayjs(val).format('YYYY-MM-DD HH:mm:ss') : '-', },`, field.FieldDesc, field.FieldJson, field.FieldJson, sortable) case field.FieldType == "picture": return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', key: '%s', width: 100, render: (val) => val ? : '-', },`, field.FieldDesc, field.FieldJson, field.FieldJson) case field.FieldType == "pictures": return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', key: '%s', width: 200, render: (val) => val?.map((url: string, i: number) => ), },`, field.FieldDesc, field.FieldJson, field.FieldJson) case field.FieldType == "file": return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', key: '%s', width: 200, render: (val) => val?.map((f: any) => {f.name}), },`, field.FieldDesc, field.FieldJson, field.FieldJson) case field.FieldType == "richtext": return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', key: '%s', width: 100, render: () => '[富文本]', },`, field.FieldDesc, field.FieldJson, field.FieldJson) case field.FieldType == "json": return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', key: '%s', width: 100, render: () => '[JSON]', },`, field.FieldDesc, field.FieldJson, field.FieldJson) default: return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', key: '%s', width: 120,%s },`, field.FieldDesc, field.FieldJson, field.FieldJson, sortable) } } // GenerateReactFormItem 生成React表单项 func GenerateReactFormItem(field AutoCodeField) string { rules := "" if field.Require { rules = fmt.Sprintf(` rules={[{ required: true, message: '%s' }]}`, field.ErrorText) } switch { case field.CheckDataSource: multiple := "" if field.DataSource != nil && field.DataSource.Association == 2 { multiple = " mode=\"multiple\"" } return fmt.Sprintf(` `, field.FieldJson, field.FieldDesc, rules, multiple, field.FieldDesc, field.FieldJson) case field.FieldType == "bool": return fmt.Sprintf(` `, field.FieldJson, field.FieldDesc, rules) case field.DictType != "": multiple := "" if field.FieldType == "array" { multiple = " mode=\"multiple\"" } return fmt.Sprintf(` `, field.FieldJson, field.FieldDesc, rules, multiple, field.FieldDesc, field.DictType) case field.FieldType == "richtext": return fmt.Sprintf(` {/* TODO: 使用富文本编辑器组件 */} `, field.FieldJson, field.FieldDesc, rules, field.FieldDesc) case field.FieldType == "json": return fmt.Sprintf(` {/* TODO: JSON编辑器 */} `, field.FieldJson, field.FieldDesc, rules) case field.FieldType == "array": return fmt.Sprintf(` {/* TODO: 根据枚举值生成选项 */} `, field.FieldJson, field.FieldDesc, rules, field.FieldDesc) case field.FieldType == "picture": return fmt.Sprintf(` {/* TODO: 图片上传组件 */} `, field.FieldJson, field.FieldDesc, rules) case field.FieldType == "pictures": return fmt.Sprintf(` {/* TODO: 多图片上传组件 */} `, field.FieldJson, field.FieldDesc, rules) case field.FieldType == "video": return fmt.Sprintf(` {/* TODO: 视频上传组件 */} `, field.FieldJson, field.FieldDesc, rules) case field.FieldType == "file": return fmt.Sprintf(` {/* TODO: 文件上传组件 */} `, field.FieldJson, field.FieldDesc, rules) default: return fmt.Sprintf(` `, field.FieldJson, field.FieldDesc, rules, field.FieldDesc) } } // GenerateReactDescriptionItem 生成React描述项 // 根据字段配置生成对应的详情展示项,支持多种字段类型 func GenerateReactDescriptionItem(field AutoCodeField) string { switch { // 数据源字段 - 从外部数据源获取显示值 case field.CheckDataSource: if field.DataSource != nil && field.DataSource.Association == 2 { // 多选数据源 - 显示为多个标签 return fmt.Sprintf(` {currentRecord?.%s?.map((v: any) => {dataSource['%s']?.find((d: any) => d.value === v)?.label || v})} `, field.FieldDesc, field.FieldJson, field.FieldJson) } // 单选数据源 - 显示标签文本 return fmt.Sprintf(` {dataSource['%s']?.find((d: any) => d.value === currentRecord?.%s)?.label || currentRecord?.%s} `, field.FieldDesc, field.FieldJson, field.FieldJson, field.FieldJson) // 字典类型字段 - 从字典获取显示值 case field.DictType != "": if field.FieldType == "array" { // 数组类型字典 - 显示为多个标签 return fmt.Sprintf(` {currentRecord?.%s?.map((v: any) => {get%sLabel(v)})} `, field.FieldDesc, field.FieldJson, ToPascalCase(field.DictType)) } // 单值字典 - 显示字典标签 return fmt.Sprintf(` {get%sLabel(currentRecord?.%s)} `, field.FieldDesc, ToPascalCase(field.DictType), field.FieldJson) // 布尔类型 - 显示是/否 case field.FieldType == "bool": return fmt.Sprintf(`{currentRecord?.%s ? '是' : '否'}`, field.FieldDesc, field.FieldJson) // 时间类型 - 格式化显示 case field.FieldType == "time.Time": return fmt.Sprintf(` {currentRecord?.%s ? dayjs(currentRecord.%s).format('YYYY-MM-DD HH:mm:ss') : '-'} `, field.FieldDesc, field.FieldJson, field.FieldJson) // 单图片类型 - 显示图片预览 case field.FieldType == "picture": return fmt.Sprintf(` {currentRecord?.%s && } `, field.FieldDesc, field.FieldJson, field.FieldJson) // 多图片类型 - 显示图片列表 case field.FieldType == "pictures": return fmt.Sprintf(` {currentRecord?.%s?.map((url: string, i: number) => )} `, field.FieldDesc, field.FieldJson) // 视频类型 - 显示视频播放器 case field.FieldType == "video": return fmt.Sprintf(` {currentRecord?.%s && ( )} `, field.FieldDesc, field.FieldJson, field.FieldJson) // 文件类型 - 显示文件列表带下载 case field.FieldType == "file": return fmt.Sprintf(` {currentRecord?.%s?.map((f: any, i: number) => ( {f.name} ))} `, field.FieldDesc, field.FieldJson) // 富文本类型 - 渲染HTML内容 case field.FieldType == "richtext": return fmt.Sprintf(`
`, field.FieldDesc, field.FieldJson) // JSON类型 - 格式化显示JSON case field.FieldType == "json": return fmt.Sprintf(`
{JSON.stringify(currentRecord?.%s, null, 2)}
`, field.FieldDesc, field.FieldJson) // 数组类型(无字典) - 显示为标签列表 case field.FieldType == "array": return fmt.Sprintf(` {currentRecord?.%s?.map((v: any, i: number) => {v})} `, field.FieldDesc, field.FieldJson) // 枚举类型 - 直接显示值 case field.FieldType == "enum": return fmt.Sprintf(`{currentRecord?.%s}`, field.FieldDesc, field.FieldJson) // 整数类型 - 显示数字 case field.FieldType == "int": return fmt.Sprintf(`{currentRecord?.%s ?? '-'}`, field.FieldDesc, field.FieldJson) // 浮点数类型 - 保留两位小数 case field.FieldType == "float64": return fmt.Sprintf(`{currentRecord?.%s !== undefined ? currentRecord.%s.toFixed(2) : '-'}`, field.FieldDesc, field.FieldJson, field.FieldJson) // 默认类型 - 直接显示值 default: return fmt.Sprintf(`{currentRecord?.%s ?? '-'}`, field.FieldDesc, field.FieldJson) } } // GenerateReactDefaultValue 生成React表单默认值 func GenerateReactDefaultValue(field AutoCodeField) string { switch field.FieldType { case "bool": return fmt.Sprintf(`%s: false,`, field.FieldJson) case "string", "richtext", "picture", "video": return fmt.Sprintf(`%s: '',`, field.FieldJson) case "int", "float64": if field.DataSource != nil { return fmt.Sprintf(`%s: undefined,`, field.FieldJson) } return fmt.Sprintf(`%s: 0,`, field.FieldJson) case "time.Time": return fmt.Sprintf(`%s: undefined,`, field.FieldJson) case "pictures", "file", "array": return fmt.Sprintf(`%s: [],`, field.FieldJson) case "json": return fmt.Sprintf(`%s: {},`, field.FieldJson) default: return fmt.Sprintf(`%s: undefined,`, field.FieldJson) } } // GenerateReactProTableColumn 生成 Ant Design Pro Table 列配置 func GenerateReactProTableColumn(field AutoCodeField) string { sorter := "" if field.Sort { sorter = "\n\t\t\tsorter: true," } // 搜索配置 hideInSearch := "" if field.FieldSearchType == "" { hideInSearch = "\n\t\t\thideInSearch: true," } switch { case field.CheckDataSource: valueType := "select" if field.DataSource != nil && field.DataSource.Association == 2 { return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', width: 150,%s%s valueType: '%s', fieldProps: { mode: 'multiple', options: dataSource['%s'], }, render: (_, record) => record.%s?.map((v: any) => ( {dataSource['%s']?.find((d: any) => d.value === v)?.label || v} )), },`, field.FieldDesc, field.FieldJson, sorter, hideInSearch, valueType, field.FieldJson, field.FieldJson, field.FieldJson) } return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', width: 150,%s%s valueType: '%s', fieldProps: { options: dataSource['%s'], }, render: (_, record) => dataSource['%s']?.find((d: any) => d.value === record.%s)?.label || record.%s, },`, field.FieldDesc, field.FieldJson, sorter, hideInSearch, valueType, field.FieldJson, field.FieldJson, field.FieldJson, field.FieldJson) case field.DictType != "": valueType := "select" if field.FieldType == "array" { return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', width: 150,%s%s valueType: '%s', fieldProps: { mode: 'multiple', options: %sOptions, }, render: (_, record) => record.%s?.map((v: any) => ( {get%sLabel(v)} )), },`, field.FieldDesc, field.FieldJson, sorter, hideInSearch, valueType, field.DictType, field.FieldJson, ToPascalCase(field.DictType)) } return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', width: 150,%s%s valueType: '%s', fieldProps: { options: %sOptions, }, render: (_, record) => get%sLabel(record.%s), },`, field.FieldDesc, field.FieldJson, sorter, hideInSearch, valueType, field.DictType, ToPascalCase(field.DictType), field.FieldJson) case field.FieldType == "bool": return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', width: 80,%s%s valueType: 'select', valueEnum: { true: { text: '是', status: 'Success' }, false: { text: '否', status: 'Default' }, }, },`, field.FieldDesc, field.FieldJson, sorter, hideInSearch) case field.FieldType == "time.Time": searchType := "" if field.FieldSearchType == "BETWEEN" { searchType = "\n\t\t\tvalueType: 'dateTimeRange'," } else { searchType = "\n\t\t\tvalueType: 'dateTime'," } return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', width: 180,%s%s%s },`, field.FieldDesc, field.FieldJson, sorter, hideInSearch, searchType) case field.FieldType == "picture": return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', width: 100, hideInSearch: true, render: (_, record) => record.%s ? : '-', },`, field.FieldDesc, field.FieldJson, field.FieldJson, field.FieldJson) case field.FieldType == "pictures": return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', width: 200, hideInSearch: true, render: (_, record) => record.%s?.map((url: string, i: number) => ( )), },`, field.FieldDesc, field.FieldJson, field.FieldJson) case field.FieldType == "file": return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', width: 200, hideInSearch: true, render: (_, record) => record.%s?.map((f: any) => {f.name}), },`, field.FieldDesc, field.FieldJson, field.FieldJson) case field.FieldType == "richtext": return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', width: 100, hideInSearch: true, render: () => [富文本], },`, field.FieldDesc, field.FieldJson) case field.FieldType == "json": return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', width: 100, hideInSearch: true, render: () => [JSON], },`, field.FieldDesc, field.FieldJson) case field.FieldType == "int", field.FieldType == "float64": valueType := "digit" if field.FieldSearchType == "BETWEEN" { valueType = "digitRange" } return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', width: 120,%s%s valueType: '%s', },`, field.FieldDesc, field.FieldJson, sorter, hideInSearch, valueType) default: return fmt.Sprintf(`{ title: '%s', dataIndex: '%s', width: 150,%s%s ellipsis: true, },`, field.FieldDesc, field.FieldJson, sorter, hideInSearch) } } // ==================== 自定义查询方法模板函数 ==================== // GenerateCustomMethodInterface 生成自定义方法的接口定义 // 用于 biz 层 Repository 接口 func GenerateCustomMethodInterface(methods []*CustomMethod, structName, pkgName string) string { if len(methods) == 0 { return "" } var builder strings.Builder builder.WriteString("\n\t// 自定义查询方法\n") for _, method := range methods { // 生成方法签名 params := generateMethodParams(method.Params) returnType := generateReturnType(method.ReturnType, structName, pkgName) comment := method.Description if comment == "" { comment = method.Name } builder.WriteString(fmt.Sprintf("\t// %s %s\n", method.Name, comment)) builder.WriteString(fmt.Sprintf("\t%s(ctx context.Context%s) %s\n", method.Name, params, returnType)) } return builder.String() } // GenerateCustomMethodImpl 生成自定义方法的实现 // 用于 data 层 Repository 实现 func GenerateCustomMethodImpl(methods []*CustomMethod, structName, pkgName, abbreviation string) string { if len(methods) == 0 { return "" } var builder strings.Builder for _, method := range methods { // 生成方法签名 params := generateMethodParams(method.Params) returnType := generateReturnType(method.ReturnType, structName, pkgName) comment := method.Description if comment == "" { comment = method.Name } builder.WriteString(fmt.Sprintf("\n// %s %s\n", method.Name, comment)) builder.WriteString(fmt.Sprintf("func (r *%sRepo) %s(ctx context.Context%s) %s {\n", abbreviation, method.Name, params, returnType)) // 生成方法体 builder.WriteString(fmt.Sprintf("\tt := query.%s\n", structName)) builder.WriteString("\tq := t.WithContext(ctx)\n\n") // 生成查询条件 conditions := generateConditions(method.Conditions, method.Params) if conditions != "" { builder.WriteString(conditions) } // 生成排序 if method.OrderBy != "" { builder.WriteString(generateOrderBy(method.OrderBy)) } // 生成限制 if method.Limit > 0 { builder.WriteString(fmt.Sprintf("\tq = q.Limit(%d)\n", method.Limit)) } // 生成返回语句 builder.WriteString(generateReturnStatement(method.ReturnType, structName, pkgName)) builder.WriteString("}\n") } return builder.String() } // generateMethodParams 生成方法参数列表 func generateMethodParams(params []*CustomMethodParam) string { if len(params) == 0 { return "" } var parts []string for _, p := range params { parts = append(parts, fmt.Sprintf("%s %s", p.Name, p.Type)) } return ", " + strings.Join(parts, ", ") } // generateReturnType 生成返回类型 func generateReturnType(returnType, structName, pkgName string) string { switch returnType { case "single": return fmt.Sprintf("(*%s.%s, error)", pkgName, structName) case "list": return fmt.Sprintf("([]*%s.%s, error)", pkgName, structName) case "count": return "(int64, error)" case "exists": return "(bool, error)" default: return fmt.Sprintf("(*%s.%s, error)", pkgName, structName) } } // generateConditions 生成查询条件 func generateConditions(conditions []*CustomCondition, params []*CustomMethodParam) string { if len(conditions) == 0 { return "" } var builder strings.Builder builder.WriteString("\t// 查询条件\n") for _, cond := range conditions { // 查找对应的参数类型 var paramType string for _, p := range params { if p.Name == cond.ParamName { paramType = p.Type break } } condStr := generateSingleCondition(cond, paramType) if condStr != "" { builder.WriteString(condStr) } } return builder.String() } // generateSingleCondition 生成单个查询条件 func generateSingleCondition(cond *CustomCondition, paramType string) string { fieldName := cond.FieldName paramName := cond.ParamName switch cond.Operator { case "eq", "=": return fmt.Sprintf("\tq = q.Where(t.%s.Eq(%s))\n", fieldName, paramName) case "neq", "!=", "<>": return fmt.Sprintf("\tq = q.Where(t.%s.Neq(%s))\n", fieldName, paramName) case "gt", ">": return fmt.Sprintf("\tq = q.Where(t.%s.Gt(%s))\n", fieldName, paramName) case "gte", ">=": return fmt.Sprintf("\tq = q.Where(t.%s.Gte(%s))\n", fieldName, paramName) case "lt", "<": return fmt.Sprintf("\tq = q.Where(t.%s.Lt(%s))\n", fieldName, paramName) case "lte", "<=": return fmt.Sprintf("\tq = q.Where(t.%s.Lte(%s))\n", fieldName, paramName) case "like": return fmt.Sprintf("\tq = q.Where(t.%s.Like(\"%%"+"\" + %s + \""+"%%\"))\n", fieldName, paramName) case "in": return fmt.Sprintf("\tq = q.Where(t.%s.In(%s...))\n", fieldName, paramName) case "notIn": return fmt.Sprintf("\tq = q.Where(t.%s.NotIn(%s...))\n", fieldName, paramName) case "between": // between 需要两个参数,假设参数名为 paramName + "Start" 和 paramName + "End" return fmt.Sprintf("\tq = q.Where(t.%s.Between(%sStart, %sEnd))\n", fieldName, paramName, paramName) case "isNull": return fmt.Sprintf("\tq = q.Where(t.%s.IsNull())\n", fieldName) case "isNotNull": return fmt.Sprintf("\tq = q.Where(t.%s.IsNotNull())\n", fieldName) default: return fmt.Sprintf("\tq = q.Where(t.%s.Eq(%s))\n", fieldName, paramName) } } // generateOrderBy 生成排序语句 func generateOrderBy(orderBy string) string { if orderBy == "" { return "" } // 解析排序字段,格式: "field_name DESC" 或 "field_name ASC" 或 "FieldName" parts := strings.Fields(orderBy) if len(parts) == 0 { return "" } fieldName := ToPascalCase(parts[0]) direction := "Asc" if len(parts) > 1 && strings.ToUpper(parts[1]) == "DESC" { direction = "Desc" } return fmt.Sprintf("\tq = q.Order(t.%s.%s())\n", fieldName, direction) } // generateReturnStatement 生成返回语句 func generateReturnStatement(returnType, structName, pkgName string) string { switch returnType { case "single": return fmt.Sprintf(` m, err := q.First() if err != nil { return nil, err } return toBiz%s(m), nil `, structName) case "list": return fmt.Sprintf(` models, err := q.Find() if err != nil { return nil, err } result := make([]*%s.%s, len(models)) for i, m := range models { result[i] = toBiz%s(m) } return result, nil `, pkgName, structName, structName) case "count": return ` count, err := q.Count() return count, err ` case "exists": return ` count, err := q.Count() if err != nil { return false, err } return count > 0, nil ` default: return fmt.Sprintf(` m, err := q.First() if err != nil { return nil, err } return toBiz%s(m), nil `, structName) } }