====== Nop 实战:新建项目与已有模块扩展 ====== 程序员在开发中有两个常见的情景。一个是从零开始构建一个项目,这个时候我们先根据项目需求,创建 model,产出 Excel 文件,再用 nop-cli 生成代码;另一种情形是使用已有的代码,就像前面我们做的一样——基于 nop-auth 扩展出我们自己的认证微服务。 本文分别介绍这两种情形。 ===== 情景一:从零开始构建一个新项目 ===== 配置好环境后,如何快速写一个 API?Nop 平台的流程是**模型驱动**的:设计 Excel 数据模型 → 代码生成 → 增量定制。 === 第一步:设计 Excel 数据模型 === 在项目根目录下创建 ''model'' 文件夹。Nop 使用特定格式的 Excel 文件(.orm.xlsx)作为模型定义源,一个文件包含多个 Sheet。 以 ''my-app.orm.xlsx'' 为例,说明各 Sheet 的格式。 **1. 目录 Sheet**——列出所有实体和对应的表名: ^ 对象名 ^ 表名 ^ 中文名 ^ 说明 ^ | Article | my_article | 文章 | 文章管理 | **2. 配置 Sheet**——应用名、包名、数据库方言等: ^ 键 ^ 值 ^ 说明 ^ | appName | my-app | | | maven.groupId | com.example | | | maven.artifactId | my-app | | | entityPackageName | com.example.myapp.entity | | | dialect | mysql | | **3. 域定义 Sheet**——自定义数据类型: ^ 名称 ^ 类型 ^ 长度 ^ 小数位数 ^ 标准域 ^ | userName | VARCHAR | 50 | | | | email | VARCHAR | 100 | | | | phone | VARCHAR | 50 | | | | image | VARCHAR | 100 | file | | **4. 实体 Sheet**——每个实体一张,以 ''Article'' 为例,列头为 Nop ORM 标准 14 列: ^ 编号 ^ 主键 ^ 标签 ^ 字段名 ^ 显示 ^ 英文名 ^ 中文名 ^ 数据域 ^ 非空 ^ 类型 ^ 长度 ^ 小数位数 ^ 缺省值 ^ 字典 ^ | 1 | PK | seq | articleId | | articleId | 文章 ID | | TRUE | VARCHAR | 50 | | | | | 2 | | | title | | title | 文章标题 | | TRUE | VARCHAR | 200 | | | | | 3 | | | content | | content | 文章内容 | | TRUE | VARCHAR | 4000 | | | | | 4 | | | author | | author | 作者 | | | VARCHAR | 100 | | | | | 5 | | clock | createTime | | createTime | 创建时间 | | | DATE | | | | | 各列含义: * **编号** — 字段序号,从 1 开始 * **主键** — 标记 PK 的字段为主键 * **标签** — 特殊标记,如 seq(自动编号)、clock(时间戳)、disp(显示用) * **字段名** — Java 属性名,自动转为下划线数据库列名 * **显示** — 表单显示控制(X=隐藏、C=创建、R=只读等) * **英文名 / 中文名** — 字段标签 * **数据域** — 引用域定义中的域名 * **非空** — TRUE 表示 NOT NULL * **类型** — VARCHAR、INTEGER、DATE、TIMESTAMP、TEXT 等 * **长度** — 字符串长度 * **小数位数** — 数字精度 * **缺省值** — 数据库默认值 * **字典** — 关联的字典编码 === 第二步:运行代码生成器 === Nop 平台提供 nop-cli 命令行工具,可以直接通过 CLI 调用代码生成。nop-cli 是一个可独立运行的 JAR,不需要 Maven 插件配置。 # 使用 nop-cli 从 Excel 模型生成代码 java -jar nop-cli.jar gen \ -o=./my-app \ -t=/nop/templates/orm \ ./model/my-app.orm.xlsx 生成的目录结构: my-app/ _gen/ ← 自动生成的代码 model/ _App.orm.xml ← ORM 模型定义 _Article.java ← 实体类 service/ _ArticleBizModel.java ← CRUD 服务 api/ _ArticleApi.java ← API 接口 src/ main/ resources/ my-app.orm.xml ← 可定制的 ORM 模型(继承 _gen 版本) ''_gen'' 目录下的文件都以 ''_'' 开头,标记为"只读生成"——不要手动修改。定制化在 ''src/'' 中完成。 === 第三步:编写 Delta 定制层 === 自动生成的服务已经可以运行,提供了完整的 CRUD 操作。如果需要定制,不要修改 _gen 下的代码,而是添加 Delta 层。 例如添加一个"获取文章统计"的接口: 在 ''src/main/resources/_my-app/service/'' 下创建文件,覆盖自动生成的服务定义: 在 Java 中编写扩展类: public class ArticleBizModelEx extends _ArticleBizModel { @BizQuery public List
getRecentPosts(@Name("days") int days) { return dao().findRecent(days); } } Delta 层的 Java 文件放在 ''src/main/java/'' 下,与自动生成的代码隔离。 === 第四步:启动服务 === 在 my-app 模块中配置好 quarkus-maven-plugin 后,运行: cd my-app ./mvnw compile quarkus:dev 启动后,自动注册的 API 端点: * GraphQL:http://localhost:8080/graphql * REST:http://localhost:8080/r/ArticleBizModel__save * REST:http://localhost:8080/r/ArticleBizModel__get?id=xxx * REST:http://localhost:8080/r/ArticleBizModel__findPage === 第五步:测试 API === **保存一篇文章:** curl -X POST http://localhost:8080/r/ArticleBizModel__save \ -H "Content-Type: application/json" \ -H "nop-tenant: 0" \ -d '{"data":{"title":"Hello Nop","content":"我的第一篇文章","author":"test"}}' **查询文章列表:** curl -X POST http://localhost:8080/r/ArticleBizModel__findPage \ -H "Content-Type: application/json" \ -H "nop-tenant: 0" \ -d '{"query":{"title__contains":"Nop"}}' ===== 情景二:基于已有项目进行扩展(Delta 定制) ===== 在实际项目中,更多时候不是从零开始,而是**对已有的模块进行扩展**。Nop 平台的可逆计算(Delta)机制使得我们可以在**不修改上游源码**的前提下,为已有的模块增加新功能。 下面以我们实际的工程为例——基于 nop-auth 扩展出物业系统的认证微服务(tom-auth-app)。整个模块的目录结构如下(标注了新增和生成的文件): tom-auth-app/ ├── pom.xml ← 新建:Maven 配置,依赖 nop-auth-* ├── model/ │ └── tom-auth.orm.xml ← 新建:源模型,定义新增实体 ├── postcompile/ │ └── gen-orm.xgen ← 新建:代码生成脚本 └── src/main/ ├── java/com/estate/auth/ │ ├── TomAuthApplication.java ← 新建:Quarkus 启动入口 │ ├── TomAuthCodeGen.java ← 新建:代码生成启动器 │ ├── entity/ │ │ ├── OrgUnit.java ← 生成:实体包装类 │ │ ├── UserOrgRel.java ← 生成:实体包装类 │ │ └── _gen/ │ │ ├── _OrgUnit.java ← 生成:实体基类 │ │ └── _UserOrgRel.java ← 生成:实体基类 │ ├── biz/ │ │ ├── IOrgUnitBiz.java ← 生成:业务接口 │ │ └── IUserOrgRelBiz.java ← 生成:业务接口 │ └── dao/ │ ├── NopAuthDaoConstants.java ← 生成:DAO 常量 │ └── _NopAuthDaoConstants.java ← 生成:DAO 常量基类 └── resources/ ├── application.yaml ← 新建:应用配置 └── _vfs/_delta/default/nop/auth/ ├── _module ← 生成:模块声明 ├── orm/ │ ├── app.orm.xml ← 生成:Delta ORM 入口 │ └── default/_app.orm.xml ← 生成:Delta ORM 定义 └── beans/ └── _dao.beans.xml ← 生成:DAO Bean 注册 说明: * **新建**(手工编写):pom.xml、model/ 源模型、postcompile/ 脚本、TomAuthApplication.java、TomAuthCodeGen.java、application.yaml * **生成**(从模型自动产出):实体类、业务接口、DAO 常量、Delta VFS 资源 === 第一步:引入上游模块为依赖 === 在 ''pom.xml'' 中引入上游 nop-auth 模块为依赖,不把源码复制到项目里: io.github.entropy-cloud nop-auth-dao io.github.entropy-cloud nop-auth-service io.github.entropy-cloud nop-auth-web 上游模块的 VFS(虚拟文件系统)资源会随 JAR 一起加载,包含 ORM 模型、xbiz 业务逻辑、AMIS 页面等。 === 第二步:创建 ORM 源模型 === 在 ''model/'' 目录下编写 ''tom-auth.orm.xml'',定义新增的实体和字段。这些实体是对上游的补充,而非替代。 === 第三步:配置代码生成器 === 在 ''postcompile/gen-orm.xgen'' 中编写代码生成脚本,指定模型路径和模板: codeGenerator.withTargetDir("../") .renderModel('../model/tom-auth.orm.xml', '/nop/templates/orm-delta', '/', $scope); 在 ''pom.xml'' 中配置 exec-maven-plugin,绑定到 ''process-classes'' 阶段,指向代码生成启动器 ''TomAuthCodeGen'': org.codehaus.mojo exec-maven-plugin 3.1.0 codegen process-classes java com.estate.auth.codegen.TomAuthCodeGen 运行 ''mvn package'' 时,编译完成后自动触发代码生成:TomAuthCodeGen 启动 → 加载 ORM 模型 → 根据 ''orm-delta'' 模板生成实体类和 Delta VFS 资源。 代码生成器产出的内容(对应目录结构中标为"生成"的文件): **Java 实体类:** * ''entity/OrgUnit.java'' / ''entity/UserOrgRel.java'' — 可定制的实体包装类 * ''entity/_gen/_OrgUnit.java'' / ''entity/_gen/_UserOrgRel.java'' — 生成的基类(含 getter/setter/ORM 方法) * ''biz/IOrgUnitBiz.java'' / ''biz/IUserOrgRelBiz.java'' — 业务接口 * ''dao/NopAuthDaoConstants.java'' / ''dao/_NopAuthDaoConstants.java'' — DAO 常量 **Delta VFS 资源(放在 ''_vfs/_delta/default/nop/auth/'' 下):** * ''orm/app.orm.xml'' — Delta ORM 入口,标记为 ''x:extends="super,default/_app.orm.xml"'' * ''orm/default/_app.orm.xml'' — 实际的 Delta ORM 定义 * ''beans/_dao.beans.xml'' — DAO Bean 注册 * ''_module'' — 模块声明 启动时,上游 nop-auth 的 ORM 和我们的 Delta ORM 自动合并,新增的 OrgUnit 和 UserOrgRel 实体就生效了。 === 第四步:编写应用启动入口 === 在 ''src/main/java/com/estate/auth/'' 下创建两个 Java 文件。 **TomAuthApplication.java**——Quarkus 应用入口,负责启动 HTTP 服务和 Nop 平台: @QuarkusMain public class TomAuthApplication { static String[] globalArgs; public void start(@Observes StartupEvent event) { QuarkusIntegration.start(); new NopApplication().run(globalArgs); } public void stop(@Observes ShutdownEvent event) { CoreInitialization.destroy(); } public static void main(String... args) { globalArgs = args; Quarkus.run(args); } } **TomAuthCodeGen.java**——代码生成启动器,放在同一个包下,负责初始化 Nop、执行 ''postcompile/'' 下的 ''.xgen'' 脚本: public class TomAuthCodeGen { public static void main(String[] args) { AppConfig.getConfigProvider().updateConfigValue( CoreConfigs.CFG_CORE_MAX_INITIALIZE_LEVEL, CoreConstants.INITIALIZER_PRIORITY_ANALYZE); CoreInitialization.initialize(); try { File projectDir = MavenDirHelper.projectDir(TomAuthCodeGen.class); XCodeGenerator.runPostcompile(projectDir, "/", false); } finally { CoreInitialization.destroy(); } } } 然后配置 ''application.yaml'',放到 ''src/main/resources/'' 下: nop: auth: login: allow-create-default-user: true jwt: enc-key: your-jwt-secret-key site-map: static-config-path: /nop/auth/auth/app.action-auth.xml orm: init-database-schema: true datasource: driver-class-name: org.h2.Driver jdbc-url: jdbc:h2:mem:test === 第五步:编译打包为独立微服务 === mvn clean package -DskipTests -Dquarkus.package.type=uber-jar 产物是一个可独立运行的 uber-jar,包含了上游 nop-auth 的全部功能和我们的 Delta 定制: java -jar tom-auth-app/target/tom-auth-app-1.0.0-SNAPSHOT-runner.jar 启动后: * 自动加载上游 nop-auth 所有功能(用户/角色/权限/部门管理) * 自动合并 Delta 定制(OrgUnit 组织架构、UserOrgRel 用户组织关系) * GraphQL API 和 AMIS 管理页面全部可用 ===== REST API 路由说明 ===== Nop 平台所有业务对象的 REST API 统一使用以下 URL 格式: /r/{BizObjName}__{method} 例如: * ''/r/LoginApi__login'' → 调用 LoginApi 的 login 方法 * ''/r/SiteMapApi__getSiteMap'' → 调用 SiteMapApi 的 getSiteMap 方法 * ''/r/NopAuthUser__findPage'' → 调用 NopAuthUser 的 findPage 方法 其中 ''/r/'' 是 Nop 框架的固定路由前缀,**不是转义符**。完整的路由体系: ^ 前缀 ^ 用途 ^ 说明 ^ | ''/r/'' | REST API | 调用任意业务对象的 CRUD 或自定义方法 | | ''/graphql'' | GraphQL | GraphQL 查询端点 | | ''/p/'' | 页面资源 | AMIS 页面 JSON 配置 | | ''/f/'' | 文件资源 | 文件上传/下载 | | ''/q/'' | 查询 | 简化查询接口 | 网关和 Vite 代理需要正确转发这些路径,确保 ''/r/'' 请求到达对应的微服务。 ===== 两种情景的对比 ===== ^ 环节 ^ 从零开始 ^ 扩展已有项目 ^ | 模型来源 | 新建 Excel 定义 | 编写 ORM XML,引用上游实体 | | 代码生成 | nop-cli gen | exec-maven-plugin 绑定到生命周期 | | 模板 | /nop/templates/orm | /nop/templates/orm-delta | | 定制方式 | 在 src/ 中覆盖 _gen/ | 在 _vfs/_delta/ 中覆盖上游 VFS | | 升级影响 | 重新运行 codegen | 只改上游版本号,Delta 层不受影响 | ===== 总结 ===== 无论从零开始还是扩展已有项目,Nop 的核心模式不变:**模型驱动 → 代码生成 → Delta 定制**。区别在于: * **从零开始**:模型是 Excel,模板是 orm,生成全套 CRUD * **扩展已有**:模型是 ORM XML,模板是 orm-delta,生成 Delta 叠加层 Delta 机制的好处是升级友好——上游 nop-entropy 发新版本时,只需改版本号,定制代码完全不受影响。 ===== 进一步阅读 ===== * [[.:setup_guide|配置开发环境指南]] * [[../reversible_computing_implementation/xdef_meta_model|XDef 元模型体系]] * [[https://gitee.com/canonical-entropy/nop-entropy/tree/master/docs/tutorial/tutorial.md|官方开发示例文档]]