脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - 解析golang 标准库template的代码生成方法

解析golang 标准库template的代码生成方法

2021-11-30 10:34千里之行,始于足下 Golang

这个项目的自动生成代码都是基于 golang 的标准库 template 的,所以这篇文章也算是对使用 template 库的一次总结,本文通过实例代码给大家介绍的非常详细,需要的朋友参考下吧

curd-gen 项目

curd-gen 项目的创建本来是为了做为 illuminant 项目的一个工具,用来生成前端增删改查页面中的基本代码。

最近,随着 antd Pro v5 的升级,将项目进行了升级,现在生成的都是 ts 代码。这个项目的自动生成代码都是基于 golang标准库 template 的,所以这篇博客也算是对使用 template 库的一次总结。

自动生成的配置

curd-gen 项目的自动代码生成主要是3部分:

  • 类型定义:用于API请求和页面显示的各个类型
  • API请求:graphql 请求语句和函数
  • 页面:列表页面,新增页面和编辑页面。新增和编辑是用弹出 modal 框的方式。

根据要生成的内容,定义了一个json格式文件,做为代码生成的基础。 json文件的说明在:https://gitee.com/wangyubin/curd-gen#curdjson

生成类型定义

类型是API请求和页面显示的基础,一般开发流程也是先根据业务定义类型,才开始API和页面的开发的。 ​

自动生成类型定义就是根据 json 文件中的字段列表,生成 ts 的类型定义。模板定义如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
const TypeDTmpl = `// @ts-ignore
/* eslint-disable */
 
declare namespace API {
  type {{.Model.Name}}Item = {
    {{- with .Model.Fields}}
    {{- range .}}
    {{- if .IsRequired}}
    {{.Name}}: {{.ConvertTypeForTs}};
    {{- else}}
    {{.Name}}?: {{.ConvertTypeForTs}};
    {{- end}}{{- /* end for if .IsRequired */}}
    {{- end}}{{- /* end for range */}}
    {{- end}}{{- /* end for with .Model.Fields */}}
  };
 
  type {{.Model.Name}}ListResult = CommonResponse & {
    data: {
      {{.Model.GraphqlName}}: {{.Model.Name}}Item[];
      {{.Model.GraphqlName}}_aggregate: {
        aggregate: {
          count: number;
        };
      };
    };
  };
 
  type Create{{.Model.Name}}Result = CommonResponse & {
    data: {
      insert_{{.Model.GraphqlName}}: {
        affected_rows: number;
      };
    };
  };
 
  type Update{{.Model.Name}}Result = CommonResponse & {
    data: {
      update_{{.Model.GraphqlName}}_by_pk: {
        id: string;
      };
    };
  };
 
  type Delete{{.Model.Name}}Result = CommonResponse & {
    data: {
      delete_{{.Model.GraphqlName}}_by_pk: {
        id: string;
      };
    };
  };
}`

除了主要的类型,还包括了增删改查 API 返回值的定义。 ​

其中用到 text/template 库相关的知识点有:

  1. 通过 **with **限制访问范围,这样,在 {{- with xxx}} 和 {{- end}} 的代码中,不用每个字段前再加 .Model.Fields 前缀了
  2. 通过 range 循环访问数组,根据数组中每个元素来生成相应的代码
  3. 通过 if 判断,根据json文件中的属性的不同的定义生成不同的代码
  4. 自定义函数 **ConvertTypeForTs **,这个函数是将json中定义的 graphql type 转换成 typescript 中对应的类型。用自定义函数是为了避免在模板中写过多的逻辑代码

生成API

