实训过程总结 源代码见 https://github.com/Eason815/sie-mes
学习笔记 Day01 2024.10.19
IIDP工业数字平台介绍 工业软件的定义 应用于工业领域 以提高工业企业研发、生产、管理水平和工业装备性能的应用软件
工业软件的分类
MES系统 <制造执行系统>
生产控制承上启下
工业软件架构演变
模型驱动设计 模型驱动架构(Model Driven Architecture, MDA) ,以模型为中心,使用元数据定义一个全新的模型体系,让所有模型皆可扩展,让应用能快速响应业务变化。
IIDP开发环境搭建
Java 1.8
MySQL 8.0+
IDEA
Nginx
新建空数据库
启动后端服务
启动前端服务
技术架构 引擎加载逻辑
架构
需求说明 目标
工厂可以制作很多产品。产品由多种物料加工组装而成。
每个产品会按着严格的工艺路线进行加工。每一个步骤都是一个工序。
每个产品都会有一个产品 BOM(Bill of Material),代表加工该产品所需物料。
每个工序也会有一个 BOM,代表该工序加工时的所需物料。
每生产一个批次的产品,都会有一个工单。
不同工单可能使用不同的工艺路线。
模型设计
模型 模型物料定义 1 2 3 4 5 6 7 8 @Model(name = "material", tableName = "ems-material", displayName = "物料", description = "物料信息", isAutoLog = Bool.True ) public class Material extends BaseModel <Material>{}
模型会使用 @Model 注解标识,并且继承 BaseModel 类型。以下是 @Model 注解的属性。上图定义了一个名为 material 的模型,并指定表名为 ems_material。
当前会先使用缓存视图
开发者中心-模型管理
选择对应模型 [生成视图]
开发者中心-视图管理
复制视图preview
``com.sie.app.demo/view/ems-material.json`
粘贴保存视图文件
``com.sie.app.demo/app.json`
更新视图列表
下次启动会使用[ems-material.json]视图
Day02 2024.10.20
测试基本类型 使用 @Property 注解可以给模型增加属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Model(name = "test_test1",displayName = "测试基本类型",type=Model.ModelType.Buss) public class TestTest1 extends BaseModel <TestTest1> { @Property(displayName = "测试字符串") private String testStr; @Property(displayName = "测试整形类型") private int testIntType; @Property(displayName = "测试整形包装类型") private Integer testIntegerType; @Property(displayName = "测试长整型类型") private Long testLongType; @Property(displayName = "测试双精度类型") private Float testFloatType; @Property(displayName = "测试浮点类型") private Double testDoubleType; @Property(displayName = "测试日期类型") private Date testDateTpe; @Property(displayName = "测试日期时间类型",dataType = DataType.DATE_TIME) private Date testDateTimeType; @Property(displayName = "测试数字计算类型",dataType = DataType.BIG_DECIMAL) private BigDecimal testBigDecimalType; }
开发者中心-模型管理
[生成视图]开发者中心-视图管理
粘贴视图 ([重置种子数据] 重新获取)
menus.json
新增二级目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 { "menus" : { "demo_root_menu" : { "name" : "demo_root_menu" , "display_name" : "物料管理系统" , "active" : true , "sequence" : 1 } , "material_menu" : { "name" : "material_menu" , "display_name" : "物料管理" , "model" : "material" , "view" : "material_grid,material_search,material_form" , "sequence" : 1 , "active" : true , "parent_ids" : { "@ref" : "demo_root_menu" } } , "product_menu" : { "name" : "product_menu" , "display_name" : "产品管理" , "model" : "product" , "view" : "product_grid,product_search,product_form" , "sequence" : 2 , "active" : true , "parent_ids" : { "@ref" : "demo_root_menu" } } }
测试校验数据 @Validate 注解提供了常用的字段属性校验。用于新增、编辑模型的时候进行数据合法性校验。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Model(name = "test_test2",displayName = "测试校验框架",type=Model.ModelType.Buss) public class TestTest2 extends BaseModel <TestTest2> { @Property(columnName = "phone",displayName = "手机号码") @Validate .Phone(message = "手机号格式不正确" ) private String phone; @Property(columnName = "email",displayName = "邮箱") @Validate .Email(message = "邮箱格式不正确" ) private String email; @Validate .NotBlank(message = "年龄不能为空" ) @Validate .Max(value = 110 ,message = "年龄不能大于110岁" ) @Validate .Min(value = 18 ,message = "年龄不能小于18岁" ) @Property(columnName = "age",displayName = "年龄") private Integer age; @Validate .NotBlank(message = "总数不能为空" ) @Validate .Max(value = 1000 ,message = "总数不能大于1000" ) @Property(columnName = "totalNum",displayName = "总数") private Double totalNum; @Validate .Size(min = 10 ,max = 100 ) @Property(displayName = "学生名字",length = 10) private String stuName; @Validate .Unique(properties ={"stuName" ,"stuCode" },message = "学生名字和学号必须唯一" ) @Property(displayName = "学号") private String stuCode; }
打包省略正则表达式过程
下拉值选项 @Selection 注解用于定义下拉取值的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Model(name = "test_test_3",displayName = "测试下拉框和字典") public class TestTest3 extends BaseModel <TestTest3> { @Property(displayName = "1、单选常量",defaultValue = "1",widget = "select",length = 256) @Selection(values = {@Option(label = "状态1",value = "1"), @Option(label = "状态2",value = "2"), @Option(label = "状态3",value = "3")}) private String status; @Property(displayName = "2、多选常量",defaultValue = "1",widget = "select",multiple = true,length = 256) @Selection(values = {@Option(label = "状态1",value = "1"), @Option(label = "状态2",value = "2"), @Option(label = "状态3",value = "3")}) private String muiltStatus; @Property(displayName = "3-字典-状态") @Dict(typeCode = "validFlag") private String validFlag; @Property(displayName = "4-多选字典-状态") @Dict(typeCode = "validFlags2",multiple = true) private String validFlags2; }
数据字典 @Dict 注解用于声明使用数据字典
数据字典使用 租户端-平台主数据-数据字典
编码与typeCode对应,即[status]
Day03 2024.10.25
应用 安装应用 复制 sie-snest-mes 软件包
sie-iidp-apps/pom.xml
1 2 3 4 5 <modules > <module > sie-snest-demo</module > <module > sie-snest-mes</module > </modules >
sie-snest-mes/pom.xml
1 <artifactId > sie-snest-mes</artifactId >
clean install 打jar包应用市场
上传jar包应用市场
[上架]应用市场
[安装]
若 应用市场 删除了 <应用> 数据库 删除 表:
meta_app meta_app_store
更新应用
修改源代码
打新的jar包clean install (//重新调试?)
应用市场
上传新的jar包
应用市场
[上架]
已安装应用
[更新]
切换用户
模型 ER 关系 例如产品BOM 与产品模型形成多对一关系 @ManyToOne 需要与 @JoinColumn 一起使用
例如产品与产品 BOM模型形成一对多关系
例如用户与角色,形成多对多 ER 关系。@ManyToMany 需要与 @JoinTable 结合使用。@JoinTable 用于声明中间表的表明及关联字段名
com.sie.app.demo/model/TestTest4.java
1 2 3 4 5 6 7 8 9 10 @Model(name = "test_test_4", displayName = "测试ER模型4") public class TestTest4 extends BaseModel <TestTest4>{ @Property(displayName = "名称") private String name; @Property(displayName = "编码") private String code; @OnetoMany private List<TestTest5> testTest5List; }
com.sie.app.demo/model/TestTest5.java
1 2 3 4 5 6 7 8 9 10 11 12 @Model(name = "test_test_5", displayName = "测试ER模型5") public class TestTest5 extends BaseModel <TestTest5> { @Property(displayName = "名称") private String name; @Property(displayName = "编码") private String code; @ManyToOne(targetModel = "test_test_4", displayName = "测试ER模型4") @JoinColumn(name = "test_test_4_id") private TestTest4 testTest4; }
com.sie.app.demo/model/TestTest6.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Model(name = "test_test_6", displayName = "测试ER模型6") public class TestTest6 extends BaseModel <TestTest6> { @Property(displayName = "名称") private String name; @Property(displayName = "编码") private String code; @ManyToMany @JoinTable(name = "test_6_and_test_7_rel",joinColumns = @JoinColumn(name = "test_test_6_id",nullable = false), inverseJoinColumns = @JoinColumn(name = "test_test_7_id",nullable = false)) @Property(displayName = "测试ER模型7") private List<TestTest7> testTest7List; }
com.sie.app.demo/model/TestTest7.java
1 2 3 4 5 6 7 8 9 10 11 12 13 @Model(name = "test_test_7", displayName = "测试ER模型7") public class TestTest7 extends BaseModel <TestTest6> { @Property(displayName = "名称") private String name; @Property(displayName = "编码") private String code; @ManyToMany @JoinTable(name = "test_6_and_test_7_rel",joinColumns = @JoinColumn(name = "test_test_6_id",nullable = false), inverseJoinColumns = @JoinColumn(name = "test_test_6_id",nullable = false)) private List<TestTest6> testTest6List; }
解决方案 应用目录栏缺失(切勿重启后端)
已安装应用
卸载对应应用
应用市场
导入jar包
应用市场
上架
应用市场
安装
切换用户
尝试更新应用
Day04 2024.10.26
模型扩展与继承 com.sie.app.demo/model/TestUser.java
1 2 3 4 5 6 7 8 9 10 11 12 @Model(name = "test_user", description = "测试用户") public class TestUser extends BaseModel <TestUser> { @Property(columnName = "name", displayName = "名称",displayForModel = true) private String name; @Validate .Email(message = "邮箱格式不正确" ) @Property(columnName = "email", displayName = "邮箱") private String email; ... }
模型扩展
如果模型的 name 与 parent 相同,就是扩展原来的模型。引擎只会有一个模型。
扩展模型可以给模型增加属性、服务。
com.sie.app.demo/model/TestUser2.java
1 2 3 4 5 6 7 8 @Model(name = "test_user", parent = "test_user") public class TestUser2 extends BaseModel <TestUser2> { @Property(displayName = "扩展aaa") private String aaa; @Property(displayName = "扩展bbb") private String bbb; }
模型继承
如果模型的 name 与 parent 不相同,就是继承原来的模型。引擎会有两个不同的模型。
继承模型可以复用父模型的属性、服务。
com.sie.app.demo/model/TestUser3.java
1 2 3 4 5 6 7 8 @Model(name = "test_user_3", parent = "test_user") public class TestUser3 extends BaseModel <TestUser3> { @Property(displayName = "儿子aaa") private String aaa1; @Property(displayName = "儿子bbb") private String bbb; }
==父类若有扩展,扩展的属性也会被继承== ==若扩展的属性与子类新属性重名,优先使用子类的属性。==
开发者中心-模型管理
只会出现两个类: test_user test_user_3
test_user内含:
TestUser.java属性
TestUser2.java属性:扩展aaa(aaa),扩展bbb(bbb)
test_user_3内含:
继承test_user不重名属性
TestUser3.java属性:儿子aaa(aaa1)
由于儿子bbb(bbb)与扩展bbb(bbb)重复,优先保留儿子bbb(bbb)
模型服务 声明服务 在模型写一个方法,并且加上 @MethodService 标记这个方法成为一个模型服务。模型的服务可以作为 API 被前端调用。 服务需要写明几个信息 1. name 服务名称 2. auth 权限点 3. description 服务描述com.sie.app.demo/model/TestMethodService.java
1 2 3 4 5 6 7 @Model(name = "test_method_service") public class TestMethodService extends BaseModel <TestMethodService>) { @MethodService(name = "hello", description = "say hello") public String hello (String name) { return "Hello World!" +name; } }
调试接口 在租户端使用demo应用中服务模板修改
例
测试基本类型-F12-Network
找到调用服务的Post转发
右键复制为bash
1 2 3 4 ``` `Postman客户端-import-Raw text` 粘贴
1 2 3 4 5 6 7 8 9 10 修改Body部分 参数 ```json "args":{ "name": "eason" } "model": "test_method_service", "service": "hello"
修改发送网址model,service
扩展服务 在声明模型的时候,我们的模型继承了 BaseModel。引擎会自动给模型加上基础增删改查的服务。
一般来说自带的服务都能满足业务要求。如果模型的服务不满足业务要求的时候,我们可以扩展原来的服务。
我们只需要在模型中声明相同方法签名的服务,然后加入新增的逻辑。
在扩展的服务中,可以使用 getMeta().get(modelname).callSuper() 方法调用模型扩展前的服务。
例com.sie.app.demo/model/TestCurdMethod.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @MethodService(name = "create",auth = "create",description = "创建") public RecordSet create (RecordSet rs, List<Map<String, Object>> valueList) { System.out.println("创建方法入参" +valueList); RecordSet rsc = (RecordSet) rs.callSuper(TestCurdMethod.class,"create" ,valueList); return rsc; } @MethodService(name = "update",auth = "update", description = "更新") public RecordSet update (RecordSet rs,Map<String,Object> values) { System.out.println("更新方法入参" +values); RecordSet rec = (RecordSet) rs.callSuper(TestCurdMethod.class,"update" ,values); return rec; } @MethodService(name = "delete",auth = "delete",description = "删除") public boolean delete (RecordSet rs) { System.out.println("删除方法入参" +rs.getId()); rs.callSuper(TestCurdMethod.class,"delete" ); return Boolean.TRUE; } @MethodService(name = "search", auth = "search",description = "分页查询方法") public List<Map<String,Object>> search (RecordSet rs, Filter filter, List<String> properties, Integer limit,Integer offset,String order) { List<Map<String,Object>> result = (List<Map<String, Object>>) rs.callSuper(TestCurdMethod.class,"search" ,filter,properties,limit,offset,order); System.out.println("分页查询方法返回参数" +result); return result; }
生成视图com.sie.app.demo/views/test-curd-method.json
断点测试CURD
Filter 条件 import com.sie.snest.engine.rule.Filter;
在服务中,通常需要调用 search 方法进行查询数据。search 方法第一个参数是 Filter 类。Filter 用于构建查询条件。
1 2 3 4 5 6 7 8 9 10 11 12 Filter.equal("name" , name); # name = “name” Filter.notEqual("name" , name); # name <> “name” Filter.like("name" , name); # name like “%name%” Filter.notLike("name" , name); # name not like “%name%” Filter.rlike("name" , name); # name like “name%” Filter.llike("name" , name); # name like “%name” Filter.greater("age" , 10 ); # age > 10 Filter.greaterOrEqual("age" , 10 ); # age >= 10 Filter.less("age" , 10 ); # age < 10 Filter.lessOrEqual("age" , 10 ); # age <= 10 Filter.in("name" , Arrays.asList("张三" , "李四" )); # name in (“张三”, “李四”) Filter.notIn("name" , Arrays.asList("张三" , "李四" )); # name not in (“张三”, “李四”)
逻辑运算 Filter 提供 AND 和 OR 方法,用于构建 SQL 中的 and 和 or 功能。
1 2 3 4 Filter a = Filter.equal("name" , name);Filter b = Filter.notEqual("name" , name);Filter c = Filter.AND(a, b); # a and bFilter d = Filter.OR(a, b); # a or b
前端表示
1 2 3 4 5 6 7 "filter" : [ "&" , [ "name" , "=" , "张三" ] , "|" , [ "age" , ">" , 10 ] , [ "id" , "in" , [ 1 , 2 ] ] ]
Day05 2024.10.27
需求实现 文件结构
mes
├── model
│ ├── Material.java
│ ├── Order.java
│ ├── Process.java
│ ├── ProcessBom.java
│ ├── ProcessRoute.java
│ ├── Product.java
│ ├── ProductBom.java
│ └── Supplier.java
├── views
│ ├── menus.json
│ ├── mes-material-view.json
│ ├── mes-order-view.json
│ ├── mes-process-bom-view.json
│ ├── mes-process-route-view.json
│ ├── mes-process-view.json
│ ├── mes-product-bom-view.json
│ ├── mes-product-view.json
│ └── mes-supplier-view.json
└── app.json
模型定义 Material.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @Model(name = "material", tableName = "material", displayName = "物料", description = "物料信息", isAutoLog = Bool.True ) public class Material extends BaseModel <Material> { @Property(displayName = "名称",displayForModel = true) private String name; @Property(displayName = "编码") private String code; @Dict(typeCode = "material_type") @Property(displayName = "类型") private String type; @Dict(typeCode = "material_unit") @Property(displayName = "单位") private String unit; @Property(displayName = "描述") private String description; @Property(displayName = "库存量") private Integer stockQuantity; @Property(displayName = "单价") private Integer price; @ManyToOne(displayName = "供应类") @JoinColumn private Supplier supplier; }
Order.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Model(name = "order",displayName = "生产订单",isAutoLog = Bool.True) public class Order extends BaseModel <Order> { @ManyToOne @JoinColumn private Product product; @Property(displayName = "数量") private Integer quantity; @Property(displayName = "订单状态",defaultValue = "1",widget = "select",length = 256) @Selection(values = {@Option(label = "设计中",value = "1"), @Option(label = "生产中",value = "2"), @Option(label = "已完成",value = "3")}) private String status; @Property(displayName = "预计完成时间",dataType = DataType.DATE_TIME) private Date dueDate; }
Process.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Model(name = "process",displayName = "工序",isAutoLog = Bool.True) public class Process extends BaseModel <Process> { @Property(displayName = "名称") private String name; @Property(displayName = "编码") private String code; @ManyToOne @JoinColumn private ProcessRoute processRoute; @OneToMany private List<ProcessBom> processBomList; }
ProcessBom.java
1 2 3 4 5 6 7 8 9 10 11 12 13 @Model(name = "process_bom",displayName = "工序BOM",isAutoLog = Bool.True) public class ProcessBom extends BaseModel <ProcessBom> { @ManyToOne(displayName = "工序") @JoinColumn private Process process; @ManyToOne(displayName = "物料") @JoinColumn private Material material; @Property(displayName = "数量") private Double amount; }
ProcessRoute.java
1 2 3 4 5 6 7 8 9 10 11 12 @Model(name = "process_route",displayName = "工艺路线", isAutoLog = Bool.True) public class ProcessRoute extends BaseModel <ProcessRoute> { @Property(displayName = "名称") private String name; @Property(displayName = "编码") private String code; @ManyToOne @JoinColumn private Product product; }
Product.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Model(name = "product", description = "产品", displayName = "产品",isAutoLog = Bool.True) public class Product extends BaseModel <Product> { @Validate .NotBlank(message = "产品名称不能为空!" ) @Property(displayName = "名称") private String name; @Validate .NotBlank(message = "产品名称不能为空!" ) @Property(displayName = "编码") private String code; @Property(displayName = "产品状态",defaultValue = "1",widget = "select",length = 256) @Selection(values = {@Option(label = "设计中",value = "1"), @Option(label = "生产中",value = "2"), @Option(label = "已完成",value = "3")}) private String status; @OneToMany private List<ProductBom> productBomList; }
ProductBom.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Model(name = "product_bom", description = "产品BOM", displayName = "产品BOM",isAutoLog = Bool.True) public class ProductBom extends BaseModel <ProductBom> { @ManyToOne(displayName = "产品") @JoinColumn private Product product; @ManyToOne(displayName = "物料") @JoinColumn private Material material; @Property(displayName = "数量") private Integer amount; }
Supplier.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Model(name = "supplier",displayName = "供应类",isAutoLog = Bool.True) public class Supplier extends BaseModel <Supplier> { @Property(displayName = "名称") private String name; @Property(columnName = "phone", displayName = "联系方式") @Validate .Phone(message = "联系方式不正确" ) private String phone; @Property(displayName = "地址") private String address; @OneToMany private List<Material> materialList; }
视图定义 生成视图
小任务 7.1 说明 为了更熟悉 IIDP 开发的流程,可以从下面四个实训任务选取一个进行开发。
7.2 产品BOM 管理功能 需求 1.完成产品管理页面,有产品名称、规格等信息 2.完成物料管理页面,有物料名称、物料编码(编码唯一)等信息。 3.完成产品BOM标签页。显示产品关联的物料。产品 BOM 有数量属性。 4.实现产品 BOM 的添加、删除功能。
提示 1.使用 @Unique 实现编码唯一校验
7.3 编码规则功能 需求 1.使用 IIDP 实现一个编码规则功能。具有增删改查功能。 2.编码规则包含前缀、流水号位数、日期格式。 3.日期格式是下拉选择。有年月日、年月、年三种选项。 4.提供一个服务,可以按指定编码规则生成一个编码。例如前缀 PM、流水号位数 6、日期格式为年月日。调用该服务,返回一个流水号,PM20231201000001。再次调用返回连续流水号的编码。
提示 1.使用 @Selection 实现下拉选择
7.4 数据隔离 需求 1.开发一个模型,实现增删改查功能。属性包含创建人、创建时间、修改人、修改时间。 2.用户只能查看自己创建的数据。 3.登录另一账号,不能查看到其他用户的数据。
提示 1.使用 isAutoLog 开启审计字段 2.创建人属性名为 create_user
7.5 服务扩展 需求 1.开发一个APP1,有一个订单模型。含有字段发货时间。订单模型具有一个“发货”服务。 2.实现简单增删改查功能。 3.调用服务,将发货时间改成当前时间。 4.开发另一个 APP2。扩展订单模型的发货服务。逻辑为调用服务时,将发货时间改成当前时间+24小时。 5.通过应用市场安装 APP2,点击“发货”按钮。订单的发货时间被修改成当前时间+24小时。
提示 1.使用服务扩展