内容来源于《Go语言编程之旅》
手动修改字符串的命名格式太累且效率太低,不如写个自动化小工具吧
shellgo get -u github.com/spf13/cobra@v1.0.0
│ go.mod │ go.sum │ main.go │ ├─cmd │ root.go 根命令 │ word.go 用于单词格式转换的子命令 word 的设置 │ ├─internal │ └─word │ word.go 具体的功能代码 │ └─pkg
常见的字符串命名格式共五种,转换也限于这五种的转换
相关的功能代码写入internal\word\word.go
中
├─internal │ └─word │ word.go 具体的功能代码
go// 大小写转换
func ToUpper(s string) string {
return strings.ToUpper(s)
}
func ToLower(s string) string {
return strings.ToLower(s)
}
go// 下划线转大写驼峰
func UnderscoreToUpperCamelCase(s string) string {
s = strings.Replace(s, "_", " ", -1) // 先将下划线替换为空格
s = strings.Title(s) // 将所有字符修改为对应的首字母大写格式
return strings.Replace(s, " ", "", -1) // 将空格去掉并返回
}
go// 下划线转小写驼峰
func UnderscoreToLowerCamelCase(s string) string {
s = UnderscoreToUpperCamelCase(s) // 先转换为大写驼峰
return string(unicode.ToLower((rune(s[0])))) + s[1:] // 将第一个字母修改为小写
}
go// 驼峰转下划线
func CamelCaseToUnderscore(s string) string {
var output []rune
for i, r := range s {
if i == 0 { // 如果是第一个字母,变为小写并继续遍历
output = append(output, unicode.ToLower(r))
continue
}
if unicode.IsUpper(r) { // 遇到大写字母,说明遇到驼峰,需要加入下划线
output = append(output, '_')
}
output = append(output, unicode.ToLower(r)) // 将大写字母变为小写
}
return string(output) // 转换为string并返回
}
将上述方法集成到Command中
相关的功能代码写入cmd\word.go
和cmd\root.go
中
├─cmd │ root.go 根命令 │ word.go 用于单词格式转换的子命令 word 的设置
goconst (
ModeUpper = iota + 1 // 全部转大写,分别为1,2,3,4,5
ModeLower // 全部转小写
ModeUnderscoreToUpperCamelCase // 下划线转大写驼峰
ModeUnderscoreToLowerCamelCase // 下划线转小写驼峰
ModeCamelCaseToUnderscore // 驼峰转下划线
)
var desc = strings.Join([]string{ // 命令提示语句
"该子命令支持各种单词格式转换,模式如下:",
"1:全部转大写",
"2:全部转小写",
"3:下划线转大写驼峰",
"4:下划线转小写驼峰",
"5:驼峰转下划线",
}, "\n")
var str string
var mode int8
var wordCmd = &cobra.Command{ // word命令
Use: "word", // 子命令的命令标识
Short: "单词格式转换", // 子命令的简短说明
Long: desc, // 子命令的完整说明
Run: func(cmd *cobra.Command, args []string) { // 命令的逻辑
var content string
switch mode { // 根据不同的模式,选择对应的功能代码
case ModeUpper:
content = word.ToUpper(str)
case ModeLower:
content = word.ToLower(str)
case ModeUnderscoreToUpperCamelCase:
content = word.UnderscoreToUpperCamelCase(str)
case ModeUnderscoreToLowerCamelCase:
content = word.UnderscoreToLowerCamelCase(str)
case ModeCamelCaseToUnderscore:
content = word.CamelCaseToUnderscore(str)
default:
log.Fatalf("暂不支持该转换模式,请执行 help word 查看帮助文档")
}
log.Printf("输出结果: %s", content)
},
}
func init() { // 获取命令行参数,给str和mode赋值
wordCmd.Flags().StringVarP(&str, "str", "s", "", "请输入单词内容")
wordCmd.Flags().Int8VarP(&mode, "mode", "m", 0, "请输入单词转换的模式")
}
govar rootCmd = &cobra.Command{}
func Execute() error { // 执行
return rootCmd.Execute()
}
func init() {
rootCmd.AddCommand(wordCmd) // 注册wordCmd
}
shellgo run main.go help word 该子命令支持各种单词格式转换,模式如下: 2:全部转小写 3:下划线转大写驼峰 4:下划线转小写驼峰 5:驼峰转下划线 Usage: word [flags] Flags: -m, --mode int8 请输入单词转换的模式 -s, --str string 请输入单词内容 go run main.go word -s=southyang -m=1 输出结果: SOUTHYANG go run main.go word -s=south_yang -m=2 输出结果: south_yang go run main.go word -s=south_yang -m=3 输出结果: SouthYang go run main.go word -s=south_yang -m=4 输出结果: southYang go run main.go word -s=SouthYang -m=5 输出结果: south_yang
├─cmd │ root.go │ time.go 时间工具子命令 time 的设置 │ word.go │ ├─internal │ ├─timer │ │ time.go 时间工具的逻辑代码 │ │ │ └─word │ word.go
gofunc GetNowTime() time.Time { // 获取当前时间
location, _ := time.LoadLocation("Asia/Shanghai") // 设置时区
return time.Now().In(location)
}
func GetCalculateTime(currentTimer time.Time, d string) (time.Time, error) { // 时间推算
duration, err := time.ParseDuration(d)
if err != nil {
return time.Time{}, err
}
return currentTimer.Add(duration), nil
}
govar descTime = strings.Join([]string{
"该命令的子命令支持各种时间工具",
"now:获取当前时间",
"calc:计算所需时间",
}, "\n")
var calculateTime string
var duration string
// 定义两种时间格式
const TimeLayout = "2006-01-02"
const TimeLayout1 = "2006-01-02 15:04:05"
const LocationTime = "Asia/Shanghai"
var timeCmd = &cobra.Command{
Use: "time",
Short: "时间格式处理",
Long: descTime,
Run: func(cmd *cobra.Command, args []string) {},
}
var nowTimeCmd = &cobra.Command{ // 输出两种时间格式
Use: "now",
Short: "获取当前时间",
Long: "获取当前时间",
Run: func(cmd *cobra.Command, args []string) {
nowTime := timer.GetNowTime()
log.Printf("输出结果: %s, %d", nowTime.Format("2006-01-02 15:04:05"), nowTime.Unix())
},
}
var calculateTimeCmd = &cobra.Command{
Use: "calc",
Short: "计算所需时间",
Long: "计算所需时间",
Run: func(cmd *cobra.Command, args []string) {
var currentTimer time.Time
location, _ := time.LoadLocation(LocationTime)
var layout = "2006-01-02 15:04:05"
if calculateTime == "" {
currentTimer = timer.GetNowTime()
} else {
var err error
space := strings.Count(calculateTime, " ")
if space == 0 { // 判断时间格式
layout = TimeLayout
}
if space == 1 {
layout = TimeLayout1
}
currentTimer, err = time.ParseInLocation(layout, calculateTime, location)
if err != nil { // 如果出现错误,则按照时间戳的格式处理
t, _ := strconv.Atoi(calculateTime)
currentTimer = time.Unix(int64(t), 0)
}
}
t, err := timer.GetCalculateTime(currentTimer, duration)
if err != nil {
log.Fatalf("timer.GetCalculateTime err: %v", err)
}
log.Printf("输出结果: %s, %d", t.Format(layout), t.Unix())
},
}
func init() {
timeCmd.AddCommand(nowTimeCmd) // 注册两个子命令
timeCmd.AddCommand(calculateTimeCmd)
// 获取命令行参数,给calculate,duration赋值
calculateTimeCmd.Flags().StringVarP(&calculateTime, "calculate", "c", "", ` 需要计算的时间,有效单位为时间戳或已格式化后的时间 `)
calculateTimeCmd.Flags().StringVarP(&duration, "duration", "d", "", ` 持续时间,有效时间单位为"ns", "us" (or "µ s"), "ms", "s", "m", "h"`)
}
在根命令中注册time
子命令
govar rootCmd = &cobra.Command{}
func Execute() error {
return rootCmd.Execute()
}
func init() {
rootCmd.AddCommand(wordCmd)
rootCmd.AddCommand(timeCmd) // 新增
}
shellgo run .\main.go help time 该命令的子命令支持各种时间工具 now:获取当前时间 calc:计算所需时间 Usage: time [command] Available Commands: now 获取当前时间 Flags: -h, --help help for time Use " time [command] --help" for more information about a command. go run main.go time now 输出结果: 2022-05-20 12:04:05, 1653019445 go run main.go time calc -c="2029-09-04 12:02:33" -d=-2h 输出结果: 2029-09-04 10:02:33, 1883210553
代码运行的环境不一样,可能会根据时区的不同出现各种问题
我们可以根据名称获取特定时区的 Location 实例
golocation, _ := time.LoadLocation("Asia/Shanghai")
对于Parse()
方法,该方法会尝试在入参的参数中中分析并读取时区信息,但是如果入参的参数没有指定时区信息的话,那么就会默认使用 UTC
时间
我们可以使用ParseInLocation()
避免这个问题
got, _ := time.ParseInLocation(layout, inputTime, location)
dateTime := time.Unix(t.Unix(), 0).In(location).Format(layout)
官方的例子如下
Jan 2 15:04:05 2006 MST 1 2 3 4 5 6 -7
我们可以将2006-01-02 15:04:05记忆为2006 年 1 月 2 日 3 点 4 分 5 秒
当我们添加新的数据表时,有时候需要添加模型结构,也就是需要我们写Model结构体,如果是手写效率太低了
我们可以通过自己实现小工具来实现数据库表到Go结构体的转换
(如果是云数据库应该会支持建表语句导出,应该有类似的转换工具可以找一找)
├─cmd 子命令集 │ root.go 根命令 │ sql.go sql struct : sql转结构体 │ time.go time now/calc : 求当前时间和所需要的时间 │ word.go word : 字符串格式转换 ├─internal 逻辑函数 │ ├─sql2struct │ │ mysql.go │ │ template.go
gotype DBModel struct {
DBEngine *sql.DB
DBInfo *DBInfo
}
type DBInfo struct {
DBType string
Host string
UserName string
Password string
Charset string
}
type TableColumn struct {
ColumnName string
DataType string
IsNullable string
ColumnKey string
ColumnType string
ColumnComment string
}
var DBTypeToStructType = map[string]string{
"int": "int32",
"tinyint": "int8",
"smallint": "int",
"mediumint": "int64",
"bigint": "int64",
"bit": "int",
"bool": "bool",
"enum": "string",
"set": "string",
"varchar": "string",
"char": "string",
"tinytext": "string",
"mediumtext": "string",
"text": "string",
"longtext": "string",
"blob": "string",
"tinyblob": "string",
"mediumblob": "string",
"longblob": "string",
"date": "time.Time",
"datetime": "time.Time",
"timestamp": "time.Time",
"time": "time.Time",
"float": "float64",
"double": "float64",
}
func NewDBModel(info *DBInfo) *DBModel {
return &DBModel{DBInfo: info}
}
func (m *DBModel) Connect() error {
var err error
s := "%s:%s@tcp(%s)/information_schema?" +
"charset=%s&parseTime=True&loc=Local"
dsn := fmt.Sprintf(
s,
m.DBInfo.UserName,
m.DBInfo.Password,
m.DBInfo.Host,
m.DBInfo.Charset,
)
m.DBEngine, err = sql.Open(m.DBInfo.DBType, dsn)
if err != nil {
return err
}
return nil
}
func (m *DBModel) GetColumns(dbName, tableName string) ([]*TableColumn, error) {
query := "SELECT COLUMN_NAME, DATA_TYPE, COLUMN_KEY, " +
"IS_NULLABLE, COLUMN_TYPE, COLUMN_COMMENT " +
"FROM COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? "
rows, err := m.DBEngine.Query(query, dbName, tableName)
if err != nil {
return nil, err
}
if rows == nil {
return nil, errors.New("没有数据")
}
defer rows.Close()
var columns []*TableColumn
for rows.Next() {
var column TableColumn
err := rows.Scan(&column.ColumnName, &column.DataType, &column.ColumnKey, &column.IsNullable, &column.ColumnType, &column.ColumnComment)
if err != nil {
return nil, err
}
columns = append(columns, &column)
}
return columns, nil
}
goconst strcutTpl = `type {{.TableName | ToCamelCase}} struct {
{{range .Columns}} {{ $length := len .Comment}} {{ if gt $length 0 }}// {{.Comment}} {{else}}// {{.Name}} {{ end }}
{{ $typeLen := len .Type }} {{ if gt $typeLen 0 }}{{.Name | ToCamelCase}} {{.Type}} {{.Tag}}{{ else }}{{.Name}}{{ end }}
{{end}}}
func (model {{.TableName | ToCamelCase}}) TableName() string {
return "{{.TableName}}"
}`
type StructTemplate struct {
strcutTpl string
}
type StructColumn struct {
Name string
Type string
Tag string
Comment string
}
type StructTemplateDB struct {
TableName string
Columns []*StructColumn
}
func NewStructTemplate() *StructTemplate {
return &StructTemplate{strcutTpl: strcutTpl}
}
func (t *StructTemplate) AssemblyColumns(tbColumns []*TableColumn) []*StructColumn {
tplColumns := make([]*StructColumn, 0, len(tbColumns))
for _, column := range tbColumns {
tag := fmt.Sprintf("`"+"json:"+"\"%s\""+"`", column.ColumnName)
tplColumns = append(tplColumns, &StructColumn{
Name: column.ColumnName,
Type: DBTypeToStructType[column.DataType],
Tag: tag,
Comment: column.ColumnComment,
})
}
return tplColumns
}
func (t *StructTemplate) Generate(tableName string, tplColumns []*StructColumn) error {
tpl := template.Must(template.New("sql2struct").Funcs(template.FuncMap{
"ToCamelCase": word.UnderscoreToUpperCamelCase,
}).Parse(t.strcutTpl))
tplDB := StructTemplateDB{
TableName: tableName,
Columns: tplColumns,
}
err := tpl.Execute(os.Stdout, tplDB)
if err != nil {
return err
}
return nil
}
govar username string
var password string
var host string
var charset string
var dbType string
var dbName string
var tableName string
var sqlCmd = &cobra.Command{
Use: "sql",
Short: "sql转换和处理",
Long: "sql转换和处理",
Run: func(cmd *cobra.Command, args []string) {},
}
var sql2structCmd = &cobra.Command{
Use: "struct",
Short: "sql转换",
Long: "sql转换",
Run: func(cmd *cobra.Command, args []string) {
dbInfo := &sql2struct.DBInfo{
DBType: dbType,
Host: host,
UserName: username,
Password: password,
Charset: charset,
}
dbModel := sql2struct.NewDBModel(dbInfo)
err := dbModel.Connect()
if err != nil {
log.Fatalf("dbModel.Connect err: %v", err)
}
columns, err := dbModel.GetColumns(dbName, tableName)
if err != nil {
log.Fatalf("dbModel.GetColumns err: %v", err)
}
template := sql2struct.NewStructTemplate()
templateColumns := template.AssemblyColumns(columns)
err = template.Generate(tableName, templateColumns)
if err != nil {
log.Fatalf("template.Generate err: %v", err)
}
},
}
func init() {
sqlCmd.AddCommand(sql2structCmd)
sql2structCmd.Flags().StringVarP(&username, "username", "", "", "请输入数据库的账号")
sql2structCmd.Flags().StringVarP(&password, "password", "", "", "请输入数据库的密码")
sql2structCmd.Flags().StringVarP(&host, "host", "", "", "请输入数据库的HOST")
sql2structCmd.Flags().StringVarP(&charset, "charset", "", "utf8mb4", "请输入数据库的编码")
sql2structCmd.Flags().StringVarP(&dbType, "type", "", "mysql", "请输入数据库实例类型")
sql2structCmd.Flags().StringVarP(&dbName, "db", "", "", "请输入数据库名称")
sql2structCmd.Flags().StringVarP(&tableName, "table", "", "", "请输入表名称")
}
govar rootCmd = &cobra.Command{}
func Execute() error {
return rootCmd.Execute()
}
func init() {
rootCmd.AddCommand(wordCmd)
rootCmd.AddCommand(timeCmd)
rootCmd.AddCommand(sqlCmd)
}
go run main.go sql struct --username 用户名 --password 密码 --host 主机地址 --db 数据库名 --table 数据表名 type GoUser struct { Phone string `json:"phone"` // uid Uid int64 `json:"uid"` // username Username string `json:"username"` } func (model GoUser) TableName() string { return "go_user" }
有时候我们在写代码的时候会遇到极为庞大的json数据,而如果我们要对其中的数据做出修改,就必须建立对应的结构体
在我写Go语言项目(CSUAPI)的时候就遇到了这个问题,庞大的图书馆座位数据需要处理,但结构体太复杂,指望我手动写结构体效率太低且无意义,所以我,一股脑全发给前端了hh
如果要优化那个项目的话,我想这次我可以用这个小工具来构建结构体,从而对数据做一些处理了
├─cmd 子命令集 │ json.go json struct : json转结构体 │ root.go 根命令 │ sql.go sql struct : sql转结构体 │ time.go time now/calc : 求当前时间和所需要的时间 │ word.go word : 字符串格式转换 ├─internal 逻辑函数 │ ├─json2struct │ │ fields.go │ │ parser.go
goconst (
TYPE_MAP_STRING_INTERFACE = "map[string]interface {}"
TYPE_INTERFACE = "[]interface {}"
)
type Parser struct {
Source map[string]interface{}
Output Output
Children Output
StructTag string
StructName string
}
type Output []string
func (o *Output) appendSegment(format, title string, args ...interface{}) {
s := []interface{}{}
s = append(s, word.UnderscoreToUpperCamelCase(title))
if len(args) != 0 {
s = append(s, args...)
format = " " + format
}
*o = append(*o, fmt.Sprintf(format, s...))
}
func (o *Output) appendSuffix() {
*o = append(*o, "}\n")
}
func NewParser(s string) (*Parser, error) {
var source map[string]interface{}
err := json.Unmarshal([]byte(s), &source)
if err != nil {
return nil, err
}
return &Parser{
Source: source,
StructTag: "type %s struct {",
StructName: "JsonStruct",
}, nil
}
func (p *Parser) Json2Struct() string {
p.Output.appendSegment(p.StructTag, p.StructName)
for parentName, parentValues := range p.Source {
valueType := reflect.TypeOf(parentValues).String()
if valueType == TYPE_INTERFACE {
p.toParentList(parentName, parentValues.([]interface{}), true)
} else {
var fields Fields
fields.appendSegment(parentName, FieldSegment{
Format: "%s",
FieldValues: []FieldValue{
{CamelCase: false, Value: valueType},
},
})
p.Output.appendSegment("%s %s", fields[0].Name, fields[0].Type)
}
}
p.Output.appendSuffix()
return strings.Join(append(p.Output, p.Children...), "\n")
}
func (p *Parser) toChildrenStruct(parentName string, values map[string]interface{}) {
p.Children.appendSegment(p.StructTag, parentName)
for fieldName, fieldValue := range values {
p.Children.appendSegment("%s %s", fieldName, reflect.TypeOf(fieldValue).String())
}
p.Children.appendSuffix()
}
func (p *Parser) toParentList(parentName string, parentValues []interface{}, isTop bool) {
var fields Fields
for _, v := range parentValues {
valueType := reflect.TypeOf(v).String()
if valueType == TYPE_MAP_STRING_INTERFACE {
fields = append(fields, p.handleParentTypeMapIface(v.(map[string]interface{}))...)
p.Children.appendSegment(p.StructTag, parentName)
for _, field := range fields.removeDuplicate() {
p.Children.appendSegment("%s %s", field.Name, field.Type)
}
p.Children.appendSuffix()
if isTop {
valueType = word.UnderscoreToUpperCamelCase(parentName)
}
}
if isTop {
p.Output.appendSegment("%s %s%s", parentName, "[]", valueType)
}
break
}
}
func (p *Parser) handleParentTypeMapIface(values map[string]interface{}) Fields {
var fields Fields
for fieldName, fieldValues := range values {
var fieldValueType = reflect.TypeOf(fieldValues).String()
var fieldSegment = FieldSegment{
Format: "%s",
FieldValues: []FieldValue{{CamelCase: true, Value: fieldValueType}},
}
switch fieldValueType {
case TYPE_INTERFACE:
fieldSegment = p.handleTypeIface(fieldName, fieldValues.([]interface{}))
case TYPE_MAP_STRING_INTERFACE:
fieldSegment = p.handleTypeMapIface(fieldName, fieldValues.(map[string]interface{}))
}
fields.appendSegment(fieldName, fieldSegment)
}
return fields
}
func (p *Parser) handleTypeIface(fieldName string, fieldValues []interface{}) FieldSegment {
fieldSegment := FieldSegment{
Format: "%s%s",
FieldValues: []FieldValue{
{CamelCase: false, Value: "[]"},
{CamelCase: true, Value: fieldName},
},
}
p.toParentList(fieldName, fieldValues, false)
return fieldSegment
}
func (p *Parser) handleTypeMapIface(fieldName string, fieldValues map[string]interface{}) FieldSegment {
fieldSegment := FieldSegment{
Format: "%s",
FieldValues: []FieldValue{
{CamelCase: true, Value: fieldName},
},
}
p.toChildrenStruct(fieldName, fieldValues)
return fieldSegment
}
gotype FieldSegment struct {
Format string
FieldValues []FieldValue
}
type FieldValue struct {
CamelCase bool
Value string
}
type Field struct {
Name string
Type string
}
type Fields []*Field
func (f *Fields) appendSegment(name string, segment FieldSegment) {
var s []interface{}
for _, v := range segment.FieldValues {
value := v.Value
if v.CamelCase {
value = word.UnderscoreToUpperCamelCase(v.Value)
}
s = append(s, value)
}
*f = append(*f, &Field{Name: word.UnderscoreToUpperCamelCase(name), Type: fmt.Sprintf(segment.Format, s...)})
}
func (f *Fields) removeDuplicate() Fields {
m := make(map[string]bool)
fields := Fields{}
for _, entry := range *f {
if _, value := m[entry.Name]; !value {
m[entry.Name] = true
fields = append(fields, entry)
}
}
return fields
}
govar jsonCmd = &cobra.Command{
Use: "json",
Short: "json转换和处理",
Long: "json转换和处理",
Run: func(cmd *cobra.Command, args []string) {},
}
var json2structCmd = &cobra.Command{
Use: "struct",
Short: "json转换",
Long: "json转换",
Run: func(cmd *cobra.Command, args []string) {
parser, err := json2struct.NewParser(str)
if err != nil {
log.Fatalf("json2struct.NewParser err: %v", err)
}
content := parser.Json2Struct()
log.Printf("输出结果: %s", content)
},
}
func init() {
jsonCmd.AddCommand(json2structCmd)
json2structCmd.Flags().StringVarP(&str, "str", "s", "", "请输入json字符串")
}
govar rootCmd = &cobra.Command{}
func Execute() error {
return rootCmd.Execute()
}
func init() {
rootCmd.AddCommand(wordCmd)
rootCmd.AddCommand(timeCmd)
rootCmd.AddCommand(sqlCmd)
rootCmd.AddCommand(jsonCmd)
}
json这里有大坑,如果不是用文件读入,而是命令行输入json做测试,要注意,三个引号表示一个引号
go run main.go json struct --str '{ """name""":"""Southyang的文档库""","""url""":"""https://doc.southyang.cn""" }' 2022/05/20 14:50:18 输出结果: type JsonStruct struct { Name string Url string }
│ go.mod │ go.sum │ main.go │ ├─cmd 子命令集 │ json.go json struct : json转结构体 │ root.go 根命令 │ sql.go sql struct : sql转结构体 │ time.go time now/calc : 求当前时间和所需要的时间 │ word.go word : 字符串格式转换 │ ├─internal 逻辑函数 │ ├─json2struct │ │ fields.go │ │ parser.go │ │ │ ├─sql2struct │ │ mysql.go │ │ template.go │ │ │ ├─timer │ │ time.go │ │ │ └─word │ word.go │ └─pkg
目前的小工具都是从《Go语言编程之旅》上学的,以后遇到了问题也要总结自己的工具集了,冲冲冲
本文作者:southyang
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!