Net如何自定义优雅实现代码生成器

需求分析与场景定义

  • 明确代码生成器的目标:生成实体类、API控制器、DTO等常见场景
  • 典型应用场景:快速开发CRUD功能、减少重复编码工作
  • 核心需求:可配置性、模板灵活性、与项目结构无缝集成

具体实现可参考NetCoreKevin的kevin.CodeGenerator模块

基于.NET构建的企业级SaaS智能应用架构,采用前后端分离设计,具备以下核心特性:
前端技术:

第一步:配置模板

模板配置示例如下图所示:
在这里插入图片描述

创建kevin.CodeGenerator模块

ICodeGeneratorService接口定义

using kevin.CodeGenerator.Dto;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace kevin.CodeGenerator
{
    public interface ICodeGeneratorService
    {
        /// <summary>
        /// 获取区域名称列表
        /// </summary>
        /// <returns></returns>
        Task<List<string>> GetAreaNames();

        /// <summary>
        /// 获取区域名称下面的表列表
        /// </summary>
        /// <returns></returns>
        Task<List<EntityItemDto>> GetAreaNameEntityItems(string areaName);

        /// <summary>
        /// 生成代码
        /// </summary>
        /// <param name="entityItems"></param>
        /// <returns></returns>
        Task<bool> BulidCode(List<EntityItemDto> entityItems);
    }
}

CodeGeneratorService实现

using kevin.CodeGenerator.Dto;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using static Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser;


namespace kevin.CodeGenerator
{
    public class CodeGeneratorService : ICodeGeneratorService
    {
        private CodeGeneratorSetting _config;

        public CodeGeneratorService(IOptionsMonitor<CodeGeneratorSetting> config)
        {
            _config = config.CurrentValue;
        }
        public async Task<List<string>> GetAreaNames()
        {
            return _config.CodeGeneratorItems.Select(t => t.AreaName).ToList();
        }

        public async Task<List<EntityItemDto>> GetAreaNameEntityItems(string areaName)
        {
            var area = _config.CodeGeneratorItems.FirstOrDefault(t => t.AreaName == areaName);
            if (area != default)
            {
                var entityItems = new List<EntityItemDto>();
                var path = "..\\..\\" + area.AreaPath.Trim().Replace(".", "\\");
                // 遍历路径下的所有 .cs 文件 
                if (!Directory.Exists(path))
                {
                    throw new ArgumentException($"CodeGeneratorSetting配置:{areaName}{area.AreaPath}不存在");
                }
                else
                {
                    var csFiles = Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories);
                    foreach (var file in csFiles)
                    {
                        // 读取文件内容
                        var code = File.ReadAllText(file);
                        var tree = CSharpSyntaxTree.ParseText(code);
                        var root = (CompilationUnitSyntax)tree.GetRoot();

                        // 查找所有类声明
                        var classDeclarations = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
                        foreach (var classDeclaration in classDeclarations)
                        {
                            // 检查类是否有 Table 特性
                            if (classDeclaration.AttributeLists.Any(list =>
                                list.Attributes.Any(attr =>
                                    attr.Name.ToString() == "Table")))
                            {
                                string description = "";
                                // 检查类是否有 Description 特性
                                var descriptionAttr = classDeclaration.AttributeLists
                                    .SelectMany(list => list.Attributes)
                                    .FirstOrDefault(attr => attr.Name.ToString() == "Description");

                                if (descriptionAttr != null)
                                {
                                    // 获取特性参数值
                                    var arg = descriptionAttr.ArgumentList?.Arguments.FirstOrDefault();
                                    if (arg?.Expression is LiteralExpressionSyntax literal)
                                    {
                                        description = literal.Token.ValueText;
                                    }
                                }
                                entityItems.Add(new EntityItemDto
                                {
                                    AreaName = area.AreaName,
                                    EntityName = classDeclaration.Identifier.Text,
                                    Description = $"{file}: {description}"
                                });

                            }
                        }
                    }

                    return entityItems;
                }

            }
            return new List<EntityItemDto>();
        }

