找回密码
 立即注册
首页 业界区 安全 Net如何自定义优雅实现代码生成器

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

毡轩 2 小时前
需求分析与场景定义


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

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

  • Vue3前端框架
  • IDS4单点登录系统
  • 一库多租户解决方案
  • 多级缓存机制
  • CAP事件集成
  • SignalR实时通信
  • 领域驱动设计
  • AI智能体框架
  • RabbitMQ消息队列
  • 项目地址:github:https://github.com/junkai-li/NetCoreKevin
    Gitee: https://gitee.com/netkevin-li/NetCoreKevin
第一步:配置模板

模板配置示例如下图所示:
1.png

创建kevin.CodeGenerator模块

ICodeGeneratorService接口定义
  1. using kevin.CodeGenerator.Dto;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace kevin.CodeGenerator
  8. {
  9.     public interface ICodeGeneratorService
  10.     {
  11.         /// <summary>
  12.         /// 获取区域名称列表
  13.         /// </summary>
  14.         /// <returns></returns>
  15.         Task<List<string>> GetAreaNames();
  16.         /// <summary>
  17.         /// 获取区域名称下面的表列表
  18.         /// </summary>
  19.         /// <returns></returns>
  20.         Task<List<EntityItemDto>> GetAreaNameEntityItems(string areaName);
  21.         /// <summary>
  22.         /// 生成代码
  23.         /// </summary>
  24.         /// <param name="entityItems"></param>
  25.         /// <returns></returns>
  26.         Task<bool> BulidCode(List<EntityItemDto> entityItems);
  27.     }
  28. }
