『贴代码』
我的博文
个人作品
优选问答
「搜索」
【本期话题】更多
三人寄语更多
没有最好的语言,只有更合适的语言!
点赞:0
能通过内网IP访问的,尽量不要使用域名访问!
点赞:1
往往会为了一个项目,搭建适合他专属的脚手架!
点赞:1
在循环里面慎重的使用await!
点赞:0
对于答案来说,更重要的是找到答案的这个过程而不是答案本身!
点赞:0
微信的app这个东西很鬼,有时候你刷新页面,会造成部分数据重置,部分不重置,不妨试试把对象放app.globalData里面去,会有意外惊喜!
点赞:0
抛开需求讲架构,和纸上谈兵无差!
点赞:1
一些奇奇怪怪的问题,一般和异步有关!
点赞:0
测试没问题的不一定没问题,测试有问题的那肯定有问题!
点赞:2
你连F12都不关注,你好意思说你是前端?
点赞:0
记PasteSpider部署工具的Windows.IIS版本开发过程之草稿-动态表单(2)
尘埃 2025-04-06 45 4 0

更多PasteSpider的操作介绍,请前往 PasteSpider操作文档


;

接1的内容,那么有这么一个需求!

需求分析

需要修改某一个配置的时候
1.从对应的api中读取消息,消息内容为Json格式的
2.基于当前的Json渲染成表单提供给管理端的客户呈现
3.管理端的用户可以基于这个表单的内容进行修改,然后提交
4.服务端基于用户提交的信息,提交给IIS Administration以便更新到IIS的网站中!
所以我们需要一个东西,可以动态编辑Json内容的东西!!!
关键点在于这个编辑非开发也能玩,当然我们也可以提供2个版本,一个表单版本,一个字符串版本

方案一

由上面的步骤可知,1获取到的数据是变动的,或者你可以说可以把几种的格式都创建成表然后入库
只入库表的结构,获取到数据后和表的结构相互结合,然后使用PasteForm提供给用户进行更新!
不要在意CRUD的规范一个表一个API,在这个需求中完全可以使用动态Route进行单API处理!

    {
        "title":"标题",
        "desc":"描述信息",
        "value":97.5
    }

以上的方案你会可见的发现,将会有非常多的工作量,可能增加的表不下100个!!!

方案二

结合现有的PasteForm框架知识,是否也可以搞一个动态的表单,理想的状态有
1.无论来的Json数据是咋样的,都给他在表单中显示
2.可以针对性质的一些字段进行配置,也就是结合方案一的内容做一个表结构的信息
3.区别于方案一的是,方案一比如有100个结构表,你得先创建100个表及对应得CRUD,而这里得动态是可以不出创建的
比如有

    {
        "title":"标题",
        "desc":"描述信息",
        "value":97.5
    }

其实在文章1中还有一个隐藏的知识点就是Json不是单层级的,比如

{
    "name": "iis_temp",
    "id": "UYZcrUKt9eaMiolk-rH_GA",
    "status": "started",
    "auto_start": "true",
    "pipeline_mode": "integrated",
    "managed_runtime_version": "",
    "enable_32bit_win64": "false",
    "queue_length": "1000",
    "start_mode": "OnDemand",
    "cpu": {
        "limit": "0",
        "limit_interval": "5",
        "action": "NoAction",
        "processor_affinity_enabled": "false",
        "processor_affinity_mask32": "0xFFFFFFFF",
        "processor_affinity_mask64": "0xFFFFFFFF"
    },
    "process_model": {
        "idle_timeout": "20",
        "max_processes": "1",
        "pinging_enabled": "true",
        "ping_interval": "30",
        "ping_response_time": "90",
        "shutdown_time_limit": "90",
        "startup_time_limit": "90",
        "idle_timeout_action": "Terminate"
    },
    "identity": {
        "identity_type": "ApplicationPoolIdentity",
        "username": "",
        "load_user_profile": "true"
    },
    "recycling": {
        "disable_overlapped_recycle": "false",
        "disable_recycle_on_config_change": "false",
        "log_events": {
            "time": "true",
            "requests": "true",
            "schedule": "true",
            "memory": "true",
            "isapi_unhealthy": "true",
            "on_demand": "true",
            "config_change": "true",
            "private_memory": "true"
        },
        "periodic_restart": {
            "time_interval": "1740",
            "private_memory": "0",
            "request_limit": "0",
            "virtual_memory": "0",
            "schedule": []
        }
    },
    "rapid_fail_protection": {
        "enabled": "true",
        "load_balancer_capabilities": "HttpLevel",
        "interval": "5",
        "max_crashes": "5",
        "auto_shutdown_exe": "",
        "auto_shutdown_params": ""
    },
    "process_orphaning": {
        "enabled": "false",
        "orphan_action_exe": "",
        "orphan_action_params": ""
    }
}

