技术分享 | 业务模板的技术实践

a28f1d140f8f87f4a232bc43e5643a56.png

289ad3a31c82ad8fc4abeb774a532610.png

源宝导读:“业务模板”作为天际·建模平台3.0推出的重要特性,它将元数据复用发挥到了极致,通过业务模板几乎可以覆盖整个建模元数据开发流程,提供业务场景级别的复用能力。本文将介绍“业务模板”的设计原理、实现方案和应用场景。

一、背景

    建模平台作为天际开放平台的核心能力域之一,是一个极致高效的低代码开发平台,支持通过数据建模、页面建模、组件建模等可视化建模方式低代码或零代码快速构建企业级应用。

77564c1222e4c1307846b6d906477065.png

    使用建模平台开发应用的过程一般是:数据建模=》页面建模=》多个页面组合成业务场景。数据模型可以被多个页面复用,组件模型也可以被多个页面复用,通过建模平台可以快速构建出相应的场景。

    例如:开发一个简单的用户管理系统,管理用户名称和用户编码信息。

    首先进行数据建模:我们可以设计一个用户表,包含用户名称和用户编码两个字段。然后进行页面建模:创建一个用户表单页面,使用用户表作为数据源,表单使用输入框组件绑定用户名称和用户编码,设置相应的输入格式和校验规则,就可以实现用户名称和用户编码的录入功能了;再创建一个用户列表页面,使用用户表作为数据源,使用用户名称和用户编码字段作为列表展示字段,就可以批量展示和管理用户数据了。

    完成这么一个简单的用户表单录入+列表展示数据场景,使用建模平台只需要花费几分钟就能完成。

    如果现在需要再开发一个客户管理系统,管理客户名称和客户编码信息。正常的做法就按照上面的建模流程就行数据建模(客户表)=》页面建模(客户表单、客户列表)=》客户管理系统。

    分析用户管理系统和客户管理系统我们会发现这两个业务场景极其类似,都是针对名称和编码(唯一标识)的管理,但是都要进行数据建模(数据隔离,表名不同,字段名一致),页面建模(结构一致,只是展示的文本描述不同)。虽然重复完成这个场景只需要几分钟,但是如果每个客户都有类似的场景,那么都需要花费几分钟;如果场景再复杂一点,涉及到多个数据源,多个页面呢,每个页面又用到复杂的组件或者功能特性,那么完成一个类似的场景需要花费多少时间呢?

二、什么是业务模板

    通过分析以上的场景我们发现,目前建模平台提供的主要是基于组件级和页面级的复用能力,针对业务场景复用能力比较薄弱。而实际产品场景中确实存在这种业务功能极其类似的场景,比如成本系统的变更业务场景,设计变更和现场签证两个具体的变更过业务十分相似,如果重复开发的话需要耗费大量的开发资源。

    为了解决这种业务场景级别的复用能力,在ERP3.0版本中,建模平台提供了业务模板功能。基于业务领域抽象的模型,提炼出一套业务模板,通过模板化的思路快速复制的方式构建有规律、符合模型的业务。

    目前建模中业务模板覆盖了大部分建模能力,从数据建模到页面建模、业务组件、业务参数以及主页、标签页和权限,以支持创建一个业务场景闭环。

b7e081660fea296eb566c226bc4928da.png

三、业务模板的原理

3.1、什么是元数据

元数据的定义:

元数据(Metadata),又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。

    元数据在建模平台中的作用是描述建模中所有特性的数据,是整个建模平台的基石,建模平台中的一切模型都是以元数据作为数据载体,建模中的元数据物理表现形式为一个个XML文件。

    例如页面模型元数据,每个页面对应的就是一个XML文件。

    页面元数据:

<?xml version="1.0" encoding="utf-8"?>
<functionPage title="合同变更基本信息表单" pageName="合同变更基本信息表单"><pageLayout pageLayoutCategory="OneColumn"><cells><cell rowIndex="0" width="100%" height="100%"><control id="appForm" entityName="主实体"  autoHeight="1" /></cell></cells></pageLayout>
</functionPage>

    根据不同的模型对应不同的元数据类型,分别存储在不同的文件夹下,各个元数据之间存在在互相引用的关系。比如页面引用数据表,主页引用页面等。

