上传文件至「/」

This commit is contained in:
2026-08-27 11:45:35 +08:00
commit 96b8361605
4 changed files with 1957 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System;
using System.Data;
using System.Configuration;
using System.Web;
using GThinking.N_MISPRO;
using GThinking.N_MISPROAS.Logging;
[assembly: AssemblyTitle("S_XLXSBJ")] // 部件ID,无版本号
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("江苏省金思维信息技术有限公司")]
[assembly: AssemblyProduct("金思维软件")]
[assembly: AssemblyCopyright("(c) 江苏省金思维信息技术有限公司 版权所有")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
// GUID。编号规则47544950-5333-4e45-5420-xxxxxxxxxxx。前四段固定。最后一段为部件ID字符串ASCII码,
// 不含平台版本号,最长6字节,超长需要缩写,不足长度补空格。
[assembly: Guid("47544950-5338-4E45-5420-535F4B435357")]
// 程序集版本信息
[assembly: AssemblyVersion("1.0.0.0")] // 固定为1.0.0.0
[assembly: AssemblyFileVersion("8.0.0.0")] // 部件版本
namespace GThinking.S_XLXSBJ
{
public class Mac : GThinking.N_MISPRO.Mac
{
internal static ILog MacLog;
static Mac()
{
// 注册哪些系统、跟踪到什么时间。只有当前系统的记录才保存。
MacLog = LogManager.GetMacLogger("S_XLXSBJ8", "", "2222-12-31");
}
public Mac()
{
// 以下设置该部件的有关信息
ID = "S_XLXSBJ8";
ClassID = "S_XLXSBJ";
Name = "线缆销售报价服务";
Protocol = "NET";
OACBVersion = 4;
Version = 8;
DBVersion = 2;
DbExtVersion = 0;
Vendor = "金思维信息技术有限公司";
Comment = "标准版";
// 以下设置各模块的信息
AddModuleInfo(102373000, 0, "线缆销售报价微信服务", "N", typeof(WXXLXSBJService));
AddAjaxService(102373000, "", typeof(WXXLXSBJService));
AddModuleInfo(102373001, 0, "线缆销售报价反馈数据接收服务", "N", typeof(XLXSBJFKJSService));
AddAjaxService(102373001, "", typeof(XLXSBJFKJSService));
AddModuleInfo(102373002, 0, "线缆销售价格表基础数据接收API接口服务", "S", typeof(XLXSJGBJCSJService));
AddAjaxService(102373002, "", typeof(XLXSJGBJCSJService));
AddModuleInfo(102373003, 0, "线缆销售调价表基础数据接收API接口服务", "S", typeof(XLXSTJBJCSJService));
AddAjaxService(102373003, "", typeof(XLXSTJBJCSJService));
AddModuleInfo(102373004, 0, "线缆销售调价因子基础数据接收API接口服务", "S", typeof(XLXSTJYZJCSJService));
AddAjaxService(102373004, "", typeof(XLXSTJYZJCSJService));
AddModuleInfo(102373006, 0, "AI助手解析页面服务", "S", typeof(AIZSJXService));
AddAjaxService(102373006, "", typeof(AIZSJXService));
AddModuleInfo(102373007, 0, "AI助手附件上传同步服务", "S", typeof(AIZSFJSCTBService));
AddAjaxService(102373007, "", typeof(AIZSFJSCTBService));
AddModuleInfo(102373010, 0, "AI助手附件上传异步服务", "S", typeof(AIZSFJSCYBService));
AddAjaxService(102373010, "", typeof(AIZSFJSCYBService));
AddModuleInfo(102373011, 0, "价格表导入服务", "S", typeof(JGBDRService));
AddAjaxService(102373011, "", typeof(JGBDRService));
AddModuleInfo(102373012, 0, "线缆报价深度解析导入服务", "S", typeof(XLBJJXDRService));
AddAjaxService(102373012, "", typeof(XLBJJXDRService));
AddModuleInfo(102373013, 0, "投标数据快速录入服务", "S", typeof(XLBJKSLRService));
AddAjaxService(102373013, "", typeof(XLBJKSLRService));
AddModuleInfo(102373016, 0, "线缆销售新批量调价服务", "S", typeof(BJDPLTJService));
AddAjaxService(102373016, "", typeof(BJDPLTJService));
AddModuleInfo(102373017, 0, "材料成本查询服务", "S", typeof(CLCBCXService));
AddAjaxService(102373017, "", typeof(CLCBCXService));
AddModuleInfo(102373018, 0, "报价材料清单接收服务", "S", typeof(BJCLQDJSService));
AddAjaxService(102373018, "", typeof(BJCLQDJSService));
AddModuleInfo(102373019, 0, "服务方法参数数据生成服务", "S", typeof(ServiceMethod));
AddAjaxService(102373019, "", typeof(ServiceMethod));
}
}
}
+943
View File
@@ -0,0 +1,943 @@
using System;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Reflection;
using System.Text;
using System.Xml.XPath;
using GThinking.N_MISPROAS.Web.AjaxServices;
using GThinking.N_MISPRO.Data;
namespace GThinking.S_XLXSBJ
{
/// <summary>
/// 服务方法管理相关服务。
/// </summary>
public class ServiceMethod
{
/// <summary>
/// 根据传入的 DLL 文件地址,反射获取其中标记了 AjaxMethod 的服务方法信息,
/// 并写入数据库表:SRCMD、SRCMETHOD、SRCMETHODPARAMS、SRCTYPE、SRCPROP。
/// </summary>
/// <param name="filePath">DLL 文件地址。</param>
/// <returns>操作结果。</returns>
[AjaxMethod(AuthRequried = false)]
public static Dictionary<string, object> SaveServiceMethodInfo(string filePath)
{
Dictionary<string, object> result = new Dictionary<string, object>();
TDatabase dberp = null;
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
{
result["success"] = false;
result["msg"] = "文件地址不能为空或文件不存在";
return result;
}
try
{
dberp = TContextDatabases.GetDatabase("DBERP");
dberp.BeginTrans();
Assembly assembly = Assembly.LoadFrom(filePath);
string macId = Truncate(assembly.GetName().Name, 20);
// 先删除该部件旧数据
DeleteOldModuleData(dberp, macId);
XmlDocHelper xmlDoc = new XmlDocHelper(filePath);
int mdIndex = 0;
Dictionary<string, string> visitedTypes = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
Type[] types = null;
try
{
types = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
types = ex.Types;
}
if (types == null)
{
dberp.CommitTrans();
result["success"] = true;
result["msg"] = "未找到可扫描的类型";
result["macId"] = macId;
return result;
}
foreach (Type type in types)
{
if (type == null)
continue;
if (!type.IsClass)
continue;
Dictionary<string, ServiceMethodInfo> methods = GetServiceMethods(type);
if (methods == null || methods.Count == 0)
continue;
mdIndex++;
double mdId = (double)mdIndex;
InsertSRCMD(dberp, macId, mdId, Truncate(type.FullName, 40));
foreach (ServiceMethodInfo smi in methods.Values)
{
string methodDesc = xmlDoc.GetMethodSummary(smi.MethodInfo);
string retDesc = xmlDoc.GetMethodReturns(smi.MethodInfo);
InsertSRCMETHOD(
dberp,
macId,
mdId,
Truncate(smi.MethodName, 32),
Truncate(GetMethodModifiers(smi.MethodInfo), 80),
smi.AuthRequried ? "Y" : "N",
smi.UseHttpGet ? "Y" : "N",
Truncate(methodDesc, 4000),
Truncate(GetTypeDisplayName(smi.ReturnType), 80),
Truncate(retDesc, 1000));
if (smi.ParameterList != null)
{
int paramXh = 0;
foreach (ServiceParameterInfo spi in smi.ParameterList)
{
string paramDesc = xmlDoc.GetParameterSummary(smi.MethodInfo, spi.Name);
InsertSRCMETHODPARAMS(
dberp,
macId,
mdId,
Truncate(smi.MethodName, 32),
(double)paramXh,
Truncate(spi.Name, 32),
Truncate(GetTypeDisplayName(spi.ParameterType), 80),
Truncate(paramDesc, 1000));
ReflectType(spi.ParameterType, dberp, visitedTypes, xmlDoc);
paramXh++;
}
}
ReflectType(smi.ReturnType, dberp, visitedTypes, xmlDoc);
}
}
dberp.CommitTrans();
result["success"] = true;
result["msg"] = "扫描完成";
result["macId"] = macId;
}
catch (Exception ex)
{
if (dberp != null && dberp.InTransaction)
dberp.RollbackTrans();
result["success"] = false;
result["msg"] = ex.Message;
}
return result;
}
/// <summary>
/// 根据 MACID 查询已扫描的服务方法数据。
/// </summary>
/// <param name="macId">服务部件ID(程序集名称)。</param>
/// <returns>包含各表数据的结果。</returns>
[AjaxMethod(AuthRequried = false)]
public static Dictionary<string, object> GetServiceMethodData(string macId)
{
Dictionary<string, object> result = new Dictionary<string, object>();
TDatabase dberp = null;
try
{
dberp = TContextDatabases.GetDatabase("DBERP");
result["SRCMD"] = QueryTableByMacId(dberp, "SRCMD", macId);
result["SRCMETHOD"] = QueryTableByMacId(dberp, "SRCMETHOD", macId);
result["SRCMETHODPARAMS"] = QueryTableByMacId(dberp, "SRCMETHODPARAMS", macId);
result["SRCTYPE"] = QueryAll(dberp, "SRCTYPE");
result["SRCPROP"] = QueryAll(dberp, "SRCPROP");
result["success"] = true;
result["msg"] = "";
}
catch (Exception ex)
{
result["success"] = false;
result["msg"] = ex.Message;
}
return result;
}
#region 数据库操作
private static void DeleteOldModuleData(TDatabase dberp, string macId)
{
string[] tables = new string[] { "SRCMETHODPARAMS", "SRCMETHOD", "SRCMD" };
TSQL sql = new TSQL(dberp);
foreach (string table in tables)
{
sql.Clear();
sql.Add("delete from " + table + " where MACID=:pMACID");
sql.ParamByName("pMACID").Value = macId;
dberp.ExecSQL(sql);
}
}
private static void DeleteTypeData(TDatabase dberp, string typeFullName)
{
TSQL sql = new TSQL(dberp);
sql.Clear();
sql.Add("delete from SRCPROP where TYPEFULLNAME=:pTYPEFULLNAME");
sql.ParamByName("pTYPEFULLNAME").Value = typeFullName;
dberp.ExecSQL(sql);
sql.Clear();
sql.Add("delete from SRCTYPE where TYPEFULLNAME=:pTYPEFULLNAME");
sql.ParamByName("pTYPEFULLNAME").Value = typeFullName;
dberp.ExecSQL(sql);
}
private static List<Dictionary<string, object>> QueryTableByMacId(TDatabase dberp, string tableName, string macId)
{
TSQL sql = new TSQL(dberp);
sql.Add("select " + GetTableColumns(tableName) + " from " + tableName + " where MACID=:pMACID");
sql.ParamByName("pMACID").Value = macId;
DataSet ds = dberp.OpenDataSet(sql);
return DataTableToList(ds.Tables[0]);
}
private static List<Dictionary<string, object>> QueryAll(TDatabase dberp, string tableName)
{
TSQL sql = new TSQL(dberp);
sql.Add("select " + GetTableColumns(tableName) + " from " + tableName);
DataSet ds = dberp.OpenDataSet(sql);
return DataTableToList(ds.Tables[0]);
}
private static string GetTableColumns(string tableName)
{
switch (tableName)
{
case "SRCMD":
return "MACID, MDID, MDNAME";
case "SRCMETHOD":
return "MACID, MDID, METHODNAME, MODIFIERS, AUTHREQUIRED, USEHTTPGET, METHODDESC, RETTYPE, RETDESC";
case "SRCMETHODPARAMS":
return "MACID, MDID, METHODNAME, PARAMXH, PARAMNAME, PARAMTYPE, PARAMDESC";
case "SRCTYPE":
return "TYPEFULLNAME, NAMESPACE, TYPENAME, TYPEDESC";
case "SRCPROP":
return "TYPEFULLNAME, PROPNAME, PROPTYPEFULLNAME, NAMESPACE, PROPTYPE, PROPDESC";
default:
return "*";
}
}
private static List<Dictionary<string, object>> DataTableToList(DataTable dt)
{
List<Dictionary<string, object>> list = new List<Dictionary<string, object>>();
foreach (DataRow row in dt.Rows)
{
Dictionary<string, object> item = new Dictionary<string, object>();
foreach (DataColumn col in dt.Columns)
{
object val = row[col];
item[col.ColumnName] = val == DBNull.Value ? null : val;
}
list.Add(item);
}
return list;
}
private static void InsertSRCMD(TDatabase dberp, string macId, double mdId, string mdName)
{
TSQL sql = new TSQL(dberp);
sql.Clear();
sql.Add("insert into SRCMD(MACID, MDID, MDNAME) values(:pMACID, :pMDID, :pMDNAME)");
sql.ParamByName("pMACID").Value = macId;
sql.ParamByName("pMDID").Value = mdId;
sql.ParamByName("pMDNAME").Value = mdName;
dberp.ExecSQL(sql);
}
private static void InsertSRCMETHOD(TDatabase dberp, string macId, double mdId, string methodName,
string modifiers, string authRequired, string useHttpGet, string methodDesc,
string retType, string retDesc)
{
TSQL sql = new TSQL(dberp);
sql.Clear();
sql.Add("insert into SRCMETHOD(MACID, MDID, METHODNAME, MODIFIERS, AUTHREQUIRED, USEHTTPGET, METHODDESC, RETTYPE, RETDESC) ");
sql.Add("values(:pMACID, :pMDID, :pMETHODNAME, :pMODIFIERS, :pAUTHREQUIRED, :pUSEHTTPGET, :pMETHODDESC, :pRETTYPE, :pRETDESC)");
sql.ParamByName("pMACID").Value = macId;
sql.ParamByName("pMDID").Value = mdId;
sql.ParamByName("pMETHODNAME").Value = methodName;
sql.ParamByName("pMODIFIERS").Value = modifiers;
sql.ParamByName("pAUTHREQUIRED").Value = authRequired;
sql.ParamByName("pUSEHTTPGET").Value = useHttpGet;
sql.ParamByName("pMETHODDESC").Value = methodDesc;
sql.ParamByName("pRETTYPE").Value = retType;
sql.ParamByName("pRETDESC").Value = retDesc;
dberp.ExecSQL(sql);
}
private static void InsertSRCMETHODPARAMS(TDatabase dberp, string macId, double mdId, string methodName,
double paramXh, string paramName, string paramType, string paramDesc)
{
TSQL sql = new TSQL(dberp);
sql.Clear();
sql.Add("insert into SRCMETHODPARAMS(MACID, MDID, METHODNAME, PARAMXH, PARAMNAME, PARAMTYPE, PARAMDESC) ");
sql.Add("values(:pMACID, :pMDID, :pMETHODNAME, :pPARAMXH, :pPARAMNAME, :pPARAMTYPE, :pPARAMDESC)");
sql.ParamByName("pMACID").Value = macId;
sql.ParamByName("pMDID").Value = mdId;
sql.ParamByName("pMETHODNAME").Value = methodName;
sql.ParamByName("pPARAMXH").Value = paramXh;
sql.ParamByName("pPARAMNAME").Value = paramName;
sql.ParamByName("pPARAMTYPE").Value = paramType;
sql.ParamByName("pPARAMDESC").Value = paramDesc;
dberp.ExecSQL(sql);
}
private static void InsertSRCTYPE(TDatabase dberp, string typeFullName, string nameSpace,
string typeName, string typeDesc)
{
TSQL sql = new TSQL(dberp);
sql.Clear();
sql.Add("insert into SRCTYPE(TYPEFULLNAME, NAMESPACE, TYPENAME, TYPEDESC) ");
sql.Add("values(:pTYPEFULLNAME, :pNAMESPACE, :pTYPENAME, :pTYPEDESC)");
sql.ParamByName("pTYPEFULLNAME").Value = typeFullName;
sql.ParamByName("pNAMESPACE").Value = nameSpace;
sql.ParamByName("pTYPENAME").Value = typeName;
sql.ParamByName("pTYPEDESC").Value = typeDesc;
dberp.ExecSQL(sql);
}
private static void InsertSRCPROP(TDatabase dberp, string typeFullName, string propName,
string propTypeFullName, string nameSpace, string propType, string propDesc)
{
TSQL sql = new TSQL(dberp);
sql.Clear();
sql.Add("insert into SRCPROP(TYPEFULLNAME, PROPNAME, PROPTYPEFULLNAME, NAMESPACE, PROPTYPE, PROPDESC) ");
sql.Add("values(:pTYPEFULLNAME, :pPROPNAME, :pPROPTYPEFULLNAME, :pNAMESPACE, :pPROPTYPE, :pPROPDESC)");
sql.ParamByName("pTYPEFULLNAME").Value = typeFullName;
sql.ParamByName("pPROPNAME").Value = propName;
sql.ParamByName("pPROPTYPEFULLNAME").Value = propTypeFullName;
sql.ParamByName("pNAMESPACE").Value = nameSpace;
sql.ParamByName("pPROPTYPE").Value = propType;
sql.ParamByName("pPROPDESC").Value = propDesc;
dberp.ExecSQL(sql);
}
#endregion
#region 反射与类型辅助
private static Dictionary<string, ServiceMethodInfo> GetServiceMethods(Type classType)
{
if (classType == null)
return null;
List<Type> typeList = new List<Type>();
Type current = classType;
typeList.Add(current);
while (current.BaseType != null)
{
current = current.BaseType;
if (current != typeof(object))
typeList.Add(current);
}
Dictionary<string, ServiceMethodInfo> methods = new Dictionary<string, ServiceMethodInfo>(StringComparer.OrdinalIgnoreCase);
BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly |
BindingFlags.Static | BindingFlags.Instance;
for (int i = typeList.Count - 1; i >= 0; --i)
{
MethodInfo[] methodInfos = typeList[i].GetMethods(flags);
foreach (MethodInfo method in methodInfos)
{
object[] attribs = method.GetCustomAttributes(typeof(AjaxMethodAttribute), true);
if (attribs.Length > 0)
{
AjaxMethodAttribute ajaxAttr = (AjaxMethodAttribute)attribs[0];
ServiceMethodInfo smi = new ServiceMethodInfo(method, ajaxAttr);
smi.LoadMethodParameters();
methods[smi.MethodName] = smi;
}
}
}
return methods;
}
private static string GetMethodModifiers(MethodInfo method)
{
StringBuilder sb = new StringBuilder();
if (method.IsPublic)
sb.Append("public ");
else if (method.IsPrivate)
sb.Append("private ");
else if (method.IsFamily)
sb.Append("protected ");
else if (method.IsAssembly)
sb.Append("internal ");
if (method.IsStatic)
sb.Append("static ");
return sb.ToString().Trim();
}
private static void ReflectType(Type type, TDatabase dberp,
Dictionary<string, string> visitedTypes, XmlDocHelper xmlDoc)
{
if (type == null)
return;
if (type.IsByRef || type.IsPointer)
{
ReflectType(type.GetElementType(), dberp, visitedTypes, xmlDoc);
return;
}
if (type.IsArray)
{
ReflectType(type.GetElementType(), dberp, visitedTypes, xmlDoc);
return;
}
Type underlying = Nullable.GetUnderlyingType(type);
if (underlying != null)
{
ReflectType(underlying, dberp, visitedTypes, xmlDoc);
return;
}
if (type.IsGenericType)
{
Type[] args = type.GetGenericArguments();
foreach (Type arg in args)
ReflectType(arg, dberp, visitedTypes, xmlDoc);
Type def = type.GetGenericTypeDefinition();
if (!IsCustomType(def))
return;
type = def;
}
if (!IsCustomType(type))
return;
string key = type.FullName;
if (string.IsNullOrEmpty(key))
return;
if (visitedTypes.ContainsKey(key))
return;
visitedTypes[key] = key;
string typeFullName = type.FullName.Replace('+', '.');
// 先清理该类型旧数据,再重新写入
DeleteTypeData(dberp, typeFullName);
InsertSRCTYPE(
dberp,
Truncate(typeFullName, 80),
Truncate(type.Namespace, 80),
Truncate(GetTypeShortName(type), 80),
Truncate(xmlDoc.GetSummary(type), 1000));
List<MemberInfo> members = new List<MemberInfo>();
members.AddRange(type.GetProperties(BindingFlags.Public | BindingFlags.Instance));
members.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.Instance));
members.Sort(new MemberNameComparer());
foreach (MemberInfo mi in members)
{
Type memberType = null;
if (mi is PropertyInfo)
memberType = ((PropertyInfo)mi).PropertyType;
else if (mi is FieldInfo)
memberType = ((FieldInfo)mi).FieldType;
else
continue;
string propTypeFullName = memberType.FullName == null ? memberType.Name : memberType.FullName.Replace('+', '.');
InsertSRCPROP(
dberp,
Truncate(typeFullName, 80),
Truncate(mi.Name, 32),
Truncate(propTypeFullName, 80),
Truncate(memberType.Namespace, 80),
Truncate(GetTypeDisplayName(memberType), 80),
Truncate(xmlDoc.GetSummary(mi), 250));
ReflectType(memberType, dberp, visitedTypes, xmlDoc);
}
}
private static bool IsCustomType(Type type)
{
if (type == null)
return false;
if (type.IsGenericParameter)
return false;
if (type.IsPrimitive)
return false;
if (type == typeof(string) || type == typeof(object) || type == typeof(decimal)
|| type == typeof(DateTime) || type == typeof(TimeSpan) || type == typeof(Guid))
return false;
if (type.Namespace != null &&
(type.Namespace.StartsWith("System") || type.Namespace.StartsWith("Microsoft")))
return false;
return true;
}
private static string GetTypeDisplayName(Type type)
{
if (type == null)
return "";
if (type.IsByRef)
return GetTypeDisplayName(type.GetElementType()) + "&";
if (type.IsPointer)
return GetTypeDisplayName(type.GetElementType()) + "*";
if (type.IsArray)
return GetTypeDisplayName(type.GetElementType()) + "[]";
Type underlying = Nullable.GetUnderlyingType(type);
if (underlying != null)
return GetTypeDisplayName(underlying) + "?";
if (type.IsGenericType)
{
Type def = type.GetGenericTypeDefinition();
string baseName = def.FullName;
if (string.IsNullOrEmpty(baseName))
baseName = type.Name;
baseName = baseName.Replace('+', '.');
int backtick = baseName.IndexOf('`');
string cleanName = backtick > 0 ? baseName.Substring(0, backtick) : baseName;
Type[] genericArgs = type.GetGenericArguments();
StringBuilder sb = new StringBuilder();
sb.Append(cleanName);
sb.Append("<");
for (int i = 0; i < genericArgs.Length; i++)
{
sb.Append(GetTypeDisplayName(genericArgs[i]));
if (i < genericArgs.Length - 1)
sb.Append(", ");
}
sb.Append(">");
return sb.ToString();
}
if (type.FullName == null)
return type.Name;
return type.FullName.Replace('+', '.');
}
private static string GetTypeShortName(Type type)
{
if (type == null)
return "";
string name = type.Name;
int backtick = name.IndexOf('`');
if (backtick > 0)
name = name.Substring(0, backtick);
return name;
}
private static string Truncate(string value, int maxLength)
{
if (string.IsNullOrEmpty(value))
return string.Empty;
if (value.Length > maxLength)
return value.Substring(0, maxLength);
return value;
}
#endregion
#region 嵌套辅助类
/// <summary>
/// 服务方法信息。
/// </summary>
private class ServiceMethodInfo
{
internal ServiceMethodInfo(MethodInfo methodInfo, AjaxMethodAttribute methodAttribute)
{
_methodInfo = methodInfo;
_methodName = methodInfo.Name;
_isPublic = methodInfo.IsPublic;
_isStatic = methodInfo.IsStatic;
_returnType = methodInfo.ReturnType;
_authRequried = methodAttribute.AuthRequried;
_useHttpGet = methodAttribute.UseHttpGet;
}
private MethodInfo _methodInfo;
internal MethodInfo MethodInfo
{
get { return _methodInfo; }
}
private string _methodName;
public string MethodName
{
get { return _methodName == null ? string.Empty : _methodName; }
set { _methodName = value; }
}
private bool _isPublic;
public bool IsPublic
{
get { return _isPublic; }
set { _isPublic = value; }
}
private bool _isStatic;
public bool IsStatic
{
get { return _isStatic; }
set { _isStatic = value; }
}
private Type _returnType;
public Type ReturnType
{
get { return _returnType; }
set { _returnType = value; }
}
private bool _authRequried;
public bool AuthRequried
{
get { return _authRequried; }
set { _authRequried = value; }
}
private bool _useHttpGet;
public bool UseHttpGet
{
get { return _useHttpGet; }
set { _useHttpGet = value; }
}
private List<ServiceParameterInfo> _parameterList;
public List<ServiceParameterInfo> ParameterList
{
get { return _parameterList; }
set { _parameterList = value; }
}
internal void LoadMethodParameters()
{
if (_methodInfo == null)
return;
List<ServiceParameterInfo> list = new List<ServiceParameterInfo>();
int index = 0;
foreach (ParameterInfo param in _methodInfo.GetParameters())
{
list.Add(new ServiceParameterInfo(param, index));
index++;
}
_parameterList = list;
}
}
/// <summary>
/// 服务方法参数信息。
/// </summary>
private class ServiceParameterInfo
{
private ParameterInfo _param;
private int _index;
internal ServiceParameterInfo(ParameterInfo param, int index)
{
_param = param;
_index = index;
}
internal ParameterInfo ParameterInfo
{
get { return _param; }
}
internal int Index
{
get { return _index; }
}
public string Name
{
get { return _param.Name; }
}
public Type ParameterType
{
get { return _param.ParameterType; }
}
}
/// <summary>
/// XML 文档注释读取辅助类。
/// </summary>
private class XmlDocHelper
{
private XPathDocument _doc;
private XPathNavigator _nav;
public XmlDocHelper(string dllPath)
{
if (string.IsNullOrEmpty(dllPath))
return;
string fullPath = Path.GetFullPath(dllPath);
string dir = Path.GetDirectoryName(fullPath);
string xmlPath = Path.Combine(dir, Path.GetFileNameWithoutExtension(fullPath) + ".xml");
if (!File.Exists(xmlPath))
return;
try
{
_doc = new XPathDocument(xmlPath);
_nav = _doc.CreateNavigator();
}
catch
{
_doc = null;
_nav = null;
}
}
public string GetSummary(Type type)
{
if (_nav == null || type == null)
return "";
string id = GetTypeDocId(type);
return GetChildText("T", id, "summary");
}
public string GetSummary(MemberInfo member)
{
if (_nav == null || member == null)
return "";
string prefix = "P";
if (member is FieldInfo)
prefix = "F";
else if (member is PropertyInfo)
prefix = "P";
else if (member is MethodInfo)
prefix = "M";
string id = GetMemberDocId(member);
return GetChildText(prefix, id, "summary");
}
public string GetMethodSummary(MethodInfo method)
{
XPathNavigator node = FindMethodNode(method);
if (node == null)
return "";
XPathNavigator sum = node.SelectSingleNode("summary");
return sum == null ? "" : sum.Value.Trim();
}
public string GetMethodReturns(MethodInfo method)
{
XPathNavigator node = FindMethodNode(method);
if (node == null)
return "";
XPathNavigator ret = node.SelectSingleNode("returns");
return ret == null ? "" : ret.Value.Trim();
}
public string GetParameterSummary(MethodInfo method, string paramName)
{
XPathNavigator node = FindMethodNode(method);
if (node == null || string.IsNullOrEmpty(paramName))
return "";
string expr = "param[@name='" + Escape(paramName) + "']";
XPathNavigator pn = node.SelectSingleNode(expr);
return pn == null ? "" : pn.Value.Trim();
}
private string GetChildText(string prefix, string id, string childName)
{
if (_nav == null)
return "";
string expr = "//member[@name='" + prefix + ":" + Escape(id) + "']";
XPathNavigator node = _nav.SelectSingleNode(expr);
if (node == null)
return "";
XPathNavigator child = node.SelectSingleNode(childName);
return child == null ? "" : child.Value.Trim();
}
private XPathNavigator FindMethodNode(MethodInfo method)
{
if (_nav == null || method == null)
return null;
string exactId = GetMethodDocId(method);
string expr = "//member[@name='M:" + Escape(exactId) + "']";
XPathNavigator node = _nav.SelectSingleNode(expr);
if (node != null)
return node;
string typeId = GetTypeDocId(method.DeclaringType);
string prefix = "M:" + Escape(typeId) + "." + method.Name;
XPathNodeIterator iter = _nav.Select("//member[starts-with(@name,'" + prefix + "')]");
int pCount = method.GetParameters().Length;
while (iter.MoveNext())
{
string name = iter.Current.GetAttribute("name", "");
int open = name.IndexOf('(');
int close = name.LastIndexOf(')');
if (open > 0 && close > open)
{
string sig = name.Substring(open + 1, close - open - 1);
int count = string.IsNullOrEmpty(sig) ? 0 : CountChar(sig, ',') + 1;
if (count == pCount)
return iter.Current.Clone();
}
}
return null;
}
private static string GetTypeDocId(Type type)
{
if (type == null)
return "";
string fullName = type.FullName;
if (string.IsNullOrEmpty(fullName))
return type.Name;
return fullName.Replace('+', '.');
}
private static string GetMemberDocId(MemberInfo member)
{
Type type = member.DeclaringType;
string typeId = GetTypeDocId(type);
if (member is MethodInfo)
return GetMethodDocId((MethodInfo)member);
return typeId + "." + member.Name;
}
private static string GetMethodDocId(MethodInfo method)
{
StringBuilder sb = new StringBuilder();
sb.Append(GetTypeDocId(method.DeclaringType));
sb.Append(".");
sb.Append(method.Name);
if (method.IsGenericMethod)
sb.Append("``").Append(method.GetGenericArguments().Length);
sb.Append("(");
ParameterInfo[] ps = method.GetParameters();
for (int i = 0; i < ps.Length; i++)
{
if (i > 0)
sb.Append(",");
sb.Append(GetParameterDocTypeName(ps[i].ParameterType));
}
sb.Append(")");
return sb.ToString();
}
private static string GetParameterDocTypeName(Type type)
{
if (type == null)
return "";
if (type.IsGenericParameter)
return type.Name;
if (type.IsByRef)
return GetParameterDocTypeName(type.GetElementType()) + "@";
if (type.IsPointer)
return GetParameterDocTypeName(type.GetElementType()) + "*";
if (type.IsArray)
return GetParameterDocTypeName(type.GetElementType()) + "[]";
Type underlying = Nullable.GetUnderlyingType(type);
if (underlying != null)
return GetParameterDocTypeName(underlying) + "?";
if (type.IsGenericType)
{
Type def = type.GetGenericTypeDefinition();
string baseName = def.FullName;
if (string.IsNullOrEmpty(baseName))
baseName = type.Name;
baseName = baseName.Replace('+', '.');
Type[] args = type.GetGenericArguments();
StringBuilder sb = new StringBuilder();
sb.Append(baseName);
sb.Append("{");
for (int i = 0; i < args.Length; i++)
{
if (i > 0)
sb.Append(",");
sb.Append(GetParameterDocTypeName(args[i]));
}
sb.Append("}");
return sb.ToString();
}
string name = type.FullName;
if (string.IsNullOrEmpty(name))
name = type.Name;
return name.Replace('+', '.');
}
private static string Escape(string s)
{
if (s == null)
return "";
return s.Replace("'", "''");
}
private static int CountChar(string s, char c)
{
int count = 0;
for (int i = 0; i < s.Length; i++)
{
if (s[i] == c)
count++;
}
return count;
}
}
/// <summary>
/// 按成员名称排序的比较器。
/// </summary>
private class MemberNameComparer : IComparer<MemberInfo>
{
public int Compare(MemberInfo x, MemberInfo y)
{
return string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase);
}
}
#endregion
}
}
+103
View File
@@ -0,0 +1,103 @@
-- ============================================================
-- 服务方法管理数据表建表脚本(Oracle)
-- 数据来源:服务方法管理数据表.docx
-- 注意:原文档中参数表名为 SRCPARAMS,已按最新要求改为 SRCMETHODPARAMS
-- ============================================================
-- 1. 服务模块表:记录被扫描 DLL 中的类(AjaxMethod 方法所在的类)
CREATE TABLE SRCMD (
MACID CHAR(20) NOT NULL, -- 服务部件ID(程序集名称)
MDID FLOAT NOT NULL, -- 服务模块号(类序号)
MDNAME CHAR(40) NULL, -- 服务模块名称(类全名)
CONSTRAINT PK_SRCMD PRIMARY KEY (MACID, MDID)
);
COMMENT ON TABLE SRCMD IS '代码扫描服务模块';
COMMENT ON COLUMN SRCMD.MACID IS '服务部件ID';
COMMENT ON COLUMN SRCMD.MDID IS '服务模块号';
COMMENT ON COLUMN SRCMD.MDNAME IS '服务模块名称';
-- 2. 服务方法表:记录类中标记了 AjaxMethod 的方法
CREATE TABLE SRCMETHOD (
MACID CHAR(20) NOT NULL, -- 服务部件ID
MDID FLOAT NOT NULL, -- 服务模块号
METHODNAME CHAR(32) NOT NULL, -- 服务方法名
MODIFIERS VARCHAR2(80) NULL, -- 方法修饰符(例如 public static)
AUTHREQUIRED CHAR(1) NULL, -- 是否需要登录才能调用(Y/N)
USEHTTPGET CHAR(1) NULL, -- 是否支持 Http GET 调用(Y/N)
METHODDESC VARCHAR2(4000) NULL, -- 服务方法功能描述(<summary/>)
RETTYPE VARCHAR2(80) NULL, -- 返回值类型(包含命名空间)
RETDESC VARCHAR2(1000) NULL, -- 返回值描述(<returns/>)
CONSTRAINT PK_SRCMETHOD PRIMARY KEY (MACID, MDID, METHODNAME)
);
COMMENT ON TABLE SRCMETHOD IS '代码扫描服务方法';
COMMENT ON COLUMN SRCMETHOD.MACID IS '服务部件ID';
COMMENT ON COLUMN SRCMETHOD.MDID IS '服务模块号';
COMMENT ON COLUMN SRCMETHOD.METHODNAME IS '服务方法名';
COMMENT ON COLUMN SRCMETHOD.MODIFIERS IS '方法修饰符';
COMMENT ON COLUMN SRCMETHOD.AUTHREQUIRED IS '是否需要登录才能调用';
COMMENT ON COLUMN SRCMETHOD.USEHTTPGET IS '是否支持Http GET调用';
COMMENT ON COLUMN SRCMETHOD.METHODDESC IS '方法功能描述';
COMMENT ON COLUMN SRCMETHOD.RETTYPE IS '返回值类型';
COMMENT ON COLUMN SRCMETHOD.RETDESC IS '返回值描述';
-- 3. 服务方法参数表:记录每个方法的参数
CREATE TABLE SRCMETHODPARAMS (
MACID CHAR(20) NOT NULL, -- 服务部件ID
MDID FLOAT NOT NULL, -- 服务模块号
METHODNAME CHAR(32) NOT NULL, -- 服务方法名
PARAMXH FLOAT NOT NULL, -- 参数序号
PARAMNAME CHAR(32) NULL, -- 参数名
PARAMTYPE VARCHAR2(80) NULL, -- 参数类型(包含命名空间)
PARAMDESC VARCHAR2(1000) NULL, -- 参数描述(<param/>)
CONSTRAINT PK_SRCMETHODPARAMS PRIMARY KEY (MACID, MDID, METHODNAME, PARAMXH)
);
COMMENT ON TABLE SRCMETHODPARAMS IS '代码扫描服务方法参数';
COMMENT ON COLUMN SRCMETHODPARAMS.MACID IS '服务部件ID';
COMMENT ON COLUMN SRCMETHODPARAMS.MDID IS '服务模块号';
COMMENT ON COLUMN SRCMETHODPARAMS.METHODNAME IS '服务方法名';
COMMENT ON COLUMN SRCMETHODPARAMS.PARAMXH IS '参数序号';
COMMENT ON COLUMN SRCMETHODPARAMS.PARAMNAME IS '参数名';
COMMENT ON COLUMN SRCMETHODPARAMS.PARAMTYPE IS '参数类型';
COMMENT ON COLUMN SRCMETHODPARAMS.PARAMDESC IS '参数描述';
-- 4. 数据类型表:记录自定义类型(参数/返回值中用到的自定义类)
CREATE TABLE SRCTYPE (
TYPEFULLNAME VARCHAR2(80) NOT NULL, -- 类型全称,例如 GThinking.S_XSDD.SOXXData
NAMESPACE VARCHAR2(80) NULL, -- 命名空间
TYPENAME VARCHAR2(80) NULL, -- 显示名称,例如 SOXXData
TYPEDESC VARCHAR2(1000) NULL, -- 类型描述(<summary/>)
CONSTRAINT PK_SRCTYPE PRIMARY KEY (TYPEFULLNAME)
);
COMMENT ON TABLE SRCTYPE IS '代码扫描数据类型';
COMMENT ON COLUMN SRCTYPE.TYPEFULLNAME IS '类型全称';
COMMENT ON COLUMN SRCTYPE.NAMESPACE IS '命名空间';
COMMENT ON COLUMN SRCTYPE.TYPENAME IS '显示名称';
COMMENT ON COLUMN SRCTYPE.TYPEDESC IS '类型描述';
-- 5. 数据类型属性表:记录自定义类型的公共属性/字段
CREATE TABLE SRCPROP (
TYPEFULLNAME VARCHAR2(80) NOT NULL, -- 类型全称
PROPNAME CHAR(32) NOT NULL, -- 属性名称
PROPTYPEFULLNAME VARCHAR2(80) NOT NULL, -- 属性类型全称
NAMESPACE VARCHAR2(80) NULL, -- 属性类型命名空间
PROPTYPE VARCHAR2(80) NULL, -- 属性类型显示名称
PROPDESC VARCHAR2(250) NULL, -- 属性描述(<summary/>)
CONSTRAINT PK_SRCPROP PRIMARY KEY (TYPEFULLNAME, PROPNAME, PROPTYPEFULLNAME)
);
COMMENT ON TABLE SRCPROP IS '代码扫描数据类型属性';
COMMENT ON COLUMN SRCPROP.TYPEFULLNAME IS '类型全称';
COMMENT ON COLUMN SRCPROP.PROPNAME IS '属性名称';
COMMENT ON COLUMN SRCPROP.PROPTYPEFULLNAME IS '属性类型全称';
COMMENT ON COLUMN SRCPROP.NAMESPACE IS '属性类型命名空间';
COMMENT ON COLUMN SRCPROP.PROPTYPE IS '属性类型';
COMMENT ON COLUMN SRCPROP.PROPDESC IS '属性描述';
-- 可选索引:加速按 MACID 查询
CREATE INDEX IDX_SRCMETHOD_MACID ON SRCMETHOD(MACID);
CREATE INDEX IDX_SRCMETHODPARAMS_MACID ON SRCMETHODPARAMS(MACID);
CREATE INDEX IDX_SRCPROP_TYPEFULLNAME ON SRCPROP(TYPEFULLNAME);
+808
View File
@@ -0,0 +1,808 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>服务方法管理 - DLL 扫描浏览</title>
<!-- Vue 2 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script>
<!-- Element UI CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.15.13/lib/theme-chalk/index.css">
<!-- Element UI JS -->
<script src="https://cdn.jsdelivr.net/npm/element-ui@2.15.13/lib/index.js"></script>
<!-- Axios -->
<script src="https://cdn.jsdelivr.net/npm/axios@0.27.2/dist/axios.min.js"></script>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
padding: 0;
background: #f0f2f5;
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
color: #303133;
}
.page-container {
max-width: 1600px;
margin: 0 auto;
padding: 20px;
}
.top-bar {
background: #fff;
border-radius: 10px;
padding: 20px 24px;
box-shadow: 0 2px 12px rgba(0,0,0,.06);
margin-bottom: 16px;
}
.page-title {
font-size: 20px;
font-weight: 600;
margin: 0 0 16px 0;
color: #303133;
}
.scan-form {
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
}
.scan-form .el-input {
flex: 1;
min-width: 280px;
}
.scan-form .el-dropdown {
flex-shrink: 0;
}
.el-dropdown-menu__item {
max-width: 500px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.stats-bar {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 12px;
margin-bottom: 16px;
}
.stat-card {
background: #fff;
border-radius: 10px;
padding: 16px 20px;
box-shadow: 0 2px 12px rgba(0,0,0,.04);
display: flex;
flex-direction: column;
}
.stat-label {
color: #909399;
font-size: 13px;
margin-bottom: 6px;
}
.stat-value {
color: #409eff;
font-size: 24px;
font-weight: 700;
word-break: break-all;
}
.filter-bar {
margin-bottom: 16px;
display: flex;
gap: 12px;
align-items: center;
flex-wrap: wrap;
}
.main-layout {
width: 100%;
}
.left-panel {
width: 100%;
}
.module-title {
font-size: 14px;
font-weight: 600;
color: #303133;
display: flex;
align-items: center;
gap: 8px;
}
.module-title i {
color: #e6a23c;
font-size: 18px;
}
.type-badge {
cursor: pointer;
color: #409eff;
font-family: Consolas, monospace;
text-decoration: underline;
text-decoration-style: dashed;
font-size: 13px;
}
.type-badge:hover {
color: #66b1ff;
background: #ecf5ff;
border-radius: 3px;
}
.type-badge.system {
color: #606266;
cursor: default;
text-decoration: none;
}
.type-badge.system:hover {
background: transparent;
}
.type-badge.active {
background: #409eff;
color: #fff;
border-radius: 3px;
padding: 0 4px;
text-decoration: none;
}
.param-desc {
color: #909399;
font-size: 12px;
}
.empty-tip {
text-align: center;
padding: 60px 0;
color: #909399;
}
.type-dialog .el-dialog__header {
background: #409eff;
padding: 16px 20px;
}
.type-dialog .el-dialog__title {
color: #fff;
font-size: 16px;
font-weight: 600;
word-break: break-all;
line-height: 1.5;
}
.type-dialog .el-dialog__headerbtn .el-dialog__close {
color: #fff;
}
.type-dialog .el-dialog__body {
padding: 20px;
max-height: 70vh;
overflow-y: auto;
}
.dialog-namespace {
color: #909399;
font-size: 13px;
margin-bottom: 12px;
}
.dialog-type-desc {
color: #606266;
font-size: 14px;
line-height: 1.8;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #ebeef5;
}
.dialog-section-title {
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
}
.dialog-prop-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.dialog-prop-item {
padding: 14px;
background: #f5f7fa;
border-radius: 6px;
}
.dialog-prop-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
margin-bottom: 6px;
flex-wrap: wrap;
}
.dialog-prop-name {
font-weight: 600;
color: #303133;
font-family: Consolas, monospace;
font-size: 14px;
word-break: break-all;
line-height: 1.5;
}
.dialog-prop-type {
color: #409eff;
font-family: Consolas, monospace;
font-size: 14px;
cursor: pointer;
word-break: break-all;
line-height: 1.5;
text-align: right;
}
.dialog-prop-type:hover {
color: #66b1ff;
}
.dialog-prop-type.system {
color: #909399;
cursor: default;
}
.dialog-prop-desc {
color: #909399;
font-size: 13px;
line-height: 1.6;
}
.dialog-empty {
text-align: center;
padding: 40px 0;
color: #909399;
}
@media (max-width: 768px) {
.type-dialog {
width: 92% !important;
}
}
.module-empty {
text-align: center;
padding: 30px 0;
color: #909399;
}
.type-table-card {
border-radius: 10px;
box-shadow: 0 2px 12px rgba(0,0,0,.04);
}
.modules-panel {
border-radius: 10px;
box-shadow: 0 2px 12px rgba(0,0,0,.04);
}
.modules-panel .el-card__header {
padding: 16px 20px;
background: #fafbfc;
}
.module-collapse {
border: none;
}
.module-collapse .el-collapse-item__header {
font-size: 14px;
font-weight: 600;
color: #303133;
padding-left: 4px;
padding-right: 4px;
}
.module-collapse .el-collapse-item__content {
padding: 0 0 16px 0;
}
.module-collapse-title {
flex: 1;
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.module-collapse-title span:first-child {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.method-table {
width: 100%;
}
.method-table .el-table__expanded-cell {
padding: 16px 20px;
background: #fafbfc;
}
.expand-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #ebeef5;
}
.expand-title {
font-weight: 600;
color: #303133;
font-size: 13px;
}
.expand-params {
display: grid;
grid-template-columns: auto 1fr;
gap: 8px 16px;
align-items: baseline;
}
.expand-params .param-name {
color: #303133;
font-family: Consolas, monospace;
font-weight: 500;
white-space: nowrap;
}
.expand-params .param-type-desc {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.expand-params .param-desc {
color: #909399;
font-size: 12px;
}
.left-panel {
min-width: 0;
}
.type-badge {
word-break: break-all;
line-height: 1.5;
}
</style>
</head>
<body>
<div id="app" v-cloak>
<div class="page-container">
<div class="top-bar">
<h1 class="page-title">服务方法管理 — DLL 扫描浏览</h1>
<div class="scan-form">
<el-input
v-model="filePath"
placeholder="请输入 DLL 文件绝对路径,例如 D:\\GTHINKING\\Bin\\S_XLXSBJA.dll"
clearable
@keyup.enter.native="scanDll">
</el-input>
<el-dropdown v-if="recentPaths.length" size="small" split-button type="info" @command="selectRecentPath" @click="filePath = recentPaths[0]">
<i class="el-icon-time"></i> 最近路径
<el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="(p, idx) in recentPaths" :key="idx" :command="p" :title="p">{{ p }}</el-dropdown-item>
<el-dropdown-item divided command="__clear__">清空记录</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-button type="primary" icon="el-icon-search" :loading="scanning" @click="scanDll">
扫描 DLL
</el-button>
<el-button icon="el-icon-refresh" :loading="loading" @click="loadData" :disabled="!macId">
刷新数据
</el-button>
</div>
</div>
<div v-if="macId" class="stats-bar">
<div class="stat-card">
<span class="stat-label">服务部件</span>
<span class="stat-value" style="font-size: 16px;">{{ macId }}</span>
</div>
<div class="stat-card">
<span class="stat-label">模块数</span>
<span class="stat-value">{{ srcmd.length }}</span>
</div>
<div class="stat-card">
<span class="stat-label">方法数</span>
<span class="stat-value">{{ srcmethod.length }}</span>
</div>
<div class="stat-card">
<span class="stat-label">参数数</span>
<span class="stat-value">{{ srcmethodparams.length }}</span>
</div>
<div class="stat-card">
<span class="stat-label">自定义类型</span>
<span class="stat-value">{{ srctype.length }}</span>
</div>
</div>
<div v-if="macId" class="filter-bar">
<el-input
v-model="filterText"
placeholder="搜索方法名 / 参数名 / 类型名"
prefix-icon="el-icon-search"
clearable
style="width: 260px;">
</el-input>
<el-select
v-if="viewMode === 'module'"
v-model="jumpModule"
placeholder="快速跳转模块"
size="small"
clearable
style="width: 220px;"
@change="jumpToModule">
<el-option
v-for="md in srcmd"
:key="md.MDID"
:label="md.MDNAME"
:value="md.MDID">
</el-option>
</el-select>
<template v-if="viewMode === 'module'">
<el-button size="small" icon="el-icon-arrow-down" @click="expandAllModules">全部展开</el-button>
<el-button size="small" icon="el-icon-arrow-up" @click="collapseAllModules">全部收起</el-button>
</template>
<el-button size="small" icon="el-icon-close" @click="clearFilter">清空筛选</el-button>
<el-radio-group v-model="viewMode" size="small">
<el-radio-button label="module">按模块浏览</el-radio-button>
<el-radio-button label="type">按类型浏览</el-radio-button>
</el-radio-group>
<el-tag v-if="viewMode === 'module' && filterText" type="info" size="small">{{ filteredMethodCount }} 个方法</el-tag>
</div>
<div v-if="macId" class="main-layout">
<!-- 左侧:模块/方法列表 -->
<div class="left-panel">
<!-- 按模块视图 -->
<template v-if="viewMode === 'module'">
<el-card class="modules-panel" shadow="never">
<div slot="header" class="module-title">
<i class="el-icon-s-operation" style="color: #409eff;"></i>
<span>模块与方法</span>
</div>
<div v-if="filteredModules.length === 0" class="module-empty">没有匹配的数据</div>
<el-collapse v-else v-model="expandedModules" class="module-collapse">
<el-collapse-item v-for="md in filteredModules" :key="md.MDID" :name="md.MDID" :data-module-id="md.MDID">
<template slot="title">
<div class="module-collapse-title">
<span :title="md.MDNAME">{{ md.MDNAME }}</span>
<el-tag size="mini" type="info">{{ md.methods.length }} 个方法</el-tag>
</div>
</template>
<div v-if="md.methods.length === 0" class="module-empty">该模块下没有 AjaxMethod 方法</div>
<el-table
v-else
:data="md.methods"
class="method-table"
size="small"
stripe
:header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: 600 }">
<el-table-column type="expand" width="40">
<template slot-scope="props">
<div class="expand-header">
<span class="expand-title">参数明细</span>
<el-button type="text" size="mini" icon="el-icon-document-copy" @click="copyMethodSignature(props.row)">复制签名</el-button>
</div>
<div class="expand-params">
<template v-for="p in props.row.params">
<span class="param-name">{{ p.PARAMNAME }}</span>
<div class="param-type-desc">
<span
:class="['type-badge', { system: isSystemType(p.PARAMTYPE), active: isActiveType(p.PARAMTYPE) }]"
@click.stop="showType(p.PARAMTYPE)">
{{ p.PARAMTYPE }}
</span>
<span v-if="p.PARAMDESC" class="param-desc">{{ p.PARAMDESC }}</span>
</div>
</template>
<template v-if="props.row.params.length === 0">
<span class="param-name"></span>
<span class="param-desc">无参数</span>
</template>
</div>
</template>
</el-table-column>
<el-table-column prop="METHODNAME" label="方法名" min-width="140"></el-table-column>
<el-table-column prop="MODIFIERS" label="修饰符" min-width="100"></el-table-column>
<el-table-column prop="AUTHREQUIRED" label="需登录" width="70" align="center"></el-table-column>
<el-table-column prop="USEHTTPGET" label="支持GET" width="80" align="center"></el-table-column>
<el-table-column label="返回类型" min-width="220">
<template slot-scope="scope">
<span
:class="['type-badge', { system: isSystemType(scope.row.RETTYPE), active: isActiveType(scope.row.RETTYPE) }]"
@click.stop="showType(scope.row.RETTYPE)">
{{ scope.row.RETTYPE || 'void' }}
</span>
</template>
</el-table-column>
<el-table-column prop="RETDESC" label="返回值描述" min-width="160" show-overflow-tooltip></el-table-column>
<el-table-column prop="METHODDESC" label="方法描述" min-width="200" show-overflow-tooltip></el-table-column>
</el-table>
</el-collapse-item>
</el-collapse>
</el-card>
</template>
<!-- 按类型视图 -->
<template v-if="viewMode === 'type'">
<el-card class="type-table-card" shadow="never">
<div slot="header" class="module-title">
<i class="el-icon-coin" style="color: #409eff;"></i>
<span>自定义类型一览</span>
</div>
<el-table :data="filteredTypes" style="width: 100%" size="small" stripe>
<el-table-column label="类型全称" min-width="260">
<template slot-scope="scope">
<el-link type="primary" @click="showType(scope.row.TYPEFULLNAME)">{{ scope.row.TYPEFULLNAME }}</el-link>
</template>
</el-table-column>
<el-table-column prop="NAMESPACE" label="命名空间" min-width="180"></el-table-column>
<el-table-column prop="TYPEDESC" label="描述" show-overflow-tooltip></el-table-column>
</el-table>
</el-card>
</template>
</div>
</div>
<!-- 类型定义弹窗 -->
<el-dialog
:visible.sync="dialogVisible"
:title="currentTypeFullName || '类型定义'"
width="70%"
:close-on-click-modal="true"
custom-class="type-dialog">
<div v-if="currentTypeNamespace" class="dialog-namespace">{{ currentTypeNamespace }}</div>
<div v-if="currentTypeDesc" class="dialog-type-desc">{{ currentTypeDesc }}</div>
<div class="dialog-section-title">公共属性 / 字段({{ currentTypeProps.length }})</div>
<div v-if="currentTypeProps.length === 0" class="dialog-empty">该类型没有公共属性/字段</div>
<div v-else class="dialog-prop-list">
<div v-for="prop in currentTypeProps" :key="prop.PROPNAME + '_' + prop.PROPTYPEFULLNAME" class="dialog-prop-item">
<div class="dialog-prop-header">
<span class="dialog-prop-name">{{ prop.PROPNAME }}</span>
<span
:class="['dialog-prop-type', { system: isSystemType(prop.PROPTYPEFULLNAME) }]"
@click="showType(prop.PROPTYPEFULLNAME)">
{{ prop.PROPTYPE || prop.PROPTYPEFULLNAME }}
</span>
</div>
<div v-if="prop.PROPDESC" class="dialog-prop-desc">{{ prop.PROPDESC }}</div>
</div>
</div>
</el-dialog>
<div v-if="!macId && !scanning && !loading" class="empty-tip">
<i class="el-icon-upload" style="font-size: 56px; color: #dcdfe6; display: block; margin-bottom: 16px;"></i>
<div>输入 DLL 文件路径并点击“扫描 DLL”开始解析服务方法</div>
</div>
</div>
</div>
<script>
new Vue({
el: '#app',
data: {
// 服务调用基地址,按项目实际部署调整
baseUrl: 'http://127.0.0.1/GTHINKING/AjaxService/S_XLXSBJ/102373019.ashx',
filePath: '',
scanning: false,
loading: false,
macId: '',
srcmd: [],
srcmethod: [],
srcmethodparams: [],
srctype: [],
srcprop: [],
filterText: '',
viewMode: 'module',
expandedModules: [],
currentTypeFullName: '',
dialogVisible: false,
recentPaths: [],
jumpModule: '',
},
computed: {
currentTypeNamespace() {
const t = this.srctype.find(x => x.TYPEFULLNAME === this.currentTypeFullName);
return t && t.NAMESPACE ? t.NAMESPACE : '';
},
currentTypeDesc() {
const t = this.srctype.find(x => x.TYPEFULLNAME === this.currentTypeFullName);
return t && t.TYPEDESC ? t.TYPEDESC : '';
},
currentTypeProps() {
return this.srcprop.filter(x => x.TYPEFULLNAME === this.currentTypeFullName);
},
filteredModules() {
const text = (this.filterText || '').toLowerCase();
return this.srcmd.map(md => {
const methods = this.srcmethod
.filter(m => m.MACID === md.MACID && m.MDID === md.MDID)
.map(m => {
const params = this.srcmethodparams.filter(p =>
p.MACID === m.MACID && p.MDID === m.MDID && p.METHODNAME === m.METHODNAME
);
return { ...m, params };
});
return { ...md, methods };
}).filter(md => {
if (!text) return true;
return md.methods.some(m =>
(m.METHODNAME || '').toLowerCase().includes(text) ||
m.params.some(p =>
(p.PARAMNAME || '').toLowerCase().includes(text) ||
(p.PARAMTYPE || '').toLowerCase().includes(text)
) ||
(m.RETTYPE || '').toLowerCase().includes(text)
);
});
},
filteredTypes() {
const text = (this.filterText || '').toLowerCase();
if (!text) return this.srctype;
return this.srctype.filter(t =>
(t.TYPEFULLNAME || '').toLowerCase().includes(text) ||
(t.TYPENAME || '').toLowerCase().includes(text) ||
(t.NAMESPACE || '').toLowerCase().includes(text)
);
},
filteredMethodCount() {
return this.filteredModules.reduce((sum, md) => sum + md.methods.length, 0);
}
},
methods: {
isSystemType(typeName) {
if (!typeName) return true;
const lower = typeName.toLowerCase();
if (lower.startsWith('system.')) return true;
if (lower.startsWith('microsoft.')) return true;
if (['string', 'int', 'bool', 'long', 'double', 'float', 'decimal', 'datetime', 'object', 'void', 'byte', 'char', 'short', 'uint', 'ulong', 'ushort'].includes(lower)) return true;
if (lower.endsWith('[]')) return this.isSystemType(lower.replace('[]', ''));
if (lower.endsWith('?')) return this.isSystemType(lower.replace('?', ''));
if (lower.includes('<')) {
const base = lower.split('<')[0];
const generic = ['list', 'ienumerable', 'icollection', 'dictionary', 'nullable'];
if (generic.some(g => base.includes(g))) return true;
}
return false;
},
isActiveType(typeName) {
if (!typeName || this.isSystemType(typeName)) return false;
let base = typeName;
if (base.endsWith('[]')) base = base.replace('[]', '');
if (base.endsWith('?')) base = base.replace('?', '');
if (base.indexOf('<') > 0) base = base.substring(0, base.indexOf('<'));
return base.trim() === this.currentTypeFullName;
},
callMethod(methodName, params) {
const url = this.baseUrl + '/' + methodName;
return axios.post(url, params, {
headers: { 'Content-Type': 'application/json' }
}).then(res => {
const data = res.data;
if (data && data.success === false) {
throw new Error(data.msg || '服务调用失败');
}
return data;
});
},
scanDll() {
if (!this.filePath) {
this.$message.warning('请输入 DLL 文件路径');
return;
}
this.scanning = true;
this.callMethod('SaveServiceMethodInfo', { filePath: this.filePath })
.then(data => {
this.$message.success(data.msg || '扫描完成');
this.macId = data.macId || '';
this.currentTypeFullName = '';
this.saveRecentPath(this.filePath);
return this.loadData();
})
.catch(err => {
this.$message.error(err.message || '扫描失败');
})
.finally(() => {
this.scanning = false;
});
},
loadData() {
if (!this.macId) {
this.$message.warning('请先扫描 DLL');
return Promise.resolve();
}
this.loading = true;
return this.callMethod('GetServiceMethodData', { macId: this.macId })
.then(data => {
this.srcmd = data.SRCMD || [];
this.srcmethod = data.SRCMETHOD || [];
this.srcmethodparams = data.SRCMETHODPARAMS || [];
this.srctype = data.SRCTYPE || [];
this.srcprop = data.SRCPROP || [];
this.expandedModules = (data.SRCMD || []).map(md => md.MDID);
this.currentTypeFullName = '';
this.$message.success('数据加载完成');
})
.catch(err => {
this.$message.error(err.message || '加载数据失败');
})
.finally(() => {
this.loading = false;
});
},
showType(typeName) {
if (!typeName) return;
if (this.isSystemType(typeName)) return;
let base = typeName;
if (base.endsWith('[]')) base = base.replace('[]', '');
if (base.endsWith('?')) base = base.replace('?', '');
if (base.indexOf('<') > 0) base = base.substring(0, base.indexOf('<'));
base = base.trim();
if (!this.srctype.some(t => t.TYPEFULLNAME === base)) {
this.$message.info('该类型未在扫描结果中找到,可能为系统类型或未被引用');
return;
}
this.currentTypeFullName = base;
this.dialogVisible = true;
},
loadRecentPaths() {
try {
const list = JSON.parse(localStorage.getItem('svcMethodRecentPaths') || '[]');
this.recentPaths = Array.isArray(list) ? list : [];
} catch (e) {
this.recentPaths = [];
}
},
saveRecentPath(path) {
if (!path) return;
let list = this.recentPaths.filter(p => p !== path);
list.unshift(path);
if (list.length > 10) list = list.slice(0, 10);
this.recentPaths = list;
try {
localStorage.setItem('svcMethodRecentPaths', JSON.stringify(list));
} catch (e) {}
},
selectRecentPath(path) {
if (path === '__clear__') {
this.clearRecentPaths();
this.$message.success('已清空最近路径');
} else {
this.filePath = path;
}
},
clearRecentPaths() {
this.recentPaths = [];
try {
localStorage.removeItem('svcMethodRecentPaths');
} catch (e) {}
},
expandAllModules() {
this.expandedModules = this.srcmd.map(md => md.MDID);
},
collapseAllModules() {
this.expandedModules = [];
},
jumpToModule() {
if (!this.jumpModule) return;
const mdId = this.jumpModule;
if (!this.expandedModules.includes(mdId)) {
this.expandedModules.push(mdId);
}
this.$nextTick(() => {
const el = document.querySelector('[data-module-id="' + mdId + '"]');
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
},
clearFilter() {
this.filterText = '';
this.jumpModule = '';
},
copyMethodSignature(method) {
const params = method.params.map(p => p.PARAMTYPE + ' ' + p.PARAMNAME).join(', ');
const sig = (method.MODIFIERS || 'public') + ' ' + (method.RETTYPE || 'void') + ' ' + method.METHODNAME + '(' + params + ')';
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(sig).then(() => {
this.$message.success('方法签名已复制');
}).catch(() => {
this.$message.warning('复制失败,请手动复制');
});
} else {
const ta = document.createElement('textarea');
ta.value = sig;
document.body.appendChild(ta);
ta.select();
try {
document.execCommand('copy');
this.$message.success('方法签名已复制');
} catch (e) {
this.$message.warning('复制失败,请手动复制');
}
document.body.removeChild(ta);
}
}
},
mounted() {
this.loadRecentPaths();
const urlParams = new URLSearchParams(window.location.search);
const path = urlParams.get('path');
if (path) {
this.filePath = path;
} else if (this.recentPaths.length) {
this.filePath = this.recentPaths[0];
}
}
});
</script>
</body>
</html>