使用中台 Admin.Core 实现了一个Razor模板的通用代码生成器

admin,core,razor · 浏览次数 : 127

小编点评

本文主要介绍了基于 Admin.Core 框架的通用代码生成器的设计与实现,重点关注了模板生成功能、预览以及代码文件压缩下载。 **主要内容概述如下**: 1. **前言**: 介绍了通用代码生成器的基础模块分组、模板、项目、项目模型和项目字段的基础功能,以及本文的主要内容和目的。 2. **项目生成管理**: 讨论了项目生成管理的概念,包括支持多模板组模板生成预览,预览页可以直接编辑模板,以及模板生成后压缩包的下载。 3. **核心功能实现**: 详细阐述了如何生成项目代码文件,包括模板引擎的使用、模型与模板的结合、文件路径与内容的生成,以及如何将所有代码内容文件打包成压缩包进行下载。 4. **前端页面设计**: 介绍了前端页面的设计要点,包括系统管理、接口管理和权限管理等功能的实现,以及如何通过前端页面展示和管理生成的代码。 5. **代码生成器架构**: 总结了代码生成器的整体架构,包括前端页面的引入、组件封装、路由信息的获取与页面跳转,以及如何通过后端返回的文件流生成压缩包。 总的来说,本文通过详细的步骤和代码示例,展示了如何构建一个功能完善的代码生成器,该生成器能够自动化地生成项目代码、预览模板并支持代码文件的下载,极大地提高了开发效率。

正文

前言

前面使用 Admin.Core 的代码生成器生成了通用代码生成器的基础模块 分组,模板,项目,项目模型,项目字段的基础功能,本篇继续完善,实现最核心的模板生成功能,并提供生成预览及代码文件压缩下载

准备

首先清楚几个模块的关系,如何使用,简单画一个流程图

image.png
前面完成了基础的模板组,模板管理,项目,模型,字段管理,都是由 Admin.Core 框架的代码生成器完成,感兴趣的可参考前篇使用,文末也会给出仓库地址,有问题欢迎交流

本文主要分享项目代码的生成,先放出效果图

  • 项目生成管理,支持多模板组

  • 模板生成预览,预览页可以直接编辑模板

  • 模板生成,将生成压缩包并下载

实现

需要实现上面效果的代码生成器,基础的增删改查都可以借由代码生成器生成,也写过几篇了这里就不再赘述,我们只需要关注核心部分:如何生成?如何预览?如何下载?

根据模板生成对应项目的代码文件

当我们有了一个模板(模板内容),也有了对应的配置项(项目&项目模型&项目字段),中间肯定是需要一个模板引擎来将模板根据配置项解析出来的。

市面上有很多模板引擎,比如 ejs,art 等,但既然是搞C#的,那不妨试试看 Razor (之前的Admin.Core代码生成器也是基于razor模板引擎,也可以换其他的或者支持多种模板引擎),C#的语法写起来还是挺舒服的,并且其实还可以新建一个.net core 的项目添加页面后可以复制模板和模型到页面为自己的模板增加智能提示

模板引擎的使用

  • 项目中引用包:RazorEngine.NetCore

    • 原版只支持 framework,大佬打包的 netcore 版本
<ItemGroup>
  <PackageReference Include="RazorEngine.NetCore" Version="3.1.0" />
</ItemGroup>
  • 使用方式:指定模型,内容,模板名称即可
var code="模板内容";
var key="模板名";
//模型名称
var model=new DevProjectRazorRenderModel();
RazorEngine.Engine.Razor.RunCompile(new LoadedTemplateSource(code), key, model.GetType(), model);
  • 模板内容的写法

    • 我这里定义了固定模型,所以需要先什么模型的变量,这里指定了 gen
    • 模板语法文档
@{
var gen = Model as ZhonTai.Module.Dev.DevProjectRazorRenderModel;
}
  • 这里定义了一个公共的项目模型,后续增加项目模型字段的信息都无需更搞代码,生成即可,另外也可以再属性模型中添加字典等属性,即可灵魂的再模板中使用动态配置了
//模型渲染
var gen = new DevProjectRazorRenderModel()
{
    Project = Mapper.Map<DevProjectGetOutput>(project),
    Model = Mapper.Map<DevProjectModelGetOutput>(model),
    Fields = Mapper.Map<List<DevProjectModelFieldGetOutput>>(modelFields),
};

模板内容的生成

  • 这里分为了两部分,一个是文件路径,一个是文件内容
  • 因为要生成的路径可能也会包含一些模块或者模型的信息,所以可以将模板路径也使用模板生成,这里直接拼接一个模板即可
var pathCodeText = @"
@{
var gen = Model as ZhonTai.Module.Dev.DevProjectRazorRenderModel;
}
" + outPath;
//转换路径
var outPath = RazorCompile(gen, $"{project.Code}_{model.Code}_{tpl.Name}_Path.tpl", pathCodeText).Trim();