这里只生成 graphql 请求的 API,是为了配合 illuminant 项目。 API的参数和返回值用到的对象就在上面自动生成的类型定义中。 ​

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
const APITmpl = `// @ts-ignore
/* eslint-disable */
import { Graphql } from '../utils';
 
const gqlGet{{.Model.Name}}List = ` + "`" + `query get_item_list($limit: Int = 10, $offset: Int = 0{{- with .Model.Fields}}{{- range .}}{{- if .IsSearch}}, ${{.Name}}: {{.Type}}{{- end}}{{- end}}{{- end}}) {
  {{.Model.GraphqlName}}(order_by: {updated_at: desc}, limit: $limit, offset: $offset{{.Model.GenGraphqlSearchWhere false}}) {
    {{- with .Model.Fields}}
    {{- range .}}
    {{.Name}}
    {{- end}}
    {{- end}}
  }
  {{.Model.GraphqlName}}_aggregate({{.Model.GenGraphqlSearchWhere true}}) {
    aggregate {
      count
    }
  }
}` + "`" + `;
 
const gqlCreate{{.Model.Name}} = ` + "`" + `mutation create_item({{.Model.GenGraphqlInsertParamDefinations}}) {
  insert_{{.Model.GraphqlName}}(objects: { {{.Model.GenGraphqlInsertParams}} }) {
    affected_rows
  }
}` + "`" + `;
 
const gqlUpdate{{.Model.Name}} = ` + "`" + `mutation update_item_by_pk($id: uuid!, {{.Model.GenGraphqlUpdateParamDefinations}}) {
  update_{{.Model.GraphqlName}}_by_pk(pk_columns: {id: $id}, _set: { {{.Model.GenGraphqlUpdateParams}} }) {
    id
  }
}` + "`" + `;
 
const gqlDelete{{.Model.Name}} = ` + "`" + `mutation delete_item_by_pk($id: uuid!) {
  delete_{{.Model.GraphqlName}}_by_pk(id: $id) {
    id
  }
}` + "`" + `;
 
export async function get{{.Model.Name}}List(params: API.{{.Model.Name}}Item & API.PageInfo) {
  const gqlVar = {
    limit: params.pageSize ? params.pageSize : 10,
    offset: params.current && params.pageSize ? (params.current - 1) * params.pageSize : 0,
    {{- with .Model.Fields}}
    {{- range .}}
    {{- if .IsSearch}}
    {{.Name}}: params.{{.Name}} ? '%' + params.{{.Name}} + '%' : '%%',
    {{- end}}
    {{- end}}
    {{- end}}
  };
 
  return Graphql<API.{{.Model.Name}}ListResult>(gqlGet{{.Model.Name}}List, gqlVar);
}
 
export async function create{{.Model.Name}}(params: API.{{.Model.Name}}Item) {
  const gqlVar = {
    {{- with .Model.Fields}}
    {{- range .}}
    {{- if not .NotInsert}}
    {{- if .IsPageRequired}}
    {{.Name}}: params.{{.Name}},
    {{- else}}
    {{.Name}}: params.{{.Name}} ? params.{{.Name}} : null,
    {{- end}}
    {{- end}}
    {{- end}}
    {{- end}}
  };
 
  return Graphql<API.Create{{.Model.Name}}Result>(gqlCreate{{.Model.Name}}, gqlVar);
}
 
export async function update{{.Model.Name}}(params: API.{{.Model.Name}}Item) {
  const gqlVar = {
    id: params.id,
    {{- with .Model.Fields}}
    {{- range .}}
    {{- if not .NotUpdate}}
    {{- if .IsPageRequired}}
    {{.Name}}: params.{{.Name}},
    {{- else}}
    {{.Name}}: params.{{.Name}} ? params.{{.Name}} : null,
    {{- end}}
    {{- end}}
    {{- end}}
    {{- end}}
  };
 
  return Graphql<API.Update{{.Model.Name}}Result>(gqlUpdate{{.Model.Name}}, gqlVar);
}
 
export async function delete{{.Model.Name}}(id: string) {
  return Graphql<API.Delete{{.Model.Name}}Result>(gqlDelete{{.Model.Name}}, { id });
}`

这个模板中也使用了几个自定义函数,GenGraphqlSearchWhere,GenGraphqlInsertParams,**GenGraphqlUpdateParams **等等。

生成列表页面,新增和编辑页面