上面只是二级的,还有一些是Array格式的,更加负责了,还可能有多级,不止二级!

结合上面的消息,所以选择的是方案二

实现单元

结合上面的内容,我的习惯是搞一个测试项目,然后做单元测试,在一个现有的项目中要添加比较大的模块的时候,
我的步骤就是先梳理大脉络,大需求,大方向
然后拆分成小的功能点
整理下有难点的,不太确定的功能点
搞一个案例项目,实现后进行单元测试下

结合以上的内容,由于也用到了贴代码PasteForm框架的内容,所以我打算在PasteTemplate案例项目上实现这个功能!

动态分析

按照方案二的说明,比如有以下内容

        {
            "va":"aabbcc",
            "score":98,
            "avg":67.5
        }

按照默认的显示应该是这样的
在这里插入图片描述
如果是一些简单的单词还好,有一些单词就不是这么好识别了,比如如下的

{
        "title":"这个是标题",
        "desc":"描述信息",
        "value":97.5
    }

我们希望他的显示是如下的,比如title这个字段
在这里插入图片描述
结合以上的内容,所以我们知道了这个动态的意思,就是如果我配置中有这个比如title字段的信息,则在表单中对应的显示
如果没有则使用JSON的字段直接显示(一般为英文字母)

以上是单元的内容的需求分析,那么还有一个需求,就是多层级的JSON,比如

{
    "name":"abc",
    "age":19,
    "actions":[
        {
            "va":"aabbcc",
            "score":98,
            "avg":67.5
        }
    ],
    "info":{
        "title":"标题",
        "desc":"描述信息",
        "value":97.5
    }
}

这个时候的显示,我希望他能分组,否则会有一大堆,
有一个关键点在于名称重复,比如name和info.name最终显示都是name,如果全显示可能非常长!
对于用户来说很容易搞混,实现后大概是这样的
在这里插入图片描述
注意看上图,按照JSON的内容,做了分组,上面的info或者actions是可以收缩的,如下
在这里插入图片描述
结合以上的需求,我设定添加2个表,一个动态表,一个动态字段!

动态表

直接上代码,创建CodeFirst

using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Entities;

namespace PasteTemplate.dynamicmodels
{
    /// <summary>
    /// 动态表
    /// </summary>
    [Index(nameof(Code), IsUnique = true)]
    public class DynamicTable : Entity<int>
    {
        /// <summary>
        /// 代码 示例:userInfo
        /// </summary>
        [MaxLength(32)]
        public string Code { get; set; }

        /// <summary>
        /// 名称 示例:用户信息
        /// </summary>
        [MaxLength(16)]
        public string Name { get; set; }

        /// <summary>
        /// 说明 示例:修改用户基本信息
        /// </summary>
        [MaxLength(128)]
        public string Desc { get; set; }

        /// <summary>
        /// 状态
        /// </summary>
        public bool IsEnable { get; set; } = true;

        /// <summary>
        /// 空字符 空字符串移除对象
        /// </summary>
        public bool RemoveEmptyStr { get; set; }

        /// <summary>
        /// 数值0 数值为0移除对象
        /// </summary>
        public bool RemoveZeroNumber { get; set; }

        /// <summary>
        /// 特性
        /// </summary>
        public string AttributeJson { get; set; }

        ///// <summary>
        ///// 数据提交地址
        ///// </summary>
        //[MaxLength(256)]
        //public string PostUrl { get; set; }
    }
}

注意上面的特性是使用贴代码PasteForm的特性的JSON内容
其他的字段信息就没啥可说的了!

动态字段

