X-Admin&ABP框架开发-代码生成器

  在日常开发中,有时会遇到一些相似的代码,甚至是只要CV一次,改几个名称,就可以实现功能了,而且总归起来,都可以由一些公用的页面更改而来,因此,结合我日常开发中使用到的页面,封装一个适合自己的代码生成器,仅处于入门阶段,包括生成的代码结构都仅是把框架展示出来,内部详细暂时没得,针对于应用服务中的接口和实现,相关Dto,MVC中的控制器、视图及视图模型进行了模板制作及生成相关的文件。

 

一、设计思路

  方案一:开始想到的是,搞个控制台,然后给一个.cs文件,然后控制台去解析其中的命名空间,类名,属性,再用配置好的razor模板去替换,再生成相关的一些文件出来,但是发现,万事开头难,第一步去解析cs文件就不好搞,找了网上的资料,不太好弄,干脆想了下,放弃这种方案,因为想到了另一种常用的方案。

 

  方案二:直接在控制台中,配置控制台去访问数据库,然后给定指定表名,去读取数据库中的表和字段,再反过来去生成相关文件,但是这里会遇到一个这样的问题,比如我使用的是mysql,mysql本身有个配置表名大小写忽视的,这样一来,获取到的表名都将是小写打头,尽管可能配置了是区分大小写,但是,我设计表时,采用Pre_table,形式区分业务表,比如是CRM模块需要用到的CRM_Client,那将用CRM打头,后面这部分Client才是实际代码中的类名,种种问题都有可能,但是作为没有那么多可能性下,比如没得前缀,不区分大小写,形式简单,那么可以考虑使用。此时,想到了abp中的Migrator控制台并想到了方案三。

 

  方案三:如果说直接搞一个控制台在代码中,模仿Abp自带的Migrator一样,启动后,给定类名,通过反射去取得该类的属性名,岂不是美滋滋,需要哪个类的相关文件,只需启动,然后输入类名,即可得到相关的文件。这几种方案的前提都是在Dto文件中会展示所有实体字段,如果需要选择性的使用字段,则还需借助人工智能,以人力去完成更改生成的文件。

 

二、Razor引擎的使用

  我选择了方案二作为入手去实现,并且采用Razor引擎作为模板解析的工具。Nuget引入RazorEngine.NetCore包,开始实现依靠模板生成代码。 

1、先尝试下Razor引擎,控制台中CV下Razor引擎提供的Demo,引入相关命名空间,学习下如何去使用。

string template = "Hello @Model.Name, welcome to RazorEngine!";
var result = Engine.Razor.RunCompile(template, "templateKey", null, new { Name = "World" });
Console.WriteLine(result);

 运行完毕,可以获取到运行结果,需要注意的是,如果是在linux或是mac跑会得到错误,该问题是Razor引擎本身的问题,暂时只能在window下跑。

 

2、熟悉了下Razor的使用方式后,开始使用简单文件形式填充一些数据模拟生成过程。

 

首先,一个文件作为填充模板,一个文件内存储Json数据作为数据源,程序启动时加载两个文件。

var templatePage = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SimpleCOders\\Templates", "templatePage.txt");
TemplatePage = File.ReadAllText(templatePage);

var templatePageJson = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SimpleCOders\\Templates", "templatePageJson.json");
TemplatePageJson = File.ReadAllText(templatePageJson);

其次,数据源整理成相应类结构,得到批量待解析数据。

var templatePageJsonList = JsonConvert.DeserializeObject<List<PageDataModel>>(TemplatePageJson);

foreach (var templatePageJson in templatePageJsonList)
{
    RazorParse(
        templatePageJson.Index ?? 1,
        templatePageJson.Date,
        templatePageJson.Index - 1,
        templatePageJson.Index + 1,
        templatePageJson.Content
    );
}

最后,设计一下解析器,将读取到的数据源,进行解析成相关的类,然后依次按照模板生成文件

var entityResult = Engine.Razor.RunCompile(TemplatePage, "templatePageKey", null, new
{
    PostData = (date ?? DateTime.Now).ToString("yyyy-MM-dd"),
    PrevIndex = prev.Value,
    NextIndex = next.Value,
    ContentHtml = content
});

按照一条数据便是一个模板文件去生成可以得到批量生成文件。

 

三、适合自己的简单代码生成器

  开始着手适合自己的简单代码生成器,思路一致,只是增加了需要读取数据库这一环节。

1、模板制作,以应用服务接口为例,常用的增删改查进行封装,利用Razor语法进行填充处理,此处对于主键类型,没有进行处理,只能支持诸如int、long之类的,后期在继续优化。