        public async Task<bool> BulidCode(List<EntityItemDto> entityItems)
        {
            //获取对应的模板文件
            var iRpTemplate = GetBuildCodeTemplate("IRp");
            var rpTemplate = GetBuildCodeTemplate("Rp");
            var iServiceTemplate = GetBuildCodeTemplate("IService");
            var service = GetBuildCodeTemplate("Service");
            foreach (var item in entityItems)
            {
                var area = _config.CodeGeneratorItems.FirstOrDefault(t => t.AreaName == item.AreaName);
                if (area != default)
                {
                    if (item.EntityName.StartsWith("T", StringComparison.OrdinalIgnoreCase))
                    {
                        item.EntityName = item.EntityName.Substring(1);
                    }
                    WriteCode(new Dictionary<string, string>
                    {
                        {  "%entityName%",item.EntityName},
                        {  "%namespacePath%",area.IRpBulidPath}
                    }, iRpTemplate, $"../../{area.IRpBulidPath.Trim().Replace(".", "\\")}/I{item.EntityName}Rp.cs");
                    WriteCode(new Dictionary<string, string>
                    {
                        {  "%entityName%",item.EntityName},
                        {  "%namespacePath%",area.RpBulidPath}
                    }, rpTemplate, $"../../{area.RpBulidPath.Trim().Replace(".", "\\")}/{item.EntityName}Rp.cs");
                    WriteCode(new Dictionary<string, string>
                    {
                        {  "%entityName%",item.EntityName},
                        {  "%namespacePath%",area.IServiceBulidPath}
                    }, iServiceTemplate, $"../../{area.IServiceBulidPath.Trim().Replace(".", "\\")}/I{item.EntityName}Service.cs");
                    WriteCode(new Dictionary<string, string>
                    {
                        {  "%entityName%",item.EntityName},
                        {  "%namespacePath%",area.ServiceBulidPath}
                    }, service, $"../../{area.ServiceBulidPath.Trim().Replace(".", "\\")}/{item.EntityName}Service.cs");
                }

            }
            return true;
        }

        /// <summary>
        /// 获取对应模板文件
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private string GetBuildCodeTemplate(string name)
        {
            return File.ReadAllText("..\\..\\" + "Kevin\\kevin.Module\\kevin.CodeGenerator\\BuildCodeTemplate\\" + name + ".txt", encoding: Encoding.UTF8);
        }
        /// <summary>
        /// 生成文件和代码
        /// </summary>
        /// <param name="paramters"></param>
        /// <param name="content"></param>
        /// <param name="savePath"></param>
        private void WriteCode(Dictionary<string, string> paramters, string content, string savePath)
        {
            foreach (var item in paramters)
            {
                content = content.Replace(item.Key, item.Value);
            }
            var dir = Path.GetDirectoryName(savePath);
            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            } 
            if (File.Exists(savePath))
            {
                Console.WriteLine($"文件{savePath}已存在,跳过生成!");
            }
            else
            {
                File.WriteAllText(savePath, content, Encoding.UTF8);
            }

        }
    }
}

CodeGeneratorSettingDto

namespace kevin.CodeGenerator.Dto
{
    public class CodeGeneratorSetting
    {
        /// <summary>
        /// 配置文件相关信息
        /// </summary>
        public List<CodeGeneratorItem> CodeGeneratorItems { get; set; } = new();
    }

    public class CodeGeneratorItem
    {
        /// <summary>
        /// 区域
        /// </summary>
        public string AreaName { get; set; } = "";

        /// <summary>
        /// 数据库实体类路径
        /// </summary>
        public string AreaPath { get; set; } = "";

        /// <summary>
        /// 仓储接口生成路径
        /// </summary>
        public string IRpBulidPath { get; set; } = "";
        /// <summary>
        /// 仓储生成路径
        /// </summary>
        public string RpBulidPath { get; set; } = "";

        /// <summary>
        /// 服务接口生成路径
        /// </summary>
        public string IServiceBulidPath { get; set; } = "";

        /// <summary>
        /// 服务生成路径
        /// </summary>
        public string ServiceBulidPath { get; set; } = "";


    }
}

配置Json文件

  ////代码生成器配置 .转换成/时要和路径一致 请配置好命名空间和路径对应关系
  "CodeGeneratorSetting": {
    "CodeGeneratorItems": [
      {
        "AreaName": "App.WebApi.v1", //项目命名
        "AreaPath": "App.Domain.Entities", //实体类路径
        "IRpBulidPath": "App.Domain.Interfaces.Repositorie.v1", //仓储接口命名空间和路径
        "RpBulidPath": "App.RepositorieRps.Repositories.v1", //仓储命名空间和路径
        "IServiceBulidPath": "App.Domain.Interfaces.Services.v1", //服务接口命名空间和路径
        "ServiceBulidPath": "App.Application.Services.v1" //服务命名空间和路径
      }
    ]
  }

服务注入

 services.AddKevinCodeGenerator(options =>
 {
     var settings = Configuration.GetRequiredSection("CodeGeneratorSetting").Get<CodeGeneratorSetting>()!;
     options.CodeGeneratorItems = settings.CodeGeneratorItems;
 
 });

使用
在这里插入图片描述
在这里插入图片描述

posted @ 2025-12-23 17:50  NetCoreKevin  阅读(5)  评论(0)    收藏  举报