一个动态表可以包含很多个字段,每个字段有不一样的信息,比如标题,说明,是否必填,长度,特性规则等!

using System.ComponentModel.DataAnnotations;
using Volo.Abp.Domain.Entities;

namespace PasteTemplate.dynamicmodels
{
    /// <summary>
    /// 动态字段信息
    /// </summary>
    public class DynamicField : Entity<int>
    {

        /// <summary>
        /// 动态表
        /// </summary>
        public int TableId { get; set; }

        /// <summary>
        /// 字段名称 CreateDate
        /// </summary>
        [MaxLength(64)]
        public string Name { get; set; } = "";

        /// <summary>
        /// 字段类型 System.DateTime
        /// </summary>
        [MaxLength(64)]
        public string DataType { get; set; } = "";

        /// <summary>
        /// 默认值
        /// </summary>
        public string DefaultValue { get; set; }

        /// <summary>
        /// 字段中文 创建日期
        /// </summary>
        [MaxLength(16)]
        public string Title { get; set; } = "";

        /// <summary>
        /// 描述
        /// </summary>
        [MaxLength(128)]
        public string Placeholder { get; set; } = "";

        /// <summary>
        /// 限定 最大长度
        /// </summary>
        public int Maxlength { get; set; } = 0;


        /// <summary>
        /// 必填
        /// </summary>
        public bool Required { get; set; } = false;

        ///// <summary>
        ///// 排序
        ///// </summary>
        //public int Sort { get; set; } = 0;

        /// <summary>
        /// 特性串
        /// </summary>
        public string AttributesJson { get; set; }

        /// <summary>
        /// 状态
        /// </summary>
        public bool IsEnable { get; set; } = true;


    }
}

以上两个cs创建后,我们使用贴代码提供的代码生成器PasteBuilder执行下
在这里插入图片描述
然后就会创建对应的Dto AppService等文件,由于我新弄了一个模块,所以要把没引入的namespace处理下,重新生成!
然后稍微改造下对应的Dto,比如DynamicTable
在这里插入图片描述
主要是上面的,给他搞一个菜单
然后是字段信息中的表ID,给搞一个从url的参数中获取
在这里插入图片描述
以上信息处理后,记得add-migration一下,然后启动项目,到管理页面后,新建一个权限,如下
在这里插入图片描述
然后刷新下页面,可以打开对应的菜单
在这里插入图片描述
一开始打开,表格内时空白的,没有数据,你可以去创建下!
到此对于动态表的添加就算完成,或者说是对JSON的字段的附加说明的内容!

逻辑代码实现

结合上的内容,那么流程应该是这样的,用户打开一个页面,然后这个页面载入要修改的JSON的数据,以表单的形式呈现,如果有配置字段的附加信息,则把附加信息也展示(就是附加信息结合JSON的值显示)
1.你没办法post一大串数据到一个页面吧!
2.JSON本身数据是动态的,所以数据提供的时候,得给他一个定义,这就是table_code的由来

实现API

按照上面的分析,我们先实现几个接口,一个是读取JSON内容的,一个是测试的,如下

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PasteFormHelper;
using PasteTemplate.Application.Contracts;
using PasteTemplate.Handler;

namespace PasteTemplate.dynamicmodels
{
    /// <summary>
    ///
    ///</summary>
    //[TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "view" })]
    public class DynamicHelperAppService : PasteTemplateAppService
    {

        private IAppCache _appCache => LazyServiceProvider.LazyGetRequiredService<IAppCache>();

        /// <summary>
        ///
        ///</summary>
        public DynamicHelperAppService() : base()
        {

        }

