第1步:从大佬的gitee:https://gitee.com/canonical-entropy/nop-entropy下载源码,进行本地编译,具体编译看项目下的readme,想偷懒的可以下载我编译后的jar,放到自己的maven仓库
https://pan.baidu.com/s/15qANnrCh5RV-T1CYCDvMdw?pwd=kq0q
我把代码上传到gitee,地址:https://gitee.com/a-crud-boy/nop-simple-demonn
第2步:创建一个maven项目,然后添加依赖
<parent>
<groupId>io.github.entropy-cloud</groupId>
<artifactId>nop-entropy</artifactId>
<version>2.0.0-SNAPSHOT</version>
</parent>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.github.entropy-cloud</groupId>
<artifactId>nop-spring-web-starter</artifactId>
</dependency>
<!--h2数据库-->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!--h2数据库-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
第3步 ,添加springboot入口
@SpringBootApplication
public class NopSpringSimpleApplication {
public static void main(String[] args) {
SpringApplication.run(NopSpringSimpleApplication.class, args);
}
}
第4步 ,添加application.yaml文件,注意是yaml,不是yml
nop:
debug: true
orm:
init-database-schema: true
datasource:
driver-class-name: org.h2.Driver
jdbc-url: jdbc:h2:./db/demo
username: sa
password:
第5步 ,创建类
import com.nop.biz.DemoRequest;
import com.nop.biz.DemoResponse;
import io.nop.api.core.annotations.biz.BizModel;
import io.nop.api.core.annotations.biz.BizMutation;
import io.nop.api.core.annotations.biz.BizQuery;
import io.nop.api.core.annotations.biz.RequestBean;
import io.nop.api.core.annotations.core.Name;
import io.nop.api.core.exceptions.NopException;
import static com.nop.biz.DemoErrors.ARG_NAME;
import static com.nop.biz.DemoErrors.ERR_DEMO_NOT_FOUND;
@BizModel("Demo")
public class DemoBizModel {
@BizQuery
public String hello(@Name("message") String message) {
return "Hi," + message;
}
@BizMutation
public DemoResponse testOk(@RequestBean DemoRequest request) {
DemoResponse ret = new DemoResponse();
ret.setName(request.getName());
ret.setResult("ok");
return ret;
}
@BizMutation
public DemoResponse testError(@RequestBean DemoRequest request) {
throw new NopException(ERR_DEMO_NOT_FOUND).param(ARG_NAME, request.getName());
}
}
package com.nop.biz.demo;
import io.nop.api.core.annotations.biz.BizModel;
import io.nop.api.core.annotations.biz.BizMutation;
import io.nop.api.core.annotations.biz.BizQuery;
import io.nop.api.core.annotations.core.Name;
import io.nop.api.core.annotations.graphql.GraphQLReturn;
import io.nop.api.core.beans.FilterBeans;
import io.nop.api.core.beans.query.QueryBean;
import io.nop.core.reflect.bean.BeanTool;
import io.nop.dao.api.IDaoProvider;
import io.nop.dao.api.IEntityDao;
import io.nop.orm.IOrmEntity;
import jakarta.inject.Inject;
import java.util.List;
import java.util.Map;
/**
* <strong> </strong> <br>
* <p>
* <strong> </strong> <br>
* </p>
* <br>
*
* @author :
* @date 2024年01月31日
* 修改人 修改日期 修改描述<br>
* -------------------------------------------<br>
* <br>
* <br>
*/
@BizModel("DemoEntity")
public class DemoEntityBizModel {
@Inject
IDaoProvider daoProvider;
@BizQuery
@GraphQLReturn(bizObjName = "DemoEntity")
public IOrmEntity getEntity(@Name("id") String id) {
IEntityDao<IOrmEntity> dao = daoProvider.dao("app.demo.DemoEntity");
return dao.getEntityById(id);
}
@BizMutation
@GraphQLReturn(bizObjName = "DemoEntity")
public IOrmEntity saveEntity(@Name("data") Map<String, Object> data) {
IEntityDao<IOrmEntity> dao = daoProvider.dao("app.demo.DemoEntity");
IOrmEntity entity = dao.newEntity();
BeanTool.instance().setProperties(entity, data);
dao.saveEntity(entity);
return entity;
}
@BizQuery
@GraphQLReturn(bizObjName = "DemoEntity")
public List<IOrmEntity> findByName(@Name("name") String name) {
IEntityDao<IOrmEntity> dao = daoProvider.dao("app.demo.DemoEntity");
QueryBean query = new QueryBean();
query.addFilter(FilterBeans.contains("name", name));
// <contains name="name" value="a" /> name like '%a%'
return dao.findAllByQuery(query);
}
// 注意,字段不能声明为private。NopIoC无法注入私有成员变量
@Inject
DemoMapper demoMapper;
@BizQuery
@GraphQLReturn(bizObjName = "DemoEntity")
public IOrmEntity findBySql(@Name("name") String name) {
return demoMapper.findFirstByName(name);
}
}
package com.nop.biz.demo;
import io.nop.api.core.annotations.core.Name;
import io.nop.api.core.annotations.orm.SqlLibMapper;
import io.nop.orm.IOrmEntity;
/**
* <strong> </strong> <br>
* <p>
* <strong> </strong> <br>
* </p>
* <br>
*
* @author :
* @date 2024年01月31日
* 修改人 修改日期 修改描述<br>
* -------------------------------------------<br>
* <br>
* <br>
*/
@SqlLibMapper("/test/demo/sql/demo.sql-lib.xml")
public interface DemoMapper {
IOrmEntity findFirstByName(@Name("name") String name);
}
package com.nop.biz;
import io.nop.api.core.annotations.core.Locale;
import io.nop.api.core.exceptions.ErrorCode;
import static io.nop.api.core.exceptions.ErrorCode.define;
/**
* <strong> </strong> <br>
* <p>
* <strong> </strong> <br>
* </p>
* <br>
*
* @author :
* @date 2024年01月31日
* 修改人 修改日期 修改描述<br>
* -------------------------------------------<br>
* <br>
* <br>
*/
@Locale("zh-CN")
public interface DemoErrors {
String ARG_NAME = "name";
ErrorCode ERR_DEMO_NOT_FOUND =
define("nop.err.demo.not-found", "指定数据不存在: {name}", ARG_NAME);
}
package com.nop.biz;
import lombok.Data;
/**
* <strong> </strong> <br>
* <p>
* <strong> </strong> <br>
* </p>
* <br>
*
* @author :
* @date 2024年01月30日
* 修改人 修改日期 修改描述<br>
* -------------------------------------------<br>
* <br>
* <br>
*/
@Data
public class DemoRequest {
private String name;
private String email;
}
package com.nop.biz;
import lombok.Data;
/**
* <strong> </strong> <br>
* <p>
* <strong> </strong> <br>
* </p>
* <br>
*
* @author :
* @date 2024年01月30日
* 修改人 修改日期 修改描述<br>
* -------------------------------------------<br>
* <br>
* <br>
*/
@Data
public class DemoResponse {
private String name;
private String result;
}
其中@BizModel @BizMutation @BizQuery @RequestBean @Name,@SqlLibMapper 是nop中规定的一些注解,BizModel 类似spring中的Controlller,RequestBean 类似于spring中的RequestBody,SqlLibMapper类似于Mapper,代表存放sql的地址文件,后面会有请求案例,
第6步 ,创建资源文件
关于目录结构后面有介绍,需要按照nop规定好的结构,千万别忘了_module,这是规定一个目录为一个模块的文件,就是一个空文件
app-demo.beans.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans x:schema="/nop/schema/beans.xdef" xmlns:x="/nop/schema/xdsl.xdef">
<bean id="DemoBizModel" class="com.nop.biz.demo.DemoBizModel"/>
<bean id="DemoEntityBizModel" class="com.nop.biz.demo.DemoEntityBizModel"/>
<bean id="com.nop.biz.demo.DemoMapper" class="io.nop.orm.sql_lib.proxy.SqlLibProxyFactoryBean"
ioc:type="@bean:id" ioc:bean-method="build">
<property name="mapperClass" value="@bean:type"/>
</bean>
</beans>
DemoEntity.xmeta
<meta x:schema="/nop/schema/xmeta.xdef" xmlns:x="/nop/schema/xdsl.xdef">
<props>
<prop name="sid" displayName="SID" queryable="true">
<schema type="String"/>
</prop>
<prop name="name" displayName="名称" queryable="true" insertable="true" updatable="true">
<schema type="String"/>
</prop>
<prop name="status" displayName="状态" queryable="true" insertable="true" updatable="true">
<schema type="Integer"/>
</prop>
<prop name="status_label" displayName="状态文本">
<schema type="String"/>
<getter>
<c:script><![CDATA[
if(entity.status == 1)
return "ACTIVE";
return "INACTIVE";
]]></c:script>
</getter>
</prop>
</props>
</meta>
app.orm.xml
<orm x:schema="/nop/schema/orm/orm.xdef" xmlns:x="/nop/schema/xdsl.xdef">
<entities>
<entity name="app.demo.DemoEntity" tableName="demo_entity"
className="io.nop.orm.support.DynamicOrmEntity" registerShortName="true">
<columns>
<column name="sid" code="SID" propId="1" stdSqlType="VARCHAR" precision="32" tagSet="seq"
mandatory="true" primary="true"/>
<column name="name" code="NAME" propId="2" stdSqlType="VARCHAR" precision="100" mandatory="true"/>
<column name="status" code="STATUS" propId="3" stdSqlType="INTEGER"/>
</columns>
</entity>
</entities>
</orm>
demo.sql-lib.xml
<sql-lib x:schema="/nop/schema/orm/sql-lib.xdef" xmlns:x="/nop/schema/xdsl.xdef">
<sqls>
<eql name="findFirstByName" sqlMethod="findFirst">
<source>
select o from DemoEntity o where o.name like ${'%' + name + '%'}
</source>
</eql>
</sqls>
</sql-lib>
第7步 ,运行项目,项目启动以后,会在项目目录下生成一个_dump 的目录,里面可以供开发者调试
Nop平台调试模式启动Nop平台的调试模式,只需设置nop.debug=true
。在调试模式下,可以通过以下链接获取所有服务定义:
/p/DevDoc__graphql
/p/DevDoc__beans
例:
-
127.0.0.1:8080/p/DevDoc__graphql
可以看到对外暴露的所有服务定义。
例如:type DemoEntity{ "SID" sid : String "名称" name : String "状态" status : Int "状态文本" status_label : String }
-
127.0.0.1:8080/p/DevDoc__beans
可以查看所有对象定义。
例如:<bean class="com.nop.biz.demo.DemoBizModel" id="DemoBizModel" ioc:aop="false"/>
注意事项
- 前端的REST链接根据对象名和方法名自动推定,无需手工指定,固定格式为:
/r/{bizObjName}__{bizMethod}
。 注意是两个下划线。 @BizQuery
允许通过GET和POST两种HTTP方法调用,而@BizMutation
只允许通过POST方法调用。- 通过GET方式调用时,可以通过URL链接来传递参数,例如
/r/Demo__hello?message=abc
。通过POST方式调用时可以通过URL来传参,也可以通过Http的body使用JSON格式传递。 - 可以通过
@Name
来一个个的指定前台参数,也可以通过@RequestBean
将前台传递过来的所有参数包装为指定的JavaBean类型。 - 服务函数总是返回POJO对象,并指定按照JSON格式进行编码。
示例API访问
REST
例1,带响应状态:
- 访问
127.0.0.1:8080/r/Demo__hello?message=abc
- 响应:
{
“data”: “Hi,abc”,
“status”: 0
}
例2,不带响应状态:
- 访问127.0.0.1:8080/p/Demo__hello?message=abc
- 响应: Hi,abc
其他类似:
-
127.0.0.1:8080/r/DemoEntity__findByName?name=abc
-
127.0.0.1:8080/r/Demo2Entity__findByName?name=abc
GraphQL
例1(查询),关键字query:
-
访问
POST 127.0.0.1:8080/graphql
-
请求体:
query{
Demo__hello(message:“2343”)
} -
响应
{
“data”: {
“Demo__hello”: “Hi,abc”
}
}
例2(更新),关键字mutation:
-
访问
POST 127.0.0.1:8080/graphql
-
请求体:
mutation {
Demo__testOk( name: “zhagnsan”, email: “123@qq.com” ) {
name
result
}
} -
响应
{
“data”: {
“Demo__hello”: “Hi,abc”
}
}
例3(保存),关键字mutation:
- 访问
POST 127.0.0.1:8080/graphql
- 请求体:
mutation {
DemoEntity__saveEntity(data: {name: “zhagnsan”, status: 1}) {
sid
name
status
}
} - 响应
{
“data”: {
“DemoEntity__saveEntity”: {
“sid”: “23410bb4cdd74fc7bd2c1795059ff8a1”,
“name”: “zhagnsan”,
“status”: 1
}
}
}
例2跟例3有点类似于spring中的加RequestBody跟不加RequestBody的区别, 用 @RequestBean注解就直接写属性字段,@Name需要把方法中的对象参数名写在前面
文件命名规范
平台内所有会被自动识别并处理的文件模式已在此文档中列举:
Nop平台内部约定了一定的资源路径模式,会自动查找满足模式的文件进行加载。
META-INF/services
io.nop.core.initialize.ICoreInitializer 使用Java内置的ServiceLoader机制注册分级初始化函数
CoreInitialization会读取所有CoreInitializer,按照优先级顺序执行
bootstrap.yaml 静态全局配置文件,其中的内容优先级最高,不会被外部配置所覆盖
application.yaml 全局的配置文件
application-{profile}.yaml 全局的配置文件,profile是通过nop.profile指定的部署环境名称
_vfs/
/_delta
/{deltaDir} 这里是delta层的名称,缺省会加载default层
这里的文件会覆盖标准路径的同名文件
/dict
{dictName}.dict.yaml 字典文件不会被自动加载,但是通过dictName加载指定字典文件
/i18n
/{locale}
{moduleName}.i18n.yaml I18nManager初始化的时候会自动加载所有i18n文件
/nop
/aop
{xxx}.annotations Nop的AOP代码生成器生成包装类时会读取所有的annotations文件,并为每个标注了指定注解的类生成AOP包装类
/autoconfig
{xxx}.beans NopIoC会自动扫描解析所有后缀名为beans的文件,加载其中的beans.xml文件
/core
/reigstry
{xxx}.register-model.xml 初始化时会自动扫描所有registry-model.xml文件,并注册对应的DSL模型解析器,
将它们和特定的文件类型关联起来
/dao
/dialect
/selector
{xxx}.selector.xml 初始化时会自动扫描所有selector.xml文件,加载数据库方言的匹配规则
{dialectName}.dialect.xml 数据库方言定义文件,按照dialectName来加载
/main
/auth
/main.action-auth.xml 全局的操作权限和菜单定义文件,在其中通过x:extends来引用其他权限文件
/main.data-auth.xml 全局的数据权限定义文件,在其中通过x:extends来引用其他数据权限文件
/{moduleId} Nop模块的moduleId必须是nop/auth这种两级目录结构
_module 每个Nop模块下都有一个_module文件来标记它是模块。
/beans
app-{xxx}.beans.xml NopIoC启动时会自动扫描每个模块的beans目录下以`app-`为前缀的beans.xml文件
/model
{bizObjName}.xbiz 所有的服务对象原则上都是要在beans.xml中注册,然后再通过对象名查找到对应的xbiz和xmeta文件
{bizObjName}.xmeta NopDynEntity对象采用了简化注册流程,直接向BizObjectManager注册,没有在beans.xml中定义服务对象
/orm
app.orm.xml NopOrm引擎初始化的时候会加载所有模块的orm目录下的app.orm.xml模型文件
/pages
/{bizObjName}
{pageId}.page.yaml 可以配置页面文件在系统初始化的情况下加载,它引用的view模型因此被连带加载
{bizObjName}.view.xml View模型不会被自动加载,但是一般会放置在这个位置
第1篇就先写这么多,后面再慢慢完善