81248ea1af4035c31917a7cd45334555.png

3.2、什么是页面模板

    建模通过可视化的方式创建建模模型,创建页面时,可以选择不同的页面模板,其中包含组合模板,单页模板等。

727939cd4d1e87c75d634462242c5a35.png

    选择一个列表+表单的组合模板,选择数据源后点击开始创建,创建成功以后我们会发现,在列表的设计器中,新增按钮默认会打开创建的表单页面:

f540a2c9796e0bc491f006406ddc7860.png

    页面元数据是如何生成的?两个页面之间是如何关联起来的呢?

    首先看下元数据在整个应用中的存在形式如下:

b5c56fcee6e3342ba8bb8f3a165e2491.png

    元数据在不同的场景对应不同的模型,在建模中对应建模模型,在程序内存中对应元数据模型,在文件中对应XML文件,在ERP中对应一个个真实的页面、数据表等,同时不同阶段的模型可以互相转换。

    建模中生成元数据的方式有两种:

  • 根据建模模型,先生成内存元数据模型,然后序列化到文件中;

  • 根据建模模型对应的文件模板,反序列化生成内存元数据模型,然后再序列化到文件中;

    第一种生成方式适合元数据结构比较简单的场景,比如数据表,视图,业务参数等。第二种方式适合结构复杂,存在默认节点或者默认值的场景,比如页面,表单,列表和数列表等。

    页面组合模板就是用的文件模板方式,将通用的文件结构提炼出来,生成模板,需要动态生成的数据使用关键字做占位符,在生成内存元数据模型前,将文件中的关键字根据上下文替换成对应的值。

页面模板:

<functionPage templateId="2C63E14E-FF7E-4E0B-AE15-FEFB8DA34213"><pageLayout pageLayoutCategory="OneColumn"><cells><cell rowIndex="0" width="100%" height="100%"><control id="[prop:setting.Customize]appGrid" entityName="主实体"  autoHeight="1"/></cell></cells></pageLayout>
</functionPage>

    其中[prop:xxx]就表示关键字,在生成元数据模型时会进行替换。例如:parentPage=“[prop:page0.Name] ”表示表单页面的父级页面即返回上级页面为第一个页面也就是列表页面的地址,所以通过点击列表新增按钮进入表单页面后,点击上一级就能返回到列表页面了。

3.3、业务模板的原理

    页面模板解决的是快速创建页面的问题,业务模板要解决的问题是快速创建一类业务场景的问题。在技术实现上,业务模板也采用文件模板的方式,根据一个完整的业务场景所包含的所有的元数据文件,提炼出一套通用的元数据文件模板,根据这套文件模板可以快速生成不同的业务场景元数据。

    模板文件中包含静态和动态两类结构。静态指通用的结构,动态指根据不同的场景显示不同数据的结构。例如在一个页面模板中,页面都包含页面布局,那么页面布局就是一个静态结构;在不同的场景页面名称需要根据对应的场景显示,比如在用户管理界面,显示为用户页面,在客户管理界面,显示为客户页面,那么页面名称就是动态数据。

    那么如何生成动态数据呢?

    答案就是关键字,使用关键字作为动态数据占位符,然后根据模板上下文动态替换关键字。

下面以成本的变更申报表单页面模板为例:

<functionPage
functionPageId="{{$system.NewId}}" name="{{$system.NewId}}"
title="{{$global.name}}变更申报表单页面" 
pageName="{{$global.name}}变更申报表单页面" 
url="/std/02010350/{{$page.ReceiptsDeclare.name}}" 
description="{{$global.name}}变更申报表表单" ><pageLayout pageLayoutCategory="OneColumn" layoutType="0"><cells>            <cell id="45a3adeb-009f-ea11-86e2-94c6910421d9"><controlid="appForm"type="Mysoft.Map6.Modeling.Controls.AppForm"metadataId="{{$form.ReceiptsDeclareForm.formId}}"entityName="主实体"/></cell></cells>
</functionPage>

其中{{xxx}}就是关键字,根据不同的使用场景关键字可以分为以下几类:

1、系统关键字

    系统关键字为系统内置关键字,目前只有$system.NewId,作用是生成新的Guid。例如每个页面的元数据id应该唯一,因此模板中functionPageId="{{$system.NewId}}"。

