386 lines
12 KiB
C#
386 lines
12 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using NewLife;
|
||
using NewLife.Collections;
|
||
using NewLife.Log;
|
||
using NewLife.Reflection;
|
||
using XCode.Code;
|
||
using XCode.DataAccessLayer;
|
||
|
||
namespace XCode;
|
||
|
||
/// <summary>魔方页面生成器</summary>
|
||
public class CubeBuilder : ClassBuilder
|
||
{
|
||
#region 属性
|
||
/// <summary>区域名</summary>
|
||
public String AreaName { get; set; }
|
||
|
||
/// <summary>根命名空间</summary>
|
||
public String RootNamespace { get; set; }
|
||
|
||
/// <summary>菜单排序</summary>
|
||
public Int32 Sort { get; set; }
|
||
|
||
/// <summary>区域模版</summary>
|
||
public String AreaTemplate { get; set; } = """
|
||
using System.ComponentModel;
|
||
using NewLife;
|
||
using NewLife.Cube;
|
||
|
||
namespace {RootNamespace}.Areas.{Name};
|
||
|
||
[DisplayName("{DisplayName}")]
|
||
public class {Name}Area : AreaBase
|
||
{
|
||
public {Name}Area() : base(nameof({Name}Area).TrimEnd("Area")) { }
|
||
}
|
||
""";
|
||
|
||
/// <summary>控制器模版</summary>
|
||
public String ControllerTemplate { get; set; } = """
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using {EntityNamespace};
|
||
using NewLife;
|
||
using NewLife.Cube;
|
||
using NewLife.Cube.Extensions;
|
||
using NewLife.Cube.ViewModels;
|
||
using NewLife.Log;
|
||
using NewLife.Web;
|
||
using XCode.Membership;
|
||
using static {EntityNamespace}.{EntityName};
|
||
|
||
namespace {RootNamespace}.Areas.{Name}.Controllers;
|
||
|
||
/// <summary>{DisplayName}</summary>
|
||
[Menu({Sort}, true, Icon = "fa-table")]
|
||
[{Name}Area]
|
||
public class {ClassName} : {BaseClass}<{EntityName}>
|
||
{
|
||
static {ClassName}()
|
||
{
|
||
//LogOnChange = true;
|
||
|
||
//ListFields.RemoveField("Id", "Creator");
|
||
ListFields.RemoveCreateField().RemoveRemarkField();
|
||
|
||
//{
|
||
// var df = ListFields.GetField("Code") as ListField;
|
||
// df.Url = "?code={Code}";
|
||
// df.Target = "_blank";
|
||
//}
|
||
//{
|
||
// var df = ListFields.AddListField("devices", null, "Onlines");
|
||
// df.DisplayName = "查看设备";
|
||
// df.Url = "Device?groupId={Id}";
|
||
// df.DataVisible = e => (e as {EntityName}).Devices > 0;
|
||
// df.Target = "_frame";
|
||
//}
|
||
//{
|
||
// var df = ListFields.GetField("Kind") as ListField;
|
||
// df.GetValue = e => ((Int32)(e as {EntityName}).Kind).ToString("X4");
|
||
//}
|
||
//ListFields.TraceUrl("TraceId");
|
||
}
|
||
|
||
//private readonly ITracer _tracer;
|
||
|
||
//public {ClassName}(ITracer tracer)
|
||
//{
|
||
// _tracer = tracer;
|
||
//}
|
||
|
||
/// <summary>高级搜索。列表页查询、导出Excel、导出Json、分享页等使用</summary>
|
||
/// <param name="p">分页器。包含分页排序参数,以及Http请求参数</param>
|
||
/// <returns></returns>
|
||
protected override IEnumerable<{EntityName}> Search(Pager p)
|
||
{
|
||
//var deviceId = p["deviceId"].ToInt(-1);
|
||
//var enable = p["enable"]?.ToBoolean();
|
||
|
||
var start = p["dtStart"].ToDateTime();
|
||
var end = p["dtEnd"].ToDateTime();
|
||
|
||
return {EntityName}.Search(start, end, p["Q"], p);
|
||
}
|
||
}
|
||
""";
|
||
#endregion
|
||
|
||
#region 静态
|
||
/// <summary>生成魔方区域</summary>
|
||
/// <param name="option">可选项</param>
|
||
/// <returns></returns>
|
||
public static Int32 BuildArea(CubeBuilderOption option, ILog log = null)
|
||
{
|
||
if (option == null)
|
||
option = new CubeBuilderOption();
|
||
else
|
||
option = option.Clone() as CubeBuilderOption;
|
||
|
||
// 自动识别并修正区域名(主要是大小写)
|
||
var areaName = FindAreaName(option.Output.GetBasePath());
|
||
if (!areaName.IsNullOrEmpty()) return 0;
|
||
|
||
// 优先使用路径最后一段作为区域名,其次再用连接名
|
||
areaName = Path.GetFileNameWithoutExtension(option.Output);
|
||
if (areaName.IsNullOrEmpty())
|
||
areaName = option.ConnName;
|
||
|
||
var file = $"{areaName}Area.cs";
|
||
file = option.Output.CombinePath(file);
|
||
file = file.GetBasePath();
|
||
|
||
// 文件已存在,不要覆盖
|
||
if (File.Exists(file)) return 0;
|
||
|
||
// 根命名空间
|
||
var root = FindProjectRootNamespace(option.Output);
|
||
if (root.IsNullOrEmpty()) root = option.ConnName + "Web";
|
||
|
||
log?.Info("生成魔方区域 {0} {1}", areaName, file);
|
||
|
||
var builder = new CubeBuilder
|
||
{
|
||
RootNamespace = root
|
||
};
|
||
|
||
var code = builder.AreaTemplate;
|
||
|
||
//code = code.Replace("{Namespace}", option.Namespace);
|
||
code = code.Replace("{RootNamespace}", builder.RootNamespace);
|
||
code = code.Replace("{Name}", areaName);
|
||
code = code.Replace("{DisplayName}", option.DisplayName);
|
||
|
||
// 输出到文件
|
||
file.EnsureDirectory(true);
|
||
File.WriteAllText(file, code, Encoding.UTF8);
|
||
|
||
return 1;
|
||
}
|
||
|
||
/// <summary>生成控制器</summary>
|
||
/// <param name="tables">表集合</param>
|
||
/// <param name="option">可选项</param>
|
||
/// <returns></returns>
|
||
public static Int32 BuildControllers(IList<IDataTable> tables, CubeBuilderOption option = null, ILog log = null)
|
||
{
|
||
if (option == null)
|
||
option = new CubeBuilderOption();
|
||
else
|
||
option = option.Clone() as CubeBuilderOption;
|
||
|
||
// 根命名空间
|
||
var root = FindProjectRootNamespace(option.Output);
|
||
if (root.IsNullOrEmpty()) root = option.ConnName + "Web";
|
||
|
||
// 自动识别并修正区域名(主要是大小写)
|
||
var areaName = FindAreaName(option.Output);
|
||
if (areaName.IsNullOrEmpty()) areaName = option.ConnName;
|
||
|
||
if (option.ClassNameTemplate.IsNullOrEmpty()) option.ClassNameTemplate = "{name}Controller";
|
||
|
||
option.Output = option.Output.CombinePath("Controllers");
|
||
|
||
log?.Info("生成控制器 {0}", option.Output.GetBasePath());
|
||
|
||
var count = 0;
|
||
var n = tables.Count;
|
||
foreach (var item in tables)
|
||
{
|
||
// 跳过排除项
|
||
if (option.Excludes.Contains(item.Name)) continue;
|
||
if (option.Excludes.Contains(item.TableName)) continue;
|
||
|
||
var builder = new CubeBuilder
|
||
{
|
||
Table = item,
|
||
Option = option.Clone(),
|
||
|
||
AreaName = areaName,
|
||
RootNamespace = root,
|
||
Sort = n * 10,
|
||
Log = log
|
||
};
|
||
|
||
if (builder.Option.BaseClass.IsNullOrEmpty())
|
||
{
|
||
if (item.InsertOnly)
|
||
builder.Option.BaseClass = "ReadOnlyEntityController";
|
||
else
|
||
builder.Option.BaseClass = "EntityController";
|
||
}
|
||
|
||
builder.Load(item);
|
||
|
||
builder.Execute();
|
||
builder.Save(null, false, false);
|
||
|
||
count++;
|
||
n--;
|
||
}
|
||
|
||
return count;
|
||
}
|
||
|
||
static String FindAreaName(String dir)
|
||
{
|
||
var di = dir.GetBasePath().AsDirectory();
|
||
if (!di.Exists) return null;
|
||
|
||
foreach (var fi in di.GetFiles("*Area.cs"))
|
||
{
|
||
var txt = File.ReadAllText(fi.FullName);
|
||
var str = txt.Substring("public class", "AreaBase")?.Trim(' ', ':');
|
||
if (!str.IsNullOrEmpty())
|
||
{
|
||
return str.TrimEnd("Area");
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// <summary>在指定目录中查找项目名</summary>
|
||
/// <param name="dir"></param>
|
||
/// <returns></returns>
|
||
static String FindProjectRootNamespace(String dir)
|
||
{
|
||
var di = dir.GetBasePath().AsDirectory();
|
||
for (var i = 0; i < 3; i++)
|
||
{
|
||
if (di.Exists)
|
||
{
|
||
foreach (var fi in di.GetFiles("*.csproj", SearchOption.TopDirectoryOnly))
|
||
{
|
||
var ns = Path.GetFileNameWithoutExtension(fi.FullName);
|
||
|
||
var xml = File.ReadAllText(fi.FullName);
|
||
if (!xml.IsNullOrEmpty())
|
||
{
|
||
var str = xml.Substring("<RootNamespace>", "</RootNamespace>");
|
||
if (!str.IsNullOrEmpty()) ns = str;
|
||
}
|
||
|
||
if (!ns.IsNullOrEmpty()) return ns;
|
||
}
|
||
}
|
||
di = di.Parent;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
#endregion
|
||
|
||
#region 方法
|
||
/// <summary>加载数据表</summary>
|
||
/// <param name="table"></param>
|
||
public override void Load(IDataTable table)
|
||
{
|
||
Table = table;
|
||
|
||
var option = Option;
|
||
|
||
// 命名空间
|
||
var str = table.Properties["Namespace"];
|
||
if (!str.IsNullOrEmpty()) option.Namespace = str;
|
||
|
||
// 输出目录
|
||
str = table.Properties["CubeOutput"];
|
||
if (!str.IsNullOrEmpty()) option.Output = str.GetBasePath();
|
||
}
|
||
|
||
/// <summary>生成前</summary>
|
||
protected override void OnExecuting()
|
||
{
|
||
var opt = Option;
|
||
var code = ControllerTemplate;
|
||
|
||
code = code.Replace("{EntityNamespace}", opt.Namespace);
|
||
code = code.Replace("{ClassName}", ClassName);
|
||
code = code.Replace("{EntityName}", Table.Name);
|
||
code = code.Replace("{RootNamespace}", RootNamespace);
|
||
code = code.Replace("{Name}", AreaName);
|
||
code = code.Replace("{DisplayName}", Table.Description);
|
||
code = code.Replace("{Sort}", Sort + "");
|
||
|
||
code = code.Replace("{BaseClass}", GetBaseClass());
|
||
|
||
if (Table.Columns.Any(c => c.Name.EqualIgnoreCase("TraceId")))
|
||
code = code.Replace("//ListFields.TraceUrl(", "ListFields.TraceUrl(");
|
||
|
||
var ss = BuildSearch();
|
||
if (!ss.IsNullOrEmpty())
|
||
{
|
||
var p1 = code.IndexOf(" //var deviceId = p[\"deviceId\"].ToInt(-1);");
|
||
var p2 = code.IndexOf("p);", p1);
|
||
|
||
code = code.Substring(0, p1) + ss + code.Substring(p2 + "p);".Length);
|
||
}
|
||
|
||
Writer.Write(code);
|
||
}
|
||
|
||
/// <summary>生成后</summary>
|
||
protected override void OnExecuted() { }
|
||
|
||
/// <summary>生成主体</summary>
|
||
protected override void BuildItems() { }
|
||
#endregion
|
||
|
||
#region 辅助
|
||
private String BuildSearch()
|
||
{
|
||
// 收集索引信息,索引中的所有字段都参与,构造一个高级查询模板
|
||
var builder = new SearchBuilder(Table) { Nullable = Option.Nullable };
|
||
var cs = builder.GetColumns();
|
||
if (cs.Count <= 0) return null;
|
||
|
||
var ps = builder.GetParameters(cs, true, true, true);
|
||
|
||
var sb = Pool.StringBuilder.Get();
|
||
|
||
var pis = new List<String>();
|
||
foreach (var dc in cs)
|
||
{
|
||
var name = dc.CamelName();
|
||
|
||
if (dc.DataType.IsInt())
|
||
{
|
||
if (dc.DataType.IsEnum)
|
||
sb.AppendLine($" var {name} = ({dc.DataType.Name})p[\"{name}\"].ToInt();");
|
||
else if (dc.DataType == typeof(Int64))
|
||
sb.AppendLine($" var {name} = p[\"{name}\"].ToLong(-1);");
|
||
else
|
||
sb.AppendLine($" var {name} = p[\"{name}\"].ToInt(-1);");
|
||
}
|
||
else if (dc.DataType == typeof(Boolean))
|
||
sb.AppendLine($" var {name} = p[\"{name}\"]?.ToBoolean();");
|
||
else if (dc.DataType == typeof(DateTime))
|
||
sb.AppendLine($" var {name} = p[\"{name}\"].ToDateTime();");
|
||
else if (dc.DataType == typeof(String))
|
||
sb.AppendLine($" var {name} = p[\"{name}\"];");
|
||
else
|
||
// 不支持的类型,跳过
|
||
continue;
|
||
|
||
pis.Add(name);
|
||
}
|
||
|
||
if (builder.DataTime != null)
|
||
{
|
||
sb.AppendLine();
|
||
sb.AppendLine(" var start = p[\"dtStart\"].ToDateTime();");
|
||
sb.AppendLine(" var end = p[\"dtEnd\"].ToDateTime();");
|
||
}
|
||
|
||
sb.AppendLine();
|
||
sb.AppendLine($" return {Table.Name}.Search({pis.Join(", ")}, p[\"Q\"], p);");
|
||
|
||
return sb.Return(true).TrimEnd();
|
||
}
|
||
#endregion
|
||
} |