最后一步,就是生成页面。列表页面是主要页面:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
const PageListTmpl = `import { useRef, useState } from 'react';
import { PageContainer } from '@ant-design/pro-layout';
import { Button, Modal, Popconfirm, message } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import type { ActionType, ProColumns } from '@ant-design/pro-table';
import ProTable from '@ant-design/pro-table';
import { get{{.Model.Name}}List, create{{.Model.Name}}, update{{.Model.Name}}, delete{{.Model.Name}} } from '{{.Page.ApiImport}}';
import {{.Model.Name}}Add from './{{.Model.Name}}Add';
import {{.Model.Name}}Edit from './{{.Model.Name}}Edit';
 
export default () => {
  const tableRef = useRef<ActionType>();
  const [modalAddVisible, setModalAddVisible] = useState(false);
  const [modalEditVisible, setModalEditVisible] = useState(false);
  const [record, setRecord] = useState<API.{{.Model.Name}}Item>({});
 
  const columns: ProColumns<API.{{.Model.Name}}Item>[] = [
    {{- with .Model.Fields}}
    {{- range .}}
    {{- if .IsColumn}}
    {
      title: '{{.Title}}',
      dataIndex: '{{.Name}}',
    {{- if not .IsSearch}}
      hideInSearch: true,
    {{- end}}
    },
    {{- end }}{{- /* end for if .IsColumn */}}
    {{- end }}{{- /* end for range . */}}
    {{- end }}{{- /* end for with */}}
    {
      title: '操作',
      valueType: 'option',
      render: (_, rd) => [
        <Button
          type="primary"
          size="small"
          key="edit"
          onClick={() => {
            setModalEditVisible(true);
            setRecord(rd);
          }}
        >
          修改
        </Button>,
        <Popconfirm
          placement="topRight"
          title="是否删除?"
          okText="Yes"
          cancelText="No"
          key="delete"
          onConfirm={async () => {
            const response = await delete{{.Model.Name}}(rd.id as string);
            if (response.code === 10000) message.info(` + "`" + `TODO: 【${rd.TODO}】 删除成功` + "`" + `);
            else message.warn(` + "`" + `TODO: 【${rd.TODO}】 删除失败` + "`" + `);
            tableRef.current?.reload();
          }}
        >
          <Button danger size="small">
            删除
          </Button>
        </Popconfirm>,
      ],
    },
  ];
 
  const addItem = async (values: any) => {
    console.log(values);
    const response = await create{{.Model.Name}}(values);
    if (response.code !== 10000) {
      message.error('创建TODO失败');
    }
 
    if (response.code === 10000) {
      setModalAddVisible(false);
      tableRef.current?.reload();
    }
  };
 
  const editItem = async (values: any) => {
    values.id = record.id;
    console.log(values);
    const response = await update{{.Model.Name}}(values);
    if (response.code !== 10000) {
      message.error('编辑TODO失败');
    }
 
    if (response.code === 10000) {
      setModalEditVisible(false);
      tableRef.current?.reload();
    }
  };
 
  return (
    <PageContainer fixedHeader header={{"{{"}} title: '{{.Page.Title}}' }}>
      <ProTable<API.{{.Model.Name}}Item>
        columns={columns}
        rowKey="id"
        actionRef={tableRef}
        search={{"{{"}}
          labelWidth: 'auto',
        }}
        toolBarRender={() => [
          <Button
            key="button"
            icon={<PlusOutlined />}
            type="primary"
            onClick={() => {
              setModalAddVisible(true);
            }}
          >
            新建
          </Button>,
        ]}
        request={async (params: API.{{.Model.Name}}Item & API.PageInfo) => {
          const resp = await get{{.Model.Name}}List(params);
          return {
            data: resp.data.{{.Model.GraphqlName}},
            total: resp.data.{{.Model.GraphqlName}}_aggregate.aggregate.count,
          };
        }}
      />
      <Modal
        destroyOnClose
        title="新增"
        visible={modalAddVisible}
        footer={null}
        onCancel={() => setModalAddVisible(false)}
      >
        <{{.Model.Name}}Add onFinish={addItem} />
      </Modal>
      <Modal
        destroyOnClose
        title="编辑"
        visible={modalEditVisible}
        footer={null}
        onCancel={() => setModalEditVisible(false)}
      >
        <{{.Model.Name}}Edit onFinish={editItem} record={record} />
      </Modal>
    </PageContainer>
  );
};`