        /// <summary>
        /// 基于datakey读取对应的模型数据和对象数据
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public async Task<DynamicModel> Read(string datakey)
        {
            if (String.IsNullOrEmpty(datakey))
            {
                throw new PasteCodeException("传递有效的参数datakey");
            }

            var _modelStr = await _appCache.GetAsync(datakey);
            if (String.IsNullOrEmpty(_modelStr))
            {
                throw new PasteCodeException("缓存数据有误,无法继续执行!");
            }

            var _model = JsonSerializer.Deserialize<DynamicModel>(_modelStr);
            //从数据库读取对应的信息
            if (!String.IsNullOrEmpty(_model.Code) || _model.TableId != 0)
            {
                var _table = await _dbContext.DynamicTable
                    .WhereIf(_model.TableId != 0, x => x.Id == _model.TableId)
                    .WhereIf(!String.IsNullOrEmpty(_model.Code), x => x.Code == _model.Code)
                    .AsNoTracking()
                    .FirstOrDefaultAsync();

                if (_table != null && _table != default)
                {
                    _model.Table = ObjectMapper.Map<DynamicTable, DynamicTableModel>(_table);
                    //二次加工

                    _model.Title = _table.Name;

                    if (!String.IsNullOrEmpty(_table.AttributeJson))
                    {
                        _model.Attributes = JsonSerializer.Deserialize<List<VoloModelAttribute>>(_table.AttributeJson);
                    }

                    var _fields = await _dbContext.DynamicField
                        .Where(x => x.TableId == _table.Id && x.IsEnable)
                        .OrderBy(x => x.Id)
                        .AsNoTracking()
                        .ToListAsync();

                    if (_fields != null && _fields.Count > 0)
                    {
                        _model.Properties = ObjectMapper.Map<List<DynamicField>, List<DynamicFieldModel>>(_fields);
                        //二次加工
                        foreach (var _field in _model.Properties)
                        {
                            if (!String.IsNullOrEmpty(_field.DefaultValue))
                            {
                                switch (_field.DataType)
                                {
                                    case "Int32":
                                        {
                                            if (int.TryParse(_field.DefaultValue, out var _val))
                                            {
                                                _field.Value = _val;
                                            }
                                        }
                                        break;
                                    case "Int64":
                                        {
                                            if (long.TryParse(_field.DefaultValue, out var _val))
                                            {
                                                _field.Value = _val;
                                            }
                                        }
                                        break;
                                    case "Double":
                                        {
                                            if (double.TryParse(_field.DefaultValue, out var _val))
                                            {
                                                _field.Value = _val;
                                            }
                                        }
                                        break;
                                    case "DateTime":
                                        {
                                            if (_field.DefaultValue == "now")
                                            {
                                                _field.Value = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
                                            }
                                            else if (_field.DefaultValue == "today")
                                            {
                                                _field.Value = DateTime.Now.ToString("yyyy-MM-dd 00:00:00");
                                            }
                                            else if (_field.DefaultValue == "month")
                                            {
                                                _field.Value = DateTime.Now.ToString("yyyy-MM-01 00:00:00");
                                            }
                                            else
                                            {
                                                if (DateTime.TryParse(_field.DefaultValue, out var _val))
                                                {
                                                    _field.Value = _val;
                                                }
                                            }
                                        }
                                        break;
                                    default:
                                        {
                                            _field.Value = _field.DefaultValue;
                                        }
                                        break;
                                }
                            }
                            if (!String.IsNullOrEmpty(_field.AttributesJson))
                            {
                                _field.Attributes = JsonSerializer.Deserialize<List<VoloModelAttribute>>(_field.AttributesJson);
                            }
                        }
                    }
                }
            }
            return _model;
        }

        /// <summary>
        /// 提交修改后的JsonString
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<string> Update(string datakey)
        {
            if (_httpContext.Request.Body != null)
            {
                using var reader = new StreamReader(_httpContext.Request.Body);
                var bodystr = await reader.ReadToEndAsync();
                reader.Close();
                reader.Dispose();
                return bodystr;
            }
            else
            {
                throw new PasteCodeException("提交数据有误,无法输出");
            }
        }


        /// <summary>
        /// 上传一个json对象,获取datakey
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        public async Task<string> Test(string code = "", int tableid = 0)
        {
            var _key = Guid.NewGuid().ToString().Replace("-", "");

            var _model = new DynamicModel { };

            _model.DataKey = _key;
            _model.Code = code;
            _model.TableId = tableid;

            var _httpContext = base._httpContext;

            if (_httpContext.Request.Body != null)
            {
                using var reader = new StreamReader(_httpContext.Request.Body);
                var bodystr = await reader.ReadToEndAsync();
                reader.Close();
                reader.Dispose();
                if (!String.IsNullOrEmpty(bodystr))
                {
                    var _jobj = System.Text.Json.JsonSerializer.Deserialize<JsonElement>(bodystr);
                    _model.Data = _jobj;
                }
            }
            var _modelStr = JsonSerializer.Serialize(_model);
            await _appCache.SetAsync(_key, _modelStr, 3600 * 4);
            return _key;
        }

    }
}

