文章目錄
- Softhub軟件下載站實戰開發(四):代碼生成器設計與實現
- 1.前言 📜
- 2.技術選型
- 3.架構概覽 🏗?
- 3.1 架構概覽
- 3.2 工作流程詳解
- 4.核心功能實現 ?
- 4.1 配置管理系統
- 4.2 數據庫表結構解析
- 4.3 模板渲染引擎
- 4.4 智能類型轉換
- 4.5 動態文件生成
- 4.6 智能覆蓋策略
- 4.7 運行
- 5.附錄 ??
- 5.1 生成器代碼
- 5.2 后端模板
- 5.3 前端模板
Softhub軟件下載站實戰開發(四):代碼生成器設計與實現
1.前言 📜
在上篇文章中我們以platform
模塊為例,詳細記錄了從創建model到編寫頁面的全過程。相信讀者已經對編寫流程有了一個大致的了解。
不難發現,整個項目分為多層:controller、service、dao等等,并且存在著大量模板代碼,手動一個個文件編寫費事費力,還容易出錯。GoFrame其實為我們提供了cli工具,可以方便快捷的生成例如model、service等代碼。
對整個項目而言,其實這還遠遠不夠,我們還有例如controller、service、前端等模板代碼需要編寫,這時候編寫一個簡單的代碼生成器就會方便很多。
2.技術選型
因為本項目代碼生成器不是重點,因此不考慮項目中集成,僅是作為一個工具存在。因為方便快捷作為第一考慮因素。
因此我們選用python作為主語言、結合jinja模板引擎快速生成模板代碼。
系統核心組件包括:
- 數據庫元數據提取 - 通過SQLAlchemy分析表結構
- 模板引擎 - 使用Jinja2渲染代碼模板
- 配置管理 - YAML配置文件支持
- 文件生成器 - 自動化文件創建和寫入
3.架構概覽 🏗?
3.1 架構概覽
3.2 工作流程詳解
4.核心功能實現 ?
4.1 配置管理系統
database:host: 127.0.0.1port: 3306user: rootpassword: 123456dbname: softhubcode_generator:# 生成類型:frontend-前端, backend-后端, all-全部generate_type: all# 表配置tables:- name: ds_platform # 表名comment: "平臺管理" # 表注釋generate_type: all # 可選:frontend, backend, allfrontend:template_name: "gfast前端模板2"output_path: "D:/output/frontend"primary_name: "name" # 主鍵名稱backend:template_name: "gfast模板"output_path: "D:/output/backend"package: "platform" # 包名package_path: "github.com/tiger1103/gfast/v3" # 包路徑前綴
配置模塊管理
class ConfigUtil:def __init__(self, config_path: Path):self.config_path = config_pathdef read_config(self) -> Dict[str, Any]:with open(self.config_path, 'r', encoding='utf-8') as f:return yaml.safe_load(f)
使用YAML配置文件管理:
- 數據庫連接信息
- 模板路徑配置
- 包名和導入路徑
- 代碼生成選項
4.2 數據庫表結構解析
class InspectUtils:def __init__(self, engine):self.engine = engineself.inspector = inspect(engine)def get_infos(self, table_name: str) -> Dict[str, Any]:columns = self.inspector.get_columns(table_name)# 處理字段信息...return {'table_name': table_name,'columns': columns,# 其他元數據...}
- 使用SQLAlchemy的Inspector獲取表元數據
- 自動將SQL類型轉換為Go類型
- 生成駝峰命名等符合編程規范的字段名
4.3 模板渲染引擎
# 設置模板路徑
template_base_path = Path('template')
template_path = template_base_path.joinpath(template_name)# 渲染模板
templates = Environment(loader=FileSystemLoader(template_path), lstrip_blocks=True, trim_blocks=True)
tl = templates.get_template(template)
render_str = tl.render(infos)
- 支持模板繼承和宏定義
- 自動處理縮進和空白字符
- 動態生成文件路徑和文件名
4.4 智能類型轉換
def _get_go_type(self, sql_type: str) -> str:"""將SQL類型轉換為Go類型"""sql_type = sql_type.lower()if 'int' in sql_type:return 'int'elif 'varchar' in sql_type or 'char' in sql_type or 'text' in sql_type:return 'string'elif 'time' in sql_type or 'date' in sql_type:return '*gtime.Time'# 其他類型處理...
4.5 動態文件生成
# 渲染相對路徑
relative_path = Template(str(item_template_path.parent.relative_to(template_path)).render(file_info)
# 渲染文件名
template_stem = item_template_path.stem
if '.' in template_stem:name_part, ext = template_stem.rsplit('.', 1)file_name = Template(name_part).render(file_info) + '.' + ext
4.6 智能覆蓋策略
# 跳過已存在的router.go和logic.go文件
if Path(file_path).exists() and (str(item_template_path.stem) in ["router.go", "logic.go"]):continue
4.7 運行
修改config\application.yml
配置
python code_generator.py
輸出類似如下
5.附錄 ??
5.1 生成器代碼
import logging as log
import os
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, Listfrom jinja2 import Environment, FileSystemLoader, Template
import sqlalchemy as sa
from sqlalchemy import inspect
import yaml# 配置日志
log.basicConfig(level=log.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')class ConfigUtil:def __init__(self, config_path: Path):self.config_path = config_pathdef read_config(self) -> Dict[str, Any]:with open(self.config_path, 'r', encoding='utf-8') as f:return yaml.safe_load(f)class InspectUtils:def __init__(self, engine):self.engine = engineself.inspector = inspect(engine)self.tables = self.inspector.get_table_names()def get_infos(self, table_name: str) -> Dict[str, Any]:columns = self.inspector.get_columns(table_name)primary_keys = self.inspector.get_pk_constraint(table_name)foreign_keys = self.inspector.get_foreign_keys(table_name)# 處理字段信息field_info = []for column in columns:# 獲取字段類型type_name = str(column['type'])go_type = self._get_go_type(type_name)# 處理字段名name = column['name']camel_name = ''.join(word.capitalize() for word in name.split('_'))if camel_name and camel_name[0].isupper():camel_name = camel_name[0].lower() + camel_name[1:]field_info.append({'name': name,'camelName': camel_name,'firstUpperName': ''.join(word.capitalize() for word in name.split('_')),'type': go_type,'comment': column.get('comment', ''),'nullable': column.get('nullable', False)})return {'table_name': table_name,'columns': columns,'primary_keys': primary_keys,'foreign_keys': foreign_keys,'field_info': field_info}def _get_go_type(self, sql_type: str) -> str:"""將SQL類型轉換為Go類型"""sql_type = sql_type.lower()if 'int' in sql_type:return 'int'elif 'varchar' in sql_type or 'char' in sql_type or 'text' in sql_type:return 'string'elif 'float' in sql_type or 'double' in sql_type or 'decimal' in sql_type:return 'float64'elif 'bool' in sql_type:return 'bool'elif 'time' in sql_type or 'date' in sql_type:return '*gtime.Time'else:return 'string'def get_file_info(self, table_name: str) -> Dict[str, str]:first_upper_name = ''.join(word.capitalize() for word in table_name.split('_'))return {'table_name': table_name,'table_name_camel': ''.join(word.capitalize() for word in table_name.split('_')),'table_name_lower': table_name.lower(),'name': table_name,'first_upper_name': first_upper_name}def get_connection(config: Dict[str, Any]):return sa.create_engine(f"mysql+pymysql://{config['user']}:{config['password']}@{config['host']}:{config['port']}/{config['dbname']}")def generate_code(config_map: Dict[str, Any], table_config: Dict[str, Any]):template_name = config_map['template_name']table_name = config_map['table_name']output_path = config_map['output_path']package = config_map.get('package', 'template')package_path = config_map.get('package_path', 'github.com/tiger1103/gfast/v3')# 讀取配置config_path = Path('config/application.yml').resolve()config_util = ConfigUtil(config_path)config = config_util.read_config()# 連接數據庫engine = get_connection(config['database'])iu = InspectUtils(engine)# 獲取表信息infos = iu.get_infos(table_name)# 添加前端信息front_info = {'search_columns': [{"camelName": "name", "comment": "變量名稱", "type": "string"}]}if "primary_name" in config_map:front_info['primaryName'] = config_map['primary_name']infos['front_info'] = front_info# 添加包信息package_info = {'api': f"{package_path}/api/v1/{package}",'service': f"{package_path}/internal/app/{package}/service",'dao': f"{package_path}/internal/app/{package}/dao",'liberr': f"{package_path}/library/liberr",'libUtils': f"{package_path}/library/libUtils",'consts': f"{package_path}/internal/app/system/consts",'model': f"{package_path}/internal/app/{package}/model",'entity': f"{package_path}/internal/app/{package}/model/entity",'utils': f"{package_path}/internal/app/{package}/utils",'common': f"{package_path}/internal/app/{package}/common",'commonDao': f"{package_path}/internal/app/common/dao",'commonEntity': f"{package_path}/internal/app/common/model/entity",'commonApi': f"{package_path}/api/v1/common",'do': f"{package_path}/internal/app/{package}/model/do",'SystemS': f"{package_path}/internal/app/system/service",'dao_internal': f"{package_path}/internal/app/{package}/dao/internal",'package': package,'packageFirstUpper': package.capitalize()}infos['package_info'] = package_info# 獲取文件信息file_info = iu.get_file_info(table_name)file_info['package'] = packagefile_info['packageFirstUpper'] = package.capitalize()# 添加表信息table_info = {'name': table_name,'lowerName': table_name.lower(),'upperName': table_name.upper(),'firstUpperName': ''.join(word.capitalize() for word in table_name.split('_')),'camelName': ''.join(word.capitalize() for word in table_name.split('_'))[0].lower() + ''.join(word.capitalize() for word in table_name.split('_'))[1:],'comment': table_config.get('comment', ''),'columns': infos['columns'],'primaryKeys': infos['primary_keys'],'foreignKeys': infos['foreign_keys']}infos['table_info'] = table_info# 設置模板路徑template_base_path = Path('template')template_path = template_base_path.joinpath(template_name)# 設置輸出路徑output_path = Path(output_path).resolve()if not output_path.exists():output_path.mkdir(parents=True)# 渲染模板templates = Environment(loader=FileSystemLoader(template_path), lstrip_blocks=True, trim_blocks=True)for template in templates.list_templates():tl = templates.get_template(template)item_template_path = Path(tl.filename)# 渲染相對路徑relative_path = Template(str(item_template_path.parent.relative_to(template_path))).render(file_info)log.info(f"Processing template: {relative_path}")# 渲染文件名(保持原始擴展名)template_stem = item_template_path.stem # 獲取不帶.j2的文件名if '.' in template_stem: # 如果文件名中包含擴展名name_part, ext = template_stem.rsplit('.', 1) # 分離名稱和擴展名file_name = Template(name_part).render(file_info) + '.' + extelse:file_name = Template(template_stem).render(file_info)render_str = tl.render(infos)parent_output = output_path.joinpath(relative_path)if not parent_output.exists():parent_output.mkdir(parents=True)file_path = parent_output.joinpath(file_name)log.info(f"Generating file: {file_path}")# 跳過已存在的router.go和logic.go文件if Path(file_path).exists() and (str(item_template_path.stem) in ["router.go", "logic.go"]):continuewith open(file_path, 'w', encoding='utf-8') as f:f.write(render_str)def main():# 讀取配置config_path = Path('config/application.yml').resolve()config_util = ConfigUtil(config_path)config = config_util.read_config()# 獲取生成器配置generator_config = config['code_generator']generate_type = generator_config['generate_type']# 處理每個表的配置for table_config in generator_config['tables']:table_name = table_config['name']table_generate_type = table_config.get('generate_type', generate_type)# 生成前端代碼if table_generate_type in ['frontend', 'all']:frontend_config = table_config['frontend']config_map = {'template_name': frontend_config['template_name'],'table_name': table_name,'output_path': frontend_config['output_path'],'primary_name': frontend_config['primary_name']}generate_code(config_map, table_config)# 生成后端代碼if table_generate_type in ['backend', 'all']:backend_config = table_config['backend']config_map = {'template_name': backend_config['template_name'],'table_name': table_name,'output_path': backend_config['output_path'],'package': backend_config['package'],'package_path': backend_config['package_path']}generate_code(config_map, table_config)if __name__ == "__main__":main()
5.2 后端模板
項目結構
gfast模板:
- api
- api\v1
- api\v1\{{package}}
- api\v1\{{package}}\{{name}}.go.j2
- internal
- internal\app
- internal\app\{{package}}
- internal\app\{{package}}\controller
- internal\app\{{package}}\controller\{{name}}.go.j2
- internal\app\{{package}}\dao
- internal\app\{{package}}\dao\internal
- internal\app\{{package}}\dao\internal\{{name}}.go.j2
- internal\app\{{package}}\dao\{{name}}.go.j2
- internal\app\{{package}}\logic
- internal\app\{{package}}\logic\logic.go.j2
- internal\app\{{package}}\logic\{{name}}
- internal\app\{{package}}\logic\{{name}}\{{name}}.go.j2
- internal\app\{{package}}\model
- internal\app\{{package}}\model\do
- internal\app\{{package}}\model\do\{{name}}.go.j2
- internal\app\{{package}}\model\entity
- internal\app\{{package}}\model\entity\{{name}}.go.j2
- internal\app\{{package}}\model\{{name}}.go.j2
- internal\app\{{package}}\router
- internal\app\{{package}}\router\router.go.j2
- internal\app\{{package}}\service
- internal\app\{{package}}\service\{{name}}.go.j2
代碼內容
api\v1{{package}}{{name}}.go.j2
package templateimport ("github.com/gogf/gf/v2/frame/g"commonApi "{{package_info.commonApi}}"model "{{package_info.model}}")type {{table_info.firstUpperName}}AddReq struct {g.Meta `path:"/{{table_info.camelName}}/add" method:"post" tags:"{{table_info.comment}}" summary:"{{table_info.comment}}-新增"`{% for item in field_info %}{% if item.camelName not in ['id','createdBy','updatedBy','createdAt','updatedAt'] %}{{ item.firstUpperName }} {{ item.type }} `json:"{{ item.camelName }}" v:"required#{{ item.comment }}不能為空"`{% endif %}{% endfor %}
}type {{table_info.firstUpperName}}AddRes struct {g.Meta `mime:"application/json" example:"string"`
}type {{table_info.firstUpperName}}DelReq struct {g.Meta `path:"/{{table_info.camelName}}/del" method:"delete" tags:"{{table_info.comment}}" summary:"{{table_info.comment}}-刪除"`Id uint `json:"id" v:"required#id不能為空"`
}type {{table_info.firstUpperName}}DelRes struct {g.Meta `mime:"application/json" example:"string"`
}type {{table_info.firstUpperName}}BatchDelReq struct {g.Meta `path:"/{{table_info.camelName}}/batchdel" method:"delete" tags:"{{table_info.comment}}" summary:"{{table_info.comment}}-批量刪除"`Ids []uint `json:"id" v:"required#id不能為空"`
}type {{table_info.firstUpperName}}BatchDelRes struct {g.Meta `mime:"application/json" example:"string"`
}type {{table_info.firstUpperName}}EditReq struct {g.Meta `path:"/{{table_info.camelName}}/edit" method:"put" tags:"{{table_info.comment}}" summary:"{{table_info.comment}}-修改"`{% for item in field_info %}{% if item.camelName not in ['createdBy','updatedBy','createdAt','updatedAt'] %}{{ item.firstUpperName }} {{ item.type }} `json:"{{ item.camelName }}" v:"required#{{ item.comment }}不能為空"`{% endif %}{% endfor %}
}type {{table_info.firstUpperName}}EditRes struct {g.Meta `mime:"application/json" example:"string"`
}type {{table_info.firstUpperName}}ListReq struct {g.Meta `path:"/{{table_info.camelName}}/list" method:"get" tags:"{{table_info.comment}}" summary:"{{table_info.comment}}-列表"`commonApi.PageReq{% for item in field_info %}{% if item.camelName not in ['id','createdBy','updatedBy','createdAt','updatedAt'] %}{{ item.firstUpperName }} {{ item.type }} `json:"{{ item.camelName }}" v:"required#{{ item.comment }}不能為空"`{% endif %}{% endfor %}
}type {{table_info.firstUpperName}}ListRes struct {g.Meta `mime:"application/json" example:"string"`commonApi.ListRes{{table_info.firstUpperName}}List []*model.{{table_info.firstUpperName}}Info `json:"{{table_info.camelName}}List"`
}type {{table_info.firstUpperName}}DetailReq struct {g.Meta `path:"/{{table_info.camelName}}/detail" method:"get" tags:"{{table_info.comment}}" summary:"{{table_info.comment}}-詳情"`Id uint `json:"id" v:"required#id不能為空"`
}type {{table_info.firstUpperName}}DetailRes struct {g.Meta `mime:"application/json" example:"string"`*model.{{table_info.firstUpperName}}Info
}
internal\app{{package}}\controller{{name}}.go.j2
package controllerimport ("context"api "{{package_info.api}}"service "{{package_info.service}}"consts "{{package_info.consts}}"
)var {{table_info.firstUpperName}} = {{table_info.camelName}}Controller{}type {{table_info.camelName}}Controller struct {BaseController
}func (c *{{table_info.camelName}}Controller) Add(ctx context.Context, req *api.{{table_info.firstUpperName}}AddReq) (res *api.{{table_info.firstUpperName}}AddRes, err error) {res = new(api.{{table_info.firstUpperName}}AddRes)err = service.{{table_info.firstUpperName}}().Add(ctx, req)return
}func (c *{{table_info.camelName}}Controller) List(ctx context.Context, req *api.{{table_info.firstUpperName}}ListReq) (res *api.{{table_info.firstUpperName}}ListRes, err error) {res = new(api.{{table_info.firstUpperName}}ListRes)if req.PageSize == 0 {req.PageSize = consts.PageSize}if req.PageNum == 0 {req.PageNum = 1}total, {{table_info.camelName}}s, err := service.{{table_info.firstUpperName}}().List(ctx, req)res.Total = totalres.CurrentPage = req.PageNumres.{{table_info.firstUpperName}}List = {{table_info.camelName}}sreturn
}func (c *{{table_info.camelName}}Controller) Get(ctx context.Context, req *api.{{table_info.firstUpperName}}DetailReq) (res *api.{{table_info.firstUpperName}}DetailRes, err error) {res = new(api.{{table_info.firstUpperName}}DetailRes)service.{{table_info.firstUpperName}}().GetById(ctx, req.Id)return
}func (c *{{table_info.camelName}}Controller) Edit(ctx context.Context, req *api.{{table_info.firstUpperName}}EditReq) (res *api.{{table_info.firstUpperName}}EditRes, err error) {err = service.{{table_info.firstUpperName}}().Edit(ctx, req)return
}func (c *{{table_info.camelName}}Controller) Delete(ctx context.Context, req *api.{{table_info.firstUpperName}}DelReq) (res *api.{{table_info.firstUpperName}}DelRes, err error) {err = service.{{table_info.firstUpperName}}().Delete(ctx, req.Id)return
}func (c *{{table_info.camelName}}Controller) BatchDelete(ctx context.Context, req *api.{{table_info.firstUpperName}}BatchDelReq) (res *api.{{table_info.firstUpperName}}BatchDelRes, err error) {err = service.{{table_info.firstUpperName}}().BatchDelete(ctx, req.Ids)return
}
internal\app{{package}}\dao\internal{{name}}.go.j2
// ==========================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// ==========================================================================package internalimport ("context""github.com/gogf/gf/v2/database/gdb""github.com/gogf/gf/v2/frame/g"
)// {{table_info.firstUpperName}}Dao is the data access object for table cts_voice.
type {{table_info.firstUpperName}}Dao struct {table string // table is the underlying table name of the DAO.group string // group is the database configuration group name of current DAO.columns {{table_info.firstUpperName}}Columns // columns contains all the column names of Table for convenient usage.
}// {{table_info.firstUpperName}}Columns defines and stores column names for table {{table_info.Name}}.
type {{table_info.firstUpperName}}Columns struct {{% for item in field_info %}{{ item.firstUpperName }} string //{{ item.comment }}{% endfor %}
}// {{table_info.camelName}}Columns holds the columns for table {{table_info.Name}}.
var {{table_info.camelName}}Columns = {{table_info.firstUpperName}}Columns{{% for item in field_info %}{{ item.firstUpperName }}: "{{ item.name }}",{% endfor %}
}// New{{table_info.firstUpperName}}Dao creates and returns a new DAO object for table data access.
func New{{table_info.firstUpperName}}Dao() *{{table_info.firstUpperName}}Dao {return &{{table_info.firstUpperName}}Dao{group: "default",table: "{{table_info.name}}",columns: {{table_info.camelName}}Columns,}
}// DB retrieves and returns the underlying raw database management object of current DAO.
func (dao *{{table_info.firstUpperName}}Dao) DB() gdb.DB {return g.DB(dao.group)
}// Table returns the table name of current dao.
func (dao *{{table_info.firstUpperName}}Dao) Table() string {return dao.table
}// Columns returns all column names of current dao.
func (dao *{{table_info.firstUpperName}}Dao) Columns() {{table_info.firstUpperName}}Columns {return dao.columns
}// Group returns the configuration group name of database of current dao.
func (dao *{{table_info.firstUpperName}}Dao) Group() string {return dao.group
}// Ctx creates and returns the Model for current DAO, It automatically sets the context for current operation.
func (dao *{{table_info.firstUpperName}}Dao) Ctx(ctx context.Context) *gdb.Model {return dao.DB().Model(dao.table).Safe().Ctx(ctx)
}// Transaction wraps the transaction logic using function f.
// It rollbacks the transaction and returns the error from function f if it returns non-nil error.
// It commits the transaction and returns nil if function f returns nil.
//
// Note that, you should not Commit or Rollback the transaction in function f
// as it is automatically handled by this function.
func (dao *{{table_info.firstUpperName}}Dao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {return dao.Ctx(ctx).Transaction(ctx, f)
}
internal\app{{package}}\dao{{name}}.go.j2
// =================================================================================
// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish.
// =================================================================================package daoimport (internal "{{package_info.dao_internal}}"
)// internal{{table_info.firstUpperName}}Dao is internal type for wrapping internal DAO implements.
type internal{{table_info.firstUpperName}}Dao = *internal.{{table_info.firstUpperName}}Dao// {{table_info.camelName}}Dao is the data access object for table {{table_info.Name}}.
// You can define custom methods on it to extend its functionality as you wish.
type {{table_info.camelName}}Dao struct {internal{{table_info.firstUpperName}}Dao
}var (// {{table_info.firstUpperName}}Dao is globally public accessible object for table cts_voice operations.{{table_info.firstUpperName}} = {{table_info.camelName}}Dao{internal.New{{table_info.firstUpperName}}Dao(),}
)// Fill with you ideas below.
internal\app{{package}}\logic\logic.go.j2
package logicimport _ "github.com/tiger1103/gfast/v3/internal/app/{{package_info.package}}/logic/{{table_info.name}}"
internal\app{{package}}\logic{{name}}{{name}}.go.j2
package {{table_info.name}}import ("context""fmt""github.com/gogf/gf/v2/frame/g"api "{{package_info.api}}"dao "{{package_info.dao}}"model "{{package_info.model}}"do "{{package_info.do}}"service "{{package_info.service}}"SystemS "{{package_info.SystemS}}"liberr "{{package_info.liberr}}"
)func init() {service.Register{{table_info.firstUpperName}}(New())
}func New() *s{{table_info.firstUpperName}} {return &s{{table_info.firstUpperName}}{}
}type s{{table_info.firstUpperName}} struct {
}func (s s{{table_info.firstUpperName}}) List(ctx context.Context, req *api.{{table_info.firstUpperName}}ListReq) (total interface{}, {{table_info.camelName}}List []*model.{{table_info.firstUpperName}}Info, err error) {err = g.Try(ctx, func(ctx context.Context) {m := dao.{{table_info.firstUpperName}}.Ctx(ctx)columns := dao.{{table_info.firstUpperName}}.Columns()//TODO 根據實際情況修改{% for item in field_info %}{% if item.camelName not in ['id','createdBy','updatedBy','createdAt','updatedAt'] %}if req.{{ item.firstUpperName }} != "" {m = m.Where(columns.{{ item.firstUpperName }}+" = ?", req.{{ item.firstUpperName }})// like//m = m.Where(fmt.Sprintf("%s like ?", columns.{{ item.firstUpperName }}), "%"+req.{{ item.firstUpperName }}+"%")}{% endif %}{% endfor %}total, err = m.Count()liberr.ErrIsNil(ctx, err, "獲取{{table_info.comment}}列表失敗")orderBy := req.OrderByif orderBy == "" {orderBy = "created_at desc"}err = m.Page(req.PageNum, req.PageSize).Order(orderBy).Scan(&{{table_info.camelName}}List)liberr.ErrIsNil(ctx, err, "獲取{{table_info.comment}}列表失敗")})return
}func (s s{{table_info.firstUpperName}}) Add(ctx context.Context, req *api.{{table_info.firstUpperName}}AddReq) (err error) {err = g.Try(ctx, func(ctx context.Context) {// TODO 查詢是否已經存在// add_, err = dao.{{table_info.firstUpperName}}.Ctx(ctx).Insert(do.{{table_info.firstUpperName}}{{% for item in field_info %}{% if item.camelName not in ['id','createdBy','updatedBy','createdAt','updatedAt'] %}{{ item.firstUpperName }}: req.{{ item.firstUpperName }}, // {{ item.comment }}{% endif %}{% endfor %}CreatedBy: SystemS.Context().GetUserId(ctx),UpdatedBy: SystemS.Context().GetUserId(ctx),})liberr.ErrIsNil(ctx, err, "新增{{table_info.comment}}失敗")})return
}func (s s{{table_info.firstUpperName}}) Edit(ctx context.Context, req *api.{{table_info.firstUpperName}}EditReq) (err error) {err = g.Try(ctx, func(ctx context.Context) {_, err = s.GetById(ctx, req.Id)liberr.ErrIsNil(ctx, err, "獲取{{table_info.comment}}失敗")//TODO 根據名稱等查詢是否存在//編輯_, err = dao.{{table_info.firstUpperName}}.Ctx(ctx).WherePri(req.Id).Update(do.{{table_info.firstUpperName}}{{% for item in field_info %}{% if item.camelName not in ['createdBy','updatedBy','createdAt','updatedAt'] %}{{ item.firstUpperName }}: req.{{ item.firstUpperName }}, // {{ item.comment }}{% endif %}{% endfor %}})liberr.ErrIsNil(ctx, err, "修改{{table_info.comment}}失敗")})return
}func (s s{{table_info.firstUpperName}}) Delete(ctx context.Context, id uint) (err error) {err = g.Try(ctx, func(ctx context.Context) {_, err = dao.{{table_info.firstUpperName}}.Ctx(ctx).WherePri(id).Delete()liberr.ErrIsNil(ctx, err, "刪除{{table_info.comment}}失敗")})return
}func (s s{{table_info.firstUpperName}}) BatchDelete(ctx context.Context, ids []uint) (err error) {err = g.Try(ctx, func(ctx context.Context) {_, err = dao.{{table_info.firstUpperName}}.Ctx(ctx).Where(dao.{{table_info.firstUpperName}}.Columns().Id+" in(?)", ids).Delete()liberr.ErrIsNil(ctx, err, "批量刪除{{table_info.comment}}失敗")})return
}func (s s{{table_info.firstUpperName}}) GetById(ctx context.Context, id uint) (res *model.{{table_info.firstUpperName}}Info, err error) {err = g.Try(ctx, func(ctx context.Context) {err = dao.{{table_info.firstUpperName}}.Ctx(ctx).Where(fmt.Sprintf("%s=?", dao.{{table_info.firstUpperName}}.Columns().Id), id).Scan(&res)liberr.ErrIsNil(ctx, err, "獲取{{table_info.comment}}失敗")})return
}
internal\app{{package}}\model\do{{name}}.go.j2
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================package doimport ("github.com/gogf/gf/v2/frame/g"
)// {{table_info.firstUpperName}} is the golang structure of table {{table_info.name}} for DAO operations like Where/Data.
type {{table_info.firstUpperName}} struct {g.Meta `orm:"table:{{table_info.name}}, do:true"`{% for item in field_info %}{{ item.firstUpperName }} interface{} //{{ item.comment }}{% endfor %}
}
internal\app{{package}}\model\entity{{name}}.go.j2
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================package entityimport "github.com/gogf/gf/v2/os/gtime"// {{table_info.firstUpperName}} is the golang structure for table {{table_info.name}}.
type {{table_info.firstUpperName}} struct {{% for item in field_info %}{{ item.firstUpperName }} {{ item.type }} `json:"{{ item.camelName }}" description:"{{ item.comment }}"`{% endfor %}
}
internal\app{{package}}\model{{name}}.go.j2
package modelimport "github.com/gogf/gf/v2/os/gtime"type {{table_info.firstUpperName}}Info struct {{% for item in field_info %}{{ item.firstUpperName }} {{ item.type }} `orm:"{{item.name}}" json:"{{item.camelName}}"` // {{ item.comment }}{% endfor %}
}
internal\app{{package}}\router\router.go.j2
/*
* @desc:后臺路由
* @company:云南奇訊科技有限公司
* @Author: yixiaohu
* @Date: 2022/2/18 17:34*/package routerimport ("context""github.com/gogf/gf/v2/net/ghttp"_ "github.com/tiger1103/gfast/v3/internal/app/{{package_info.package}}/logic""github.com/tiger1103/gfast/v3/internal/app/{{package_info.package}}/controller""github.com/tiger1103/gfast/v3/internal/app/system/service""github.com/tiger1103/gfast/v3/library/libRouter"
)var R = new(Router)type Router struct{}func (router *Router) BindController(ctx context.Context, group *ghttp.RouterGroup) {group.Group("/{{package_info.package}}", func(group *ghttp.RouterGroup) {//登錄驗證攔截service.GfToken().Middleware(group)//context攔截器group.Middleware(service.Middleware().Ctx, service.Middleware().Auth)//后臺操作日志記錄group.Hook("/*", ghttp.HookAfterOutput, service.OperateLog().OperationLog)group.Bind(controller.{{table_info.firstUpperName}},)//自動綁定定義的控制器if err := libRouter.RouterAutoBind(ctx, router, group); err != nil {panic(err)}})
}
internal\app{{package}}\service{{name}}.go.j2
package serviceimport ("context"api "{{package_info.api}}"model "{{package_info.model}}"
)type I{{table_info.firstUpperName}} interface {List(ctx context.Context, req *api.{{table_info.firstUpperName}}ListReq) (total interface{}, res []*model.{{table_info.firstUpperName}}Info, err error)Add(ctx context.Context, req *api.{{table_info.firstUpperName}}AddReq) (err error)Edit(ctx context.Context, req *api.{{table_info.firstUpperName}}EditReq) (err error)Delete(ctx context.Context, id uint) (err error)BatchDelete(ctx context.Context, ids []uint) (err error)GetById(ctx context.Context, id uint) (res *model.{{table_info.firstUpperName}}Info, err error)
}var local{{table_info.firstUpperName}} I{{table_info.firstUpperName}}func {{table_info.firstUpperName}}() I{{table_info.firstUpperName}} {if local{{table_info.firstUpperName}} == nil {panic("implement not found for interface I{{table_info.firstUpperName}}, forgot register?")}return local{{table_info.firstUpperName}}
}func Register{{table_info.firstUpperName}}(i I{{table_info.firstUpperName}}) {local{{table_info.firstUpperName}} = i
}
5.3 前端模板
項目結構
gfast前端模板2:
- src
- src\api
- src\api\{{package}}
- src\api\{{package}}\{{camel_name}}
- src\api\{{package}}\{{camel_name}}\index.ts.j2
- src\views
- src\views\{{package}}
- src\views\{{package}}\{{camel_name}}
- src\views\{{package}}\{{camel_name}}\component
- src\views\{{package}}\{{camel_name}}\component\edit{{first_upper_name}}.vue.j2
- src\views\{{package}}\{{camel_name}}\index.vue.j2
代碼內容
src\api{{package}}{{camel_name}}\index.ts.j2
import request from '/@/utils/request';export function get{{table_info.firstUpperName}}List(query?:Object) {return request({url: '/api/v1/{{package_info.package}}/{{table_info.camelName}}/list',method: 'get',params:query})
}export function add{{table_info.firstUpperName}}(data:object) {return request({url: '/api/v1/{{package_info.package}}/{{table_info.camelName}}/add',method: 'post',data:data})
}export function edit{{table_info.firstUpperName}}(data:object) {return request({url: '/api/v1/{{package_info.package}}/{{table_info.camelName}}/edit',method: 'put',data:data})
}export function delete{{table_info.firstUpperName}}(id:number) {return request({url: '/api/v1/{{package_info.package}}/{{table_info.camelName}}/del',method: 'delete',data:{id}})
}export function batchDelete{{table_info.firstUpperName}}(ids:number[]) {return request({url: '/api/v1/{{package_info.package}}/{{table_info.camelName}}/batchdel',method: 'delete',data:{ids}})
}
src\views{{package}}{{camel_name}}\component\edit{{first_upper_name}}.vue.j2
<template><div class="system-edit-post-container"><el-dialog v-model="state.isShowDialog" width="769px"><template #header><div>{{'{{'}}(state.formData.id===0?'添加':'修改')+'{{table_info.comment}}'{{'}}'}}</div></template><el-form ref="formRef" :model="state.formData" :rules="state.rules" size="default" label-width="90px">{% for item in field_info %}{% if item.camelName not in ['id', 'createdBy','updatedBy','createdAt','updatedAt'] %}<el-form-item label="{{ item.comment }}" prop="{{ item.camelName }}"><el-input v-model="state.formData.{{ item.camelName }}" placeholder="請輸入{{ item.comment }}名稱" clearable /></el-form-item>{% endif %}{% endfor %}</el-form><template #footer><span class="dialog-footer"><el-button @click="onCancel" size="default">取 消</el-button><el-button type="primary" @click="onSubmit" size="default" :loading="state.loading">{{'{{'}}state.formData.id===0?'新 增':'修 改'{{'}}'}}</el-button></span></template></el-dialog></div>
</template><script setup lang="ts">
import { ref, reactive } from 'vue';
import { add{{ table_info.firstUpperName }}, edit{{ table_info.firstUpperName }} } from "/@/api/{{ package_info.package }}/{{ table_info.camelName }}/index.ts";
import { ElMessage, ElForm } from 'element-plus';interface DialogRow {{% for item in field_info %}{% if item.camelName not in ['createdBy','updatedBy','createdAt','updatedAt'] %}{{ item.camelName }}: {{ item.tsType }}; // {{ item.comment }}{% endif %}{% endfor %}
}const formRef = ref<InstanceType<typeof ElForm> | null>(null);
const state = reactive({loading: false,isShowDialog: false,formData: {{% for item in field_info %}{% if item.camelName not in ['createdBy','updatedBy','createdAt','updatedAt'] %}{% if item.tsType == 'string' %}{{ item.camelName }}: '', // {{ item.comment }}{% else %}{{ item.camelName }}: 0, // {{ item.comment }}{% endif %}{% endif %}{% endfor %}} as DialogRow,rules: {{% for item in field_info %}{% if item.camelName not in ['id', 'createdBy','updatedBy','createdAt','updatedAt'] %}{{ item.camelName }}: [{ required: true, message: "{{ item.comment }}不能為空", trigger: "blur" }],{% endif %}{% endfor %}},
});const openDialog = (row?: DialogRow) => {resetForm();if (row) {state.formData = row;}state.isShowDialog = true;
};const closeDialog = () => {state.isShowDialog = false;
};const onCancel = () => {closeDialog();
};const onSubmit = () => {const formWrap = formRef.value;if (!formWrap) {return;}formWrap.validate((valid: boolean) => {if (!valid) {return;}state.loading = true;if (state.formData.id === 0) {add{{ table_info.firstUpperName }}(state.formData).then(() => {ElMessage.success('{{ table_info.comment }}添加成功');closeDialog();emit('get{{ table_info.firstUpperName }}List');}).finally(() => {state.loading = false;});} else {edit{{ table_info.firstUpperName }}(state.formData).then(() => {ElMessage.success('{{ table_info.comment }}編輯成功');closeDialog();emit('get{{ table_info.firstUpperName }}List');}).finally(() => {state.loading = false;});}});
};const resetForm = () => {state.formData = {{% for item in field_info %}{% if item.camelName not in ['createdBy','updatedBy','createdAt','updatedAt'] %}{% if item.tsType == 'string' %}{{ item.camelName }}: '', // {{ item.comment }}{% else %}{{ item.camelName }}: 0, // {{ item.comment }}{% endif %}{% endif %}{% endfor %}} as DialogRow;if (formRef.value) {formRef.value.resetFields();}
};defineExpose({openDialog,closeDialog,
});const emit = defineEmits(['get{{ table_info.firstUpperName }}List']);
</script><style scoped lang="scss">
</style>
src\views{{package}}{{camel_name}}\index.vue.j2
<template><div class="system-{{table_info.camelName}}-container"><el-card shadow="hover"><div class="system-{{table_info.camelName}}-search mb15"><el-form :inline="true">{% for item in front_info.serach_columns %}<el-form-item label="{{item.comment}}"><el-input size="default" v-model="state.tableData.param.{{item.camelName}}" style="width: 240px" placeholder="請輸入{{item.comment}}" class="w-50 m-2" clearable/></el-form-item>{% endfor %}<el-form-item><el-button size="default" type="primary" class="ml10" @click="{{table_info.camelName}}List"><el-icon><ele-Search /></el-icon>查詢</el-button><el-button size="default" type="success" class="ml10" @click="onOpenAdd{{table_info.firstUpperName}}"><el-icon><ele-FolderAdd /></el-icon>新增{{table_info.comment}}</el-button><el-button size="default" type="danger" class="ml10" @click="onRowDel(null)"><el-icon><ele-Delete /></el-icon>刪除{{table_info.comment}}</el-button></el-form-item></el-form></div><el-table :data="state.tableData.data" style="width: 100%" @selection-change="handleSelectionChange"><el-table-column type="selection" width="55" align="center" /><el-table-column type="index" label="序號" width="60" />{% for item in field_info %}{% if item.camelName not in ['id', 'createdBy','updatedBy','createdAt','updatedAt'] %}<el-table-column prop="{{item.camelName}}" label="{{item.comment}}" show-overflow-tooltip></el-table-column>{% endif %}{% endfor %}<el-table-column label="操作" width="200"><template #default="scope"><el-button size="small" text type="primary" @click="onOpenEdit{{table_info.firstUpperName}}(scope.row)">修改</el-button><el-button size="small" text type="primary" @click="onRowDel(scope.row)">刪除</el-button></template></el-table-column></el-table><paginationv-show="state.tableData.total>0":total="state.tableData.total"v-model:page="state.tableData.param.pageNum"v-model:limit="state.tableData.param.pageSize"@pagination="{{table_info.camelName}}List"/></el-card><Edit{{table_info.firstUpperName}} ref="edit{{table_info.firstUpperName}}Ref" @get{{table_info.firstUpperName}}List="{{table_info.camelName}}List"/></div>
</template><script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { ElMessageBox, ElMessage } from 'element-plus';
import Edit{{table_info.firstUpperName}} from '/@/views/{{package_info.package}}/{{table_info.camelName}}/component/edit{{table_info.firstUpperName}}.vue';
import { batchDelete{{table_info.firstUpperName}}, get{{table_info.firstUpperName}}List } from "/@/api/{{package_info.package}}/{{table_info.camelName}}";// 定義接口來定義對象的類型
interface TableData {{% for item in field_info %}{{ item.camelName }}: {{ item.tsType }}; // {{ item.comment }}{% endfor %}
}interface TableDataState {ids: number[];tableData: {data: Array<TableData>;total: number;loading: boolean;param: {{% for item in front_info.serach_columns %}{{ item.camelName }}: {{ item.type }}; // {{ item.comment }}{% endfor %}pageNum: number; // 當前頁碼pageSize: number; // 每頁條數};};
}const edit{{table_info.firstUpperName}}Ref = ref();
const state = reactive<TableDataState>({ids: [],tableData: {data: [],total: 0,loading: false,param: {{% for item in front_info.serach_columns %}{% if item.type == 'string' %}{{ item.camelName }}: '',{% else %}{{ item.camelName }}: 0,{% endif %}{% endfor %}pageNum: 1,pageSize: 10,},},
});// 初始化表格數據
const initTableData = () => {{{ table_info.camelName }}List();
};// 查詢{{ table_info.comment }}列表數據
const {{ table_info.camelName }}List = () => {get{{ table_info.firstUpperName }}List(state.tableData.param).then(res => {state.tableData.data = res.data.{{ table_info.camelName }}List ?? [];state.tableData.total = res.data.total;});
};// 打開新增{{ table_info.comment }}彈窗
const onOpenAdd{{ table_info.firstUpperName }} = () => {edit{{ table_info.firstUpperName }}Ref.value.openDialog();
};// 打開修改{{ table_info.comment }}彈窗
const onOpenEdit{{ table_info.firstUpperName }} = (row: Object) => {row = Object.assign({}, row);edit{{ table_info.firstUpperName }}Ref.value.openDialog(row);
};// 刪除{{ table_info.comment }}
const onRowDel = (row: any) => {let msg = '你確定要刪除所選{{ table_info.comment }}?';let ids: number[] = [];if (row) {msg = `此操作將永久刪除{{ table_info.comment }}:“${row.{{ front_info.primarkName }}}”,是否繼續?`;ids = [row.id];} else {ids = state.ids;}if (ids.length === 0) {ElMessage.error('請選擇要刪除的數據。');return;}ElMessageBox.confirm(msg, '提示', {confirmButtonText: '確認',cancelButtonText: '取消',type: 'warning',}).then(() => {batchDelete{{ table_info.firstUpperName }}(ids).then(() => {ElMessage.success('刪除成功');{{ table_info.camelName }}List();});}).catch(() => {});
};// 分頁改變
const onHandleSizeChange = (val: number) => {state.tableData.param.pageSize = val;{{ table_info.camelName }}List();
};// 分頁改變
const onHandleCurrentChange = (val: number) => {state.tableData.param.pageNum = val;{{ table_info.camelName }}List();
};// 多選框選中數據
const handleSelectionChange = (selection: Array<TableData>) => {state.ids = selection.map(item => item.id);
};// 頁面加載時
onMounted(() => {initTableData();
});
</script>