跳到主要内容

源码分析

提示
  • Nop 平台还处于开发阶段, 本文档中的实践方案可能会部分失效,但本人精力有限,无法及时跟进,请自行按照最新代码调整;
  • 您可以与智谱清言 AI 进行问答互动以了解 Nop 平台相关内容;
  • 若此文对您有很大帮助,请投币支持一下吧;
版权声明

整体架构

同一信息在不同的坐标系中具有不同的表现形式(表象)。 Nop 平台以 DSL 方式定义了一个与具体实现无关的领域坐标系, 由 DSL 来描述业务和数据模型,再通过 Generator 将 DSL 转换到具体的实现坐标系(不同语言的代码,就是不同的实现坐标系)上, 从而实现从业务领域到具体实现的坐标投射

领域坐标系实现坐标系投射: Generator(DSL)

同样的业务在不同的框架、不同的语言中的表达式具有不同的形式, 但是这些不同的形式背后存在统一的抽象内容, 然后把它投射到不同的坐标系中就自动产生了不同的具体表达。

可逆计算下的 DSL 还支持差量(Delta)和差量合并(定点修改坐标系内确定位置的数据), 可以从业务层面实现对应用的定制开发,并且可以通过在 DSL 上附加不同的 Delta 来保证同一信息在不同形式之间的双向自由转换。而差量合并,则是一种对需求变动的最小度量机制, 也就是,以最少的变动量来精确描述需求的变化,属于一种更高层次的复用技术。

用数学公式来表达即为:

App=ΔDeltaGeneratorDSL\begin{aligned} App &= \Delta Delta \oplus Generator\langle DSL \rangle \end{aligned}

在 Nop 平台中的实现机制如下:

DSLDeltax-extendsGenerator其他 DSL代码AMIS JSON...XNode

也就是,可以将 Nop 平台看做是一个 DSL 虚拟机,其将 DSL 与 Delta 做差量合并(x-extends)并生成 XNode,再通过 Generator 生成代码、AMIS JSON(页面布局结构)或其他形式的输出。

若与生物的演化进行类比,可以把 DNA 看作是某种承载信息的 DSL, 生物的成长过程便对应于 Generator,它根据 DSL 的信息并结合环境信息塑造出生物的本体。 同时生物在应对具体问题挑战的时候,还可以利用外部的各种 Delta。 比如人可以穿上潜水服这个 Delta,获得在水中活动的能力,可以加上不同厚度的衣服, 获得在极寒和极热地区活动的能力。而一般的动植物能够加装的 Delta 很少, 比如飞鸟,如果它在水中长时间潜水,飞行用的翅膀反而是一种阻碍。

DSL 解析

SchemaLoaderSchemaLoaderCoreInitializationCoreInitializationICoreInitializerICoreInitializerXLangCoreInitializerXLangCoreInitializerResourceComponentManagerResourceComponentManager初始化[001]#initialize()[002]#initialize()[003] [004]#initialize()[005]#registerXDef()[006]配置 XDef 加载器:ComponentModelConfig#loader[007]配置 XMeta 转换器:ComponentModelConfig#transformer[008]注册 ComponentModelConfig:#registerComponentModelConfig(config)[009] [010]返回 Cancellable[011] 解析[012]#loadXDefinition(modelPath)[013]#loadComponentModel(modelPath, "xdef")[014] [015]返回 IXDefinition[016]#loadXMeta(modelPath)[017]#loadComponentModel(modelPath, "xmeta")[018] [019]返回 IObjMeta销毁[020]#destroy()[021]#destroy()[022] [023]#destroy()[024]ICancellable#cancel()[025]卸载[026] [027] [028] 

JSON 序列化 XDef 和 XMeta:

AppConfig.getConfigProvider().updateConfigValue(CFG_JSON_PARSE_IGNORE_UNKNOWN_PROP, true);
// 不初始化 IoC
CoreInitialization.initializeTo(new IocCoreInitializer().order() - 1);


IXDefinition xdef = SchemaLoader.loadXDefinition("/nop/schema/orm/orm.xdef");

String json = JsonTool.serialize(xdef.toNode(), true);
log.info(json);

IObjMeta xmeta = SchemaLoader.loadXMeta("/nop/schema/orm/orm.xdef");
json = JsonTool.serialize(xmeta.toNode(), true);
log.info(json);


CoreInitialization.destroy();

页面加载

浏览器浏览器PageProviderBizModelPageProviderBizModelPageProviderPageProviderResourceComponentManagerResourceComponentManager初始化[001]#init()[002]注册 view.xml 加载器#registerXView()[003]#registerComponentModelConfig(config)[004]返回 Cancellable[005]注册 page.xml/page.yaml/... 加载器#registerXPage()[006]#registerComponentModelConfig(config)[007]返回 Cancellable加载[008]获取待加载页面路径[009]/graphql:query= query PageProvider__getPage(...)path= /xxx/pages/Xxx/main.page.yaml[010]#getPage(path)[011]#getPage(path)[012]#loadComponentModel(path)[013] [014]返回 PageModel[015]PageModel#getData()[016]返回页面结构(JObject)[017]返回页面结构 JSON:{"type": "page", "name": "main", "body": { ... }}[018]根据页面结构 JSON 渲染页面销毁[019]#destroy()[020]ICancellable#cancel()

