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

前言

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

准备

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

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

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

  • 项目生成管理,支持多模板组
  • 模板生成预览,预览页可以直接编辑模板
  • 模板生成,将生成压缩包并下载

实现

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

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

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

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

模板引擎的使用

  • 项目中引用包:RazorEngine.NetCore

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

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

模板内容的生成

  • 这里分为了两部分,一个是文件路径,一个是文件内容
  • 因为要生成的路径可能也会包含一些模块或者模型的信息,所以可以将模板路径也使用模板生成,这里直接拼接一个模板即可
csharp 复制代码
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获取到文件信息后写入文件,再将目录进行打包返回即可
csharp 复制代码
/// <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 文档

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

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

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

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

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

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

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

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

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

ini 复制代码
<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>

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

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

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

typescript 复制代码
interface Props {
  modelValue: number[] | null | undefined
  selectFirstNode: boolean,
  projectId: number,
  groupIds: number[]
}

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

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

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

xml 复制代码
<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 是开放了方法的

scss 复制代码
defineExpose({
  open,
})

下载压缩包文件

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

ini 复制代码
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 吧

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

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

相关文档

相关推荐
追逐时光者39 分钟前
免费、简单、直观的数据库设计工具和 SQL 生成器
后端·mysql
初晴~1 小时前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
盖世英雄酱581361 小时前
InnoDB 的页分裂和页合并
数据库·后端
小_太_阳2 小时前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾2 小时前
scala借阅图书保存记录(三)
开发语言·后端·scala
星就前端叭2 小时前
【开源】一款基于Vue3 + WebRTC + Node + SRS + FFmpeg搭建的直播间项目
前端·后端·开源·webrtc
小林coding3 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
AI理性派思考者3 小时前
【保姆教程】手把手教你在Linux系统搭建早期alpha项目cysic的验证者&证明者
后端·github·gpu
从善若水4 小时前
【2024】Merry Christmas!一起用Rust绘制一颗圣诞树吧
开发语言·后端·rust
机器之心4 小时前
终于等来能塞进手机的文生图模型!十分之一体量,SnapGen实现百分百的效果
人工智能·后端