golang中标准库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 的类型定义。模板定义如下:
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 库相关的知识点有:
- 通过 **with **限制访问范围,这样,在 { { - with xxx} } 和 { { - end} } 的代码中,不用每个字段前再加 .Model.Fields 前缀了
- 通过 range 循环访问数组,根据数组中每个元素来生成相应的代码
- 通过 if 判断,根据json文件中的属性的不同的定义生成不同的代码
- 自定义函数 **ConvertTypeForTs **,这个函数是将json中定义的 graphql type 转换成 typescript 中对应的类型。用自定义函数是为了避免在模板中写过多的逻辑代码
生成API
这里只生成 graphql 请求的 API,是为了配合 illuminant 项目。 API的参数和返回值用到的对象就在上面自动生成的类型定义中。
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 GraphqlAPI.{
{
.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 GraphqlAPI.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 GraphqlAPI.Update{
{
.Model.Name}
}
Result>
(gqlUpdate{
{
.Model.Name}
}
, gqlVar);
}
export async function delete{
{
.Model.Name}
}
(id: string) {
return GraphqlAPI.Delete{
{
.Model.Name}
}
Result>
(gqlDelete{
{
.Model.Name}
}
, {
id }
);
}
`
这个模板中也使用了几个自定义函数,GenGraphqlSearchWhere,GenGraphqlInsertParams,**GenGraphqlUpdateParams **等等。
生成列表页面,新增和编辑页面
最后一步,就是生成页面。列表页面是主要页面:
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 = useRefActionType>
();
const [modalAddVisible, setModalAddVisible] = useState(false);
const [modalEditVisible, setModalEditVisible] = useState(false);
const [record, setRecord] = useStateAPI.{
{
.Model.Name}
}
Item>
({
}
);
const columns: ProColumnsAPI.{
{
.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}
}
' }
}
>
ProTableAPI.{
{
.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>
);
}
;
`
新增页面和编辑页面差别不大,分开定义是为了以后能分别扩展。新增页面:
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的代码生成步骤是什么?”已经有了进一步的了解,更多相关的问题,欢迎关注网络或到官网咨询客服。
声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!
若转载请注明出处: golang中标准库template的代码生成步骤是什么?
本文地址: https://pptw.com/jishu/653544.html
