Upgrade Nuget
大石头 authored at 2025-12-08 14:24:22
12.47 KiB
XCoder
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 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.FullName})p[\"{name}\"].ToInt(-1);");
                else if (!dc.Properties["Type"].IsNullOrEmpty())
                    sb.AppendLine($"        var {name} = ({dc.Properties["Type"]})p[\"{name}\"].ToInt(-1);");
                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();");

            pis.Add("start");
            pis.Add("end");
        }

        sb.AppendLine();
        sb.AppendLine($"        return {Table.Name}.Search({pis.Join(", ")}, p[\"Q\"], p);");

        return sb.Return(true).TrimEnd();
    }
    #endregion
}