csharp/agebullhu/MicroZero/src/Core/ZeroNetCore/Discover/ZeroDiscover.cs

Discover
ZeroDiscover.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Serialization;
using Agebull.Common.ApiDocameents;
using Agebull.Common.Reflection;
using Agebull.Common.Rpc;
using Agebull.ZeroNet.Core;
using Gboxt.Common.DataModel;
using Newtonsoft.Json;

namespace Agebull.ZeroNet.ZeroApi
{
    /// 
    /// ZeroStation发现工具
    /// 
    internal clast ZeroDiscover
    {
        #region API发现
        /// 
        /// 主调用程序集
        /// 
        public astembly astembly { get; set; }

        public string StationName { get; set; }

        /// 
        /// 站点文档信息
        /// 
        public Dictionary StationInfo = new Dictionary();
        public ZeroDiscover()
        {
            XmlMember.Load(GetType().astembly);
        }

        private StationDocameent _defStation;

        /// 
        /// 查找API
        /// 
        public void FindApies()
        {
            XmlMember.Load(astembly);
            StationInfo.Add(StationName, _defStation = new StationDocameent
            {
                Name = StationName
            });
            var types = astembly.GetTypes().Where(p => p.IsSubclastOf(typeof(ApiController))).ToArray();
            foreach (var type in types)
            {
                FindApi(type, false);
            }
            RegistToZero();

            RegistDocameent();
        }
        void RegistToZero()
        {
            foreach (var sta in StationInfo.Values)
            {
                if (sta.Aips.Count == 0)
                    continue;
                var station = (ApiStation)ZeroApplication.TryGetZeroObject(sta.Name);
                if (station == null)
                {
                    ZeroApplication.RegistZeroObject(station = new ApiStation
                    {
                        Name = sta.Name,
                        StationName = sta.Name
                    });
                }
                foreach (var api in sta.Aips)
                {
                    var action = (ApiActionInfo)api.Value;
                    var a = action.HaseArgument
                        ? station.RegistAction(api.Key, action.ArgumentAction, action.AccessOption, action)
                        : station.RegistAction(api.Key, action.Action, action.AccessOption, action);
                    a.ArgumenType = action.ArgumenType;
                }
            }
        }
        /// 
        /// 查找API
        /// 
        /// 
        /// 
        private void FindApi(Type type, bool onlyDoc)
        {
            if (type.IsAbstract)
                return;
            StationDocameent station;
            var sa = type.GetCustomAttribute();
            if (sa != null)
            {
                if (!StationInfo.TryGetValue(sa.Name, out station))
                {
                    StationInfo.Add(sa.Name, station = new StationDocameent
                    {
                        Name = sa.Name
                    });
                }
            }
            else
            {
                station = _defStation;
            }
            //station.Copy(XmlMember.Find(type));
            string routeHead = null;
            var attrib = type.GetCustomAttribute();
            if (attrib != null)
            {
                routeHead = attrib.Name;
            }
            else
            {
                var attrib2 = type.GetCustomAttribute();
                if (attrib2 != null)
                {
                    routeHead = attrib2.Name;
                }
            }

            if (string.IsNullOrWhiteSpace(routeHead))
                routeHead = null;
            else
                routeHead = routeHead.Trim(' ', '\t', '\r', '\n', '/') + "/";

            var methods = type.GetMethods(BindingFlags.Instance
                                                                    | BindingFlags.Public
                                                                    | BindingFlags.NonPublic);

            var xdoc = XmlMember.Find(type);
            foreach (var method in methods)
            {
                var route = method.GetCustomAttribute();
                if (route == null)
                {
                    //ZeroTrace.WriteError("ApiDiscover", "exclude", station.Name, type.Name, method.Name);
                    continue;
                }
                if (method.Name.Length > 4 && (method.Name.IndexOf("get_") == 0 || method.Name.IndexOf("set_") == 0))
                    continue;
                if (method.GetParameters().Length > 1)
                {
                    //ZeroTrace.WriteError("ApiDiscover", "argument size must 0 or 1", station.Name, type.Name, method.Name);
                    continue;
                }
                var name = route?.Name == null
                    ? $"{routeHead}{method.Name}"
                    : $"{routeHead}{route.Name.Trim(' ', '\t', '\r', '\n', '/')}";
                var accessOption = method.GetCustomAttribute();
                var ca = method.GetAttribute();
                var api = new ApiActionInfo
                {
                    Name = method.Name,
                    ApiName= route?.Name ?? method.Name,
                    RouteName = name,
                    Category = ca?.Category ?? xdoc?.Caption,
                    AccessOption = accessOption?.Option ?? ApiAccessOption.Public | ApiAccessOption.ArgumentCanNil,
                    ResultInfo = ReadEnsaty(method.ReturnType, "result")
                };
                var doc = XmlMember.Find(type, method.Name, "M");
                api.Copy(doc);

                var arg = method.GetParameters().FirstOrDefault();
                api.HaseArgument = arg != null;
                //动态生成并编译
                if (api.HaseArgument)
                {
                    api.ArgumentInfo = ReadEnsaty(arg.ParameterType, "argument") ?? new TypeDocameent();
                    api.ArgumentInfo.Name = arg.Name;
                    if (doc != null)
                        api.ArgumentInfo.Caption = doc.Arguments.Values.FirstOrDefault();

                    if (!onlyDoc)
                    {
                        api.ArgumenType = arg.ParameterType;
                        api.ArgumentAction = CreateFunc(type.GetTypeInfo(),
                            method.Name,
                            arg.ParameterType.GetTypeInfo(),
                            method.ReturnType.GetTypeInfo());
                    }
                }
                else if (!onlyDoc)
                {
                    api.Action = CreateFunc(type.GetTypeInfo(), method.Name, method.ReturnType.GetTypeInfo());
                }
                station.Aips.Add(api.RouteName, api);
            }
        }

        /// 生成动态匿名调用内部方法(参数由TArg转为实际类型后调用,并将调用返回值转为TRes)
        /// 参数类型(接口)
        /// 返回值类型(接口)
        /// 调用对象类型
        /// 原始参数类型
        /// 原始返回值类型
        /// 原始调用方法
        /// 匿名委托
        public static Func CreateFunc(TypeInfo callInfo, string methodName, TypeInfo argInfo, TypeInfo resInfo)
        {
            ConstructorInfo constructor = callInfo.GetConstructor(Type.EmptyTypes);
            if (constructor == (ConstructorInfo)null)
                throw new ArgumentException("类型" + callInfo.FullName + "没有无参构造函数");
            MethodInfo method = callInfo.GetMethod(methodName);
            if (method == (MethodInfo)null)
                throw new ArgumentException("类型" + callInfo.FullName + "没有名称为" + methodName + "的方法");
            if (method.ReturnType != (Type)resInfo)
                throw new ArgumentException("类型" + callInfo.FullName + "的方法" + methodName + "返回值不为" + resInfo.FullName);
            ParameterInfo[] parameters = method.GetParameters();
            if (parameters.Length != 1)
                throw new ArgumentException("类型" + callInfo.FullName + "的方法" + methodName + "参数不是一个");
            if (parameters[0].ParameterType != (Type)argInfo)
                throw new ArgumentException("类型" + callInfo.FullName + "的方法" + methodName + "唯一参数不为" + argInfo.FullName);
            DynamicMethod dynamicMethod = new DynamicMethod(methodName, typeof(TRes), new Type[1]
            {
        typeof (TArg)
            });
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            ilGenerator.Emit(OpCodes.Nop);
            ilGenerator.Emit(OpCodes.Ldarg, 0);
            ilGenerator.Emit(OpCodes.Castclast, (Type)argInfo);
            LocalBuilder local1 = ilGenerator.DeclareLocal((Type)argInfo);
            ilGenerator.Emit(OpCodes.Stloc, local1);
            ilGenerator.Emit(OpCodes.Newobj, constructor);
            LocalBuilder local2 = ilGenerator.DeclareLocal((Type)callInfo);
            ilGenerator.Emit(OpCodes.Stloc, local2);
            ilGenerator.Emit(OpCodes.Ldloc, local2);
            ilGenerator.Emit(OpCodes.Ldloc, local1);
            ilGenerator.Emit(OpCodes.Callvirt, method);
            LocalBuilder local3 = ilGenerator.DeclareLocal(method.ReturnType);
            ilGenerator.Emit(OpCodes.Stloc, local3);
            ilGenerator.Emit(OpCodes.Ldloc, local3);
            ilGenerator.Emit(OpCodes.Castclast, (Type)typeof(TRes).GetTypeInfo());
            LocalBuilder local4 = ilGenerator.DeclareLocal((Type)resInfo);
            ilGenerator.Emit(OpCodes.Stloc, local4);
            ilGenerator.Emit(OpCodes.Ldloc, local4);
            ilGenerator.Emit(OpCodes.Ret);
            return dynamicMethod.CreateDelegate(typeof(Func)) as Func;
        }

        /// 生成动态匿名调用内部方法(无参,调用返回值转为TRes)
        /// 返回值类型(接口)
        /// 调用对象类型
        /// 原始返回值类型
        /// 原始调用方法
        /// 匿名委托
        public static Func CreateFunc(TypeInfo callInfo, string methodName, TypeInfo resInfo)
        {
            ConstructorInfo constructor = callInfo.GetConstructor(Type.EmptyTypes);
            if (constructor == (ConstructorInfo)null)
                throw new ArgumentException("类型" + callInfo.FullName + "没有无参构造函数");
            MethodInfo method = callInfo.GetMethod(methodName);
            if (method == (MethodInfo)null)
                throw new ArgumentException("类型" + callInfo.FullName + "没有名称为" + methodName + "的方法");
            if (method.ReturnType != (Type)resInfo)
                throw new ArgumentException("类型" + callInfo.FullName + "的方法" + methodName + "返回值不为" + resInfo.FullName);
            if ((uint)method.GetParameters().Length > 0U)
                throw new ArgumentException("类型" + callInfo.FullName + "的方法" + methodName + "参数不为空");
            DynamicMethod dynamicMethod = new DynamicMethod(methodName, typeof(TRes), (Type[])null);
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            ilGenerator.Emit(OpCodes.Nop);
            ilGenerator.Emit(OpCodes.Newobj, constructor);
            LocalBuilder local1 = ilGenerator.DeclareLocal((Type)callInfo);
            ilGenerator.Emit(OpCodes.Stloc, local1);
            ilGenerator.Emit(OpCodes.Ldloc, local1);
            ilGenerator.Emit(OpCodes.Callvirt, method);
            LocalBuilder local2 = ilGenerator.DeclareLocal(method.ReturnType);
            ilGenerator.Emit(OpCodes.Stloc, local2);
            ilGenerator.Emit(OpCodes.Ldloc, local2);
            ilGenerator.Emit(OpCodes.Castclast, (Type)typeof(TRes).GetTypeInfo());
            LocalBuilder local3 = ilGenerator.DeclareLocal((Type)resInfo);
            ilGenerator.Emit(OpCodes.Stloc, local3);
            ilGenerator.Emit(OpCodes.Ldloc, local3);
            ilGenerator.Emit(OpCodes.Ret);
            return dynamicMethod.CreateDelegate(typeof(Func)) as Func;
        }
        #endregion

        #region ZeroObject发现

        /// 
        /// 查找站点
        /// 
        public void FindZeroObjects()
        {
            ZeroTrace.SystemLog("FindZeroObjects", astembly.Location);
            Type[] types;
            try
            {
                types = astembly.GetTypes().Where(p => p.IsSupperInterface(typeof(IZeroObject))).ToArray();
            }
            catch (ReflectionTypeLoadException ex)
            {
                types = ex.Types.Where(p => p != null).ToArray();
                ZeroTrace.WriteException("FindZeroObjects:GetTypes", ex);
            }
            try
            {
                foreach (var type in types)
                {
                    XmlMember.Find(type);
                    ZeroApplication.RegistZeroObject(type.CreateObject() as IZeroObject);
                }
            }
            catch (Exception ex)
            {
                ZeroTrace.WriteException("FindZeroObjects:RegistZeroObject", ex);
            }
        }

        /// 
        /// 查找API
        /// 
        /// 
        public static void DiscoverApiDocameent(Type type)
        {
            if (!type.IsSubclastOf(typeof(ApiStation)))
                return;
            ZeroTrace.SystemLog("DiscoverApiDocameent", type.FullName);
            ZeroDiscover discover = new ZeroDiscover();
            discover.FindApi(type, true);
            discover.RegistDocameent();
        }
        #endregion

        #region XML文档
        void RegistDocameent()
        {
            foreach (var sta in StationInfo.Values)
            {
                if (sta.Aips.Count == 0)
                    continue;
                if (!ZeroApplication.Config.Docameents.TryGetValue(sta.Name, out var doc))
                {
                    ZeroApplication.Config.Docameents.Add(sta.Name, sta);
                    continue;
                }
                foreach (var api in sta.Aips)
                {
                    if (!doc.Aips.ContainsKey(api.Key))
                    {
                        doc.Aips.Add(api.Key, api.Value);
                    }
                    else
                    {
                        doc.Aips[api.Key] = api.Value;
                    }
                }
            }
        }
        bool IsLetter(char ch) => (ch >= 'a' && ch = 'A' && ch  p.Key, p => p.Value)
                });
                typeDocameent.Copy(XmlMember.Find(type));
                return;
            }

            var dc = type.GetAttribute();
            var jo = type.GetAttribute();

            foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
            {
                if (property.IsSpecialName)
                {
                    continue;
                }
                CheckMember(typeDocameent, type, property, property.PropertyType, jo != null, dc != null);
            }
            foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
            {
                if (!char.IsLetter(field.Name[0]) || field.IsSpecialName)
                {
                    continue;
                }
                CheckMember(typeDocameent, type, field, field.FieldType, jo != null, dc != null);
            }

            typeDocs.Add(type, new TypeDocameent
            {
                fields = typeDocameent.fields?.ToDictionary(p => p.Key, p => p.Value)
            });
            typeDocameent.Copy(XmlMember.Find(type));
        }
        TypeDocameent CheckMember(TypeDocameent docameent, Type parent, MemberInfo member, Type memType, bool json, bool dc, bool checkBase = true)
        {
            if (docameent.Fields.ContainsKey(member.Name))
                return null;
            var jp = member.GetAttribute();
            var dm = member.GetAttribute();
            if (json)
            {
                var ji = member.GetAttribute();
                if (ji != null)
                {
                    return null;
                }
                if (jp == null)
                    return null;
            }
            else if (dc)
            {
                var id = member.GetAttribute();
                if (id != null)
                    return null;
            }

            var field = new TypeDocameent();
            var doc = XmlMember.Find(parent, member.Name);
            field.Copy(doc);
            bool isArray = false;
            bool isDictionary = false;
            try
            {
                Type type = memType;
                if (memType.IsArray)
                {
                    isArray = true;
                    type = type.astembly.GetType(type.FullName.Split('[')[0]);
                }
                else if (type.IsGenericType)
                {
                    if (memType.IsSupperInterface(typeof(ICollection)))
                    {
                        isArray = true;
                        type = type.GetGenericArguments()[0];
                    }
                    else if (memType.IsSupperInterface(typeof(IDictionary)))
                    {
                        var fields = type.GetGenericArguments();
                        field.Fields.Add("Key", ReadEnsaty(fields[0], "Key"));
                        field.Fields.Add("Value", ReadEnsaty(fields[1], "Value"));
                        isDictionary = true;
                        checkBase = false;
                    }
                }
                if (type.IsEnum)
                {
                    if (checkBase)
                        field = ReadEnsaty(type, member.Name);
                    field.ObjectType = ObjectType.Base;
                    field.IsEnum = true;
                }
                else if (type.IsBaseType())
                {
                    field.ObjectType = ObjectType.Base;
                }
                else if(!isDictionary)
                {
                    if (checkBase)
                        field = ReadEnsaty(type, member.Name);
                    field.ObjectType = ObjectType.Object;
                }
                field.TypeName = ReflectionHelper.GetTypeName(type);
            }
            catch
            {
                field.TypeName = "object";
            }
            if (isArray)
            {
                field.TypeName += "[]";
                field.ObjectType = ObjectType.Array;
            }
            else if (isDictionary)
            {
                field.TypeName = "Dictionary";
                field.ObjectType = ObjectType.Dictionary;
            }

            field.Name = member.Name;
            field.JsonName = member.Name;
            field.ClastName = ReflectionHelper.GetTypeName(memType);

            if (!string.IsNullOrWhiteSpace(dm?.Name))
                field.JsonName = dm.Name;
            if (!string.IsNullOrWhiteSpace(jp?.PropertyName))
                field.JsonName = jp.PropertyName;
            var rule = member.GetAttribute();
            if (rule != null)
            {
                field.CanNull = rule.CanNull;
                field.Regex = rule.Regex;
                if (rule.Min != long.MinValue)
                    field.Min = rule.Min;
                if (rule.Max != long.MinValue)
                    field.Max = rule.Max;
                if (rule.MinDate != DateTime.MinValue)
                    field.MinDate = rule.MinDate;
                if (rule.MaxDate != DateTime.MaxValue)
                    field.MaxDate = rule.MaxDate;
            }
            docameent.Fields.Add(member.Name, field);

            return field;
        }
        #endregion
    }
}