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 | ||
| 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/ 下创建文件,覆盖自动生成的服务定义:
<beans> <bean id="ArticleBizModel" class="my.app.service.ArticleBizModelEx" /> </beans>
在 Java 中编写扩展类:
public class ArticleBizModelEx extends _ArticleBizModel { @BizQuery public List<Article> 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
第五步:测试 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 模块为依赖,不把源码复制到项目里:
<dependencies> <dependency> <groupId>io.github.entropy-cloud</groupId> <artifactId>nop-auth-dao</artifactId> </dependency> <dependency> <groupId>io.github.entropy-cloud</groupId> <artifactId>nop-auth-service</artifactId> </dependency> <dependency> <groupId>io.github.entropy-cloud</groupId> <artifactId>nop-auth-web</artifactId> </dependency> </dependencies>
上游模块的 VFS(虚拟文件系统)资源会随 JAR 一起加载,包含 ORM 模型、xbiz 业务逻辑、AMIS 页面等。
第二步:创建 ORM 源模型
在 model/ 目录下编写 tom-auth.orm.xml,定义新增的实体和字段。这些实体是对上游的补充,而非替代。
<orm ext:entityPackageName="com.estate.auth.entity" ext:basePackageName="com.estate.auth" ext:deltaDir="default"> <entities> <entity className="com.estate.auth.entity.OrgUnit" name="com.estate.auth.entity.OrgUnit" tableName="estate_org_unit"> <columns> <column code="ORG_ID" name="orgId" primary="true"/> <column code="TENANT_ID" name="tenantId"/> <column code="PARENT_ID" name="parentId"/> <column code="PATH" name="path"/> </columns> </entity> <entity className="com.estate.auth.entity.UserOrgRel" name="com.estate.auth.entity.UserOrgRel" tableName="estate_user_org_rel"/> </entities> </orm>
第三步:配置代码生成器
在 postcompile/gen-orm.xgen 中编写代码生成脚本,指定模型路径和模板:
<c:script> codeGenerator.withTargetDir("../") .renderModel('../model/tom-auth.orm.xml', '/nop/templates/orm-delta', '/', $scope); </c:script>
在 pom.xml 中配置 exec-maven-plugin,绑定到 process-classes 阶段,指向代码生成启动器 TomAuthCodeGen:
<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.1.0</version> <executions> <execution> <id>codegen</id> <phase>process-classes</phase> <goals><goal>java</goal></goals> </execution> </executions> <configuration> <mainClass>com.estate.auth.codegen.TomAuthCodeGen</mainClass> </configuration> </plugin>
运行 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 发新版本时,只需改版本号,定制代码完全不受影响。