• 前后端分离的轻量级内容管理框架


    分享一个前后端分离的轻量级内容管理框架(第二篇前后端交互数据结构分析)

     

    这是 CMS 框架系列文章的第二篇,第一篇开源了该框架的代码和简要介绍了框架的目的、作用和思想,这篇主要解析如何把sql 转成标准 xml 配置文件和把前端post的增删改数据规范成方便后台解析的结构,以实现后端自动化操作数据库。

    【开源.NET】 分享一个前后端分离的轻量级内容管理框架(第一篇)
    【开源.NET】 分享一个前后端分离的轻量级内容管理框架(第二篇前后端交互数据结构分析)

    信息管理系统

    信息管理系统关键功能:列表分页和搜索、方便数据展示和录入。业务复杂度通常在于多表关联的搜索、录入以及表与表和字段与字段之间的约束,还有就是报表统计了。除去报表不说,其它功能其实就是对数据库表进行增删改查,它们是独立于业务存在的,所以可对它们进行规范化和自动化。
    信息管理系统就是为了方便数据的展示和录入,简化为 “需求数据 - SQL - 数据库”, SQL 作为“需求数据” 与“数据库”的中介。想要自动化增删改查,必须要规范化“需求数据的结构”以及添加规范化的“配置文件”,用程序对它们进行分析以生成中介“SQL”。

    一、自动化搜索和分页

    1、设计图

    2、Sql查询转换成规范化的“配置文件”

    配置文件是手动配置,由后端程序处理的,不涉及到传输,所以应该选择 XML 格式,可读性和性能都可达到平衡。
    先看一下一条简单的 select 语句: Select main.* From VideoMain Where main.IsDeleted != 1,这里有 3 个关键字"Select, From, Where", 它们与业务无关,抽出来,组成xml:

    <Select>
      main.*
    </Select>
    <From>
      VideoMain main
    </From>
    <Where>
      main.IsDeleted != 1
    </Where>

    看去非常直观,和写 sql 没多大区别。
    再看一条复杂点的 select 语句:

    Select main.*, owner.Name as _OwnerName
    From VideoMain 
    Left Join BasOwner owner On owner.Id = main.OwnerId
    Where main.IsDeleted != 1 And main.Name like '%教程%'

    转成xml配置

    <Select>
      main.*, owner.Name as _OwnerName
    </Select>
    <From>
      VideoMain 
      Left Join BasOwner owner On owner.Id = main.OwnerId
    </From>
    <Where>
      main.IsDeleted != 1 And main.Name like '%教程%'
    </Where>

    不好的是条件写死了,但条件是由前端返回的,应该是动态的。其实 where 条件是由比较符号“=,!=, >, < , >=, <=, like, in, between and” 和逻辑“And, Or”组合起来的 ,可对它们进行规范化。
    首先把比较符号转换成有意义的单词,方便配xml,

    equal   : =
    notequal: !=
    bigger  : >=
    smaller : <=
    like    : like
    in      : in

    重新规范Where:

    <Select>
      main.*, owner.Name as _OwnerName
    </Select>
    <From>
      VideoMain 
      Left Join BasOwner owner On owner.Id = main.OwnerId
    </From>
    <Where>
      <Fields>
        <Field Name="IsDeleted" Prefix="main" Cp="notequal"></Field>
        <Field Name="Name" Prefix="main" Cp="like"></Field>
      </Fields>
    </Where>

    这样把一条查询的 sql 规范成xml,然后写程序进行解析,就容易了。

    3、把规范化的 xml 转换成标准的 sql

    看一段解析条件比较的代码:

    public string GetSql(string cp, string paraName, string dbname, string value, string sqlExpress = null, string dataType = null, bool isAnd = true, bool isAddQuotes = true)
    {
        string sql = "";
        if (!string.IsNullOrEmpty(sqlExpress))
        {
            if (isAddQuotes)
            {
                sql = sqlExpress.Replace("@" + paraName, "'" + ParseValue(value, dataType) + "'");
            }
            else
            {
                //sql = sqlExpress.Replace("@" + paraName, ParseValue(value, dataType));
                sql = sqlExpress.Replace("@" + paraName, "'" + ParseValue(value, dataType) + "'");
            }
        }
        else
        {
            value = string.IsNullOrEmpty(dataType) ? value : ParseValue(value, dataType);
            string orAnd = isAnd ? "And" : "Or";
            switch (cp.ToLower())
            {
                case "equal":
                    sql = Equal(dbname, value, orAnd);
                    break;
                case "like":
                    sql = Like(dbname, value, orAnd);
                    break;
                case "notequal":
                    sql = NotEqual(dbname, value, orAnd);
                    break;
                case "daterange":
                    sql = DateRange(dbname, value, orAnd);
                    break;
                case "bigger":
                    sql = Bigger(dbname, value, orAnd);
                    break;
                case "smaller":
                    sql = Smaller(dbname, value, orAnd);
                    break;
                case "in":
                    sql = In(dbname, value, orAnd);
                    break;
            }
        }
        return sql;
    }
    
    
    
    public string In(string fieldName, string value, string orAnd = "And")
    {
        return string.Format(" {2} {0} in ('{1}')", fieldName, FilterSql.FilterValue(value).Replace(",", "','"), orAnd);
    }
    public string Equal(string fieldName, string value, string OrAnd = "And")
    {
        return string.Format(" {2} {0} = '{1}'", fieldName, FilterSql.FilterValue(value), OrAnd);
    }
    public string Like(string fieldName, string value, string OrAnd = "And")
    {
        return string.Format(" {2} {0} like '%{1}%'", fieldName, FilterSql.FilterValue(value), OrAnd);
    }
    public string NotEqual(string fieldName, string value, string OrAnd = "And")
    {
        return string.Format(" {2} {0} <> '{1}'", fieldName, FilterSql.FilterValue(value), OrAnd);
    }
    public string Bigger(string fieldName, string value, string OrAnd = "And")
    {
        return string.Format(" {2} {0} >= '{1}'", fieldName, FilterSql.FilterValue(value), OrAnd);
    }
    public string Smaller(string fieldName, string value, string OrAnd = "And")
    {
        return string.Format(" {2} {0} <= '{1}'", fieldName, FilterSql.FilterValue(value), OrAnd);
    }
    

    有兴趣的,下载源码看,解析 xml 的代码在 Core/Easy.DataProxy 项目下。

    4、前端请求:
    <div class="dh-form">
        <div class="row">
            <div class="col2">菜单编码</div>
            <div class="col2"><input class="easyui-uc_validatebox" data-bind="value:form.Code" /></div>
            <div class="col2">菜单名称</div>
            <div class="col2"><input class="easyui-uc_validatebox" data-bind="value:form.Name" /></div>
            <div class="col1"><a class="easyui-uc_linkbutton" data-bind="click:_query()">搜索</a></div>
        </div>
    </div>

    form.Code 和 form.Name 对应后台配置文件的 Where:

      <Where>
        <Fields>
          <Field Name="Code" Prefix="main" Cp="like"></Field>
          <Field Name="Name" Prefix="main" Cp="like"></Field>
        </Fields>
      </Where>
    请求 /Bas/Menu/list?pageNumber=1&pageSize=20

    请求 /Sys/Menu/list?Code=sys&Name=系统&pageNumber=1&pageSize=20

    二、自动化增删改

    1、设计图

    将前端返回合法的 json 数据结合后台配置好的xml, 经由 EasyCore 解析生成标准的 sql。

    2、单表

    假设有张视频表,后台管理员可对它进行增删改操作。

    在界面“新增或编辑”,点“保存”后,将数据组织成 json 格式 POST 回后台,后台程序解析该 json, 生成 sql 对数据库相应的表进行增删改。
    这里有 3 个关键要素: 1.对应的表, 2.对表的操作类型(增、删、该),3.表字段的值。
    post 回后台的 json 结构大概是: {tableName:VideoMain, operation:'inserted', data:{Id:1, Name:"test1"}}
    如果要兼容批量操作的 json 结构是:

    [
    {tableName:VideoMain, operation:'inserted', data:{Id:1, Name:"test1"}},
    {tableName:VideoMain, operation:'inserted', data:{Id:2, Name:"test2"}},
    {tableName:VideoMain, operation:'updated', data:{Id:3, Name:"test3"}},
    {tableName:VideoMain, operation:'updated', data:{Id:4, Name:"test4"}},
    ]

    出现好多重复的标签: tableName, operation, data, 抽取出来,简化成:

    {
    tableName:{
        inserted:[{Id:1, Name:"test1"},{Id:2, Name:"test2"}],
        updated:[{Id:3, Name:"test3"},{Id:4, Name:"test4"}]
      }
    }
    
    3、主从表

    假设用户可对视频单独进行“评论”或“评分”, 还可以对“评论”进行“顶、踩”,而后台管理员可对它们进行增删改操作,简化的表结构如下:

    这就是一个典型的“主 - 从 - 从”的表结构,这里比单表多了一从信息:子表,json 结构如下:

    {
    VideoMain:{
        inserted:[
         { 
            data:{Name:"test1"},
            children:{
              VideoMark:{
                  inserted:[
                    {
                      data:{Mark:5}
                      },
                    {
                      data:{Mark:4}
                      }
                          ]
                         },
               VideoComment:{
                  inserted:[
                    {
                      data:{Comment:'good!'}, 
                      children:{
                         VideoCommentUpdown:{
                              inserted:[
                                {
                                  data:{IsUp:true}
                                  }
                                ]
                            }
                         }
                      },
                      {                                  {
                      data:{Comment:'very good!'}, 
                      children:{
                         VideoCommentUpdown:{
                              inserted:[
                                {
                                  data:{IsUp:true}
                                  }
                                ]
                            }
                         }
                      }
                    }
                    ]
                   },
                }
          },
          updated:[
            {
              data:{Id:3, Name:"test3"}
            }
          ]
        ]
      }
    }
    

    json 结构难以看出它的规律,把它换成脑图:

    其实就是一颗树,提取它的结构:

    从图可看出,树的差异结构最深层级为 4 级,4级之后又从 1 开始。 每一级的意义:

    • 第一级,表名: master/child1/child2;
    • 第二级,对表的操作类型: inserted/updated/deleted;
    • 第三级,批量操作的记录集合(数组);
    • 第四级,左节点:记录的数据,右节点:该条记录的子表数据,子表数据结构重复着 1-4级别;

    该 json 结构已很好的携带业务数据信息了,但并不完整,自增字段、主键、外键等约束信息和更新或删除所需的逻辑条件都没有,这些关系到数据库安全的信息不可能开放给前端去配的,所以还需要后台作相关的配置。

    4、后台配置
    </SqlConfig>
      <Table>VideoMain</Table>
      <ID>Id</ID>
      <PKs>Id</PKs>
      <Insert>
        <Fields>
          <Field Name="CreatedDate" IsIgnore="true"></Field>
          <Field Name="UpdatedDate" IsIgnore="true"></Field>
        </Fields>
      </Insert>
      <Update>
        <Fields>
          <Field Name="CreatedDate" IsIgnore="true"></Field>
          <Field Name="UpdatedDate" IsIgnore="true"></Field>
        </Fields>
        <Where>
          <Fields>
            <Field Name="Id" Cp="equal"></Field>
          </Fields>
        </Where>
      </Update>
      <Delete>
        <DeleteAnyway>false</DeleteAnyway>
        <Where>
          <Fields>
            <Field Name="Id" Cp="equal"></Field>
          </Fields>
        </Where>
      </Delete>
      <Children>
        <SqlConfig>
          <Table>VideoMark</Table>
          <JsonName>marks</JsonName>
          <ID>Id</ID>
          <PKs>Id</PKs>
          <Dependency>
            <Fields>
              <Field Name="MainId" DependencyName="Id"></Field>
            </Fields>
          </Dependency>
          <Update>
            <Where>
              <Fields>
                <Field Name="Id" Cp="equal"></Field>
              </Fields>
            </Where>
          </Update>
          <Delete>
            <Where>
              <Fields>
                <Field Name="Id" Cp="equal"></Field>
              </Fields>
            </Where>
          </Delete>
        </SqlConfig>
        <SqlConfig>
          <Table>VideoComment</Table>
          <JsonName>comments</JsonName>
          <ID>Id</ID>
          <PKs>Id</PKs>
          <Dependency>
            <Fields>
              <Field Name="MainId" DependencyName="Id"></Field>
            </Fields>
          </Dependency>
          <Update>
            <Where>
              <Fields>
                <Field Name="Id" Cp="equal"></Field>
              </Fields>
            </Where>
          </Update>
          <Delete>
            <Where>
              <Fields>
                <Field Name="Id" Cp="equal"></Field>
              </Fields>
            </Where>
          </Delete>
          <Children>
            <SqlConfig>
              <Table>VideoCommentUpdown</Table>
              <JsonName>commentUpdowns</JsonName>
              <ID>Id</ID>
              <PKs>Id</PKs>
              <Dependency>
                <Fields>
                  <FieldName="CommentId"DependencyName="Id"></Field>
                </Fields>
              </Dependency>
              <Update>
                <Where>
                  <Fields>
                    <FieldName="Id"Cp="equal"></Field>
                  </Fields>
                </Where>
              </Update>
              <Delete>
                <Where>
                  <Fields>
                    <FieldName="Id"Cp="equal"></Field>
                  </Fields>
                </Where>
              </Delete>
            </SqlConfig>
          </Children>
        </SqlConfig>
      </Children>
    </SqlConfig>

    SqlConfig xml 对应的对象

    public class SqlConfig
    {
        public SqlConfig()
        {
            this.Where = new Where();
            this.Children = new List<SqlConfig>();
            this.OrderBy = new OrderBy();
            this.GroupBy = new GroupBy();
            this.Dependency = new Dependency();
            this.Insert = new Insert();
            this.Update = new Update();
            this.Delete = new Delete();
            this.SingleQuery = new SingleQuery();
            this.Export = new Export();
            this.Import = new Import();
            this.BillCodeRule = new BillCodeRule();
        }
    
        private string _settingName;
        /// <summary>
        /// 配置名称,默认和表名一致,一般不会用到,方法以后扩展,如一个配置文件出现相同的表时,用来区分不同的配置
        /// </summary>
        public string SettingName
        {
            get
            {
                if (string.IsNullOrEmpty(_settingName))
                {
                    _settingName = Table;
                }
                return _settingName;
            }
            set
            {
                _settingName = value;
            }
        }
    
        #region 查询配置
        /// <summary>
        /// 查询的字段
        /// </summary>
        public string Select { get; set; }
        /// <summary>
        /// 查询的表名以及关联的表名,如 left join, right join
        /// </summary>
        public string From { get; set; }
        /// <summary>
        /// 查询的条件
        /// 前端返回的查询条件,只有出现在这些配置好的字段,才会生成为了 sql 的 where 条件,
        /// 没出现的字段会被忽略
        /// </summary>
        public Where Where { get; set; }
        /// <summary>
        /// 分页时必须会乃至的排序规则
        /// </summary>
        public OrderBy OrderBy { get; set; }
    
        public GroupBy GroupBy { get; set; }
        /// <summary>
        /// 页码
        /// </summary>
        public int PageNumber { get; set; }
        /// <summary>
        /// 页大小
        /// </summary>
        public int PageSize { get; set; }
    
    
        #endregion 查询配置
    
        /// <summary>
        /// 指定该配置所属于的表
        /// </summary>
        public string Table { get; set; }
    
        #region 增删改配置
        /// <summary>
        /// 对应前端返回的 json 格式数据的键名
        /// e.g.: {master:{inserted:[{data:{}}]}} 中的 master 就是这里要对应的 JsonName
        /// 注意默认主表的 jsonName 是 master, 所以主表一般可省略不写, 但子表必须得指定
        /// </summary>
        public string JsonName { get; set; }
        /// <summary>
        /// 自增的字段,指定了自增的字段,在 insert 时会自动忽略该字段
        /// </summary>
        public string ID { get; set; }
        /// <summary>
        /// 主键, 在保存成功后会返回主键的值; 
        /// </summary>
        public string PKs { get; set; }
        /// <summary>
        /// 唯一值的字段,对应数据库 unique, 在 insert,update 前会判断是否已存在
        /// </summary>
        public string Uniques { get; set; }
        /// <summary>
        /// 唯一值的字段的值是否允许为空
        /// </summary>
        public string UniqueAllowEmptys { get; set; }
        /// <summary>
        /// 所属的父级配置, 在 xml 中不用指定,程序会自动分析
        /// </summary>
        public SqlConfig Parent { get; set; }
        /// <summary>
        /// 包含的子级配置, 即子表的配置,需要在 xml 中配置
        /// </summary>
        public List<SqlConfig> Children { get; set; }
        /// <summary>
        /// 依赖父表的字段
        /// </summary>
        public Dependency Dependency { get; set; }
        /// <summary>
        /// insert 的配置
        /// </summary>
        public Insert Insert { get; set; }
        /// <summary>
        /// update 的配置
        /// </summary>
        public Update Update { get; set; }
        /// <summary>
        /// delete 的配置
        /// </summary>
        public Delete Delete { get; set; }
        #endregion
        /// <summary>
        /// 单条记录查询的配置,一般用在配置列表双击弹出那条记录的获取的 sql 
        /// </summary>
        public SingleQuery SingleQuery { get; set; }
        /// <summary>
        /// 导出配置
        /// </summary>
        public Export Export { get; set; }
        /// <summary>
        /// 导入配置
        /// </summary>
        public Import Import { get; set; }
        /// <summary>
        /// 是否物理删除?
        /// </summary>
        public bool DeleteAnyway { get; set; }
        /// <summary>
        /// 表单编码的生成配置
        /// </summary>
        public BillCodeRule BillCodeRule { get; set; }
    
    
    }
    
    5、批量增删改截图

    单表

    主-从

    主-从-从

    源码 https://github.com/grissomlau/Grissom.CMS

    初始化登录名:admin, 密码: 123
    

     
    分类: C#
  • 相关阅读:
    海康威视web插件安装后还是不能看视频问题
    超详细解释 react,flux,redux 的概念与关系
    vue3和react虚拟DOM的diff算法区别
    leetcode 78. 子集 js 实现
    js 实现扁平数组转为树形结构数组及树形结构数组转为扁平数组
    vite 为什么比 webpack 快?
    实现一个简单版 Vue2 双向数据绑定
    leetcode 415. 字符串相加 js 实现
    transform rotate实现环形进度条
    leetcode 258. 各位相加 js 实现
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/6421813.html
Copyright © 2020-2023  润新知