新增页面和编辑页面差别不大,分开定义是为了以后能分别扩展。新增页面:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const PageAddTmpl = `import ProForm, {{.Model.GenPageImportCtrls}}
import { formLayout } from '@/common';
import { Row, Col, Space } from 'antd';
 
export default (props: any) => {
  return (
    <ProForm
      {...formLayout}
      layout="horizontal"
      onFinish={props.onFinish}
      submitter={{"{{"}}
        // resetButtonProps: { style: { display: 'none' } },
        render: (_, dom) => (
          <Row>
            <Col offset={10}>
              <Space>{dom}</Space>
            </Col>
          </Row>
        ),
      }}
    >
    {{- with .Model.Fields}}
    {{- range .}}
{{- .GenPageCtrl}}
    {{- end}}
    {{- end}}
    </ProForm>
  );
};`

页面生成中有个地方困扰了我一阵,就是页面中有个和 text/template 标记冲突的地方,也就是 {{ 的显示。比如上面的 submitter={{"{{"}} ,页面中需要直接显示 {{ 2个字符,但 {{ }} 框住的部分是模板中需要替换的部分。

所以,模板中需要显示 {{ 的地方,可以用 {{"{{"}} 代替。

总结

上面的代码生成虽然需要配合 illuminant 项目一起使用,但是其思路可以参考。

代码生成无非就是找出重复代码的规律,将其中变化的部分定义出来,然后通过模板来生成不同的代码。通过模板来生成代码,跟拷贝相似代码来修改相比,可以有效减少很多人为造成的混乱,比如拷贝过来后漏改,或者有些多余代码未删除等等。

到此这篇关于golang 标准库template的代码生成的文章就介绍到这了,更多相关golang 标准库template内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://www.cnblogs.com/wang_yb/p/15524632.html

延伸 · 阅读

精彩推荐
  • GolangGo语言实现自动填写古诗词实例代码

    Go语言实现自动填写古诗词实例代码

    这篇文章主要给大家介绍了关于Go语言实现自动填写古诗词的相关资料,这是最近在项目中遇到的一个需求,文中通过示例代码介绍的非常详细,需要的朋...

    FengY5862020-05-14
  • GolangGo语言基础单元测试与性能测试示例详解

    Go语言基础单元测试与性能测试示例详解

    这篇文章主要为大家介绍了Go语言基础单元测试与性能测试示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助祝大家多多进步...

    枫少文7812021-12-05
  • GolangGO语言字符串处理Strings包的函数使用示例讲解

    GO语言字符串处理Strings包的函数使用示例讲解

    这篇文章主要为大家介绍了GO语言字符串处理Strings包的函数使用示例讲解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加...

    Jeff的技术栈6882022-04-14
  • GolangGolang实现四种负载均衡的算法(随机,轮询等)

    Golang实现四种负载均衡的算法(随机,轮询等)

    本文介绍了示例介绍了Golang 负载均衡的四种实现,主要包括了随机,轮询,加权轮询负载,一致性hash,感兴趣的小伙伴们可以参考一下...

    Gundy_8442021-08-09
  • GolangGolang 语言极简类型转换库cast的使用详解

    Golang 语言极简类型转换库cast的使用详解

    本文我们通过 cast.ToString() 函数的使用,简单介绍了cast 的使用方法,除此之外,它还支持很多其他类型,在这没有多多介绍,对Golang 类型转换库 cast相关知...

    Golang语言开发栈6112021-12-02
  • Golang深入浅析Go中三个点(...)用法

    深入浅析Go中三个点(...)用法

    这篇文章主要介绍了深入浅析Go中三个点(...)用法,需要的朋友可以参考下...

    踏雪无痕SS6472021-11-17
  • Golanggo语言获取系统盘符的方法

    go语言获取系统盘符的方法

    这篇文章主要介绍了go语言获取系统盘符的方法,涉及Go语言调用winapi获取系统硬件信息的技巧,具有一定参考借鉴价值,需要的朋友可以参考下 ...

    无尽海3862020-04-24
  • GolangGo语言range关键字循环时的坑

    Go语言range关键字循环时的坑

    今天小编就为大家分享一篇关于Go语言range关键字循环时的坑,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来...

    benben_20154202020-05-23