文末附完整代码

内容文件的下载

  • 因为是多模板组多模板,所以每次生成项目代码都基本是多个文件,一个个下载很明显不合理,所以可以将所有代码内容文件打包成压缩包进行下载,下面是核心压缩代码,无需引用包,暂时只在Windows中测试使用
  • 完整代码如下,做了一些文件和文件夹的判断处理,可自行封装
  • 通过GenerateAsync获取到文件信息后写入文件,再将目录进行打包返回即可
/// <summary>
/// 下载
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
public async Task<ActionResult> DownAsync(DevProjectGenGenerateInput input)
{
    var path = Path.Combine(AppContext.BaseDirectory, "DownCodes", DateTime.Now.ToString("yyyyMMddHHmmss"));
    var zipFileName = $"源码{DateTime.Now.ToString("yyyyMMddHHmmss")}.zip";
    var zipPath = Path.Combine(AppContext.BaseDirectory, "DownCodes", zipFileName);
    try
    {
        if (!Directory.Exists(path))
        {
            Directory.CreateDirectory(path);
        }
        //获取内容信息
        var codes = await GenerateAsync(input);
        foreach (var code in codes)
        {
            var codePath = Path.Combine(path, code.Path);
            var directory = Path.GetDirectoryName(codePath);
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }
            if (!File.Exists(codePath))
            {
                using (var fs = File.Open(codePath, FileMode.Create, FileAccess.ReadWrite))
                {
                    await fs.WriteAsync(Encoding.UTF8.GetBytes(code.Content));
                }
            }
        }
        ZipFile.CreateFromDirectory(path, zipPath);
        var bytes = await File.ReadAllBytesAsync(zipPath);
        return new FileContentResult(bytes, "application/zip")
        {
            FileDownloadName = zipFileName
        };
    }
    finally
    {
        if (Directory.Exists(path))
        {
            Directory.Delete(path, true);
        }
        if (File.Exists(zipPath))
        {
            File.Delete(zipPath);
        }
    }
}

前端对应需要支持文件下载,可以修改为生成对应下载URL,用GET请求直接打开新窗口进行下载

项目中前端页面的重点

整个框架主要使用者肯定是.net开发,如果没有写过 vue 项目,写起来的时候可能会有一些吃力,但因为现在有了代码生成器,大部分代码都可以生成,也可以做参考,所以这里只做一些关键点的说明

框架页面菜单的添加说明:具体可参考前文进行创建

  • 系统管理-添加视图->指定 vue 页面文件的路径,可再权限菜单中复用这个视图生成不同的路由地址
  • 接口管理-同步接口->将后端服务映射为权限点,对应后端功能权限
  • 权限管理-添加权限->添加菜单,功能点
  • 用户-角色->通过角色分配角色权限,用户管理角色

Vue 文档

有时间的话至少过一遍再开始,磨刀不误砍柴工

生命周期的一定花时间看看:官方文档

页面中获取路由信息与页面跳转

以项目生成页面调整到预览页面为例

  • 项目生成页和预览页引入定义
//引入路由
import { useRoute, useRouter } from 'vue-router'

//路由信息
const route = useRoute()
//路由跳转
const router = useRouter()
  • 跳转到预览页
  router.push({
    path: '/dev/dev-project-gen/preview', query: {
      projectId: row.projectId,
      groupIds: row.groupIds_Values
    }
  })
  • 预览页获取生成列表传递的参数
//从路由中获取query参数
onMounted(() => {
  state.filter.projectId = route.query.projectId
  state.filter.groupIds = route.query.groupIds
})

左侧树/列表右侧预览实现

将左侧的列表列封装为一个组件,右侧如果简单可封装,也可以直接写到预览页面

这里参考框架中用户列表做的

<my-layout class="my-layout">
    <pane size="30" min-size="20" max-size="35">
      <div class="my-flex-column w100 h100">
        <group-template-menu :groupIds="state.filter.groupIds" :projectId="state.filter.projectId"
          @node-click="onNodeClick" select-first-node></group-template-menu>
      </div>
    </pane>
    <pane size="70" v-loading="state.loading">
      <div class="my-flex-column w100 h100">
          内容预览部分
      </div>
    </pane>
  </my-layout>

当然,封装了组件记得引入用到的组件

const GroupTemplateMenu = defineAsyncComponent(() => import('./components/dev-group-template-menu.vue'))
const MyLayout = defineAsyncComponent(() => import('/@/components/my-layout/index.vue'))

组件中获取传递的参数示例

interface Props {
  modelValue: number[] | null | undefined
  selectFirstNode: boolean,
  projectId: number,
  groupIds: number[]
}

