『贴代码』
项目介绍
项目精选
优选问答
【本期话题】更多
三人寄语更多
时好时坏的结果,往往是多线程引起的逻辑混乱导致的!
点赞:0
能通过内网IP访问的,尽量不要使用域名访问!
点赞:1
需求就是系统的千年杀,相爱相杀那种!
点赞:0
时间与空间总是在换来换去,鱼和熊掌往往不可同得!
点赞:0
没有最好的语言,只有更合适的语言!
点赞:0
由于时间的问题,我们往往会给自己埋坑,等着后续或者下一任来填!
点赞:0
逻辑注解清晰的代码优于那些一眼看不明白的语法糖
点赞:0
谋而后动,往往会让你对自己的代码更具信心!
点赞:1
在循环里面慎重的使用await!
点赞:0
测试没问题的不一定没问题,测试有问题的那肯定有问题!
点赞:2
PasteForm又添加一个大将,快速的帮你把你的项目升级成PasteForm类型,只需要引入一个文件即可!!!
知乎也 2025-07-05 227 0 1
反射,在高级语言中非常常见,但是我们开发中其实用得还是比较少的,毕竟有性能开销嘛,那么是否有地方适合我们使用呢?一起来看看PasteForm是如何看代或者说取舍反射的!

更多特性PasteForm的介绍,请前往PasteForm操作文档查看 PasteForm操作文档与介绍


最近呢,我有一个项目旧的想升级到PasteForm类型,为啥呢!!!

1.简洁,干净

都知道PasteForm框架出名的是啥?
管理端啊,开发只要专注业务端和用户端,
管理端完全不用管,这里说得是管理端的页面
也就是我们说得系统后台,就是各种表单,各种表格,报表那个
你就说爽不爽!!!

2.直接,干脆

另外一个很重要的特点是,几乎的限制等在Dto的特性中
比如限制最大长度
比如说这里是上传图片的
比如说这里是选择时间区间的
你都只要写一行特性即可!!!

3.快捷,迅速

这里说得不是运行速度哈,毕竟用了文档和反射,性能上是有点吃亏的,但是为啥还用呢
因为这个只对管理端起作用,也就是对于你得用户端,完全没影响啊!!!
快捷在于,你只要专注Dto的特性标记即可,
迅速是因为你可以快速迭代,可以说只要业务逻辑不是非常复杂的,我可以做到全部开发完成后再测试
也就是压根不需要频繁的测试发布测试等!!!

开始构思

好了上面说了一堆,其实就是吹了一顿,那么开始干活
现在的情况是,我这个项目以前是用老方式写的
管理端的页面就有400多个啊!!!
还是各种VUE混合原生的,各种页面,我只能说,望洋兴叹啊!!!
也是因为这个问题,所以我才更加想升级到PasteForm,因为PasteForm的管理端页面不会多于10个!!!
而且要是没有特殊需求的话,压根不需要开发管理端啊!!!

PasteForm规则

既然要升级到PasteForm的,就需要按照PasteForm的规则办事,对吧!
举个例子
用户在新建XXX表单的时候,比如创建用户,创建商品,创建分类的时候
需要从API读取对应的表单内容,也就是表单的格式等信息
问题来了,我有大概150个表!!!
哎,灾难啊!!!虽然有代码生成器PasteBuilder
不过这个东西是给项目前期准备的,要是后期修改了字段,自定义了逻辑等就不太合适了!!
那有没有一个办法,通用,就是写一个通用的接口给所有的表用呢???
有特例的再进行特别写呢!!!
基于反射,理论上是可行的,因为你看几乎的新增表单数据是这个样子的

        /// <summary>
        /// 读取AddDto的数据模型
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "add" })]
        public VoloModelInfo ReadAddModel()
        {
            var _model = PasteBuilderHelper.DynamicReadModelProperty<GradeInfoAddDto>(new GradeInfoAddDto());
            return _model;
        }
        /// <summary>
        /// 读取AddDto的数据模型
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "root", "root" })]
        public VoloModelInfo ReadAddModel()
        {
            //当前的路由为 /api/app/userInfo/readAddModel
            var _model = PasteBuilderHelper.ReadModelProperty<RoleInfoAddDto>(new RoleInfoAddDto());
            return _model;
        }