页面请求和响应数据的结构如下:

/graphql
// 请求数据
{
"query": "query PageProvider__getPage($path:String){\nPageProvider__getPage(path:$path)\n}",
"variables": {
"path": "/xxx/pages/Xxx/main.page.yaml"
}
}

// 响应数据
{
"data": {
"PageProvider__getPage": {
"type": "page",
"name": "main",
"body": { ... }
}
}
}

也就是,通过 PageProviderBizModel 提供的 GraphQL 接口, 将会向浏览器返回指定页面 /xxx/pages/Xxx/main.page.yaml 的 JSON 结构树,进而由 AMIS 等页面渲染引擎进行页面布局。

页面解析

默认生成页面 /xxx/pages/Xxx/main.page.yaml 的内容如下:

/xxx/pages/Xxx/main.page.yaml
x:gen-extends: |
<web:GenPage view="Xxx.view.xml" page="main" xpl:lib="/nop/web/xlib/web.xlib" />

其解析过程如下(path 即为待加载页面的路径):

ResourceComponentManagerResourceComponentManagerPageProviderPageProviderJsonToolJsonToolDeltaJsonLoaderDeltaJsonLoader[001]#loadComponentModel(path)[002]#loadPage(path)[003]调用 VirtualFileSystem#getResource(path)返回 IResource: resource[004]#loadDeltaBean(resource)[005]#loadFromResource(resource)[006]返回页面结构(Map)[007]#resolveExtends(map)[008]调用JsonExtender#xtend(map)[009]返回 x-extends后的页面结构(Map)[010]#evalAndCastType(map, JObject.class)[011]返回页面结构(JObject)[012]返回 PageModel

其中,JsonExtender#xtend(map) 的调用过程如下:

JsonExtenderJsonExtenderDeltaExtendsGeneratorDeltaExtendsGeneratorXLangCompileToolXLangCompileToolResourceComponentManagerResourceComponentManagerPageProviderPageProvider[001]#xtend(map)[002]#xtendMap(map)[003]#loadStaticExtends(map)[004]#loadDynamicExtends(map)[005]从 map 中获取键为 x:gen-extends 的值[006]#DeltaExtendsGenerator#genExtends解析 x:gen-extends 的值[007]#compileXjson[008]执行 /nop/web/xlib/web.xlib中的 GenPage[009]加载 GenPage 的参数 view指向的资源。该逻辑定义在web/impl_GenPage.xpl[010]#parseViewModel("Xxx.view.xml")[011]DslModelParser#parseFromVirtualPath("Xxx.view.xml")[012]返回 IComponentModel[013]返回 IComponentModel[014]展开 GenPage 的参数 page指向的 view.xml 中<pages/> 下的子标签[015]返回编译结果[016]返回编译结果[017]返回 x:gen-extends 的解析结果[018]返回 x:gen-extends 的解析结果[019]合并前两个 extends 的结果:JsonMerger#merge(...)[020]返回解析结果

最终,解析到 Xxx.view.xml 的 JSON 结构与 /xxx/pages/Xxx/main.page.yaml 中额外修订的页面结构合并后的 JSON 数据便为最终返回给前端的页面结构树。

页面资源访问控制

Nop 平台以 site 组织资源(资源的表现形式即为菜单项), 在资源中可设置访问权限,仅具备指定权限的用户才能看到相应的资源。 获取 site 结构数据的 API 及其参数和响应数据如下:

/r/SiteMapApi__getSiteMap
// 请求参数
{ "siteId": "main" }

// 响应数据
{
status: 0,
resources: [{
id: 'xxx',
displayName: 'xxx',
children: [...]
}, ...]
}
浏览器浏览器SiteMapApiBizModelSiteMapApiBizModelSiteMapProviderImplSiteMapProviderImplDslModelParserDslModelParser[001]/r/SiteMapApi__getSiteMap:{ "siteId": "main" }[002]#getSiteMap(siteId)[003]#getSiteMap(siteId)[004]#loadSiteData()[005]#loadStaticSiteMap()[006]#parseFromResource[007] [008]返回 ActionAuthModel[009]返回 SiteMapBean[][010]返回 SiteCacheData[011]SiteCacheData#getSite(siteId)[012]返回 site 结构数据 siteMap[013]#filterForUser(siteMap)[014]#filterAllowedMenu(siteMap)[015]#applyAuthFilter(siteMap.getResources())[016]标记无权限的资源为 disabled[017]返回标记后的 siteMap[018]调用 siteMap.removeInactive()从 siteMap 中移除标记为 disabled 的资源[019]返回过滤后 siteMap[020]返回 siteMap 的 JSON 序列化数据

注意,site 的 DSL 文件路径通过配置项 nop.auth.site-map.static-config-path 指定,默认为 /xx/xx/auth/app.action-auth.xml

接口访问控制

数据访问控制

注意事项

参考资料