2、全局关键字

    由用户生成模板实例时输入的业务标识和业务名称、以及当前环境上下文生成。例如页面名称应该跟业务场景有关,因此模板中pageName="{{$global.name}}变更申报表单页面"。

关键字

说明

$global.id业务的唯一标识
$global.name业务名称
... ...... ...

3、配置关键字

    根据模板配置生成配置界面,由用户选择的配置项生成。

关键字

说明

$config.[字段]创建实例时选择的配置值
$config.[字段].field配置对应的字段
... ...... ...

4、模板内容关键字

    元数据之间存在着各种联系,比如数据关系记录的是两个数据表之间的关系,标准图形化数据源页面引用了数据表作为数据源。因此模板文件不仅包含关键字,同时模板文件也作为关键字的提供者,提供关键字供其他模板引用。

类型

关键字

实体关键字$entity.[模板英文名称].[属性名称]
关系关键字$realtion.[模板英文名称].[RelationshipId]
... ...... ...

    在变更申报表单的业务模板中,由于引用了变更申报表单大控件,同时变更申报表单也是使用模板生成的,所以页面跟表单是引用的关系metadataId="{{$form.ReceiptsDeclareForm.formId}}" 。

3.4、什么是模板实例

    定义了业务模板以后就可以根据不同的业务场景动态替换模板文件中的关键字,从而生成一组有业务关联的元数据文件,我们将由同一个业务模板生成的不同业务场景的元数据称为业务模板的一个实例,业务模板实例元数据是描述这个实例的数据。

生成模板实例的流程如下:

995af78f7c6fe8c14928a5e01057c429.png

四、如何开发业务模板

4.1、提炼业务模板内容

    根据已经存在的元数据文件提炼通用的业务模板,分清楚哪些是静态结构,哪些是动态结构,模板和模板之间是如何互相引用的。

    以下以开发一个客户表为例说明如何创建客户表模板:

    1、首先在建模中创建一个客户表(test_Customer),增加客户名称(Name)和客户编码(Code)两个字段,对应的元数据:

客户表”元数据:

<?xml version="1.0" encoding="utf-8"?>
<MetadataEntity EntityId="91c51e44-0bf2-4ef6-43bc-08d94ab36d6f" Name="test_Customer" DisplayName="客户表"><Attributes><MetadataAttribute><AttributeId>4cabeff5-0515-4920-6d86-08d94ab36d6f</AttributeId><Name>Code</Name><DbType>nvarchar</DbType><Length>128</Length></MetadataAttribute></Attributes>
</MetadataEntity>

    2、分析元数据中的静态数据和动态数据,例如元数据的id和表名name要唯一,字段的id要唯一,因此这些不能重复的值就作为动态数据局,要使用关键字替换;不同数据表的字段的名称可以一样,因此数据字段名称可以作为静态数据。

    3、使用关键字替换动态结构

客户表”元数据模板:

<?xml version="1.0" encoding="utf-8"?>
<MetadataEntity EntityId="{{$system.NewId}}" Name="test_{{$global.id}}" DisplayName="{{$global.name}}"><Attributes><MetadataAttribute><!-- 用表达式占位,生成随机GUID--><AttributeId>{{$system.NewId}}</AttributeId><Name>Code</Name>            <DbType>nvarchar</DbType><Length>128</Length></MetadataAttribute>
</MetadataEntity>

4.2、定义业务模板配置

    业务模板配置由:控件配置、布局配置和联动配置组成。

    控件配置定义控件和字段信息:

<configs><!-- 将配置项定义成单选框--><configItem itemId="AlterApply_IsInvalidCostEnable" label="启用无效成本" field="AlterApply_IsInvalidCostEnable" control="Radio" dataType="String"><options><option text="是" value="1" isDefault="true"/><option text="否" value="0" isDefault="false"/></options></configItem><configItem itemId="AlterApply_IsIdleCostSupplement" label="启用无效成本补录" field="AlterApply_IsIdleCostSupplement" control="Radio" dataType="String"><options><option text="是" value="1" isDefault="true"/><option text="否" value="0" isDefault="false"/></options></configItem>
</configs>

布局配置关联控件配置从而定义布局显示:

<layout><regions><region title="阶段配置"><group title="申报阶段"><rows><row><cells><!-- refld引用之前定义的两个配置项--><cell colSpan="1"><configItem refId="AlterApply_IsInvalidCostEnable"/></cell><cell colSpan="2"><configItem refId="AlterApply_IsIdleCostSupplement"/></cell></cells></row></rows></group>
</region></regions></layout>

联动规则定义配置之间的联动规则:

<rule itemId="AlterApply_IsIdleCostSupplement"><ruleConditions><!-- 条件判断来自配置项的值--><ruleCondition field="AlterApply_IsInvalidCostEnable" operator="eq" value="1"/></ruleConditions><ruleActions><!-- 条件为true时执行显示动作--><ruleAction type="trueAction"><props><value>0</value><visible>true</visible></props></ruleAction><!-- 条件为false时执行隐藏动作--><ruleAction type="falseAction"><props><value>0</value><visible>false</visible></props></ruleAction></ruleActions>
</rule>

4.3、订阅业务模板事件

    在业务模板生成实例的过程中,产品需要扩展部分逻辑,比如成本合同变更生成实例以后,需要向流程中心发现业务数据。我们在模板操作过程中会发布事件,产品通过订阅对应的事件进行业务逻辑扩展。

事件名称

事件

说明

业务模板实例前事件TemplateInstanceCreatingEven
tData
订阅此事件可以在生成实例前修改配置参数等
业务模板实例初始化事件TemplateInstanceInitEventData订阅此事件可以获取实例的信息
业务模板实例删除事件TemplateInstanceDeleteEventData订阅此事件可以做一些业务数据清理的操作

    所有的事件都是派生自BusinessUnitEventData的类,通过事件总线发布事件。要订阅处理事件,就要实现BusinessUnitEventHandler抽象类。

事件订阅:

/// <summary>
/// 业务事件的抽象类
/// </summary>
/// <typeparam name="TBusinessUnitEventData"></typeparam>
public abstract class BusinessUnitEventHandler<TBusinessUnitEventData>: IBusinessUnitEventHandler<TBusinessUnitEventData>where TBusinessUnitEventData : BusinessUnitEventData
{/// <summary>/// 订阅者具体的业务逻辑。/// </summary>/// <param name="businessUnitEventData"></param>public abstract void HandleEvent(TBusinessUnitEventData businessUnitEventData);
}

五、如何使用业务模板

    业务模板的使用非常简单,用户不用关心业务模板的开发原理,只用关注相应的配置项的作用即可。

    例如使用成本系统合同变更业务模板,通过修改相应的配置项就可以生成一个重计量的业务实例:

948db77d0f27bb6ea10b4344a0add17a.png

    通过查看业务模板实例信息查看生成的元数据:

182478583e6f3c772927aa33cdeacd4a.png

六、应用案例

    目前ERP产品中主要是成本系统和全域主数据应用了业务模板,成本系统开发了变更业务模板,同时产品出厂的时候会自带现场签证和设计变更两个模板实例。

    合同变更业务模板:

31465786e9602242e197c8f46fdada67.png

    模板实例:

f2b926360d18805631baec5cdcbc0978.png

    全域主数据目前也正在使用业务模板开发主数据模板,主要分为列表型主数据模板,用以扩展无层级类主数据,比如供应商和客户;以及层级型主数据,用以扩展层积累主数据,比如科目信息。

七、总结

    业务模板主要是解决同一类业务场景的快速构建问题,基于业务领域抽象的模型,提炼出一套业务模板,通过模板化的思路快速复制的方式构建有规律、符合模型的业务。

    但是目前模板的内容是基于元数据文件进行提炼的,元数据结构比较复杂,相互之间的关联密切,导致开发业务模板的门槛比较高,同时生成元数据的过程比较复杂,出现问题很难排查定位。

    未来,我们将考虑通过可视化的方式构建业务模板,降低开发难度,让业务模板被更多的开发者使用,发挥更大的价值。

------ END ------

作者简介

李同学: 研发工程师,目前负责天际·建模平台相关研发工作。

也许您还想看:

技术分享|Java SDK 动态类型

技术分享|NodeJS分布式链路追踪实现

更多明源云·天际开放平台场景案例与开发小知识,可以关注明源云天际开发者社区公众号:

