using Grpc.Extension.BaseService.Model; using ProtoBuf; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; namespace Grpc.Extension.Common.Internal { public static class ProtoGenerator { /// /// proto的message可能的开头的关键字 /// internal static List protoMsgStartWithKeywords { get; set; } = new List { "message", "enum" }; /// /// 添加proto /// public static void AddProto(string entityName) { if (!ProtoMethodInfo.Protos.ContainsKey(entityName)) { var msg = Serializer.GetProto(ProtoBuf.Meta.ProtoSyntax.Proto3); ProtoMethodInfo.Protos.TryAdd(entityName, msg.FilterHead().AddMessageComment()); } } /// /// 获取实体对应的proto /// internal static string GetProto(string entityName) { var rst = ProtoMethodInfo.Protos.TryGetValue(entityName, out string proto); return rst ? proto : null; } /// /// 过滤头部 只保留message部分 /// internal static string FilterHead(this string proto) { var lines = new List(); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(proto))) using (var sr = new StreamReader(ms)) { var readEnable = false; while (sr.Peek() > 0) { var line = sr.ReadLine(); if (protoMsgStartWithKeywords.Any(q => line.StartsWith(q))) { readEnable = true; } if (readEnable) { lines.Add(line); } } } return string.Join(Environment.NewLine, lines); } /// /// 生成grpc的message的proto内容 /// private static string GenGrpcMessageProto(string pkgName,string srv, List msgProtos, bool spiltProto) { var sb = new StringBuilder(); if (spiltProto) { sb.AppendLine("syntax = \"proto3\";"); if (!string.IsNullOrWhiteSpace(GrpcExtensionsOptions.Instance.ProtoNameSpace)) { sb.AppendLine("option csharp_namespace = \"" + GrpcExtensionsOptions.Instance.ProtoNameSpace.Trim() +"."+ srv + "\";"); sb.AppendLine("option java_package = \"" + GrpcExtensionsOptions.Instance.ProtoNameSpace.Trim() + "." + srv + "\";"); } if (!string.IsNullOrWhiteSpace(pkgName)) { sb.AppendLine($"package {pkgName.Trim()};"); } } sb.AppendLine(); sb.AppendLine(Environment.NewLine); //过滤重复的message var sbMsg = new StringBuilder(); foreach (var proto in msgProtos) { sbMsg.AppendLine(proto); } var msg = sbMsg.ToString(); var msgMapProtos = new Dictionary>(); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(msg))) using (var sr = new StreamReader(ms)) { var msgName = ""; var lines = new List(); while (sr.Peek() > 0) { var line = sr.ReadLine(); if (protoMsgStartWithKeywords.Any(q => line.StartsWith(q))) { msgName = line.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries)[1]; } lines.Add(line); if (line.StartsWith("}")) { if (!msgMapProtos.ContainsKey(msgName)) { msgMapProtos.Add(msgName, lines.Select(q => q).ToList()); } lines.Clear(); } } } msg = string.Join(Environment.NewLine + Environment.NewLine, msgMapProtos.Select(q => string.Join(Environment.NewLine, q.Value))); sb.Append(msg); return sb.ToString(); } /// /// 生成grpc的service的proto内容 /// private static string GenGrpcServiceProto(string msgProtoName, string pkgName, string srvName, List methodInfo, bool spiltProto) { var sb = new StringBuilder(); sb.AppendLine("syntax = \"proto3\";"); if (!string.IsNullOrWhiteSpace(GrpcExtensionsOptions.Instance.ProtoNameSpace)) { sb.AppendLine("option csharp_namespace = \"" + GrpcExtensionsOptions.Instance.ProtoNameSpace.Trim() +"."+ srvName + "\";"); sb.AppendLine("option java_package = \"" + GrpcExtensionsOptions.Instance.ProtoNameSpace.Trim() + "." + srvName + "\";"); } if (!string.IsNullOrWhiteSpace(pkgName)) { sb.AppendLine($"package {pkgName.Trim()};"); } if (spiltProto) { sb.AppendLine(string.Format("import \"{0}\";", msgProtoName)); } sb.AppendLine(Environment.NewLine); sb.AppendLine("service " + srvName + " {"); var template = @" rpc {0}({1}) returns({2})"; methodInfo.ForEach(q => { var requestName = q.RequestName; var responseName = q.ResponseName; switch (q.MethodType) { case Core.MethodType.Unary: break; case Core.MethodType.ClientStreaming: requestName = "stream " + requestName; break; case Core.MethodType.ServerStreaming: responseName = "stream " + responseName; break; case Core.MethodType.DuplexStreaming: requestName = "stream " + requestName; responseName = "stream " + responseName; break; } ProtoCommentGenerator.AddServiceComment(q, sb); sb.AppendLine(string.Format(template, q.MethodName, requestName, responseName) + ";" + Environment.NewLine); }); sb.AppendLine("}"); return sb.ToString(); } /// /// 生成proto文件 /// public static void Gen(string dir, bool spiltProto) { if (ProtoInfo.Methods == null || ProtoInfo.Methods.Count == 0) return; if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } foreach (var grp in ProtoInfo.Methods.GroupBy(q => q.ServiceName)) { var arr = grp.Key.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); if (arr.Length > 2) continue; var pkg = arr.Length == 2 ? arr[0] : null; var srv = arr.Length == 2 ? arr[1] : arr[0]; #region message var protoName = srv;//grp.Key; var msgProtoName = $"{protoName}{(spiltProto ? ".message" : "")}.proto"; var msgProtoPath = Path.Combine(dir, msgProtoName); var msgProtos = new List(); var rqNames = grp.ToList().Select(q => q.RequestName).ToList(); var rsNames = grp.ToList().Select(q => q.ResponseName).ToList(); var msgNames = rqNames.Union(rsNames).Distinct().ToList(); foreach (var n in msgNames) { msgProtos.Add(GetProto(n)); } var msgProtoContent = GenGrpcMessageProto(pkg, srv, msgProtos, spiltProto); #endregion #region service var srvProtoName = $"{protoName}{(spiltProto ? ".service" : "")}.proto"; var srvProtoPath = Path.Combine(dir, srvProtoName); var methodInfos = grp.ToList(); var srvProtoContent = GenGrpcServiceProto(msgProtoName, pkg, srv, methodInfos, spiltProto); #endregion //是否拆分message和service协议 if (spiltProto) { //写message协议文件 if (File.Exists(msgProtoPath)) File.Delete(msgProtoPath); File.AppendAllText(msgProtoPath, msgProtoContent); //写service协议文件 if (File.Exists(srvProtoPath)) File.Delete(srvProtoPath); File.AppendAllText(srvProtoPath, srvProtoContent); } else { var protoPath = Path.Combine(dir, $"{protoName}.proto"); //写协议文件 if (File.Exists(protoPath)) File.Delete(protoPath); File.AppendAllText(protoPath, srvProtoContent); File.AppendAllText(protoPath, msgProtoContent); } } } } }