跳到主要内容

平台设计

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

模型层级结构

客户端DatabaseXPage(main.page.yaml)XView(Xxx.view.xml)XMeta(Xxx.xmeta)XORM(app.orm.xml)XBiz(Xxx.xbiz)输出 JSON提供 GraphQL 接口组件组合视图映射业务处理持久化映射存储

实际进行低代码开发时,可以围绕 XMeta 进行设计,再通过其定义推导 XORMXView

DSL 文件类型

注意,以下划线开头的以下后缀文件均为自动生成,不能手工修改以避免被覆盖, 手工修改的部分应该放在 src/resources/_vfs/_delta/<xxx>/ 下的同名文件中(可在不同的子工程中,通过依赖引入即可),其中,<xxx> 表示 Delta 分层标识, 默认分层为 default,可以在定制化需求场景中增加不同的分层来管理不同场景下的 Delta。

分层的顺序由 nop.core.vfs.delta-layer-ids 控制, 如,nop.core.vfs.delta-layer-ids=base,hunan 表示先应用 base 再应用 hunan

  • *.xdef:DSL 的 Schema 定义
  • *.xlibXpl 模板语言的函数库,用于将公共的可执行逻辑抽取并定义为一个 DSL 标签,以便于在 DSL 中复用执行逻辑
  • *.xgen:按照 Nop 的 _vfs 路径生成模板代码
    • 在使用 maven 打包功能时,会自动执行工程的 precompilepostcompile 目录下的 *.xgen 代码, 其中 precompile 在 compile 阶段之前执行,执行环境可以访问所有依赖库, 但是不能访问当前工程的类目录,而 postcompile 在 compile 阶段之后执行, 可以访问已编译的类和资源文件
  • *.xrunnop-codegen 专用 DSL,用于按照模板生成代码和 Delta 文件
  • *.xbiz:对无代码开发模式的支持,可以在不修改 Java 源代码的情况下, 在线增加、修改后台 GraphQL 模型中的 Query/Mutation/DataLoader, 其内置了有限自动机模型,一些简单的状态迁移逻辑无需在 Java 中编程,通过配置即可完成
  • *.xmeta:用于定义模型的描述信息,据此可以自动实现对数据增删改查的全部逻辑
  • _module:空白文件,放在 src/resources/_vfs/xxx/yyy/appNamexxx-yyy) 中用于标识当前包是否为一个 Nop 模块,若为模块, 则会自动加载其 /_vfs/xxx/yyy/beans/ 目录下的 app-*.beans.xml 文件

DSL 引用关系

nop-demo 项目中的 Region 管理页面为例:

startNopDemoApplicationNopApplicationapplication.yamlnop.auth.site-map.static-config-path =/auth/app.action-auth.xml/auth/app.action-auth.xmlx:extends =/auth/nop-demo.action-auth.xml/auth/nop-demo.action-auth.xmlresource#url =/pages/Region/main.page.yaml/pages/Region/main.page.yamlx:gen-extends = web:GenPage(view:/pages/Region/Region.view.xml, page: main)/pages/Region/Region.view.xmlpages/curd#name = maincontrolLib =/nop/web/xlib/control.xlibobjMeta =/model/Region/Region.xmeta/model/Region/Region.xmetax:gen-extends = meta-gen:DefaultMetaGenExtendsx:post-extends = meta-gen:DefaultMetaPostExtendsweb:GenPagedefined in/nop/web/xlib/web.xlib:<web:GenPage ... xpl:lib='/nop/web/xlib/web.xlib'/>DefaultMetaGenExtends&DefaultMetaPostExtendsdefined in/nop/core/xlib/meta-gen.xlibobjMetaused in/nop/web/xlib/control.xlibParseExtendsParseParse view'smainpageviaweb:GenPageGet structure ofobjMeta

application.yaml 中指定了前端页面的定义文件为 /auth/app.action-auth.xml, 并在其中定义了应用前端的各个模块资源 resource(即,导航菜单)的组织结构和样式:

app.action-auth.xml
<auth ...>
<site id="xxx" ...>
<resource id="xxx" displayName="测试nop-demo" resourceType="TOPM"
routePath="/test-orm-nop-demo"
component="layouts/default/index" ...>
<children>
<resource id="xxx" displayName="地区" resourceType="SUBM"
url="/nop/demo/pages/Region/main.page.yaml"
component="AMIS" ...>
</resource>
...
</children>
</resource>
...
</site>
</auth>

在用户点击导航菜单以访问其页面资源时,会向后端传递 resource#url 上所设置的页面路径, 后端对该文件 /pages/Region/main.page.yaml 进行解析并得到页面结构后, 将该结构返回给前端,再由前端根据该页面结构进行渲染:

http://localhost:8080/graphql
// 请求数据
{
"query": "query PageProvider__getPage($path:String){\nPageProvider__getPage(path:$path)\n}",
"variables": {
"path": "/nop/demo/pages/Region/main.page.yaml"
}
}

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

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

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

其表示在编译阶段(x:gen-extends)调用 Xpl 库 /nop/web/xlib/web.xlib 中的 GenPage(调用时的标签前缀与库文件名相同)函数解析页面视图 Region.view.xml 并获取其 name="main" 的页面的结构:

Region.view.xml
<view ...>
...
<pages>
<crud name="main" .../>
...
</pages>
</view>

可以查看 _dump 目录中编译后的页面文件 /pages/Region/main.page.yaml 内容, 以了解完整的页面结构。

页面视图会与一个业务模型绑定,以便于在编译页面时,向页面组件填充与业务模型相关的配置数据:

Region.view.xml
<view ...>
<objMeta>/nop/demo/model/Region/Region.xmeta</objMeta>
<controlLib>/nop/web/xlib/control.xlib</controlLib>
...
</view>

而页面组件在 controlLib 指向的 Xpl 库中进行定义:

control.xlib
<lib ...>
<tags>
<edit-tree-parent outputMode="xjson">
...
<attr name="objMeta" mandatory="true"/>
<attr name="bizObjName" mandatory="true"/>

<source>
<c:script><![CDATA[
function filter_cond(){
if(objMeta.tree.levelProp)
return 'filter_' + objMeta.tree.levelProp + '=' + (objMeta.tree.rootLevelValue ?? '__null');
return 'filter_' + objMeta.tree.parentProp + '=__null';
}
]]></c:script>

<tree-select clearable="@:true">
<source>
<url>
@query:${bizObjName}__findList/value:id,label:${objMeta.displayProp || 'id'},
${objMeta.tree.childrenProp} @TreeChildren(max:5)?${filter_cond()}
</url>
</source>
</tree-select>
</source>
</edit-tree-parent>
...
</tags>
</lib>

其中,objMeta 便是在页面视图中通过 <objMeta/> 所绑定的业务模型。 该业务模型的结构则会在其对应的 *.xmeta 文件中进行定义:

Region.xmeta
<meta ...>
<entityName>io.nop.demo.dao.entity.Region</entityName>

<primaryKey>id</primaryKey>
<displayProp>name</displayProp>

<x:gen-extends>
<!-- 标签前缀与库文件名相同 -->
<meta-gen:DefaultMetaGenExtends xpl:lib="/nop/core/xlib/meta-gen.xlib"/>
</x:gen-extends>

<x:post-extends>
<!-- 标签前缀与库文件名相同 -->
<meta-gen:DefaultMetaPostExtends xpl:lib="/nop/core/xlib/meta-gen.xlib"/>
</x:post-extends>

<props>s
<prop name="id" displayName="地区ID" ...>
<schema type="java.lang.String" precision="32"/>
</prop>
<prop name="name" displayName="地区名称" ...>
<schema type="java.lang.String" precision="500"/>
</prop>
...
</props>
</meta>

默认在该业务模型的结构中还将调用 Xpl 库 /nop/core/xlib/meta-gen.xlib 中的函数 DefaultMetaGenExtendsDefaultMetaPostExtends 对业务模型结构进行自动化调整,比如,在当前结构中附加必要的 GraphQL 相关的配置数据。

注意事项

  • xpl:lib 必须为 Delta 层的绝对路径,不能是相对路径
  • 使用 xpl:lib 的标签前缀需与库文件名相同