上面的数据中的DynamicModel内容如下

    /// <summary>
    /// 
    /// </summary>
    public class DynamicModel
    {
        /// <summary>
        /// 数据key 必填
        /// </summary>
        public string DataKey { get; set; }

        /// <summary>
        /// Table.Code 二选一,如果有模型数据的话
        /// </summary>
        public string Code { get; set; }

        /// <summary>
        /// Table.Id 二选一,如果有模型数据的话
        /// </summary>
        public int TableId { get; set; }

        /// <summary>
        /// 名称
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// 模型自己的属性规则列表
        /// </summary>
        public List<VoloModelAttribute> Attributes { get; set; }

        /// <summary>
        /// 表信息 可能为null
        /// </summary>
        public DynamicTableModel Table { get; set; }

        /// <summary>
        /// 字段信息 字段格式信息,可能为null
        /// </summary>
        public List<DynamicFieldModel> Properties { get; set; }

        /// <summary>
        /// System.Text.Json 必填表示Json的数据
        /// </summary>
        public JsonElement Data { get; set; }

    }

主要的意思就是,提交JSON信息的生产者需要知道当前的JSON是哪个的信息(table_id/table_code),然后是数据本身(Data)
这里要注意,由于Data是动态的,这里涉及到JSON的序列化和反序列化,这个和AppService的默认处理方式有关!
就是在AppService中,如果你写的返回值为Object,返回到前端往往是一个JSONString,其实这里是有一个中间件在转格式输出的,目前有System.Text.Json和Newtonsoft的方式,2边需要一致,否则会造成数据丢失的可能!
比如你提交的数据为

{
    "info":{
        "title":"标题",
        "desc":"描述信息",
        "value":97.5
    }
}

前端接收到的为

{
    "info":{
        "title":[],
        "desc":[],
        "value":[]
    }
}

惊不惊喜!!!

到此,完成了大概动态表单的API部分,实现的内容有
1.搞一个动态表,动态字段,用于承载配置,针对JSON的某一些字段做补充,或者说是所有字段
2.实现JSON数据的暂存,生产者获得JSON数据后,加工下存储在一个key中,给前端,前端基于这个key进行对应的数据的读取!
这个数据是经过加工的,比如说添加了table_code,对应的1中的模型数据,也可能没有配置,所以这里有一个很好的解耦动作!

下一章将介绍动态表单的UI部分的内容,和这章中的动态字段中的Name的关键说法!

下期见… .. .

评论列表
尘埃
4 45 0
快捷注册
热门推荐更多
PasteSpiderFile
PasteSpider的项目文件客户端,开发中能够快速发布!;
最新动态
  • 232.****.231 正在查看 PasteSpiderV5版本更新内容一览 !
  • 112.****.223 正在查看 Serilog在appsettings.json中的配置 !
  • 112.****.223 正在查看 Serilog在appsettings.json中的配置 !
  • 108.****.83 正在查看 PasteTemplate和PasteBuilder的使用教程 !
  • 108.****.83 正在查看 PasteTemplate和PasteBuilder的使用教程 !
  • 203.****.223 正在查看 使用ABP框架不得不留意的一个工具,PasteBuilder代码生成器使用介绍,特别适用于PasteForm框架 !
  • 203.****.223 正在查看 使用ABP框架不得不留意的一个工具,PasteBuilder代码生成器使用介绍,特别适用于PasteForm框架 !
  • 215.****.226 正在查看 Postgresql的安装 !
  • 215.****.226 正在查看 Postgresql的安装 !
  • 142.****.187 正在查看 PasteSpider的配置appsettings.json说明,安装前必读 !
  • 142.****.187 正在查看 PasteSpider的配置appsettings.json说明,安装前必读 !
欢迎加入QQ讨论群 296245685 [PasteSpider]介绍 [PasteForm]介绍 @2022-2023 PasteCode.cn 版权所有 ICP证 闽ICP备2021013869号-2