复制代码
CodeGeneratorService实现
  1. using kevin.CodeGenerator.Dto;
  2. using Microsoft.CodeAnalysis;
  3. using Microsoft.CodeAnalysis.CSharp;
  4. using Microsoft.CodeAnalysis.CSharp.Syntax;
  5. using Microsoft.Extensions.Options;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Reflection.Metadata;
  11. using System.Text;
  12. using static Microsoft.CodeAnalysis.CSharp.SyntaxTokenParser;
  13. namespace kevin.CodeGenerator
  14. {
  15.     public class CodeGeneratorService : ICodeGeneratorService
  16.     {
  17.         private CodeGeneratorSetting _config;
  18.         public CodeGeneratorService(IOptionsMonitor config)
  19.         {
  20.             _config = config.CurrentValue;
  21.         }
  22.         public async Task<List<string>> GetAreaNames()
  23.         {
  24.             return _config.CodeGeneratorItems.Select(t => t.AreaName).ToList();
  25.         }
  26.         public async Task<List<EntityItemDto>> GetAreaNameEntityItems(string areaName)
  27.         {
  28.             var area = _config.CodeGeneratorItems.FirstOrDefault(t => t.AreaName == areaName);
  29.             if (area != default)
  30.             {
  31.                 var entityItems = new List<EntityItemDto>();
  32.                 var path = "..\\..\" + area.AreaPath.Trim().Replace(".", "\");
  33.                 // 遍历路径下的所有 .cs 文件
  34.                 if (!Directory.Exists(path))
  35.                 {
  36.                     throw new ArgumentException($"CodeGeneratorSetting配置:{areaName}{area.AreaPath}不存在");
  37.                 }
  38.                 else
  39.                 {
  40.                     var csFiles = Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories);
  41.                     foreach (var file in csFiles)
  42.                     {
  43.                         // 读取文件内容
  44.                         var code = File.ReadAllText(file);
  45.                         var tree = CSharpSyntaxTree.ParseText(code);
  46.                         var root = (CompilationUnitSyntax)tree.GetRoot();
  47.                         // 查找所有类声明
  48.                         var classDeclarations = root.DescendantNodes().OfType<ClassDeclarationSyntax>();
  49.                         foreach (var classDeclaration in classDeclarations)
  50.                         {
  51.                             // 检查类是否有 Table 特性
  52.                             if (classDeclaration.AttributeLists.Any(list =>
  53.                                 list.Attributes.Any(attr =>
  54.                                     attr.Name.ToString() == "Table")))
  55.                             {
  56.                                 string description = "";
  57.                                 // 检查类是否有 Description 特性
  58.                                 var descriptionAttr = classDeclaration.AttributeLists
  59.                                     .SelectMany(list => list.Attributes)
  60.                                     .FirstOrDefault(attr => attr.Name.ToString() == "Description");
  61.                                 if (descriptionAttr != null)
  62.                                 {
  63.                                     // 获取特性参数值
  64.                                     var arg = descriptionAttr.ArgumentList?.Arguments.FirstOrDefault();
  65.                                     if (arg?.Expression is LiteralExpressionSyntax literal)
  66.                                     {
  67.                                         description = literal.Token.ValueText;
  68.                                     }
  69.                                 }
  70.                                 entityItems.Add(new EntityItemDto
  71.                                 {
  72.                                     AreaName = area.AreaName,
  73.                                     EntityName = classDeclaration.Identifier.Text,
  74.                                     Description = $"{file}: {description}"
  75.                                 });
  76.                             }
  77.                         }
  78.                     }
  79.                     return entityItems;
  80.                 }
  81.             }
  82.             return new List<EntityItemDto>();
  83.         }
  84.         public async Task<bool> BulidCode(List<EntityItemDto> entityItems)
  85.         {
  86.             //获取对应的模板文件
  87.             var iRpTemplate = GetBuildCodeTemplate("IRp");
  88.             var rpTemplate = GetBuildCodeTemplate("Rp");
  89.             var iServiceTemplate = GetBuildCodeTemplate("IService");
  90.             var service = GetBuildCodeTemplate("Service");
  91.             foreach (var item in entityItems)
  92.             {
  93.                 var area = _config.CodeGeneratorItems.FirstOrDefault(t => t.AreaName == item.AreaName);
  94.                 if (area != default)
  95.                 {
  96.                     if (item.EntityName.StartsWith("T", StringComparison.OrdinalIgnoreCase))
  97.                     {
  98.                         item.EntityName = item.EntityName.Substring(1);
  99.                     }
  100.                     WriteCode(new Dictionary<string, string>
  101.                     {
  102.                         {  "%entityName%",item.EntityName},
  103.                         {  "%namespacePath%",area.IRpBulidPath}
  104.                     }, iRpTemplate, $"../../{area.IRpBulidPath.Trim().Replace(".", "\")}/I{item.EntityName}Rp.cs");
  105.                     WriteCode(new Dictionary<string, string>
  106.                     {
  107.                         {  "%entityName%",item.EntityName},
  108.                         {  "%namespacePath%",area.RpBulidPath}
  109.                     }, rpTemplate, $"../../{area.RpBulidPath.Trim().Replace(".", "\")}/{item.EntityName}Rp.cs");
  110.                     WriteCode(new Dictionary<string, string>
  111.                     {
  112.                         {  "%entityName%",item.EntityName},
  113.                         {  "%namespacePath%",area.IServiceBulidPath}
  114.                     }, iServiceTemplate, $"../../{area.IServiceBulidPath.Trim().Replace(".", "\")}/I{item.EntityName}Service.cs");
  115.                     WriteCode(new Dictionary<string, string>
  116.                     {
  117.                         {  "%entityName%",item.EntityName},
  118.                         {  "%namespacePath%",area.ServiceBulidPath}
  119.                     }, service, $"../../{area.ServiceBulidPath.Trim().Replace(".", "\")}/{item.EntityName}Service.cs");
  120.                 }
  121.             }
  122.             return true;
  123.         }
  124.         /// <summary>
  125.         /// 获取对应模板文件
  126.         /// </summary>
  127.         /// <param name="name"></param>
  128.         /// <returns></returns>
  129.         private string GetBuildCodeTemplate(string name)
  130.         {
  131.             return File.ReadAllText("..\\..\" + "Kevin\\kevin.Module\\kevin.CodeGenerator\\BuildCodeTemplate\" + name + ".txt", encoding: Encoding.UTF8);
  132.         }
  133.         /// <summary>
  134.         /// 生成文件和代码
  135.         /// </summary>
  136.         /// <param name="paramters"></param>
  137.         /// <param name="content"></param>
  138.         /// <param name="savePath"></param>
  139.         private void WriteCode(Dictionary<string, string> paramters, string content, string savePath)
  140.         {
  141.             foreach (var item in paramters)
  142.             {
  143.                 content = content.Replace(item.Key, item.Value);
  144.             }
  145.             var dir = Path.GetDirectoryName(savePath);
  146.             if (!Directory.Exists(dir))
  147.             {
  148.                 Directory.CreateDirectory(dir);
  149.             }
  150.             if (File.Exists(savePath))
  151.             {
  152.                 Console.WriteLine($"文件{savePath}已存在,跳过生成!");
  153.             }
  154.             else
  155.             {
  156.                 File.WriteAllText(savePath, content, Encoding.UTF8);
  157.             }
  158.         }
  159.     }
  160. }