using Abp.Application.Services;
using Abp.Application.Services.Dto;
using System.Collections.Generic;
using System.Threading.Tasks;
using @Model.ProjectNameSpace.@Model.ProjectModule.@(Model.EntityName)s.Dto;

namespace @Model.ProjectNameSpace.@Model.ProjectModule.@(Model.EntityName)s
{
    /// <summary>
    /// @(Model.EntityDescription)应用服务接口
    /// </summary>
    public interface I@(Model.EntityName)AppService : IApplicationService
    {
        /// <summary>
        /// 获取@(Model.EntityDescription)数据集合
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        Task<PagedResultDto<@(Model.EntityName)ListDto>> GetPaged@(Model.EntityName)(GetPaged@(Model.EntityName)Input input);

        /// <summary>
        /// 获取@(Model.EntityDescription)编辑信息
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        Task<Get@(Model.EntityName)ForEditOutput> Get@(Model.EntityName)ForEdit(NullableIdDto<@Model.EntityKeyType> input);

        /// <summary>
        /// 创建或修改@(Model.EntityDescription)信息
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        Task CreateOrUpdate@(Model.EntityName)(CreateOrUpdate@(Model.EntityName)Input input);

        /// <summary>
        /// 删除@(Model.EntityDescription)
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        Task Delete@(Model.EntityName)(List<EntityDto<@Model.EntityKeyType>> inputs);
    }
}

 2、设置相应的解析器,与之前的尝试不同,这次使用了具体的类型,这是Razor中的另一种方式,解析完毕后将文件按照指定路径保存,尽量符合项目的路径存储。

var iRazorAppService = Engine.Razor.RunCompile(IRazorAppService, nameof(IRazorAppService), typeof(TemplateParseModel), templateParseModel);
UtilHelper.Save(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, applicationPath, $"I{templateParseModel.EntityName}AppService.cs"), iRazorAppService);
builder.Append(iRazorAppService);

 3、数据库连接读取表结构,控制台下,采用直接读取的形式,不走DbContext方式,Nuget中引入MySql.Data包(我本地用的Mysql),增加Appsettings.json文件并配置好连接字符串,用sql语句形式直接读取数据库中的信息,此处封装了一个DbHelper类及将读取到的信息封装到指定类中。

using (var SqlConnection = new MySqlConnection(connectionStr))
{
    SqlConnection.Open();
    var columsInfo = string.Format(@"select table_name,column_name,ordinal_position,is_nullable,data_type,character_maximum_length,column_key,column_comment
        from information_schema.COLUMNS
        where table_schema = '{0}' and table_name = '{1}'", dbschema, tablename);

    MySqlCommand mySqlCommand = new MySqlCommand(columsInfo, SqlConnection);
    MySqlDataReader dataReader = mySqlCommand.ExecuteReader();

    List<ColumnInfo> sqlDatasList = new List<ColumnInfo>();
    while (dataReader.Read())
    {
        var columnInfo = new ColumnInfo()
        {
            TableName = dataReader[dataReader.GetName(0)].ToString(),
            Name = dataReader[dataReader.GetName(1)].ToString(),
            OrdinalPosition = StringExtension.GetValueOrNull<int>(dataReader[dataReader.GetName(2)].ToString()),
            IsNullable = dataReader[dataReader.GetName(3)].ToString(),
            DataType = dataReader[dataReader.GetName(4)].ToString(),
            CharacterMaximumLength = StringExtension.GetValueOrNull<int>(dataReader[dataReader.GetName(2)].ToString()),
            ColumnKey = dataReader[dataReader.GetName(6)].ToString(),
            ColumnComment = dataReader[dataReader.GetName(7)].ToString(),
        };
        sqlDatasList.Add(columnInfo);
    }

    dataReader.Close();
    SqlConnection.Close();
    return sqlDatasList;

4、启动后输入表名、实体名、实体描述(并未保存到数据库中),再通过手动将其加入到项目中,诸如命名空间及模块名称都加入到了配置文件中,方便配置,至少相对手动去一个个添加来讲,减少了部分工作量,也达到了辅助的效果,但是要达到全面辅助,还得在进行继续优化,针对其中的类等等,暂时没有加入属性,只放置了Id、Name等等,之后得考虑把数据库中字段也循环输出到模板文件中。

 至此,依靠Razor引擎制作一个简单的(算是减少了工作量)代码生成器初步完成了,年后继续完善,加入丰富的功能,并移入到框架中作为提高生产力的手段。新年快乐~

 

 仓库地址:https://gitee.com/530521314/Partner.TreasureChest.git

2020-01-01,望技术有成后能回来看见自己的脚步
posted @ 2020-01-01 00:02  微笑刺客D  阅读(1035)  评论(3编辑  收藏
返回顶部