【建模】文档服务提供高保真打印模式

明源云·天际硬核技术认可:获华为鲲鹏技术认证书

天际·开发者社区“重装发布”!

f800536bfe267d82f86a1c68cf8e1ab3.png

63c7671d2d3d259e05362182d96cdcad.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/296980.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么那么好的女孩子还单身?

1 终于知道家里的吃的都是怎么没的了&#xff01;2 我的小鱼干&#xff01;快给我&#xff01;3 这几只狗子真是太皮了&#xff01;4 您点的肉香满溢披萨到了~5 一位新手爸爸分享了自己关于为人父母的感悟6 应采儿&#xff1a;为什么那么好的女孩子还单身&#xff1f; 陈小春&a…

RHCS创建高可用集群apche服务器

实验环境&#xff1a; 系统版本&#xff1a;RHEL5.4 将selinux关闭&#xff0c;刷新iptables -F 配置yum仓库 192.168.0.25 station25.example.com 宿主机 192.168.0.24 station24.example.com 节点(虚拟机) 192.168.0.39 station39.example.com 节点(…

成不了天才,但为何也没成人材?(转)

长期以来&#xff0c;"软件业"一直被视为"智力密集"型的"朝阳"产业&#xff0c;大多数从业者都受过高等教育&#xff0c;其平均素质居于社会各行业的前列&#xff0c;这个产业的顶尖人物被公众视为"知识英雄"&#xff0c;比如微软公司…

.NET 6 RC2 版本发布

原文&#xff1a;bit.ly/3FS9xm7作者&#xff1a;Richard日期&#xff1a;2021-10-12翻译&#xff1a;精致码农-王亮说明&#xff1a;文中有大量的超链接&#xff0c;这些链接在公众号文章中被自动剔除&#xff0c;一部分包含超链接列表的小段落被我删减了&#xff0c;如果你对…

最诡异数学悖论:1+1=1

全世界只有3.14 % 的人关注了爆炸吧知识今天&#xff0c;8岁表妹的老师给她奖励了一块大巧克力&#xff0c;超模君打趣她能不能分给我点&#xff0c;遭到残忍拒绝&#xff0c;超模君很愤怒&#xff0c;暗下决心要神不知鬼不觉地吃上表妹的巧克力。超模君趁表妹在认真做作业的时…

JS URL Parser

为什么80%的码农都做不了架构师&#xff1f;>>> /** *param {string} url 完整的URL地址 *returns {object} 自定义的对象 *description 用法示例&#xff1a;var myURL parseURL(http://abc.com:8080/dir/index.html?id255&mhello#top); myURL.fileindex.ht…

如何使用Instruments诊断App(Swift版):起步

2019独角兽企业重金招聘Python工程师标准>>> 本文由Mr_cyz&#xff08;博客&#xff09;翻译自raywenderlich&#xff0c;欢迎参与我们的翻译活动。原文&#xff1a;Instruments Tutorial with Swift: Getting Started 更新记录&#xff1a;该教程由 James Frost 更…

tcp抓包返回fin_TCP/IP学习二TCP链接建立与断开

今天详细学习下TCP链接的三次握手四次挥手&#xff0c;因为开发web服务还是会经常遇到一些网络问题的。其实这方面的资料很多&#xff0c;可能我们看过很多次但也忘了无数次[捂脸]&#xff0c;这次我主要通过抓包例子来展示这个过程。TCP传输控制协议(TransmissionControlProto…

70%的单身女孩都是这样想的!

1 父爱如山&#xff0c;山就是杵在那里一动不动&#xff01;2 3 单身的女孩不要着急4 哎呀&#xff0c;没有两条小鱼干是起不来了&#xff01;5 一分钟教你画二哈&#xff0c;这次真的厉害了&#xff01;6 披着羊皮的狼&#xff1f;&#xff1f;&#xff1f;你点的每个赞&#…

.NET基金会讨论 .NET 开源事业之路

【编者按】从闭源走向开源&#xff0c;.NET 背后都发生了哪些有趣的故事。本文采访了 6 位微软 .NET 团队成员&#xff0c;分享他们在 GitHub 以及建立 .NET 开源项目的经历。作者 | Richard Lander 译者 | 弯月出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff0…