发觉没有,其实高度重复的!
对应的其他也是一样的,比如更新等

        /// <summary>
        /// 读取UpdateDto的数据模型
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "edit" })]
        public async Task<VoloModelInfo> ReadUpdateModel(int id)
        {
            var _info = await _dbContext.GradeInfo.Where(x => x.Id == id).AsNoTracking().FirstOrDefaultAsync();
            if (_info == null || _info == default)
            {
                throw new PasteCodeException("查询的信息不存在,无法执行编辑操作!");
            }
            var dto = ObjectMapper.Map<GradeInfo,GradeInfoUpdateDto>(_info);
            var _dataModel = PasteBuilderHelper.DynamicReadModelProperty<GradeInfoUpdateDto>(dto);
            return _dataModel;
        }

DefaultAppService

按照上面的思路,我们只要使用路由中的表名(这个其实可以做一个映射哈,比如管理端用Abc,系统表示Tableb),直接上代码

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PasteForm.Application.Contracts;
using PasteForm.Handler;
using PasteForm.usermodels;
using PasteFormHelper;
using System;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Auditing;

namespace PasteForm.Application
{
    /// <summary>
    /// 默认读取 这个很关键,实现了统一的新增 更新 详细 列表规范读取 相当于兜底的!
    ///</summary>
    [TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "view" })]
    [DisableAuditing]
    public class DefaultAppService : PasteFormAppService
    {

        /// <summary>
        /// 读取AddDto的数据模型 用于新增
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "view" })]
        [Route("/api/app/{entityName}/readAddModel", Order = 10)]
        public VoloModelInfo ReadAddModel([FromRoute] string entityName)
        {
            // 1. 根据实体名称查找对应的实体类型和 DTO 类型
            var entityType = FindEntityType(entityName);
            if (entityType == null)
            {
                throw new PasteCodeException($"找不到对应的实体类型: {entityName}");
            }
            var dtoType = FindDtoType(entityName, "AddDto");

            if (dtoType == null)
            {
                throw new PasteCodeException($"找不到对应的 DTO 类型: {entityName}AddDto");
            }

            //new一个出来
            var dto = Activator.CreateInstance(dtoType);

            // 4. 动态读取 DTO 的属性信息
            var dataModel = ReadModelProperties(dto, dtoType);

            return dataModel;

        }

        /// <summary>
        /// 用于更新表单
        /// </summary>
        /// <param name="entityName"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        /// <exception cref="PasteCodeException"></exception>
        [HttpGet]
        [TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "view" })]
        [Route("/api/app/{entityName}/readUpdateModel", Order = 10)]
        public async Task<VoloModelInfo> ReadUpdateModel([FromRoute] string entityName, int id)
        {
            // 1. 根据实体名称查找对应的实体类型和 DTO 类型
            var entityType = FindEntityType(entityName);
            var dtoType = FindDtoType(entityName, "UpdateDto");

            if (entityType == null || dtoType == null)
            {
                throw new PasteCodeException($"找不到对应的实体或 DTO 类型: {entityName}");
            }
            // 2. 动态查询实体
            var entity = await FindEntityByIdAsync(entityType, id);
            if (entity == null)
            {
                throw new PasteCodeException("查询的信息不存在,无法执行编辑操作!");
            }
            // 3. 动态映射实体到 DTO
            var dto = MapEntityToDto(entity, entityType, dtoType);

            //构建外表等 Include的如何搞定???
            await BuildOuterQuery(dto, dtoType);

            // 4. 动态读取 DTO 的属性信息
            var dataModel = ReadModelProperties(dto, dtoType);
            return dataModel;
        }

        /// <summary>
        /// 用于查看详情
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Route("/api/app/{entityName}/readDetailModel", Order = 10)]
        [TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "view" })]
        public async Task<VoloModelInfo> ReadDetailModel([FromRoute] string entityName, int id)
        {
            //var _info = await _dbContext.StoreCashWay.Where(x => x.Id == id).AsNoTracking().FirstOrDefaultAsync();
            //if (_info == null || _info == default)
            //{
            //    throw new PasteCodeException("查询的信息不存在,无法执行编辑操作!");
            //}
            //var dto = ObjectMapper.Map<StoreCashWay, StoreCashWayDto>(_info);
            //var _dataModel = PasteBuilderHelper.ReadModelProperty<StoreCashWayDto>(dto);
            //return _dataModel;

            // 1. 根据实体名称查找对应的实体类型和 DTO 类型
            var entityType = FindEntityType(entityName);
            var dtoType = FindDtoType(entityName, "Dto");

            if (entityType == null || dtoType == null)
            {
                throw new PasteCodeException($"找不到对应的实体或 DTO 类型: {entityName}");
            }

            // 2. 动态查询实体
            var entity = await FindEntityByIdAsync(entityType, id);
            if (entity == null)
            {
                throw new PasteCodeException("查询的信息不存在,无法执行编辑操作!");
            }

            // 3. 动态映射实体到 DTO
            var dto = MapEntityToDto(entity, entityType, dtoType);

            //构建外表等 Include的如何搞定???
            await BuildOuterQuery(dto, dtoType);

            // 4. 动态读取 DTO 的属性信息
            var dataModel = ReadModelProperties(dto, dtoType);

            return dataModel;

        }

        /// <summary>
        /// 用户查看表格
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Route("/api/app/{entityName}/readListModel", Order = 10)]
        [TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "view" })]
        public VoloModelInfo ReadListModel([FromRoute] string entityName, string searchName = "InputSearchBase")
        {
            // 动态查找实体对应的 ListDto 类型
            var listDtoType = FindDtoType(entityName, "ListDto");
            // 创建 ListDto 实例
            var listDtoInstance = Activator.CreateInstance(listDtoType);
            // 动态读取 ListDto 的属性信息
            //var listModel = PasteBuilderHelper.ReadModelProperty(listDtoInstance, true);
            var listModel = ReadModelProperties(listDtoInstance, listDtoType, true);
            if (searchName != "InputSearchBase")
            {
                // 保持搜索模型的静态处理(假设 InputSearchBase 是固定类)
                var searchModel = PasteBuilderHelper.ReadModelProperty(new InputSearchBase());
                if (searchModel != null)
                {
                    listModel.QueryProperties = searchModel.Properties;
                }
            }
            else
            {
                var search_type = FindType(searchName);
                if (search_type != null)
                {
                    var search_model = Activator.CreateInstance(search_type);
                    //var searchModel = PasteBuilderHelper.ReadModelProperty(search_model);
                    var searchModel = ReadModelProperties(search_model, search_type, true);
                    if (searchModel != null)
                    {
                        listModel.QueryProperties = searchModel.Properties;
                    }
                }
            }
            return listModel;
        }

        #region 以下是辅助函数

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dto"></param>
        /// <param name="dtoType"></param>
        /// <returns></returns>
        private async Task BuildOuterQuery(object dto, Type dtoType)
        {
            #region 看看是否要执行外表查询
            foreach (var aty in dtoType.GetProperties())
            {
                //var _type = aty.PropertyType;
                var attributes = aty.GetCustomAttributes<PasteShortAttribute>();
                if (attributes?.Any() == true)
                {
                    var short_attr = attributes.FirstOrDefault();
                    if (short_attr != null && short_attr != default)
                    {
                        var foreignKeyId = aty.GetValue(dto);
                        if (foreignKeyId == null || foreignKeyId.Equals(0))
                        {
                            continue;
                        }
                        var shortAttr = short_attr;
                        //var pro_val =//??? 当前字段的值
                        //short_attr.Args1 = "GradeInfo";//表示对应的外表的EntityName
                        //short_attr.Args2 = "ExtendGrade";//表示结果写入到这个字段
                        //short_attr.Args3 = "ToShortUser()";//表示要转换的类型 比如_dbContext.GradeInfo.Where(x=>x.Id==pro_val).Select(x=>x.ToShortUser()).FirstOrDefaultAsync();
                        //如何获取字段的当前值?
                        //await _dbContext.GradeInfo.Where(x => x.Id == 1).Select(y => y.ToShortGrade()).FirstOrDefaultAsync();
                        //ToShortGrade()是GradeInfo的扩展,返回的就是ExtendGrade的类型

                        // 获取外表的Entity类型和目标属性
                        var relatedEntityType = FindEntityType(shortAttr.Args1);
                        var targetProperty = dtoType.GetProperty(shortAttr.Args2);

                        if (relatedEntityType == null || targetProperty == null)
                        {
                            continue;
                        }
                        try
                        {
                            // 获取关联实体的DbSet
                            var dbSet = _dbContext.GetType()
                                .GetProperties()
                                .First(p => p.PropertyType.GenericTypeArguments.Contains(relatedEntityType))
                                .GetValue(_dbContext);

                            // 动态调用FindAsync方法获取关联实体
                            var findAsyncMethod = dbSet.GetType()
                                .GetMethod("FindAsync", new[] { typeof(object[]) });

                            // 执行查询
                            var valueTask = findAsyncMethod.Invoke(dbSet, new object[] { new object[] { foreignKeyId } });

                            // 获取ValueTask的结果
                            object relatedEntity;

                            // 检查是否是ValueTask
                            if (valueTask.GetType().Name.StartsWith("ValueTask"))
                            {
                                // 手动获取ValueTask的结果
                                var getAwaiterMethod = valueTask.GetType().GetMethod("GetAwaiter");
                                var awaiter = getAwaiterMethod.Invoke(valueTask, null);

                                var getResultMethod = awaiter.GetType().GetMethod("GetResult");
                                relatedEntity = getResultMethod.Invoke(awaiter, null);
                            }
                            else
                            {
                                // 普通Task,直接等待
                                await (Task)valueTask;
                                relatedEntity = ((Task<object>)valueTask).Result;
                            }


                            if (relatedEntity != null)
                            {
                                // 处理转换逻辑
                                object convertedResult = null;

                                // 检查是否需要特殊转换
                                if (!string.IsNullOrEmpty(shortAttr.Args3))
                                {
                                    var extend = typeof(LinqExtend).GetMethod(shortAttr.Args3.Replace("()", ""), new[] { relatedEntityType });
                                    // 处理特殊转换方法(例如:ToShortUser())
                                    //convertedResult = ConvertEntity(relatedEntity, relatedEntityType, shortAttr.Args3);
                                    convertedResult = extend.Invoke(null, new object[] { relatedEntity });
                                }
                                else
                                {
                                    // 默认转换:直接使用目标属性类型
                                    convertedResult = relatedEntity;
                                }

                                // 设置目标属性的值
                                targetProperty.SetValue(dto, convertedResult);
                            }
                        }
                        catch (Exception ex)
                        {
                            Logger.LogError(ex, $"加载关联数据失败: {shortAttr.Args1}");
                            // 可以选择记录错误但继续处理其他属性
                        }
                        //------------------------------------------------
                    }
                }
            }

            #endregion
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityName"></param>
        /// <returns></returns>
        private Type FindEntityType(string entityName)
        {
            // 将前端传递的名称转换为 PascalCase (例如: storeCashWay -> StoreCashWay)
            var pascalCaseName = ConvertToPascalCase(entityName);
            var assembly = typeof(UserInfo).Assembly;//这里对不对呢... .. .
            return assembly.GetTypes().FirstOrDefault(t => t.Name == entityName || t.Name == $"{pascalCaseName}");//这里可能有问题了

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityName"></param>
        /// <param name="suffix"></param>
        /// <returns></returns>
        private Type FindDtoType(string entityName, string suffix)
        {
            var pascalCaseName = ConvertToPascalCase(entityName);
            var dtoName = $"{pascalCaseName}{suffix}";

            return typeof(InputSearchBase).Assembly.GetTypes().FirstOrDefault(t => t.Name == dtoName);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="typeName"></param>
        /// <returns></returns>
        private Type FindType(string typeName)
        {
            var assembly = Assembly.GetExecutingAssembly();
            //return assembly.GetTypes().FirstOrDefault(t => t.Name == typeName);
            return typeof(InputSearchBase).Assembly.GetTypes().FirstOrDefault(t => t.Name == typeName);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityType"></param>
        /// <param name="id"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        /// <exception cref="PasteCodeException"></exception>
        private async Task<object> FindEntityByIdAsync(Type entityType, int id, CancellationToken cancellationToken = default)
        {
            // 获取实体对应的 DbSet 属性
            var dbSetProperty = _dbContext.GetType()
                .GetProperties()
                .FirstOrDefault(p =>
                    p.PropertyType.IsGenericType &&
                    p.PropertyType.GetGenericTypeDefinition() == typeof(DbSet<>) &&
                    p.PropertyType.GetGenericArguments()[0] == entityType);

            if (dbSetProperty == null)
            {
                throw new PasteCodeException($"找不到实体 {entityType.Name} 的 DbSet");
            }

            // 获取 DbSet 实例
            var dbSet = dbSetProperty.GetValue(_dbContext);

            // 获取 FindAsync 方法
            var findAsyncMethod = dbSet.GetType()
                .GetMethod("FindAsync", new[] { typeof(object[]), typeof(CancellationToken) });

            // 调用方法
            var valueTask = findAsyncMethod.Invoke(dbSet, new object[] { new object[] { id }, cancellationToken });

            // 处理 ValueTask<T>
            if (valueTask is Task task)
            {
                await task;
                var resultProperty = task.GetType().GetProperty("Result");
                return resultProperty.GetValue(task);
            }
            else
            {
                // 手动处理 ValueTask<T>
                var getAwaiterMethod = valueTask.GetType().GetMethod("GetAwaiter");
                var awaiter = getAwaiterMethod.Invoke(valueTask, null);

                var getResultMethod = awaiter.GetType().GetMethod("GetResult");
                return getResultMethod.Invoke(awaiter, null);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sourceType"></param>
        /// <param name="destinationType"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException"></exception>
        private MethodInfo GetMapMethod(Type sourceType, Type destinationType)
        {
            // 获取 Map<TSource, TDestination> 方法定义
            var map = base.ObjectMapper;
            var mapMethod = typeof(Volo.Abp.ObjectMapping.IObjectMapper)
                .GetMethods(BindingFlags.Public | BindingFlags.Instance)
                .FirstOrDefault(m =>
                    m.Name == "Map" &&
                    m.IsGenericMethodDefinition &&
                    m.GetGenericArguments().Length == 2 &&
                    m.GetParameters().Length == 1
                );

            if (mapMethod == null)
            {
                throw new InvalidOperationException("未找到 Map<TSource, TDestination> 方法");
            }

            // 构造具体的泛型方法
            return mapMethod.MakeGenericMethod(sourceType, destinationType);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sourceInstance"></param>
        /// <param name="sourceType"></param>
        /// <param name="destinationType"></param>
        /// <returns></returns>
        public object MapEntityToDto(object sourceInstance, Type sourceType, Type destinationType)
        {
            // 获取 ObjectMapper 实例
            var mapper = ObjectMapper;

            // 获取映射方法
            var mapMethod = GetMapMethod(sourceType, destinationType);

            // 调用映射方法
            return mapMethod.Invoke(ObjectMapper, new object[] { sourceInstance });
        }

        /// <summary>
        /// 动态读取模型属性
        /// </summary>
        /// <param name="model"></param>
        /// <param name="modelType"></param>
        /// <param name="IsListModel">针对枚举的备注过滤</param>
        /// <returns></returns>
        private VoloModelInfo ReadModelProperties(object model, Type modelType, bool IsListModel = false)
        {
            // 获取目标泛型方法定义
            var genericMethodDefinition = typeof(PasteBuilderHelper)
                .GetMethods(BindingFlags.Public | BindingFlags.Static)
                .First(m => m.Name == "DynamicReadModelProperty" && m.IsGenericMethodDefinition);

            // 构建具体的泛型方法(传入实际类型参数)
            var constructedGenericMethod = genericMethodDefinition
                .MakeGenericMethod(modelType);

            // 调用方法并转换结果
            return (VoloModelInfo)constructedGenericMethod
                .Invoke(null, new object[] { model, IsListModel });
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        private string ConvertToPascalCase(string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                return input;
            }

            if (char.IsUpper(input[0]))
            {
                return input;
            }

            // 将第一个单词全部转换为小写,后面部分保持不变
            return input.Substring(0, 1).ToUpper() + input.Substring(1);
        }

        #endregion

    }


}

注意要点

上面中主要要注意的是程序集,还是我目前没有做啥缓存的操作
程序集呢,主要是我们程序一般不是单个的,也就是有dll的限制,所以有一个约定哈!
Entity的要放对象UserInfo同一个dll,这个要是你没有UserInfo你换一个有的就行,反正是为了锁定程序集
所有的Dto则锁定为InputSearchBase表示查询的基础对象

查询不用仓储,用的是DbContext
为啥不用仓储呢。。。
我感觉没必要,我看过很多资料,其实都是一个意思,都不知道仓储这个玩意干嘛用得!
有人说为了兼容不同数据库???
其实这个DbContext也是一样的
为了实现不一样的逻辑,或者说共性!
其实你继承某一个接口,一样可以实现,真的!!!
我之前有一个多层级排序的就是这么干的,我感觉其实和仓储一样!

上面的DefaultAPPService我测试了下,可以使用,还能处理外表类型的!爽歪歪!

其他问题等待你得反馈哈!

评论列表
尘埃

[Route("/api/app/{entityName}/readUpdateModel", Order = 10)]
兜底的干活,所以你要是要实现个性化,直接按照旧的方式写就行,优先级高于这个!

知乎也
0 227 1
快捷注册
热门推荐更多
PasteForm
贴代码框架的项目案例,里面有PasteForm的案例代码等;
最新动态
  • 131.****.185 正在查看 记PasteSpider部署工具的Windows.IIS版本开发过程之草稿-动态表单UI(3) !
  • 131.****.185 正在查看 记PasteSpider部署工具的Windows.IIS版本开发过程之草稿-动态表单UI(3) !
  • 225.****.213 正在查看 PasteSpiderV5在WindowsServer中以Service的方式运行 !
  • 225.****.213 正在查看 PasteSpiderV5在WindowsServer中以Service的方式运行 !
  • 110.****.169 正在查看 PasteForm框架开发之Entity多级嵌套的表单的实现 !
  • 227.****.53 正在查看 贴代码框架PasteForm特性介绍之file特性(上传文件) !
  • 227.****.53 正在查看 贴代码框架PasteForm特性介绍之file特性(上传文件) !
  • 188.****.46 正在查看 PasteSpider中关于项目,服务,环境,文件模式等对象的说明 !
  • 188.****.46 正在查看 PasteSpider中关于项目,服务,环境,文件模式等对象的说明 !
  • 154.****.176 正在查看 记PasteSpider部署工具的Windows.IIS版本开发过程之草稿-效果展示(4) !
  • 154.****.176 正在查看 记PasteSpider部署工具的Windows.IIS版本开发过程之草稿-效果展示(4) !
欢迎加入QQ讨论群 296245685 [PasteSpider]介绍 [PasteForm]介绍 @2022-2023 PasteCode.cn 版权所有 ICP证 闽ICP备2021013869号-2