const props = withDefaults(defineProps<Props>(), {
  modelValue: () => [],
  selectFirstNode: false,
  projectId: 0,
  groupIds: () => []
})

在预览左侧菜单中,我们可以看到一个标记的小图标,用来直接编辑模板

这个弹窗其实是直接引用了编辑模板的组件

<template>
...
<dev-template-form ref="devTemplateFormRef" :title="'编辑模板'"></dev-template-form>
...
</template>
<script>
...
// 引入组件
const DevTemplateForm = defineAsyncComponent(() => import('../../dev-template/components/dev-template-form.vue'))
//使用
const devTemplateFormRef = ref()
const editTemplate = (node, data) => {
  devTemplateFormRef.value.open({
    id: data.id
  })
}
...
</script>

defineExpose({
  open,
})

如上可以看到我们使用和组件同名的 const devTemplateFormRef = ref() 即可获取到组件引用

另外调用的方法可以查看 dev-template-form.vue 是开放了方法的

defineExpose({
  open,
})

下载压缩包文件

首先需要后端返回文件流,然后调用对应接口的时候指定format格式为blob,并创建下载连接点击即可

const genCode = async (row: DevProjectGenGetOutput) => {
  new DevProjectGenApi().down({ projectId: row.projectId, groupIds: row.groupIds_Values?.map(s => Number(s)) }, {
    loading: false,
    showErrorMessage: false,
    format: 'blob'
  })
    .then((res) => {
      const a = document.createElement('a');
      a.href = URL.createObjectURL(res as Blob);
      a.download = '源码.zip';
      a.click();
    });
}

后语

本文所有代码皆在 yimogit/Emo.Dev 仓库中可以找到,觉得有用的来个 Star 吧

基于前面代码生成器生成的功能模块上,周末花了两天完善项目生成,终于算是搞定

后面还需要逐步完善生成器,欢迎点个赞,留个言,交流指点一二

相关文档

与使用中台 Admin.Core 实现了一个Razor模板的通用代码生成器相似的内容:

使用中台 Admin.Core 实现了一个Razor模板的通用代码生成器

前言 前面使用 Admin.Core 的代码生成器生成了通用代码生成器的基础模块 分组,模板,项目,项目模型,项目字段的基础功能,本篇继续完善,实现最核心的模板生成功能,并提供生成预览及代码文件压缩下载 准备 首先清楚几个模块的关系,如何使用,简单画一个流程图 前面完成了基础的模板组,模板管理,项目

中台框架模块开发实践-代码生成器的添加及使用

前言 之前已经分享过几篇关于中台项目框架的文章,相关介绍就不再赘述 所谓工欲善其事必先利其器,一个项目拥有一个代码生成器是很有必要的,能够大大的节省时间,减少手误,提供开发效率(ps:特别小团队搞微服务但是没有代码生成器,简直要了老命) 本文将分享如何在中台框架项目 Admin.Core 中添加代码

02.前后端分离中台框架前端 admin.ui.plus 学习-介绍与简单使用

## 中台框架前台项目 admin.ui.plus 的初识 > 基于 vue3.x + CompositionAPI setup 语法糖 + typescript + vite + element plus + vue-router-next + pinia 技术,内置支持一键生成微服务接口,适配手

03.前后端分离中台框架 zhontai 项目代码生成器的使用

使用可视化代码生成器生成zhontai框架的前后台代码

浏览器DevTools使用技巧

我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。 本文作者:正则 作为一名前端开发人员,平时开发中使用最多的就是 Chrome devtools,但可能很多同学像我一样平时用的最多也就 Console、Elements

解密Elasticsearch:深入探究这款搜索和分析引擎

最近使用Elasticsearch实现画像系统,实现的dmp的数据中台能力。同时调研了竞品的架构选型。以及重温了redis原理等。特此做一次es的总结和回顾。网上没看到有人用Elasticsearch来完成画像的。我来做第一次尝试。

antd 3.x升4.x踩坑之路~

我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。 兼容性问题 第三方依赖兼容问题 React - 最低 v16.9,部分组件使用 hooks 重构 react升级相关文档 Less - 最低 v3.1.0,建议升级到

React跨路由组件动画

我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。 本文作者:佳岚 回顾传统React动画 对于普通的 React 动画,我们大多使用官方推荐的 react-transition-group,其提供了四个基本组件 Tra

流程图渲染方式:Canvas vs SVG

我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。 本文作者:霁明 背景 我们产品中会有一些流程图应用,例如审批中心的审批流程图: 我们数栈产品内的流程图,基本都是使用的 mxGraph 实现的,mxGraph 使用了S

Jest + React 单元测试最佳实践

我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。 前言 单元测试是一种用于测试“单元”的软件测试方法,其中“单元”的意思是指软件中各个独立的组件或模块。开发者需要为他们的代码编写测试用例以确保这些代码可以正常使用。 在