复制代码
CodeGeneratorSettingDto
  1. namespace kevin.CodeGenerator.Dto
  2. {
  3.     public class CodeGeneratorSetting
  4.     {
  5.         /// <summary>
  6.         /// 配置文件相关信息
  7.         /// </summary>
  8.         public List CodeGeneratorItems { get; set; } = new();
  9.     }
  10.     public class CodeGeneratorItem
  11.     {
  12.         /// <summary>
  13.         /// 区域
  14.         /// </summary>
  15.         public string AreaName { get; set; } = "";
  16.         /// <summary>
  17.         /// 数据库实体类路径
  18.         /// </summary>
  19.         public string AreaPath { get; set; } = "";
  20.         /// <summary>
  21.         /// 仓储接口生成路径
  22.         /// </summary>
  23.         public string IRpBulidPath { get; set; } = "";
  24.         /// <summary>
  25.         /// 仓储生成路径
  26.         /// </summary>
  27.         public string RpBulidPath { get; set; } = "";
  28.         /// <summary>
  29.         /// 服务接口生成路径
  30.         /// </summary>
  31.         public string IServiceBulidPath { get; set; } = "";
  32.         /// <summary>
  33.         /// 服务生成路径
  34.         /// </summary>
  35.         public string ServiceBulidPath { get; set; } = "";
  36.     }
  37. }
复制代码
配置Json文件
  1.   ////代码生成器配置 .转换成/时要和路径一致 请配置好命名空间和路径对应关系
  2.   "CodeGeneratorSetting": {
  3.     "CodeGeneratorItems": [
  4.       {
  5.         "AreaName": "App.WebApi.v1", //项目命名
  6.         "AreaPath": "App.Domain.Entities", //实体类路径
  7.         "IRpBulidPath": "App.Domain.Interfaces.Repositorie.v1", //仓储接口命名空间和路径
  8.         "RpBulidPath": "App.RepositorieRps.Repositories.v1", //仓储命名空间和路径
  9.         "IServiceBulidPath": "App.Domain.Interfaces.Services.v1", //服务接口命名空间和路径
  10.         "ServiceBulidPath": "App.Application.Services.v1" //服务命名空间和路径
  11.       }
  12.     ]
  13.   }
复制代码
服务注入
  1. services.AddKevinCodeGenerator(options =>
  2. {
  3.      var settings = Configuration.GetRequiredSection("CodeGeneratorSetting").Get()!;
  4.      options.CodeGeneratorItems = settings.CodeGeneratorItems;
  5. });
复制代码
使用
2.png

3.png


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册