经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
解析golang 标准库template的代码生成方法
来源:jb51  时间:2021/11/8 19:51:48  对本文有异议

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

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

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

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

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

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

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

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

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

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

总结

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

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

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

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号