Process Explorer 15.2:微软增强型任务管理器

Process Explorer 是一款免费的增强型任务管理器&#xff0c;同时也是微软著名工具包Sysinternals下的组件之一。 Process Explorer 能让使用者了解看不到的在后台执行的处理程序&#xff0c;可以使用 Process Explorer 方便地管理你的程序进程。Process Explorer 已经完美的支…

号称最强“抓取”工具,没有搞不到的资源!

全世界只有3.14 % 的人关注了爆炸吧知识分享一个超级好用的工具&#xff0c;绝对用心挑选&#xff0c;让你惊艳&#xff01;不仅帮你赚钱&#xff0c;还能完善生活的方方面面。“能轻松赚钱&#xff0c;真的超爽&#xff01;”好朋友雪球&#xff0c;前段时间负责一个市场营销的…

oracle cpu 100%原因,oracle 12.1 cpu 100%

查看整体负载cpu使用居高不下;基本上整体CPU是百分80%以上了;此时:物理读600MB/s,REDO也不算太高;全为latch free,一般为bug高发区检查相关SQL基本为系统SQL:4b4wp0a8dvkf0,11bzv8fm87zvr,3uqkkfu1crw8y,frjd8zfy2jfdq,8mdz49zkajhw3相关BUG:1 Frequent Execution of Recursive…

C#多线程开发-处理子线程中的异常

C#多线程开发-处理子线程中的异常在平时的多线程开发中&#xff0c;对于异常的处理是至关重要的&#xff0c;千万不能马虎。如果在实际的项目中&#xff0c;对于某些线程中的异常没有处理&#xff0c;会直接导致整个程序崩溃&#xff0c;软件无法使用。其中需要说明的是&#x…

GAC及其作用

http://www.cnblogs.com/smallstone/archive/2010/06/29/1767508.html 一、GAC的作用 全称是Global Assembly Cache作用是可以存放一些有很多程序都要用到的公共Assembly&#xff0c;例如System.Data、System.Windows.Forms等等。这样&#xff0c;很多程序就可以从GAC里面取得…

高校教师抄袭豆瓣博主文章,学校证实:基本属实!记过并调离教学科研岗位...

全世界只有3.14 % 的人关注了爆炸吧知识导读作为高层次人才被杭州市引进&#xff0c;并于杭州师范大学任教的教师郭某某最近陷入了一则被指博士论文涉嫌抄袭豆瓣网友事件&#xff0c;近日&#xff0c;学校调查后证实。本文来源&#xff1a;募格学术综合自&#xff1a;红星新闻 …

linux默认归档目录,Linux系统管理(第4章:目录和文件管理二)

Linux系统管理(第4章&#xff1a;目录和文件管理二)一.练习文件查看及检索操作1.查看/etc/filesystems文件&#xff0c;确认当前系统支持的文件系统类型Cat&#xff1a;用于连接多个文件的内容&#xff0c;更多用于查看文件内容2.分页查看/etc/services文件&#xff0c;了解各种…

一组动图看懂3D打印原理

全世界只有3.14 % 的人关注了爆炸吧知识3D打印是制造业领域的一项新兴技术&#xff0c;被称为“具有工业革命意义的制造技术”。近年来&#xff0c;随着工业技术的进步&#xff0c;3D打印技术得到迅速发展并得到媒体的广泛关注&#xff0c;各类3D打印技术被纷纷报道。下面&…

生产者消费者_【线程通信】生产者消费者模型

1生产者消费者模型介绍生产者消费者模型&#xff0c;是每一个学习多线程的的人都需要知道的模型; 大致情况就是&#xff1a;有两个线程&#xff0c;一个负责生产产品&#xff0c;一个消费产品&#xff0c;两者公用同一块内存区域&#xff0c;也就是产品放在了同一块内存上面&am…

在Orchard中使用Image Gallery模块

作为ASP.NET MVC领域一款优秀的开源CMS&#xff0c;Orchard值得所有.NET Web开发人员学习和研究&#xff0c;然后二次开发&#xff0c;最后在其基础上创新。也是遵循国内人员学习IT技术的路线&#xff1a;引进->吸收->消化。 Orchard有很多优秀的功能&#xff0c;在此不一…