32504f1a344ec706f1b63758f739c266c0e64711
.idea/.gitignore
... | ... | @@ -0,0 +1,9 @@ |
1 | +# Default ignored files |
|
2 | +/shelf/ |
|
3 | +/workspace.xml |
|
4 | +# Editor-based HTTP Client requests |
|
5 | +/httpRequests/ |
|
6 | +# Datasource local storage ignored files |
|
7 | +/dataSources/ |
|
8 | +/dataSources.local.xml |
|
9 | +/.idea/ |
|
... | ... | \ No newline at end of file |
.idea/iidp-wiki.iml
... | ... | @@ -0,0 +1,9 @@ |
1 | +<?xml version="1.0" encoding="UTF-8"?> |
|
2 | +<module type="JAVA_MODULE" version="4"> |
|
3 | + <component name="NewModuleRootManager" inherit-compiler-output="true"> |
|
4 | + <exclude-output /> |
|
5 | + <content url="file://$MODULE_DIR$" /> |
|
6 | + <orderEntry type="inheritedJdk" /> |
|
7 | + <orderEntry type="sourceFolder" forTests="false" /> |
|
8 | + </component> |
|
9 | +</module> |
|
... | ... | \ No newline at end of file |
.idea/modules.xml
... | ... | @@ -0,0 +1,8 @@ |
1 | +<?xml version="1.0" encoding="UTF-8"?> |
|
2 | +<project version="4"> |
|
3 | + <component name="ProjectModuleManager"> |
|
4 | + <modules> |
|
5 | + <module fileurl="file://$PROJECT_DIR$/.idea/iidp-wiki.iml" filepath="$PROJECT_DIR$/.idea/iidp-wiki.iml" /> |
|
6 | + </modules> |
|
7 | + </component> |
|
8 | +</project> |
|
... | ... | \ No newline at end of file |
.idea/vcs.xml
... | ... | @@ -0,0 +1,6 @@ |
1 | +<?xml version="1.0" encoding="UTF-8"?> |
|
2 | +<project version="4"> |
|
3 | + <component name="VcsDirectoryMappings"> |
|
4 | + <mapping directory="" vcs="Git" /> |
|
5 | + </component> |
|
6 | +</project> |
|
... | ... | \ No newline at end of file |
.redirects.gollum
... | ... | @@ -0,0 +1,15 @@ |
1 | +--- |
|
2 | +Home.md: 首页.md |
|
3 | +首页.md: Home.md |
|
4 | +开发手册.md: IIDP开发手册.md |
|
5 | +iidp-plugin设计与实现.md: iidp-plugin 设计与实现.md |
|
6 | +流程教学视频.md: 流程视频.md |
|
7 | +流程视频.md: 审批流视频.md |
|
8 | +审批流视频.md: workflow-video.md |
|
9 | +iidp-plugin/tutor.md: iidp-plugin/tutorials.md |
|
10 | +01.开发手册/06.常见问题QA/crud服务重写.md: 01.开发手册/06.常见问题QA/02.crud服务重写.md |
|
11 | +常见问题/日志问题:日志太多,idea异常日志被滚动覆盖.md: 常见问题/日志问题:通过将重要日志写入文件解决idea日志被覆盖的问题.md |
|
12 | +常见问题/创建表失败:Invliad-default-vlalue-for_update_date.md: 常见问题/创建表失败Invliad-default-vlalue-for_update_date.md |
|
13 | +hazelcast分布式内存同步设计与实现.md: Hazelcast分布式内存同步设计与实现.md |
|
14 | +版本发布/beta版本.md: 版本发布/前后端版本更新信息.md |
|
15 | +升级指引.md: upgrad.md |
01.\345\274\200\345\217\221\346\211\213\345\206\214/01.\346\246\202\350\277\260/01.\346\246\202\350\277\260.m.md
01.\345\274\200\345\217\221\346\211\213\345\206\214/01.\346\246\202\350\277\260/01.\346\246\202\350\277\260.md
... | ... | @@ -0,0 +1,14 @@ |
1 | + |
|
2 | +## 1、谷神工业数字平台说明 |
|
3 | + |
|
4 | +企业数字化转型,需要部署大量企业级应用,随着业务的发展,需求无法得到及时响应,大大增加了数字化转型的成本。 |
|
5 | + |
|
6 | +谷神工业数字平台基于”元模型驱动“,以类似搭积木的方式通过安装app对应用能力进行扩展;平台支持通过sdk基于原生java进行模型定义,引擎会解析定义的模型(er关系、继承与扩展关系)构建通用的模型服务能力;对于业务层的开发,只需要关注复杂业务场景的实现。 |
|
7 | + |
|
8 | +## 2、文档目的 |
|
9 | + |
|
10 | +本手册旨在通过谷神工业数字平台相关概念与开发代码示例说明,指导app开发人员基于平台进行开发、测试验证、app上架、安装、卸载。 |
|
11 | + |
|
12 | +## 3、适用对象 |
|
13 | + |
|
14 | +本手册适用于需要进行技术讲解的售前,以及产品经理、项目经理、软件架构师、开发人员。 |
|
... | ... | \ No newline at end of file |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/01.\347\216\257\345\242\203\345\207\206\345\244\207.md
... | ... | @@ -0,0 +1,41 @@ |
1 | +---
|
|
2 | +title: 环境准备
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/a3de45/
|
|
5 | +---
|
|
6 | +建议采用统一的开发工具,方便团队进行内部协作,如统一排错,标*的是强制性要求:
|
|
7 | +
|
|
8 | + (1) **开发电脑配置(建议)**
|
|
9 | +
|
|
10 | + 16G内存+ 4核+
|
|
11 | +
|
|
12 | + (2) ***JDK要求(必须)**
|
|
13 | +
|
|
14 | + 1.8 (小版本112+)
|
|
15 | +
|
|
16 | + (3) ***数据库要求(必须)**
|
|
17 | +
|
|
18 | + mysql8 (最新小版本)
|
|
19 | +
|
|
20 | + (4) **Maven版本建议(建议)**
|
|
21 | +
|
|
22 | + 3.6.3+
|
|
23 | +
|
|
24 | + (5) **开发工具(建议)**
|
|
25 | +
|
|
26 | + idea 2021、Navicat 15、SourceTree
|
|
27 | +
|
|
28 | + (6) ***谷神工业数字平台提供的jar(必须)**
|
|
29 | +
|
|
30 | +引擎jar: sie-iidp-engine-1.0-SNAPSHOT
|
|
31 | +
|
|
32 | +sdk jar: sie-iidp-sdk-2.0-SNAPSHOT
|
|
33 | +
|
|
34 | +base jar(辅助调试):
|
|
35 | +
|
|
36 | +sie-iidp-maven-plugin(打包插件)
|
|
37 | +
|
|
38 | +maven私服地址为:http://192.168.168.156:8081/repository/maven-public/
|
|
39 | +
|
|
40 | +
|
|
41 | +
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/02.\346\246\202\345\277\265\350\257\264\346\230\216.md
... | ... | @@ -0,0 +1,30 @@ |
1 | +---
|
|
2 | +title: 概念说明
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/83593f/
|
|
5 | +---
|
|
6 | +模型驱动架构(Model Driven Architecture, MDA) ,以模型为中心,使用元数据定义一个全新的模型体系,让所有模型皆可扩展,让应用能快速响应业务变化。
|
|
7 | +
|
|
8 | +### 2.2.1. **元模型**
|
|
9 | +
|
|
10 | +元模型即定义我们常见模型的模型,万物一切皆可以用元模型来定义,在谷神工业数字平台开发平台中,将元模型进行了分类:包含app元模型、实体元模型(Model)、属性元模型(Property)、服务元模型(Service)、方法元模型(Method)、事件元模型(Event)、继承元模型(Inherit)、ER关系元模型(One2Many等)、扩展元模型(ServiceExtension,ModelExtension)等,例如我们的User模型,即是Model元模型的一个实例,而name则是Property元模型的实例,User具有name属性,则通过元元模型来定义元模型之间的组合关系来构建(参考图1)。
|
|
11 | +
|
|
12 | +SDK提供的是模型定义的能力,定义之后的解析、逻辑调用则是在引擎中实现,谷神工业数字平台采用声明+运行时动态组合的方式,突破了java继承、扩展语法的限制,可以在多个java类里声明模型的扩展,通过重新构建模型继承链,实现模型纵向多继承扩展+横向组合式扩展。参考Python的MRO(Method resolve order)算法,实现继承链的解析。
|
|
13 | +
|
|
14 | +### 2.2.2. **模型容器**
|
|
15 | +
|
|
16 | +模型容器(MetaContainer)保存所有加载的元数据信息。不同租户或环境(生产、测试等)可以加载不同的元模型。
|
|
17 | +
|
|
18 | +### 2.2.3. **app**
|
|
19 | +
|
|
20 | +app在我们谷神工业数字平台中被定义为最小交付单元,它可能小到只有一个业务模型,也可能大到涵盖整个交付系统,我们建议一个产品的app尽量分为两类,一个是涵盖基本能力的base app,另外的都是extensive app,对base app进行扩展。
|
|
21 | +
|
|
22 | +每个产品我们希望是以app的方式进行扩展,例如:假设我们为100家公司提供hr app,可能每一家都有细微差别,但总有一些是共同的点,那么我们可以将某几个app共同的功能点抽离出来形成一个base app,将不同的功能点形成定制extensive app,hr的base app结合这些extensive app可以动态自由组合为每一家公司提供不同的服务能力。
|
|
23 | +
|
|
24 | +### 2.2.4. **引擎**
|
|
25 | +
|
|
26 | +谷神工业数字平台通过引擎来加载并解析sdk定义的模型,并结合数据集的能力,提供app在线构建能力、app在线安装能力、app扩展能力、基本的权限认证、基础服务以及服务api能力等等,引擎即是谷神工业数字平台的最小内核。
|
|
27 | +
|
|
28 | +### 2.2.5. **谷神工业数字平台 Java Sdk模型定义**
|
|
29 | +
|
|
30 | +谷神工业数字平台通过是谷神工业数字平台 java sdk包来支持原生java开发来对模型进行定义,主要是定义了一些注解和解释程序,将我们定义的模型、属性、服务、关系等生成满足元模型标准的Json文件,由引擎加载、解析。 |
|
... | ... | \ No newline at end of file |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/03.\345\274\200\345\217\221\346\265\201\347\250\213\350\257\264\346\230\216.md
... | ... | @@ -0,0 +1,46 @@ |
1 | +---
|
|
2 | +title: 开发流程说明
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/6a7ffc/
|
|
5 | +---
|
|
6 | +传统开发流程与谷神工业数字平台开发流程差异说明:
|
|
7 | +
|
|
8 | +### 2.3.1. **IDEA开发环境**
|
|
9 | +
|
|
10 | +首先检查我们的maven仓库私库地址是不是如下图所示:
|
|
11 | +
|
|
12 | +<mirror> <id>nexus</id> <mirrorOf>*</mirrorOf> <url>http://192.168.168.156:8081/repository/maven-public/</url></mirror>
|
|
13 | +
|
|
14 | +
|
|
15 | +
|
|
16 | +Mysql创建我们自己的库名,例如我们创建为iidp,Mysql我们建议8.0版本,如下图所示:
|
|
17 | +
|
|
18 | +
|
|
19 | +
|
|
20 | +将IDEA iidp-demo-server项目resources资源路径下的dbcp.properties文件,改为自己的库名iidp,url地址改为localhost:3306/iidp地址,username = root, password=123456
|
|
21 | +
|
|
22 | +
|
|
23 | +
|
|
24 | +### 2.3.2. **微服务开发流程**
|
|
25 | +
|
|
26 | + 新系统开发流程:领域建模 -> 服务划分 -> 建表、 建实体、service、dao -> 自定义服务开发 -> 出api清单 -> 部署、前端联调 -> 测试 -> 上线
|
|
27 | +
|
|
28 | +新需求开发流程:增加/修改原服务代码(即修改原代码的方式)
|
|
29 | +
|
|
30 | +### 2.3.3. **谷神工业数字平台开发流程**
|
|
31 | +
|
|
32 | +新系统开发流程:领域建模 -> App划分 -> 平台建模、设置种子数据(菜单、个性化视图、初始化数据) -> 自定义服务开发 -> 测试 ->上架 ->安装、卸载
|
|
33 | +
|
|
34 | +新需求开发流程:可创建一个extensive app对源app(通过构建扩展app的方式)
|
|
35 | +
|
|
36 | +### 2.3.4. **开发流程主要差异分析**
|
|
37 | +
|
|
38 | + (1) 平台不需要再建表,类似于jpa/hibernate,建完模型、后,引擎会自动建表
|
|
39 | +
|
|
40 | + (2) 平台不需要再建controller、service、dao,平台是基于模型解释运行的,会自动提供该能力
|
|
41 | +
|
|
42 | + (3) 平台不需要再出具api清单,平台会基于模型会生成标准的api清单
|
|
43 | +
|
|
44 | + (4) 常规页面平台不需要进行前端页面开发,前端渲染引擎会自动根据模型信息渲染页面;对于复杂页面平台提供规范指引开发,在保留整个平台扩展能力的前提下,简化功能开发
|
|
45 | +
|
|
46 | + (5) 平台推荐base app + 多个extensive app的方式进行扩展,不建议直接修改已上线的app代码,以安装、卸载的方式升级或降级app。
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/04.demo\345\234\272\346\231\257\350\256\276\350\256\241.md
... | ... | @@ -0,0 +1,34 @@ |
1 | +---
|
|
2 | +title: demo场景设计
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/85fd9d/
|
|
5 | +---
|
|
6 | + 本次demo app命名为:newSdkApp,主要包括三个模型,分别是角色、用户、组织:
|
|
7 | +
|
|
8 | +### 2.4.1. **模型定义**
|
|
9 | +
|
|
10 | + (1) 组织
|
|
11 | +
|
|
12 | +包括组织名称、上级组织
|
|
13 | +
|
|
14 | + (2) 角色
|
|
15 | +
|
|
16 | + 包括角色名称
|
|
17 | +
|
|
18 | + (3) 用户
|
|
19 | +
|
|
20 | + 包括用户名称、密码
|
|
21 | +
|
|
22 | +### 2.4.2. **增删改查**
|
|
23 | +
|
|
24 | + (1) 单表crud
|
|
25 | +
|
|
26 | +角色、用户、组织
|
|
27 | +
|
|
28 | + (2) 字段校验
|
|
29 | +
|
|
30 | +邮箱格式必须正确,用户密码不能为空
|
|
31 | +
|
|
32 | + (3) er关系crud
|
|
33 | +
|
|
34 | +角色关联用户、用户关联角色、用户关联组织 |
|
... | ... | \ No newline at end of file |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/05.\344\273\243\347\240\201\347\273\223\346\236\204.md
... | ... | @@ -0,0 +1,432 @@ |
1 | +---
|
|
2 | +title: 代码结构
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/944f78/
|
|
5 | +---
|
|
6 | +### 2.5.1. **新建server**
|
|
7 | +
|
|
8 | + 由于引擎是一个jar包,在开发环境中,通过一个引擎jar包来加载正在开发的apps,对于调试非常不友好,建议通过新建一个server项目依赖引擎jar包,以及必要的配置(如端口配置)来启动引擎,加载正在开发的apps进行调试。
|
|
9 | +
|
|
10 | +
|
|
11 | +
|
|
12 | +server启动时,会默认加载apps目录下在apps.json中配置的所有app jar包;iidp-demo-apps是我们需要开发的app工程,一般来说我们现在都分多个工程进行开发,可以在iidp-demo-apps下面建多个module。
|
|
13 | +
|
|
14 | +#### 2.5.1.1. **server pom配置说明**
|
|
15 | +
|
|
16 | +```xml
|
|
17 | +<?xml>
|
|
18 | + ......
|
|
19 | + <dependencies>
|
|
20 | + <!-- springboot -->
|
|
21 | + <dependency>
|
|
22 | + <groupId>org.springframework.boot</groupId>
|
|
23 | + <artifactId>spring-boot-starter-web</artifactId>
|
|
24 | + <version>2.1.8.RELEASE</version>
|
|
25 | + </dependency>
|
|
26 | + <!-- 引擎 -->
|
|
27 | + <dependency>
|
|
28 | + <groupId>com.sie.meta</groupId>
|
|
29 | + <artifactId>sie-iidp-engine</artifactId>
|
|
30 | + <version>1.0-SNAPSHOT</version>
|
|
31 | + <scope>compile</scope>
|
|
32 | + </dependency>
|
|
33 | + <!-- 脚本引擎(可选) -->
|
|
34 | + <dependency>
|
|
35 | + <groupId>com.sie.meta</groupId>
|
|
36 | + <artifactId>sie-iidp-script-engine</artifactId>
|
|
37 | + <version>1.0-SNAPSHOT</version>
|
|
38 | + </dependency>
|
|
39 | + </dependencies>
|
|
40 | + <build>
|
|
41 | + <plugins>
|
|
42 | + <plugin>
|
|
43 | + <groupId>org.springframework.boot</groupId>
|
|
44 | + <artifactId>spring-boot-maven-plugin</artifactId>
|
|
45 | + <version>2.7.2</version>
|
|
46 | + <executions>
|
|
47 | + <execution>
|
|
48 | + <goals>
|
|
49 | + <goal>repackage</goal>
|
|
50 | + </goals>
|
|
51 | + </execution>
|
|
52 | + </executions>
|
|
53 | + </plugin>
|
|
54 | + <plugin>
|
|
55 | + <groupId>org.apache.maven.plugins</groupId>
|
|
56 | + <artifactId>maven-compiler-plugin</artifactId>
|
|
57 | + <version>3.3</version>
|
|
58 | + <configuration>
|
|
59 | + <source>1.8</source>
|
|
60 | + <target>1.8</target>
|
|
61 | + <compilerArgs>
|
|
62 | + <arg>-parameters</arg>
|
|
63 | + </compilerArgs>
|
|
64 | + </configuration>
|
|
65 | + </plugin>
|
|
66 | + </plugins>
|
|
67 | + </build>
|
|
68 | +</xml>
|
|
69 | +```
|
|
70 | +
|
|
71 | +compilerArgs arg -parameters JDK1.8版本反射拿不参数名,必须配置-parameters参数才能拿到。
|
|
72 | +
|
|
73 | +#### 2.5.1.2. **config配置说明**
|
|
74 | +
|
|
75 | +数据库配置(文件dbcp.properties)如下:
|
|
76 | +
|
|
77 | +```js
|
|
78 | +########DBCP##########
|
|
79 | +driverClassName=com.mysql.cj.jdbc.Driver
|
|
80 | +#url
|
|
81 | +url=jdbc:mysql://127.0.0.1:3307/iidp?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
|
|
82 | +#url=jdbc:mysql://192.168.168.156:3306/iidp?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false
|
|
83 | +username=root
|
|
84 | +password=123456
|
|
85 | +initialSize=5
|
|
86 | +maxActive=30
|
|
87 | +minIdle=5
|
|
88 | +maxWait=6000
|
|
89 | +filters=stat
|
|
90 | +timeBetweenEvictionRunsMillis=60000
|
|
91 | +minEvictableIdleTimeMillis=300000
|
|
92 | +validationQuery=select 'x'
|
|
93 | +testOnBorrow=false
|
|
94 | +testOnReturn=false
|
|
95 | +testWhileIdle=true
|
|
96 | +poolPreparedStatements: true
|
|
97 | +maxOpenPreparedStatements: 20
|
|
98 | +
|
|
99 | +########Oracle########
|
|
100 | +#driverClassName=oracle.jdbc.OracleDriver
|
|
101 | +#url=jdbc:oracle:thin:@192.168.175.193:1521:SMOMDB
|
|
102 | +#username=sie_smom
|
|
103 | +#password=SIE_SMOM#123
|
|
104 | +#validationQuery=SELECT 'x' FROM DUAL
|
|
105 | +
|
|
106 | +```
|
|
107 | +
|
|
108 | +日志使用logback-classic,可自行配置
|
|
109 | +
|
|
110 | +引擎相关配置参数可以填写在application.properties或application-dev.properties文件中。
|
|
111 | +
|
|
112 | +#### 2.5.1.3. **apps.json配置说明**
|
|
113 | +
|
|
114 | +```json
|
|
115 | +{
|
|
116 | + "loaders": {
|
|
117 | + "API": "com.sie.iidp.engine.loaders.ApiLoader",
|
|
118 | + "SDK": "com.sie.iidp.loaders.SdkLoader"
|
|
119 | + },
|
|
120 | + "apps": {
|
|
121 | + "master": {
|
|
122 | + "SDK": {
|
|
123 | + "base": "sie-iidp-base-1.0-SNAPSHOT.jar",
|
|
124 | + “app名称: ”jar包名称”
|
|
125 | + }
|
|
126 | + }
|
|
127 | + }
|
|
128 | +}
|
|
129 | +```
|
|
130 | +
|
|
131 | +apps.json文件是引擎必须的文件,默认在${user.dir}/apps/apps.json,需要把app打包生成的jar放置在此,并按照上面的格式配置。
|
|
132 | +
|
|
133 | +sie-iidp-base-1.0-SNAPSHOT.jar由引擎提供,开发阶段可从平台处获得。
|
|
134 | +
|
|
135 | +
|
|
136 | +
|
|
137 | +#### **2.5.1.4 main方法**
|
|
138 | +
|
|
139 | +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })@ComponentScan(basePackages = "com.sie.iidp")public class Server { public static void main(String[] args) { // 启动引擎 Loader.setLoader(new BaseLoader()); Engine.start(); // 启动SpringBoot SpringApplication.run(Server.class, args); }}
|
|
140 | +
|
|
141 | +端口可自行配置,运行成功的话,可访问http://localhost:{port}/root/api/master。
|
|
142 | +
|
|
143 | +### 2.5.2. **业务app**
|
|
144 | +
|
|
145 | +server apps是最终部署的工程的apps资源仓库,即甲方最终购买的所有app资源清单,包括apps涉及的jar、json等文件。我们正在开发的所有apps下的jar也会自动copy到本目录。
|
|
146 | +
|
|
147 | +#### 2.5.2.1. **pom配置说明**
|
|
148 | +
|
|
149 | +iidp-demo-apps是iidp-demo的子模块,所有app的父模块。里面主要放置了两个重要的插件,一个是用于打包app的打包插件,另一个是编译插件。
|
|
150 | +
|
|
151 | +所有app的公共依赖项可以放置于此。
|
|
152 | +
|
|
153 | +iidp-demo-apps.pom配置如下:
|
|
154 | +
|
|
155 | +```xml
|
|
156 | +<?xml version="1.0" encoding="UTF-8"?>
|
|
157 | +<project xmlns="http://maven.apache.org/POM/4.0.0"
|
|
158 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
159 | + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
160 | + <parent>
|
|
161 | + <artifactId>iidp-demo</artifactId>
|
|
162 | + <groupId>com.sie.demo</groupId>
|
|
163 | + <version>1.0-SNAPSHOT</version>
|
|
164 | + </parent>
|
|
165 | + <modelVersion>4.0.0</modelVersion>
|
|
166 | +
|
|
167 | + <artifactId>iidp-demo-apps</artifactId>
|
|
168 | +
|
|
169 | + <packaging>pom</packaging>
|
|
170 | +
|
|
171 | + <modules>
|
|
172 | + <module>newSdkApp</module>
|
|
173 | + <module>newSdkOthers</module>
|
|
174 | + </modules>
|
|
175 | +
|
|
176 | + <dependencies>
|
|
177 | + <dependency>
|
|
178 | + <groupId>com.sie.meta</groupId>
|
|
179 | + <artifactId>sie-iidp-sdk</artifactId>
|
|
180 | + <version>2.0-SNAPSHOT</version>
|
|
181 | + <scope>compile</scope>
|
|
182 | + </dependency>
|
|
183 | +
|
|
184 | + <dependency>
|
|
185 | + <groupId>junit</groupId>
|
|
186 | + <artifactId>junit</artifactId>
|
|
187 | + <version>4.13.2</version>
|
|
188 | + <scope>test</scope>
|
|
189 | + </dependency>
|
|
190 | + </dependencies>
|
|
191 | +
|
|
192 | + <properties>
|
|
193 | + <maven.compiler.source>8</maven.compiler.source>
|
|
194 | + <maven.compiler.target>8</maven.compiler.target>
|
|
195 | + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
196 | + </properties>
|
|
197 | +
|
|
198 | + <build>
|
|
199 | + <resources>
|
|
200 | + <resource>
|
|
201 | + <directory>src/main/resources</directory>
|
|
202 | + <filtering>true</filtering>
|
|
203 | + </resource>
|
|
204 | +
|
|
205 | + <resource>
|
|
206 | + <directory>src/main/java</directory>
|
|
207 | + <includes>
|
|
208 | + <include>**/*.*</include>
|
|
209 | + </includes>
|
|
210 | + <excludes>
|
|
211 | + <exclude>**/*.java</exclude>
|
|
212 | + </excludes>
|
|
213 | + </resource>
|
|
214 | +
|
|
215 | + <resource>
|
|
216 | + <directory>src/main/resources</directory>
|
|
217 | + <filtering>true</filtering>
|
|
218 | + <targetPath>WEB-INF/classes</targetPath>
|
|
219 | + <!-- <includes>-->
|
|
220 | + <!-- <include>application-${package.environment}.yml</include>-->
|
|
221 | + <!-- </includes>-->
|
|
222 | + </resource>
|
|
223 | + </resources>
|
|
224 | + <plugins>
|
|
225 | +
|
|
226 | +<!-- 打包及清理插件 -->
|
|
227 | + <plugin>
|
|
228 | + <groupId>com.sie.meta</groupId>
|
|
229 | + <artifactId>sie-iidp-maven-plugin</artifactId>
|
|
230 | + <version>1.0-SNAPSHOT</version>
|
|
231 | + <configuration>
|
|
232 | +<!-- 生成的包将会被拷贝的目录 -->
|
|
233 | + <copyDir>${basedir}/../../apps</copyDir>
|
|
234 | + </configuration>
|
|
235 | + <executions>
|
|
236 | + <execution>
|
|
237 | + <id>package</id>
|
|
238 | + <phase>package</phase>
|
|
239 | + <goals>
|
|
240 | + <goal>repackage</goal>
|
|
241 | + </goals>
|
|
242 | + </execution>
|
|
243 | + <execution>
|
|
244 | + <id>clean</id>
|
|
245 | + <phase>clean</phase>
|
|
246 | + <goals>
|
|
247 | + <goal>repackage</goal>
|
|
248 | + </goals>
|
|
249 | + </execution>
|
|
250 | + </executions>
|
|
251 | + </plugin>
|
|
252 | +
|
|
253 | +<!-- 编译添加参数 -->
|
|
254 | + <plugin>
|
|
255 | + <groupId>org.apache.maven.plugins</groupId>
|
|
256 | + <artifactId>maven-compiler-plugin</artifactId>
|
|
257 | + <version>3.3</version>
|
|
258 | + <configuration>
|
|
259 | + <source>1.8</source>
|
|
260 | + <target>1.8</target>
|
|
261 | + <compilerArgs>
|
|
262 | + <arg>-parameters</arg>
|
|
263 | + </compilerArgs>
|
|
264 | + </configuration>
|
|
265 | + </plugin>
|
|
266 | +
|
|
267 | + </plugins>
|
|
268 | + </build>
|
|
269 | +
|
|
270 | +</project>
|
|
271 | +```
|
|
272 | +
|
|
273 | +
|
|
274 | +
|
|
275 | +app1就是一个用户自定义app,所有的用户代码、文件、配置应在此添加。借助app依赖,继承和扩展,合理组织多个app,完成共同目标。每个app.pom都需要指明自己的独有依赖项。
|
|
276 | +
|
|
277 | +sie-iidp-sdk是系统提供且必需的依赖项,也可以放置到上一层(sie-iidp-demo-apps.pom),这样就不需要每个app都单独引用了。
|
|
278 | +
|
|
279 | +
|
|
280 | +
|
|
281 | +#### 2.5.2.2. **app.json配置说明**
|
|
282 | +
|
|
283 | +app.json路径:必须位于resolved路径: com.sie.apps.qms
|
|
284 | +
|
|
285 | +
|
|
286 | +
|
|
287 | +
|
|
288 | +
|
|
289 | +app.json
|
|
290 | +
|
|
291 | +应用配置信息
|
|
292 | +
|
|
293 | + ● 应用信息
|
|
294 | +
|
|
295 | +| 属性 | 类型 | 可选值 | 备注 |
|
|
296 | +| ------------ | ------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
|
297 | +| name | String | 英文字母例如:newSdkApp | 应用名称 与apps路径下的apps.json下的SDK名称一致注意:均要保证一致meta_app应用 JAR包 apps.json清单 |
|
|
298 | +| displayName | String | 中文名称例如:测试sdk2.0 | 中文名 |
|
|
299 | +| author | String | | 作者 |
|
|
300 | +| company | String | | 公司名称 |
|
|
301 | +| category | String | | 分类 |
|
|
302 | +| categoryDesc | String | | 分类中文描述 |
|
|
303 | +| product | String | | 产品线 |
|
|
304 | +| productDesc | String | | 产品线中文描述 |
|
|
305 | +| productIcon | String | | 产品线图标 |
|
|
306 | +| description | String | | 详情描述 |
|
|
307 | +| summary | String | | 简短描述 |
|
|
308 | +| type | String | JSON/API/SDK | SDK类型,代表用Java SDK代码开发的应用API类型,GME在线建模api,可以实时JSON类型,GME导出json文件 |
|
|
309 | +| tag | String | master | 版本 |
|
|
310 | +| version | String | 0.0.1 | 应用所属版本 |
|
|
311 | +| resolved | String | com.sie.app.newsdk | 包名 |
|
|
312 | +| dependencies | Array | | 依赖项 |
|
|
313 | +| application | Boolean | true | 应用true 模组 false 暂无差异 |
|
|
314 | +| icon | String | | 图标 |
|
|
315 | +| license | | | 开源协议 |
|
|
316 | +| view | Array | views/sdk_meus.jsonviews/sdk_user_view.jsonviews/sdk_role_view.json | 视图文件,views 相对路径 |
|
|
317 | +| data | Array | data/test_user.json | 种子数据文件,data 相对于路径 |
|
|
318 | +
|
|
319 | +
|
|
320 | +
|
|
321 | +📕注意
|
|
322 | +
|
|
323 | + ● version 考虑>=
|
|
324 | +
|
|
325 | + ● tag 版本
|
|
326 | +
|
|
327 | + ● dependent 依赖项
|
|
328 | +
|
|
329 | +多应用加载规则:
|
|
330 | +
|
|
331 | +遇到多级应用依赖LIb1.0,必须要把Lib升级同一版本。
|
|
332 | +
|
|
333 | +如果是同级树根据权重加载,如果不是要先遍历最外层子树以依赖关系为准。
|
|
334 | +
|
|
335 | +Lib升级版本要考虑向下兼容,例如Lib1.0 升级Lib2.0的时候要考虑兼容Lib1.0。
|
|
336 | +
|
|
337 | +否则新的应用引入LIb2.0没办法引用其他采用Lib1.0的应用
|
|
338 | +
|
|
339 | +
|
|
340 | +
|
|
341 | +| apps | 存放app的jar包文件夹 |
|
|
342 | +| ------------------------------------- | -------------------------------- |
|
|
343 | +| apps/ sie-iidp-base-1.0-SNAPSHOT.jar | 平台提供,基础app |
|
|
344 | +| iidp-demo-apps | app的父模块,在此处开发自定义app |
|
|
345 | +| iidp-demo-server | server模块,辅助调试 |
|
|
346 | +
|
|
347 | +#### 2.5.2.3. **demo工程说明**
|
|
348 | +
|
|
349 | + 在iidp-demo-apps下可以建立module,例如demo项目newSdkApp,结构如下:
|
|
350 | +
|
|
351 | +
|
|
352 | +
|
|
353 | +
|
|
354 | +
|
|
355 | +#### 2.5.2.4. **调试说明**
|
|
356 | +
|
|
357 | +在iidp-demo-apps下,执行mvn clean package命令(也可在ide上直接点击对应按钮),该指令会自动清理并打包所有app到apps目录。
|
|
358 | +
|
|
359 | +配置${user.dir}/apps/apps.json。
|
|
360 | +
|
|
361 | +以debug模式启动iidp-demo-server里面的main方法,等待程序启动完毕。访问http://localhost:{port}/root/api/master。
|
|
362 | +
|
|
363 | +可以在打开的open-api网页里访问开发的模型的所有服务,测试功能是否正常,必要时可以设置断点。
|
|
364 | +
|
|
365 | +测试app正常运行后,就可以把app的jar包上传到应用商城了。
|
|
366 | +
|
|
367 | +### 2.5.3. **base应用说明**
|
|
368 | +
|
|
369 | +应用结构说明
|
|
370 | +
|
|
371 | + ● data 种子数据
|
|
372 | +
|
|
373 | +rbac_user.json 用户种子文件
|
|
374 | +
|
|
375 | +| 字段名 | 中文名 | 可选值 |
|
|
376 | +| ---------- | ------ | ----------------------------- |
|
|
377 | +| model | 模型名 | rbac_role |
|
|
378 | +| properties | 属性名 | {"name":"管理员""is_admin":1} |
|
|
379 | +
|
|
380 | +种子数据文件的作用是为了数据库在删除 rbac_role 这个表的时候,数据还能在恢复过来。
|
|
381 | +
|
|
382 | +示例代码:
|
|
383 | +
|
|
384 | +"rbac_role_admin": { "model": "rbac_role", "properties": { "name": "管理员", "is_admin": 1 }}
|
|
385 | +
|
|
386 | + ● jwt 认证
|
|
387 | +
|
|
388 | +JWT , 全写JSON Web Token, 是开放的行业标准RFC7591,用来实现端到端安全验证.
|
|
389 | +
|
|
390 | +简单来说, 就是通过一些算法对加密字符串和JSON对象之间进行加解密。
|
|
391 | +
|
|
392 | +JWT加密JSON,保存在客户端,不需要在服务端保存会话信息。,可以应用在前后端分离的用户验证上,后端对前端输入的用户信息进行加密产生一个令牌字符串, 前端再次请求时附加此字符串,后端再使用算法解密。
|
|
393 | +
|
|
394 | +Token存储信息
|
|
395 | +
|
|
396 | +| 字段名 | 中文名 | 备注 |
|
|
397 | +| --------- | ---------- | ------- |
|
|
398 | +| login | 登录名 | |
|
|
399 | +| userId | 用户id | |
|
|
400 | +| isAdmin | 是否管理员 | |
|
|
401 | +| date | 过期时间 | 3600 |
|
|
402 | +| algorithm | 算法 | HMAC256 |
|
|
403 | +
|
|
404 | + ● meta 模型文件
|
|
405 | +
|
|
406 | +meta_app 应用
|
|
407 | +
|
|
408 | +将系统安装应用存储在meta_app表中
|
|
409 | +
|
|
410 | +meta_model 模型
|
|
411 | +
|
|
412 | +将元模型信息存储在数据库meta_model表
|
|
413 | +
|
|
414 | +meta_model_service 服务
|
|
415 | +
|
|
416 | +将系统的所有服务存储在meta_model_service服务表
|
|
417 | +
|
|
418 | +meta_model_property 属性
|
|
419 | +
|
|
420 | + ● rbac 权限
|
|
421 | +
|
|
422 | + ● ui 菜单 界面
|
|
423 | +
|
|
424 | +ui_menu 菜单
|
|
425 | +
|
|
426 | +将应用中menu.json菜单中的文件生成到ui_menu表中
|
|
427 | +
|
|
428 | + ● version 版本
|
|
429 | +
|
|
430 | + ● views 视图文件
|
|
431 | +
|
|
432 | + ● app.json 应用信息
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/06.\346\226\260\345\273\272\346\250\241\345\236\213\345\222\214\345\261\236\346\200\247.md
... | ... | @@ -0,0 +1,292 @@ |
1 | +---
|
|
2 | +title: 新建模型和属性
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/81496a/
|
|
5 | +---
|
|
6 | +基于元模型驱动的引擎,核心在于不用理会机械的CRUD,而只需要将主要经历专注在业务领域建模上,当模型建完后,crud的能力会自动具备。以下是三个demo模型:
|
|
7 | +
|
|
8 | +#### 2.6.1.1. **组织**
|
|
9 | +
|
|
10 | + 通过@Model注解定义模型名称,@Property声明属性, @ManyToOne和@OneToMany声明er关系:
|
|
11 | +
|
|
12 | +```java
|
|
13 | +@Model(name = "TestOrg",description = "测试组织")
|
|
14 | +public class TestOrg extends BaseModel {
|
|
15 | +
|
|
16 | + @Property(displayName = "名称",displayForModel=true )
|
|
17 | + private String name;
|
|
18 | +
|
|
19 | + @ManyToOne
|
|
20 | + @JoinColumn(name = "parent_id")
|
|
21 | + private TestOrg parent;
|
|
22 | +
|
|
23 | + @OneToMany
|
|
24 | + private List<TestUser> userList;
|
|
25 | +
|
|
26 | + public TestOrg getParent() {
|
|
27 | + return (TestOrg) this.get("parent");
|
|
28 | + }
|
|
29 | +
|
|
30 | + public TestOrg setParent(TestOrg parent) {
|
|
31 | + this.set("parent", parent);
|
|
32 | + return this;
|
|
33 | + }
|
|
34 | +
|
|
35 | +
|
|
36 | + public List<TestUser> getUserList() {
|
|
37 | + return (List<TestUser>) this.get("userList");
|
|
38 | + }
|
|
39 | +
|
|
40 | + public TestOrg setUserList(List<TestUser> userList) {
|
|
41 | + this.set("userList", userList);
|
|
42 | + return this;
|
|
43 | + }
|
|
44 | +
|
|
45 | + public String getName() {
|
|
46 | + return (String) this.get("name");
|
|
47 | + }
|
|
48 | +
|
|
49 | + public TestOrg setName(String name) {
|
|
50 | + this.set("name", name);
|
|
51 | + return this;
|
|
52 | + }
|
|
53 | +}
|
|
54 | +```
|
|
55 | +
|
|
56 | +
|
|
57 | +
|
|
58 | +#### 2.6.1.2. **用户**
|
|
59 | +
|
|
60 | + 也可以只增加@Model,引擎会将类名作为模型名称以及表名。通过@Validate可以增加各类校验,get和set方法可以通过idea模板自动生成:
|
|
61 | +
|
|
62 | +```js
|
|
63 | +@Model
|
|
64 | +public class TestUser extends BaseModel {
|
|
65 | +
|
|
66 | + @Property(columnName = "name", displayName = "名称",displayForModel = true)
|
|
67 | + //@Validate.NotBlank
|
|
68 | + private String name;
|
|
69 | +
|
|
70 | + @Validate.Email(message = "邮箱格式不正确")
|
|
71 | + @Property(columnName = "email", displayName = "邮箱",dataType = DataType.TEXT)
|
|
72 | + private String email;
|
|
73 | +
|
|
74 | +
|
|
75 | + @Property(columnName = "status", displayName = "状态", dataType = DataType.ENUM, values = {
|
|
76 | + @ModelEnum(label = "启用", value = "1"),
|
|
77 | + @ModelEnum(label = "禁用", value = "0")}, defaultValue = "1")
|
|
78 | + private Boolean status;
|
|
79 | +
|
|
80 | + @Property(columnName = "phone", displayName = "电话号码")
|
|
81 | + @Validate.Phone(message = "手机号格式不正确")
|
|
82 | + private String phone;
|
|
83 | +
|
|
84 | + @Validate.Max(110)
|
|
85 | + @Property(columnName = "age", displayName = "年龄")
|
|
86 | + private Integer age;
|
|
87 | +
|
|
88 | + @Validate.NotBlank
|
|
89 | + @Property(columnName = "password", displayName = "密码")
|
|
90 | + private String password;
|
|
91 | +
|
|
92 | + @Property(columnName = "test", displayName = "测试")
|
|
93 | + private String test;
|
|
94 | +
|
|
95 | + @Property(columnName = "method", computeMethod = "method", displayName = "计算方法")
|
|
96 | + private String method;
|
|
97 | +
|
|
98 | + @Property(columnName = "script", computeScript = "3+100", displayName = "计算脚本")
|
|
99 | + private String script;
|
|
100 | +
|
|
101 | + @ManyToOne(displayName = "组织机构", cascade = {CascadeType.DEL_SET_NULL})
|
|
102 | + @JoinColumn(name = "org_id", referencedColumnName = "id")
|
|
103 | + private TestOrg org;
|
|
104 | +
|
|
105 | +
|
|
106 | + @Property(displayName = "selectStatus", defaultValue = "1")
|
|
107 | + @Selection(values = { @Option(label = "启用", value = "1"), @Option(label = "禁用", value = "2"), @Option(label = "已删除", value = "3") })
|
|
108 | + private String selectStatus;
|
|
109 | +
|
|
110 | + @Property(displayName = "selectMetod")
|
|
111 | + @Selection(method = "selectMetod")
|
|
112 | + private String selectMetod;
|
|
113 | +
|
|
114 | + @Property(displayName = "selectModel")
|
|
115 | + @Selection(model = "TestOrg", properties = "name", orderBy = "name desc")
|
|
116 | + private String selectModel;
|
|
117 | +
|
|
118 | +
|
|
119 | + public Date getCreate() {
|
|
120 | + return getDate("create");
|
|
121 | + }
|
|
122 | +
|
|
123 | + public void setCreate(Date create) {
|
|
124 | + this.set("create", create);
|
|
125 | + }
|
|
126 | +
|
|
127 | + public String getPhone() {
|
|
128 | + return getStr("phone");
|
|
129 | + }
|
|
130 | +
|
|
131 | + public void setPhone(String phone) {
|
|
132 | + this.set("phone", phone);
|
|
133 | + }
|
|
134 | +
|
|
135 | + public String getEmail() {
|
|
136 | + return getStr("email");
|
|
137 | + }
|
|
138 | +
|
|
139 | + public void setEmail(String email) {
|
|
140 | + this.set("email", email);
|
|
141 | + }
|
|
142 | +
|
|
143 | + public void setPassword(String password) {
|
|
144 | + this.set("password", password);
|
|
145 | + }
|
|
146 | +
|
|
147 | + public String getPassword() {
|
|
148 | + return getStr("password");
|
|
149 | + }
|
|
150 | +
|
|
151 | +
|
|
152 | + public String getName() {
|
|
153 | + return getStr("name");
|
|
154 | + }
|
|
155 | +
|
|
156 | + public void setName(String name) {
|
|
157 | + this.set("name", name);
|
|
158 | + }
|
|
159 | +
|
|
160 | + public Integer getAge() {
|
|
161 | + return getInt("age");
|
|
162 | + }
|
|
163 | +
|
|
164 | + public void setAge(Integer age) {
|
|
165 | + this.set("age", age);
|
|
166 | + }
|
|
167 | +
|
|
168 | + public TestOrg getOrg() {
|
|
169 | + return (TestOrg) this.get("org");
|
|
170 | + }
|
|
171 | +
|
|
172 | + public TestUser setOrg(TestOrg org) {
|
|
173 | + this.set("org", org);
|
|
174 | + return this;
|
|
175 | + }
|
|
176 | +}
|
|
177 | +```
|
|
178 | +
|
|
179 | +get模板为:
|
|
180 | +
|
|
181 | +```
|
|
182 | +#if($field.modifierStatic)
|
|
183 | +static ##
|
|
184 | +#end
|
|
185 | +$field.type ##
|
|
186 | +#if($field.recordComponent)
|
|
187 | + ${field.name}##
|
|
188 | +#else
|
|
189 | +#set($name = $StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field, $project))))
|
|
190 | +#if ($field.boolean && $field.primitive)
|
|
191 | + is##
|
|
192 | +#else
|
|
193 | + get##
|
|
194 | +#end
|
|
195 | +${name}##
|
|
196 | +#end
|
|
197 | +() {
|
|
198 | + #if($field.type == "java.util.Date")
|
|
199 | + return getDate("$field.name");
|
|
200 | + #elseif($field.type == "java.sql.Timestamp")
|
|
201 | + return getTimestamp("$field.name");
|
|
202 | + #elseif($field.type == "java.time.LocalDateTime")
|
|
203 | + return getLocalDateTime("$field.name");
|
|
204 | + #elseif($field.type == "java.sql.Time")
|
|
205 | + return getTime("$field.name");
|
|
206 | + #elseif($field.type == "java.lang.Integer")
|
|
207 | + return getInt("$field.name");
|
|
208 | + #elseif($field.type == "java.lang.Long")
|
|
209 | + return getLong("$field.name");
|
|
210 | + #elseif($field.type == "java.lang.Double")
|
|
211 | + return getDouble("$field.name");
|
|
212 | + #elseif($field.type == "java.lang.Float")
|
|
213 | + return getFloat("$field.name");
|
|
214 | + #elseif($field.type == "java.lang.Short")
|
|
215 | + return getShort("$field.name");
|
|
216 | + #elseif($field.type == "java.lang.Byte")
|
|
217 | + return getByte("$field.name");
|
|
218 | + #elseif($field.type == "java.lang.Boolean")
|
|
219 | + return getBoolean("$field.name");
|
|
220 | + #elseif($field.type == "java.lang.String")
|
|
221 | + return getStr("$field.name");
|
|
222 | + #elseif($field.type == "java.lang.Number")
|
|
223 | + return getNumber("$field.name");
|
|
224 | + #elseif($field.type == "java.math.BigDecimal")
|
|
225 | + return getBigDecimal("$field.name");
|
|
226 | + #elseif($field.type == "java.math.BigInteger")
|
|
227 | + return getBigInteger("$field.name");
|
|
228 | + #else
|
|
229 | + return ($field.type)this.get("$field.name");
|
|
230 | + #end
|
|
231 | +}
|
|
232 | +```
|
|
233 | +
|
|
234 | + set模板为:
|
|
235 | +
|
|
236 | +```
|
|
237 | +#set($paramName = $helper.getParamName($field, $project))
|
|
238 | +#if($field.modifierStatic)
|
|
239 | +static ##
|
|
240 | +#end
|
|
241 | +$classname set$StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field, $project)))($field.type $paramName) {
|
|
242 | +#if ($field.name == $paramName)
|
|
243 | + #if (!$field.modifierStatic)
|
|
244 | + this.##
|
|
245 | + #else
|
|
246 | + $classname.##
|
|
247 | + #end
|
|
248 | +#end
|
|
249 | +set("$field.name", $paramName);
|
|
250 | +return this;
|
|
251 | +}
|
|
252 | +```
|
|
253 | +
|
|
254 | +
|
|
255 | +
|
|
256 | +### 2.6.2. **属性校验说明**
|
|
257 | +
|
|
258 | + TestUser的password通过@Validate.NotBlank设置不能为空的校验 ,呈现效果:
|
|
259 | +
|
|
260 | +
|
|
261 | +
|
|
262 | + TestUser的email通过@Validate.Phone(message = "手机号格式不正确")设置手机格式的校验,呈现效果:
|
|
263 | +
|
|
264 | +
|
|
265 | +
|
|
266 | +
|
|
267 | +
|
|
268 | +### 2.6.3. **ER关系说明**
|
|
269 | +
|
|
270 | + ● OneToMany 一对多
|
|
271 | +
|
|
272 | +```java
|
|
273 | +@OneToMany
|
|
274 | +private List<TestUser> userList;
|
|
275 | +```
|
|
276 | +
|
|
277 | + ● ManyToOne 多对一
|
|
278 | +
|
|
279 | +```
|
|
280 | +@ManyToOne(displayName = "组织机构")@JoinColumn(name = "org_id", referencedColumnName = "id")
|
|
281 | +private TestOrg org;
|
|
282 | +```
|
|
283 | +
|
|
284 | + ● ManyToMany 多对多
|
|
285 | +
|
|
286 | +```
|
|
287 | +@ManyToMany
|
|
288 | +@JoinTable(name = "role_user", joinColumns = @JoinColumn(name = "role_id", nullable = false),
|
|
289 | +inverseJoinColumns = @JoinColumn(name = "user_id", nullable = false))
|
|
290 | +private List<TestUser> userList;
|
|
291 | +```
|
|
292 | +
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/07.\345\220\257\345\212\250\344\272\213\344\273\266\351\205\215\347\275\256.md
... | ... | @@ -0,0 +1,16 @@ |
1 | +---
|
|
2 | +title: 启动事件配置
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/085fdc/
|
|
5 | +---
|
|
6 | + 可以在app.info里面通过在events节点下配置startUp启动事件,指定多个启动应用时调用的模型方法:
|
|
7 | +
|
|
8 | +```
|
|
9 | +"events":{
|
|
10 | + "startUp": [
|
|
11 | + "iiot_thing_manager::startUp",
|
|
12 | + "iiot_thing_entity::load"
|
|
13 | + ]
|
|
14 | +}
|
|
15 | +```
|
|
16 | +
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/08.\351\205\215\347\275\256\347\247\215\345\255\220\346\225\260\346\215\256.md
... | ... | @@ -0,0 +1,187 @@ |
1 | +---
|
|
2 | +title: 配置种子数据
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/3fb4d1/
|
|
5 | +---
|
|
6 | +### 2.7.1. **视图种子数据**
|
|
7 | +
|
|
8 | + 包含菜单和业务模型视图,菜单即系统前端左侧展现的菜单信息;视图即每个模型的呈现方式,主要包括上下布局、Tab页、分页、按钮、检索栏等信息,平台能够以json的方式进行配置,从而达到无需前端人员也可以做出常规的页面。
|
|
9 | +
|
|
10 | +#### 2.7.1.1. **配置约束**
|
|
11 | +
|
|
12 | + 视图种子数据文件必须配置在与app.info同级的views文件夹下:
|
|
13 | +
|
|
14 | +
|
|
15 | +
|
|
16 | +并且文件名字需要在app.info中进行配置:
|
|
17 | +
|
|
18 | +
|
|
19 | +
|
|
20 | +#### 2.7.1.2. **菜单**
|
|
21 | +
|
|
22 | + 菜单以柱状结构的json构建而成,其中sequence为排序号,@ref是指会以指定的key的id存储在数据库中(id为自动生成),model为模型名称,view默认为“grid,search,form”,其中grid为查询列表、search为查询条件栏、form为查看和编辑表单。 sdk_menus.json内容明细如下:
|
|
23 | +
|
|
24 | +```json
|
|
25 | +{
|
|
26 | + "menus": {
|
|
27 | + "sdk_root_menu": {
|
|
28 | + "name": "sdk_root_menu",
|
|
29 | + "display_name": "newsdk",
|
|
30 | + "active": true,
|
|
31 | + "sequence": 10
|
|
32 | + },
|
|
33 | + "sdk_user_menu": {
|
|
34 | + "name": "sdk_test_user",
|
|
35 | + "display_name": "测试用户",
|
|
36 | + "model": "TestUser",
|
|
37 | + "view": "grid,search,form",
|
|
38 | + "sequence": 1,
|
|
39 | + "active": true,
|
|
40 | + "parent_ids": {
|
|
41 | + "@ref": "sdk_root_menu"
|
|
42 | + }
|
|
43 | + },
|
|
44 | + "sdk_role_menu": {
|
|
45 | + "name": "sdk_test_role",
|
|
46 | + "display_name": "测试角色",
|
|
47 | + "model": "TestRole",
|
|
48 | + "view": "grid,search,form",
|
|
49 | + "sequence": 2,
|
|
50 | + "active": true,
|
|
51 | + "parent_ids": {
|
|
52 | + "@ref": "sdk_root_menu"
|
|
53 | + }
|
|
54 | + },
|
|
55 | + "sdk_org_menu": {
|
|
56 | + "name": "sdk_test_org",
|
|
57 | + "display_name": "测试组织",
|
|
58 | + "model": "TestOrg",
|
|
59 | + "view": "grid,search,form",
|
|
60 | + "sequence": 3,
|
|
61 | + "active": true,
|
|
62 | + "parent_ids": {
|
|
63 | + "@ref": "sdk_root_menu"
|
|
64 | + }
|
|
65 | + }
|
|
66 | + }
|
|
67 | +}
|
|
68 | +```
|
|
69 | +
|
|
70 | +
|
|
71 | +
|
|
72 | +#### 2.7.1.3. **视图文件**
|
|
73 | +
|
|
74 | + 组织、用户、角色的视图为常规视图,不需要手工编写,如果需要做自定义条件,可参考菜单种子数据,如组织机构视图,在views包下新增sdk_org_view.json文件,并在app.info中注册该文件,组织机构默认视图文件为:
|
|
75 | +
|
|
76 | +```json
|
|
77 | +{
|
|
78 | + "views": {
|
|
79 | + "TestOrg_grid": {
|
|
80 | + "mode": "primary",
|
|
81 | + "name": "TestOrg表格",
|
|
82 | + "model": "TestOrg",
|
|
83 | + "type": "grid",
|
|
84 | + "body": {
|
|
85 | + "buttons": [
|
|
86 | + "@defaults"
|
|
87 | + ],
|
|
88 | + "columns": [
|
|
89 | + "name",
|
|
90 | + "parent"
|
|
91 | + ],
|
|
92 | + "tbar": [
|
|
93 | + "@defaults"
|
|
94 | + ],
|
|
95 | + "type": "grid"
|
|
96 | + }
|
|
97 | + },
|
|
98 | + "TestOrg_form": {
|
|
99 | + "mode": "primary",
|
|
100 | + "name": "TestOrg表单",
|
|
101 | + "model": "TestOrg",
|
|
102 | + "type": "form",
|
|
103 | + "body": {
|
|
104 | + "buttons": [
|
|
105 | + "@defaults"
|
|
106 | + ],
|
|
107 | + "tabs": [
|
|
108 | + {
|
|
109 | + "header": "用户",
|
|
110 | + "rowspan": 3,
|
|
111 | + "body": {
|
|
112 | + "type": "grid,form",
|
|
113 | + "field": "userList",
|
|
114 | + "columns": [
|
|
115 | + "name"
|
|
116 | + ]
|
|
117 | + }
|
|
118 | + }
|
|
119 | + ],
|
|
120 | + "columns": [
|
|
121 | + "name",
|
|
122 | + "parent"
|
|
123 | + ],
|
|
124 | + "tbar": [
|
|
125 | + {
|
|
126 | + "name":"添加",
|
|
127 | + "action":"addEr",
|
|
128 | + "icon":"add"
|
|
129 | + },
|
|
130 | + {"name":"删除",
|
|
131 | + "action":"deleteEr"
|
|
132 | + }
|
|
133 | + ],
|
|
134 | + "type": "grid"
|
|
135 | + }
|
|
136 | + },
|
|
137 | + "TestOrg_search": {
|
|
138 | + "mode": "primary",
|
|
139 | + "name": "TestOrg搜索",
|
|
140 | + "model": "TestOrg",
|
|
141 | + "type": "search",
|
|
142 | + "body": {
|
|
143 | + "columns": [
|
|
144 | + "name"
|
|
145 | + ],
|
|
146 | + "type": "search"
|
|
147 | + }
|
|
148 | + }
|
|
149 | + }
|
|
150 | +}
|
|
151 | +
|
|
152 | +```
|
|
153 | +
|
|
154 | +
|
|
155 | +
|
|
156 | +### 2.7.2. **业务种子数据**
|
|
157 | +
|
|
158 | +如果需要app启动后自动加载一些初始化数据、或测试数据,可以通过增加业务种子数据达成目的。本例中,我们需要应用启动后自动增加一条名为"sie"的用户:
|
|
159 | +
|
|
160 | +#### 2.7.2.1. **配置约束**
|
|
161 | +
|
|
162 | +业务种子数据文件必须配置在与app.info同级的data文件夹下:
|
|
163 | +
|
|
164 | +
|
|
165 | +
|
|
166 | +并将文件名称在app.info中注册:
|
|
167 | +
|
|
168 | +
|
|
169 | +
|
|
170 | +#### 2.7.2.2. **业务种子数据文件**
|
|
171 | +
|
|
172 | +test_user.json中,通过model指定模型名称,通过properties指定新增数据的属性值,详细内容如下:
|
|
173 | +
|
|
174 | +```
|
|
175 | +{
|
|
176 | + "data": {
|
|
177 | + "rbac_role_admin": {
|
|
178 | + "model": "TestUser",
|
|
179 | + "properties": {
|
|
180 | + "name": "sie"
|
|
181 | + }
|
|
182 | + }
|
|
183 | + }
|
|
184 | +}
|
|
185 | +
|
|
186 | +```
|
|
187 | +
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/09.\345\220\257\345\212\250server\345\271\266\347\231\273\345\275\225.md
... | ... | @@ -0,0 +1,26 @@ |
1 | +---
|
|
2 | +title: 启动server并登录
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/d73898/
|
|
5 | +---
|
|
6 | +部署好前端渲染引擎后,修改配置指定后端地址为我们本机的server地址;通过Maven插件package后,启动server即可正常登录并访问应用。效果参考下图:
|
|
7 | +
|
|
8 | +### 2.8.1. **登陆并打开应用市场**
|
|
9 | +
|
|
10 | +
|
|
11 | +
|
|
12 | +### 2.8.2. **上架应用**
|
|
13 | +
|
|
14 | + 上传我们开发的jar包进行应用上架
|
|
15 | +
|
|
16 | +
|
|
17 | +
|
|
18 | +### 2.8.3. **安装应用**
|
|
19 | +
|
|
20 | +
|
|
21 | +
|
|
22 | +### 2.8.4. **效果查看**
|
|
23 | +
|
|
24 | +
|
|
25 | +
|
|
26 | +接下来就可以操作增删改查了,顺便可以通过F12查看我们的后台api。
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_174857_Nwmsn3qDYtdCAuWy_1672383326.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_174857_Nwmsn3qDYtdCAuWy_1672383326.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_24249_4VqgsiaLQPJJihwj_1672383968w=1280&h=273.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_24249_4VqgsiaLQPJJihwj_1672383968w=1280&h=273.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_566526_T7xPlCNMYko_d7bx_1672383887w=1280&h=647.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_566526_T7xPlCNMYko_d7bx_1672383887w=1280&h=647.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_622945_zN9R0Rg0h85FbSCo_1672379476w=1280&h=414.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_622945_zN9R0Rg0h85FbSCo_1672379476w=1280&h=414.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_633679_tHXBuGttoeRrJQrV_1672381187.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_633679_tHXBuGttoeRrJQrV_1672381187.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_707527_QUEYKsDbMw8lddKj_1672381143.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_707527_QUEYKsDbMw8lddKj_1672381143.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_741522_yvNZnpYufycL5nrh_1672383741w=1280&h=625.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_741522_yvNZnpYufycL5nrh_1672383741w=1280&h=625.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_861905_B1WZZPeB5n82aRJj_1672383351.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_861905_B1WZZPeB5n82aRJj_1672383351.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_935953_uF69TGR02M2xDXJr_1672379524w=1280&h=119.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_935953_uF69TGR02M2xDXJr_1672379524w=1280&h=119.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_95145_2-ny1r4XSeGyQglN_1672380830.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_95145_2-ny1r4XSeGyQglN_1672380830.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_964603_wfon-VgSfQ6GGHcD_1672384019w=1280&h=330.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDExNTYwMTY5MjA_964603_wfon-VgSfQ6GGHcD_1672384019w=1280&h=330.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDMwNDMxMjA1MTQ_769720_WiwuW6ZChFpNuvgF_1672718142w=1280&h=583.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDMwNDMxMjA1MTQ_769720_WiwuW6ZChFpNuvgF_1672718142w=1280&h=583.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDMwNDMxMjA1MTQ_895868_Bas-UY2qx9IP_eh__1672717798w=1280&h=475-1674994709695-3.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDMwNDMxMjA1MTQ_895868_Bas-UY2qx9IP_eh__1672717798w=1280&h=475-1674994709695-3.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDMwNDMxMjA1MTQ_895868_Bas-UY2qx9IP_eh__1672717798w=1280&h=475.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTMxMDI3MDMwNDMxMjA1MTQ_895868_Bas-UY2qx9IP_eh__1672717798w=1280&h=475.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTY4ODg1NzU0NjMyMjY3Mw_223482_WtefS9JuEMu-t79j_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTY4ODg1NzU0NjMyMjY3Mw_223482_WtefS9JuEMu-t79j_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTY4ODg1NzU0NjMyMjY3Mw_224121_xT6GpOEWFHvEMdMj_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/02.\345\277\253\351\200\237\344\270\212\346\211\213/images/MTY4ODg1NzU0NjMyMjY3Mw_224121_xT6GpOEWFHvEMdMj_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/01.\345\210\233\345\273\272\346\250\241\345\236\213.md
... | ... | @@ -0,0 +1,718 @@ |
1 | +---
|
|
2 | +title: 创建模型
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/0dc273/
|
|
5 | +---
|
|
6 | +# 3.1. **创建模型**
|
|
7 | +
|
|
8 | +BaseModel是业务模型的基类所有业务模型继承该模型
|
|
9 | +
|
|
10 | +## 3.1.1. **通过@Model注解声明模型**
|
|
11 | +
|
|
12 | +@Model参数说明
|
|
13 | +
|
|
14 | +| 参数 | 说明 | 类型 | 默认值 |
|
|
15 | +| ----------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -------------- |
|
|
16 | +| name | 模型的名称,与inherit应至少指定一个 | String | 默认为类名 |
|
|
17 | +| type | 模型类型 | Define:定义模型Buss:业务模型Memory:内存模型Data:瞬态模型Cache:缓存模型Config:配置模型 | |
|
|
18 | +| description | 模型的说明 | String | |
|
|
19 | +| parent | 继承的模型,与name应至少指定一个如果name已设置,则表示要继承的模型的名称如果name未设置,则表示扩展的单个模型的名称 | String[] | |
|
|
20 | +| tableName | 模型的表名 | String | 默认为类名小写 |
|
|
21 | +| isAbstract | 是否抽象模型,抽象模型不生成数据库 | Bool | |
|
|
22 | +| orderBy | 默认排序SQL,如:name asc | String | |
|
|
23 | +| files | 配置模型对应的文件 文件可以不填后缀名,则以此搜索 .properties .json .yml | String[] | |
|
|
24 | +| isAutoLog | 是否自动生成创建人、创建时间、修改人、修改时间字段 | Bool | false |
|
|
25 | +
|
|
26 | +### 3.1.2. **通过extends BaseModel标识模型具备元模型能力**
|
|
27 | +
|
|
28 | +BaseModel提供了操作模型的通用接口,包括如下:
|
|
29 | +
|
|
30 | +| 接口名 | 接口描述 | 入参 | 返回值 |
|
|
31 | +| ---------- | ---------- | ------------------------------------------------------------ | ------- |
|
|
32 | +| create | 新增 | 无 | T |
|
|
33 | +| delete | 删除 | 无 | void |
|
|
34 | +| count | 统计条数 | Filter filter | void |
|
|
35 | +| search | 查询 | Filter filter, List`<String>` properties, Integer limit, Integer offset, String order | List`<T>` |
|
|
36 | +| selectById | 根据id查询 | String id | T |
|
|
37 | +| update | 更新 | update | void |
|
|
38 | +
|
|
39 | +
|
|
40 | +
|
|
41 | +### 3.1.3. **内置模型类型**
|
|
42 | +
|
|
43 | +开发者在内置模型的基础上自定义模型。内置模型按类型提供了具有不同特色的功能,但都具有模型的所有基本功能,即提供增删改查服务,继承扩展等。
|
|
44 | +
|
|
45 | +#### 3.1.3.1. **业务模型**
|
|
46 | +
|
|
47 | +默认选项。
|
|
48 | +
|
|
49 | + type = Model.ModelType.Buss
|
|
50 | +
|
|
51 | +默认持久化到数据库,支持一对多、多对多和多对一关系,支持所有属性类型。
|
|
52 | +
|
|
53 | +业务模型是功能强大丰富的模型类型,适合建立各种常规模型。
|
|
54 | +
|
|
55 | +#### 3.1.3.2. **数据模型**
|
|
56 | +
|
|
57 | + type = Model.ModelType.Data
|
|
58 | +
|
|
59 | +又称为瞬时模型。不持久化,当会话结束时自动销毁。不适合大量数据。
|
|
60 | +
|
|
61 | +数据模型建立方便,增删改查消耗少,速度快。
|
|
62 | +
|
|
63 | +#### 3.1.3.3. **内存模型**
|
|
64 | +
|
|
65 | + type = Model.ModelType.Memory
|
|
66 | +
|
|
67 | +内存模型是带有持久化功能的数据模型。可以更新到数据库,查询则只通过内存查询。
|
|
68 | +
|
|
69 | +#### 3.1.3.4. **树状模型**
|
|
70 | +
|
|
71 | +type = Model.ModelType.Tree
|
|
72 | +
|
|
73 | +树状模型以树形结构存储数据,适合非关系型数据结构。
|
|
74 | +
|
|
75 | +#### 3.1.3.5. **配置模型**
|
|
76 | +
|
|
77 | +type = Model.ModelType.Config
|
|
78 | +
|
|
79 | +配置模型可以读取本地文件,同步远程配置中心数据(比如阿波罗配置中心,nacos)
|
|
80 | +
|
|
81 | +
|
|
82 | +
|
|
83 | +### 3.1.4. **模型的继承和扩展**
|
|
84 | +
|
|
85 | +系统提供了以模块化的方式来继承或扩展模型:
|
|
86 | +
|
|
87 | + ● 子模型继承父模型,子模型会拥有父模型所有的属性和服务
|
|
88 | +
|
|
89 | + ● 扩展已定义的模型,为已有模型增加能力,或改变已有模型的现有能力
|
|
90 | +
|
|
91 | +#### 3.1.4.1. **继承**
|
|
92 | +
|
|
93 | +通过@Model指定name和parent,当name和parent不一致时为模型继承关系,parent是父模型名称,name是子模型的名称。新模型将继承原模型的所有字段、方法和服务。示例如下:
|
|
94 | +
|
|
95 | +@Model(name = "a_model",parent = "b_model")public class AModel extends TestUser{}
|
|
96 | +
|
|
97 | +继承服务移除:子模型继承了父模型,但是不想具备父模型中个别的服务,这时需要用到服务移除@Service(remove=[“serviceName1”,”serviceName2”]
|
|
98 | +
|
|
99 | +#### 3.1.4.2. **扩展**
|
|
100 | +
|
|
101 | + 通过@Model指定name和parent,当name和parent一致时为模型扩展关系。扩展已定义的模型,为已有模型增加能力,或改变已有模型的现有能力,示例如下:
|
|
102 | +
|
|
103 | +@Model(name = "TestUser",parent = "TestUser")
|
|
104 | +
|
|
105 | +public class TestUserExt extends TestUser{}
|
|
106 | +
|
|
107 | +#### 3.1.4.3. **多继承**
|
|
108 | +
|
|
109 | + @Model 中parent是个数组,可以通过设置多个parent,表示多继承,该模型会自动继承多个父模型。
|
|
110 | +
|
|
111 | +### 3.1.5. **关联模型维护**
|
|
112 | +关联模型维护是指在维护一个模型时,同时对其关联的模型进行操作,简单的说就是通过一个API接口同时操作有ER关系的多个模型CRUD。
|
|
113 | +
|
|
114 | +#### 3.1.5.1. **指令集**
|
|
115 | +
|
|
116 | +**指令集: OneToMany and ManyTomany级联操作使用特殊的指令集**
|
|
117 | +
|
|
118 | +OneToMany 和 ManyToMany 使用特殊的 “命令” 格式来操纵存储在字段中/与字段相关联的记录集。
|
|
119 | +
|
|
120 | +此格式是按顺序执行的三元组列表,其中每个三元组是在记录集上执行的命令。 并非所有命令都适用于所有情况。 可能的命令是:
|
|
121 | +
|
|
122 | +
|
|
123 | +
|
|
124 | +ManyToMany
|
|
125 | +
|
|
126 | +(0,0,{values}) 根据values里面的信息新建一个记录。
|
|
127 | +
|
|
128 | +(1,ID,{values})更新id=ID的记录(写入values里面的数据)
|
|
129 | +
|
|
130 | +(2,ID) 删除id=ID的数据(调用unlink方法,删除数据以及整个主从数据链接关系)
|
|
131 | +
|
|
132 | +(3,ID) 切断主从数据的链接关系但是不删除这个数据
|
|
133 | +
|
|
134 | +(4,ID) 为id=ID的数据添加主从链接关系。
|
|
135 | +
|
|
136 | +(5) 删除所有的从数据的链接关系就是向所有的从数据调用(3,ID)
|
|
137 | +
|
|
138 | +(6,0,[IDs]) 用IDs里面的记录替换原来的记录(就是先执行(5)再执行循环IDs执行(4,ID))
|
|
139 | +
|
|
140 | +例子[(6, 0, [8, 5, 6, 4])] 设置 ManyTomany to ids [8, 5, 6, 4]
|
|
141 | +
|
|
142 | +OneToMany
|
|
143 | +
|
|
144 | +(0, 0,{ values })根据values里面的信息新建一个记录。
|
|
145 | +
|
|
146 | +(1,ID,{values}) 更新id=ID的记录(对id=ID的执行write 写入values里面的数据)
|
|
147 | +
|
|
148 | +(2,ID) 删除id=ID的数据(调用unlink方法,删除数据以及整个主从数据链接关系)
|
|
149 | +
|
|
150 | +
|
|
151 | +
|
|
152 | +#### 3.1.5.2. **一对多创建:创建模型和添加属性**
|
|
153 | +
|
|
154 | +**使用指令(0, 0, values)**
|
|
155 | +
|
|
156 | +添加从提供的值 values创建的新记录。
|
|
157 | +
|
|
158 | +```json
|
|
159 | +{
|
|
160 | + "id": "guid",
|
|
161 | + "jsonrpc": "2.0",
|
|
162 | + "method": "service",
|
|
163 | + "params": {
|
|
164 | + "args": {
|
|
165 | + "valuesList": [
|
|
166 | + {
|
|
167 | + "tag": "master",
|
|
168 | + "source": "manual",
|
|
169 | + "is_abstract": false,
|
|
170 | + "auto_log": false,
|
|
171 | + "name": "iot_device",
|
|
172 | + "display_name": "iot_device",
|
|
173 | + "table_name": "iot_device",
|
|
174 | + "description": "设备",
|
|
175 | + "app_ids": "021r397wz5fcw",
|
|
176 | + "property_ids": [
|
|
177 | + [
|
|
178 | + 0,
|
|
179 | + 0,
|
|
180 | + {
|
|
181 | + "name": "status",
|
|
182 | + "data_type": "Integer",
|
|
183 | + "display_name": "状态",
|
|
184 | + "description": null,
|
|
185 | + "display": true,
|
|
186 | + "source": "manual",
|
|
187 | + "default_value": null,
|
|
188 | + "length": null,
|
|
189 | + "display_for_model": false,
|
|
190 | + "required": false,
|
|
191 | + "read_only": false,
|
|
192 | + "unique": false,
|
|
193 | + "store": true,
|
|
194 | + "db_index": false,
|
|
195 | + "property_type": "Normal",
|
|
196 | + "compute_script": null
|
|
197 | + }
|
|
198 | + ],
|
|
199 | + [
|
|
200 | + 0,
|
|
201 | + 0,
|
|
202 | + {
|
|
203 | + "name": "name",
|
|
204 | + "data_type": "String",
|
|
205 | + "display_name": "设备名称",
|
|
206 | + "description": "设备名称",
|
|
207 | + "display": true,
|
|
208 | + "source": "manual",
|
|
209 | + "default_value": null,
|
|
210 | + "length": null,
|
|
211 | + "display_for_model": false,
|
|
212 | + "required": false,
|
|
213 | + "read_only": false,
|
|
214 | + "unique": false,
|
|
215 | + "store": true,
|
|
216 | + "db_index": false,
|
|
217 | + "property_type": "Normal",
|
|
218 | + "compute_script": null
|
|
219 | + }
|
|
220 | + ]
|
|
221 | + ]
|
|
222 | + }
|
|
223 | + ]
|
|
224 | + },
|
|
225 | + "context": {
|
|
226 | + "uid": "",
|
|
227 | + "lang": "zh_CN"
|
|
228 | + },
|
|
229 | + "model": "meta_model",
|
|
230 | + "tag": "master",
|
|
231 | + "service": "create"
|
|
232 | + }
|
|
233 | +}
|
|
234 | +```
|
|
235 | +
|
|
236 | +
|
|
237 | +
|
|
238 | +
|
|
239 | +
|
|
240 | +#### 3.1.5.3. **一对多删除**
|
|
241 | +
|
|
242 | +**使用指令集: (2, id, 0)**
|
|
243 | +
|
|
244 | +从集合中删除 id id 的记录,然后删除它(从数据库中)。 不能在 create中使用。
|
|
245 | +
|
|
246 | +```json
|
|
247 | +{
|
|
248 | + "id": "guid",
|
|
249 | + "jsonrpc": "2.0",
|
|
250 | + "method": "service",
|
|
251 | + "params": {
|
|
252 | + "args": {
|
|
253 | + "values": {
|
|
254 | + "property_ids": [
|
|
255 | + [
|
|
256 | + 2,
|
|
257 | + "022568sr2jbpc",
|
|
258 | + 0
|
|
259 | + ]
|
|
260 | + ]
|
|
261 | + },
|
|
262 | + "ids": [
|
|
263 | + "022568sqik4xs"
|
|
264 | + ]
|
|
265 | + },
|
|
266 | + "context": {
|
|
267 | + "uid": "",
|
|
268 | + "lang": "zh_CN"
|
|
269 | + },
|
|
270 | + "model": "meta_model",
|
|
271 | + "tag": "master",
|
|
272 | + "service": "update"
|
|
273 | + }
|
|
274 | +}
|
|
275 | +```
|
|
276 | +
|
|
277 | +
|
|
278 | +
|
|
279 | +#### 3.1.5.4. **一对多修改子表**
|
|
280 | +
|
|
281 | +**使用指令集 (1, id, values)**
|
|
282 | +
|
|
283 | +使用值中的值更新 id id的现有记录。 不能在create中使用
|
|
284 | +
|
|
285 | +```json
|
|
286 | +{
|
|
287 | + "id": "guid",
|
|
288 | + "jsonrpc": "2.0",
|
|
289 | + "method": "service",
|
|
290 | + "params": {
|
|
291 | + "args": {
|
|
292 | + "values": {
|
|
293 | + "app_ids": "021r397wz5fcw",
|
|
294 | + "description": "设备",
|
|
295 | + "source": "manual",
|
|
296 | + "display_name": "iot_device",
|
|
297 | + "table_name": "iot_device",
|
|
298 | + "is_abstract": false,
|
|
299 | + "auto_log": false,
|
|
300 | + "property_ids": [
|
|
301 | + [
|
|
302 | + 1,
|
|
303 | + "022568sr2jbpc",
|
|
304 | + {
|
|
305 | + "name": "ip",
|
|
306 | + "data_type": "String",
|
|
307 | + "display_name": "ip",
|
|
308 | + "description": null,
|
|
309 | + "display": true,
|
|
310 | + "source": "manual",
|
|
311 | + "default_value": null,
|
|
312 | + "length": null,
|
|
313 | + "display_for_model": false,
|
|
314 | + "required": false,
|
|
315 | + "read_only": false,
|
|
316 | + "unique": false,
|
|
317 | + "store": true,
|
|
318 | + "db_index": false,
|
|
319 | + "property_type": "Normal",
|
|
320 | + "compute_script": null
|
|
321 | + }
|
|
322 | + ]
|
|
323 | + ],
|
|
324 | + "id": "022568sqik4xs"
|
|
325 | + },
|
|
326 | + "ids": [
|
|
327 | + "022568sqik4xs"
|
|
328 | + ]
|
|
329 | + },
|
|
330 | + "context": {
|
|
331 | + "uid": "",
|
|
332 | + "lang": "zh_CN"
|
|
333 | + },
|
|
334 | + "model": "meta_model",
|
|
335 | + "tag": "master",
|
|
336 | + "service": "update"
|
|
337 | + }
|
|
338 | +}
|
|
339 | +```
|
|
340 | +
|
|
341 | +
|
|
342 | +
|
|
343 | +#### 3.1.5.5. **一对多添加-模型添加属性**
|
|
344 | +
|
|
345 | +**使用指令(0, 0, values)**
|
|
346 | +
|
|
347 | +
|
|
348 | +
|
|
349 | +{ "id": "guid", "jsonrp
|
|
350 | +
|
|
351 | +```json
|
|
352 | +{
|
|
353 | + "id": "guid",
|
|
354 | + "jsonrpc": "2.0",
|
|
355 | + "method": "service",
|
|
356 | + "params": {
|
|
357 | + "args": {
|
|
358 | + "values": {
|
|
359 | + "app_ids": "021r397wz5fcw",
|
|
360 | + "description": "设备",
|
|
361 | + "source": "manual",
|
|
362 | + "display_name": "iot_device",
|
|
363 | + "table_name": "iot_device",
|
|
364 | + "is_abstract": false,
|
|
365 | + "auto_log": false,
|
|
366 | + "property_ids": [
|
|
367 | + [
|
|
368 | + 0,
|
|
369 | + 0,
|
|
370 | + {
|
|
371 | + "name": "ip",
|
|
372 | + "data_type": "String",
|
|
373 | + "display_name": "ip",
|
|
374 | + "description": null,
|
|
375 | + "display": true,
|
|
376 | + "source": "manual",
|
|
377 | + "default_value": null,
|
|
378 | + "length": null,
|
|
379 | + "display_for_model": false,
|
|
380 | + "required": false,
|
|
381 | + "read_only": false,
|
|
382 | + "unique": false,
|
|
383 | + "store": true,
|
|
384 | + "db_index": false,
|
|
385 | + "property_type": "Normal",
|
|
386 | + "compute_script": null
|
|
387 | + }
|
|
388 | + ]
|
|
389 | + ],
|
|
390 | + "name": "iot_device",
|
|
391 | + "id": "02254bc01kz5s",
|
|
392 | + "tag": "master"
|
|
393 | + },
|
|
394 | + "ids": [
|
|
395 | + "02254bc01kz5s"
|
|
396 | + ]
|
|
397 | + },
|
|
398 | + "context": {
|
|
399 | + "uid": "",
|
|
400 | + "lang": "zh_CN"
|
|
401 | + },
|
|
402 | + "model": "meta_model",
|
|
403 | + "tag": "master",
|
|
404 | + "service": "update"
|
|
405 | + }
|
|
406 | +}
|
|
407 | +```
|
|
408 | +
|
|
409 | +
|
|
410 | +
|
|
411 | +
|
|
412 | +
|
|
413 | +#### 3.1.5.6. **一对多查询**
|
|
414 | +
|
|
415 | +模型-属性:
|
|
416 | +
|
|
417 | +第一步:根据模型查询模型所有属性,property_ids返回所有的属性id列表
|
|
418 | +
|
|
419 | +```json
|
|
420 | +{
|
|
421 | + "id": "guid",
|
|
422 | + "jsonrpc": "2.0",
|
|
423 | + "method": "service",
|
|
424 | + "params": {
|
|
425 | + "args": {
|
|
426 | + "filter": [
|
|
427 | + [
|
|
428 | + "name",
|
|
429 | + "=",
|
|
430 | + "meta_app"
|
|
431 | + ]
|
|
432 | + ],
|
|
433 | + "offset": 0,
|
|
434 | + "limit": 25,
|
|
435 | + "properties": [
|
|
436 | + "name",
|
|
437 | + "display_name",
|
|
438 | + "table_name",
|
|
439 | + "description",
|
|
440 | + "parents",
|
|
441 | + "tag",
|
|
442 | + "source",
|
|
443 | + "app_ids",
|
|
444 | + "is_abstract",
|
|
445 | + "auto_log",
|
|
446 | + "order_by",
|
|
447 | + "display_format",
|
|
448 | + "property_ids"
|
|
449 | + ]
|
|
450 | + },
|
|
451 | + "context": {
|
|
452 | + "uid": "",
|
|
453 | + "lang": "zh_CN"
|
|
454 | + },
|
|
455 | + "model": "meta_model",
|
|
456 | + "tag": "master",
|
|
457 | + "service": "search"
|
|
458 | + }
|
|
459 | +}
|
|
460 | +```
|
|
461 | +
|
|
462 | +第二步:返回的,property_ids列表查询所有的属性
|
|
463 | +
|
|
464 | +```json
|
|
465 | +{
|
|
466 | + "id": "guid",
|
|
467 | + "jsonrpc": "2.0",
|
|
468 | + "method": "service",
|
|
469 | + "params": {
|
|
470 | + "args": {
|
|
471 | + "filter": [
|
|
472 | + [
|
|
473 | + "id",
|
|
474 | + "in",
|
|
475 | + [
|
|
476 | + "0224zv0atrmkg",
|
|
477 | + "0224zv09ftnnk",
|
|
478 | + "0224zv0bdqtc0",
|
|
479 | + "0224zv0b19bls",
|
|
480 | + "0224zv0bnqeps",
|
|
481 | + "0224zv09nbcow"
|
|
482 | + ]
|
|
483 | + ]
|
|
484 | + ],
|
|
485 | + "offset": 0,
|
|
486 | + "limit": 0,
|
|
487 | + "properties": [
|
|
488 | + "name",
|
|
489 | + "display_name",
|
|
490 | + "data_type",
|
|
491 | + "required",
|
|
492 | + "read_only",
|
|
493 | + "store",
|
|
494 | + "db_index"
|
|
495 | + ],
|
|
496 | + "order": ""
|
|
497 | + },
|
|
498 | + "context": {
|
|
499 | + "uid": "",
|
|
500 | + "lang": "zh_CN"
|
|
501 | + },
|
|
502 | + "model": "meta_model_property",
|
|
503 | + "tag": "master",
|
|
504 | + "service": "search"
|
|
505 | + }
|
|
506 | +}
|
|
507 | +```
|
|
508 | +
|
|
509 | +
|
|
510 | +
|
|
511 | +#### 3.1.5.7. **多对多创建**
|
|
512 | +
|
|
513 | +```json
|
|
514 | +{
|
|
515 | + "id": "guid",
|
|
516 | + "jsonrpc": "2.0",
|
|
517 | + "method": "service",
|
|
518 | + "params": {
|
|
519 | + "args": {
|
|
520 | + "valuesList": [
|
|
521 | + {
|
|
522 | + "name": "开发者",
|
|
523 | + "is_admin": false,
|
|
524 | + "user_ids": [
|
|
525 | + [
|
|
526 | + 4,
|
|
527 | + "02gonoedj1a0x",
|
|
528 | + 0
|
|
529 | + ]
|
|
530 | + ]
|
|
531 | + }
|
|
532 | + ]
|
|
533 | + },
|
|
534 | + "context": {
|
|
535 | + "uid": "",
|
|
536 | + "lang": "zh_CN"
|
|
537 | + },
|
|
538 | + "model": "rbac_role",
|
|
539 | + "tag": "master",
|
|
540 | + "service": "create",
|
|
541 | + "app": "base"
|
|
542 | + }
|
|
543 | +}
|
|
544 | +```
|
|
545 | +
|
|
546 | +
|
|
547 | +
|
|
548 | +#### 3.1.5.8. **多对多删除**
|
|
549 | +
|
|
550 | +```json
|
|
551 | +{
|
|
552 | + "id": "guid",
|
|
553 | + "jsonrpc": "2.0",
|
|
554 | + "method": "service",
|
|
555 | + "params": {
|
|
556 | + "args": {
|
|
557 | + "values": {
|
|
558 | + "is_admin": false,
|
|
559 | + "name": "普通用户",
|
|
560 | + "id": "02gonoe8t86ip",
|
|
561 | + "user_ids": [
|
|
562 | + [
|
|
563 | + 3,
|
|
564 | + "02gonoedbjkzk",
|
|
565 | + 0
|
|
566 | + ]
|
|
567 | + ]
|
|
568 | + },
|
|
569 | + "ids": [
|
|
570 | + "02gonoe8t86ip"
|
|
571 | + ]
|
|
572 | + },
|
|
573 | + "context": {
|
|
574 | + "uid": "",
|
|
575 | + "lang": "zh_CN"
|
|
576 | + },
|
|
577 | + "model": "rbac_role",
|
|
578 | + "tag": "master",
|
|
579 | + "service": "update",
|
|
580 | + "app": "base"
|
|
581 | + }
|
|
582 | +}
|
|
583 | +```
|
|
584 | +
|
|
585 | +
|
|
586 | +
|
|
587 | +#### 3.1.5.9. **多对多添加**
|
|
588 | +
|
|
589 | +```json
|
|
590 | +{
|
|
591 | + "id": "guid",
|
|
592 | + "jsonrpc": "2.0",
|
|
593 | + "method": "service",
|
|
594 | + "params": {
|
|
595 | + "args": {
|
|
596 | + "valuesList": [
|
|
597 | + {
|
|
598 | + "user_ids": [
|
|
599 | + [
|
|
600 | + 4,
|
|
601 | + "024shzay3nwn4",
|
|
602 | + 0
|
|
603 | + ]
|
|
604 | + ],
|
|
605 | + "name": "111111111111",
|
|
606 | + "is_admin": true
|
|
607 | + }
|
|
608 | + ]
|
|
609 | + },
|
|
610 | + "context": {
|
|
611 | + "uid": "",
|
|
612 | + "lang": "zh_CN",
|
|
613 | + "useDisplayForModel": true
|
|
614 | + },
|
|
615 | + "model": "rbac_role",
|
|
616 | + "tag": "master",
|
|
617 | + "service": "create"
|
|
618 | + }
|
|
619 | +}
|
|
620 | +```
|
|
621 | +
|
|
622 | +
|
|
623 | +
|
|
624 | +#### 3.1.5.10. **多对多查询**
|
|
625 | +
|
|
626 | +分2步,第一步先查询主表数据
|
|
627 | +
|
|
628 | +```json
|
|
629 | +{
|
|
630 | + "id": "guid",
|
|
631 | + "jsonrpc": "2.0",
|
|
632 | + "method": "service",
|
|
633 | + "params": {
|
|
634 | + "args": {
|
|
635 | + "filter": [
|
|
636 | + [
|
|
637 | + "id",
|
|
638 | + "=",
|
|
639 | + "02gwfpd3os2kg"
|
|
640 | + ]
|
|
641 | + ],
|
|
642 | + "offset": 0,
|
|
643 | + "limit": 1,
|
|
644 | + "order": "",
|
|
645 | + "properties": [
|
|
646 | + "name",
|
|
647 | + "is_admin",
|
|
648 | + "user_ids",
|
|
649 | + "permission_ids"
|
|
650 | + ],
|
|
651 | + "useDisplayForModel": true
|
|
652 | + },
|
|
653 | + "context": {
|
|
654 | + "uid": "",
|
|
655 | + "lang": "zh_CN"
|
|
656 | + },
|
|
657 | + "model": "rbac_role",
|
|
658 | + "tag": "master",
|
|
659 | + "service": "search",
|
|
660 | + "app": "base"
|
|
661 | + }
|
|
662 | +}
|
|
663 | +```
|
|
664 | +
|
|
665 | +第2步,根据主表id去查询关联表
|
|
666 | +
|
|
667 | +```json
|
|
668 | +{
|
|
669 | + "id": "guid",
|
|
670 | + "jsonrpc": "2.0",
|
|
671 | + "method": "service",
|
|
672 | + "params": {
|
|
673 | + "args": {
|
|
674 | + "filter": [
|
|
675 | + [
|
|
676 | + "role_ids",
|
|
677 | + "=",
|
|
678 | + "02gwfpd3os2kg"
|
|
679 | + ]
|
|
680 | + ],
|
|
681 | + "offset": 0,
|
|
682 | + "limit": 31,
|
|
683 | + "properties": [
|
|
684 | + "login",
|
|
685 | + "email",
|
|
686 | + "mobile",
|
|
687 | + "name"
|
|
688 | + ],
|
|
689 | + "order": "",
|
|
690 | + "useDisplayForModel": true
|
|
691 | + },
|
|
692 | + "context": {
|
|
693 | + "uid": "",
|
|
694 | + "lang": "zh_CN"
|
|
695 | + },
|
|
696 | + "model": "rbac_user",
|
|
697 | + "tag": "master",
|
|
698 | + "service": "search",
|
|
699 | + "app": "base"
|
|
700 | + }
|
|
701 | +}
|
|
702 | +```
|
|
703 | +
|
|
704 | +### 3.1.6.索引
|
|
705 | +
|
|
706 | +可以在模型上,为最终生成的表添加索引
|
|
707 | +
|
|
708 | +```java
|
|
709 | +// 复合唯一索引
|
|
710 | +@Model(name = "model_name", indexes = { @Index(name = "IDX_USER_IP", columnList = {"user_ids","ip" }, unique = true)})
|
|
711 | +
|
|
712 | +// 复合索引
|
|
713 | +@Model(name = "model_name", indexes = { @Index(name = "UQ_USER_IP", columnList = {"user_ids","ip" })})
|
|
714 | +
|
|
715 | +// 单索引
|
|
716 | +@Model(name = "model_name", indexes = { @Index(name = "IDX_USER", columnList = {"user_ids"})})
|
|
717 | +```
|
|
718 | +
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/02.\346\267\273\345\212\240\345\261\236\346\200\247.md
... | ... | @@ -0,0 +1,937 @@ |
1 | +--- |
|
2 | +title: 添加属性 |
|
3 | +date: 2023-09-25 17:31:34 |
|
4 | +permalink: /pages/a0dcea/ |
|
5 | +--- |
|
6 | + |
|
7 | + |
|
8 | +# 注解 |
|
9 | + |
|
10 | +## 3.2.1@Property注解 |
|
11 | + |
|
12 | +| 参数 | 说明 | 类型 | 默认值 | |
|
13 | +| ----------------- | ------------------------------------------------------------ | ------- | ------------------------------------ | |
|
14 | +| name | 属性名字,也是数据库字段名 | String | | |
|
15 | +| displayName | 用户看到的字段的标签,如果未设置,默认显示字段名 | String | | |
|
16 | +| tooltips | 用户看到的字段的帮助提示 | String | | |
|
17 | +| isReadonly | 该字段是否为只读。这只会对 UI 产生影响。代码中的任何字段设置值都将起作用 | boolean | false | |
|
18 | +| isRequired | 字段的值是否是必需的 | boolean | false | |
|
19 | +| isStore | 字段是否存储在数据库中,对计算字段不生效 | boolean | true | |
|
20 | +| length | 字段长度 | int | 字符串默认长度为240,小数默认长度为22 | |
|
21 | +| scale | 小数位精度 | int | 2 | |
|
22 | +| dbIndex | 该字段是否在数据库中被索引,对于非存储字段不生效 | boolean | false | |
|
23 | +| display | 是否显示 | boolean | true | |
|
24 | +| ruleScript | 规则脚本 | String | | |
|
25 | +| computeScript | 计算字段,通过计算获取值,不保存于数据库。可以由元方法计算,也可以是groovy脚本计算 | String | | |
|
26 | +| computeMethod | 计算方法 | String | | |
|
27 | +| format | 字符串格式化 | String | | |
|
28 | +| isDisplayForModel | 是否为模型的显示字段 | boolean | false | |
|
29 | +| unique | 是否唯一,如果是true,将纳入唯一性校验(与数据库无关) | boolean | false | |
|
30 | +| validationType | 校验类型 | String | | |
|
31 | +| defaultValue | 字段的默认值。可以是常量值,可是通过元方法提供值,也可以通过groovy脚本提供值 | String | | |
|
32 | +| defaultMethod | 默认值方法名 | String | | |
|
33 | +| widget | 显示组件名称 | String | | |
|
34 | +| indexNo | 排序字段 | int | | |
|
35 | +| percent | 是否显示为百分比 | boolean | | |
|
36 | +| dateFormat | 日期时间格式 | String | | |
|
37 | +| password | 是否密码 | | | |
|
38 | +| groovy | groovy脚本 | | | |
|
39 | +| multiple | 是否多选 | boolean | false | |
|
40 | +| contentType | 附件类型 | String | String | |
|
41 | +| onChange | 字段变化时,触发事件 | | | |
|
42 | + |
|
43 | +### **属性get set** |
|
44 | + |
|
45 | +继承了BaseModel因此拥有基本的CURD能力,也获得HashMap对象,因此拥有Map对象的get set能力,通过CGLIB调用引擎的服务查询数据。 |
|
46 | + |
|
47 | +###### **get 方法写法** |
|
48 | + |
|
49 | +get方法 columnName 定义的是下划线rolel_name,但是实际get是驼峰命名的roleName |
|
50 | + |
|
51 | +``` |
|
52 | +@Property(columnName = "role_name", displayName = "角色名称") |
|
53 | +private String roleName;// * 这里需要注意的是get对象为roleName |
|
54 | +public String getRoleName(){ |
|
55 | + return (String) get("roleName"); |
|
56 | +} |
|
57 | +``` |
|
58 | + |
|
59 | + |
|
60 | + |
|
61 | +###### **set 方法写法** |
|
62 | + |
|
63 | +set方法 columnName 定义的是下划线role_name ,但是实际set对象是以驼峰命名的roleName |
|
64 | + |
|
65 | +``` |
|
66 | +public void setRoleName(String roleName){ |
|
67 | + set("roleName",roleName); |
|
68 | +} |
|
69 | +``` |
|
70 | + |
|
71 | + |
|
72 | + |
|
73 | +###### **配置IDEA get set 代码模板** |
|
74 | + |
|
75 | +getter模板 |
|
76 | + |
|
77 | +``` |
|
78 | +#if($field.modifierStatic) |
|
79 | +static ## |
|
80 | +#end |
|
81 | +$field.type ## |
|
82 | +#if($field.recordComponent) |
|
83 | + ${field.name}## |
|
84 | +#else |
|
85 | +#set($name = $StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field, $project)))) |
|
86 | +#if ($field.boolean && $field.primitive) |
|
87 | + is## |
|
88 | +#else |
|
89 | + get## |
|
90 | +#end |
|
91 | +${name}## |
|
92 | +#end |
|
93 | +() { |
|
94 | + #if($field.type == "java.util.Date") |
|
95 | + return getDate("$field.name"); |
|
96 | + #elseif($field.type == "java.sql.Timestamp") |
|
97 | + return getTimestamp("$field.name"); |
|
98 | + #elseif($field.type == "java.time.LocalDateTime") |
|
99 | + return getLocalDateTime("$field.name"); |
|
100 | + #elseif($field.type == "java.sql.Time") |
|
101 | + return getTime("$field.name"); |
|
102 | + #elseif($field.type == "java.lang.Integer") |
|
103 | + return getInt("$field.name"); |
|
104 | + #elseif($field.type == "java.lang.Long") |
|
105 | + return getLong("$field.name"); |
|
106 | + #elseif($field.type == "java.lang.Double") |
|
107 | + return getDouble("$field.name"); |
|
108 | + #elseif($field.type == "java.lang.Float") |
|
109 | + return getFloat("$field.name"); |
|
110 | + #elseif($field.type == "java.lang.Short") |
|
111 | + return getShort("$field.name"); |
|
112 | + #elseif($field.type == "java.lang.Byte") |
|
113 | + return getByte("$field.name"); |
|
114 | + #elseif($field.type == "java.lang.Boolean") |
|
115 | + return getBoolean("$field.name"); |
|
116 | + #elseif($field.type == "java.lang.String") |
|
117 | + return getStr("$field.name"); |
|
118 | + #elseif($field.type == "java.lang.Number") |
|
119 | + return getNumber("$field.name"); |
|
120 | + #elseif($field.type == "java.math.BigDecimal") |
|
121 | + return getBigDecimal("$field.name"); |
|
122 | + #elseif($field.type == "java.math.BigInteger") |
|
123 | + return getBigInteger("$field.name"); |
|
124 | + #else |
|
125 | + return ($field.type)this.get("$field.name"); |
|
126 | + #end |
|
127 | +} |
|
128 | +``` |
|
129 | + |
|
130 | +setter模板 |
|
131 | + |
|
132 | +```js |
|
133 | +#set($paramName = $helper.getParamName($field, $project)) |
|
134 | +#if($field.modifierStatic) |
|
135 | +static ## |
|
136 | +#end |
|
137 | +$classname set$StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field, $project)))($field.type $paramName) { |
|
138 | +#if ($field.name == $paramName) |
|
139 | + #if (!$field.modifierStatic) |
|
140 | + this.## |
|
141 | + #else |
|
142 | + $classname.## |
|
143 | + #end |
|
144 | +#end |
|
145 | +set("$field.name", $paramName); |
|
146 | +return this; |
|
147 | +} |
|
148 | +``` |
|
149 | + |
|
150 | +### 数据类型 |
|
151 | + |
|
152 | +- BigDecimal |
|
153 | +- Boolean 存储true/false 值 |
|
154 | +- Date日期,默认格式为yyyy-MM-dd |
|
155 | +- DateTime时间日期,默认格式为yyyy-MM-dd HH:mm:ss |
|
156 | +- Double双精度浮点型。精度可由位数和小数位数对来定义,默认精度为2 |
|
157 | +- Selection选择类型: 包括常量枚举; 下拉框单选,下拉框多选,下拉框联动, 参考 [3.2.6 Selection下拉选项](#3.2.6. **Selection下拉选项-单选和多选**) |
|
158 | + |
|
159 | +- File 文件类型,文件类型可由contentType指定 |
|
160 | + |
|
161 | +- Integer整形 |
|
162 | + |
|
163 | +- Long 长整形 |
|
164 | + |
|
165 | +- List 存储List对象,以json格式存储数据,读取的时候反序列化为List对象 |
|
166 | + |
|
167 | +- Map 存储Map对象,以json格式存储数据,读取的时候反序列化为Map对象 |
|
168 | + |
|
169 | +- Object 存储对象,以json格式存储数据,读取的时候反序列化为对象 |
|
170 | + |
|
171 | +- String 字符串 |
|
172 | + |
|
173 | +- Text 长文本: |
|
174 | + |
|
175 | + ```java |
|
176 | + 1.对于MySQL数据库 : |
|
177 | + |
|
178 | + length >20000,dataType=MediumText; |
|
179 | + length >5500000,dataType=LongText |
|
180 | + |
|
181 | + @Property(columnName = "text", displayName = "text",dataType = DataType.TEXT,length = 60000) |
|
182 | + private String text; |
|
183 | + |
|
184 | + 2.对于Oracle数据库 |
|
185 | + length >4000,dataType=clob; |
|
186 | + ``` |
|
187 | + |
|
188 | + |
|
189 | +## 3.2.2@**Validate注解** |
|
190 | + |
|
191 | +Validate 提供的校验方式为在类的属性上加入相应的注解来达到校验的目的。validator提供的用于校验的注解如下: |
|
192 | + |
|
193 | +| 注解 | 说明 | |
|
194 | +| ---------------- | ----------------------------------------------------- | |
|
195 | +| @NotBlank | 不能为null | |
|
196 | +| @Pattern(regex=) | 被注释的元素必须符合指定的正则表达式 | |
|
197 | +| @Email | 检查是否是一个有效的email地址 | |
|
198 | +| @Max | 该字段的值只能小于或等于该值 | |
|
199 | +| @Min | 该字段的值只能大于或等于该值 | |
|
200 | +| @Size(min=,max=) | 检查所属的字段的长度是否在min和max之间,只能用于字符串 | |
|
201 | +| @Null | 能为null | |
|
202 | +| @Unique | 唯一校验。默认只校验当前字段。可以设置 properties,指定多个字段联合唯一 | |
|
203 | + |
|
204 | + |
|
205 | + |
|
206 | +如何定义分组校验 |
|
207 | + |
|
208 | +TestDataSource 数据源根据数据源类型分组 |
|
209 | + |
|
210 | +```java |
|
211 | +@Model(name = "TestDataSource", description = "数据源") |
|
212 | +public class TestDataSource extends BaseModel { |
|
213 | + |
|
214 | + @Property(displayName = "数据源类型", widget = "radio-group") |
|
215 | + @Selection(values = {@Option(label = "DB/SQL", value = "DB", groups = Db.class), @Option(label = "API", value = "API", groups = Api.class), |
|
216 | + @Option(label = "Excel", value = "Excel"), @Option(label = "IIOT", value = "IIOT", groups = Iiot.class)}) |
|
217 | + private String type; |
|
218 | + |
|
219 | + |
|
220 | + interface Db { |
|
221 | + } |
|
222 | + |
|
223 | + interface Api { |
|
224 | + } |
|
225 | + |
|
226 | + interface Iiot { |
|
227 | + |
|
228 | + } |
|
229 | +} |
|
230 | +``` |
|
231 | + |
|
232 | +TestDataSourceApi 扩展模型增加了请求头、请求参数分组为Api.class |
|
233 | + |
|
234 | +```java |
|
235 | +@Model(name = "TestDataSource", parent = "TestDataSource") |
|
236 | +public class TestDataSourceApi extends BaseModel { |
|
237 | + @Property(columnName = "header", displayName = "请求头") |
|
238 | + @Validate.NotBlank(groups = TestDataSource.Api.class) |
|
239 | + private String header; |
|
240 | + |
|
241 | + @Validate.NotBlank(groups = TestDataSource.Api.class) |
|
242 | + @Property(columnName = "parameters", displayName = "请求参数") |
|
243 | + private String parameters; |
|
244 | +} |
|
245 | +``` |
|
246 | + |
|
247 | + |
|
248 | + |
|
249 | +使用的方式有两种: |
|
250 | + |
|
251 | +1.API方式,前端直接请求create,update接口,引擎会根据类型校验分组。 |
|
252 | + |
|
253 | +```json |
|
254 | +{ |
|
255 | + "id": "guid", |
|
256 | + "jsonrpc": "2.0", |
|
257 | + "method": "service", |
|
258 | + "params": { |
|
259 | + "args": { |
|
260 | + "valuesList": [ |
|
261 | + { |
|
262 | + "type": "API", |
|
263 | + "header": "aaaaaaaa", |
|
264 | + "parameters": "ssssssssssssss" |
|
265 | + } |
|
266 | + ] |
|
267 | + }, |
|
268 | + "app": "newSdkApp", |
|
269 | + "service": "create", |
|
270 | + "context": { |
|
271 | + "uid": "", |
|
272 | + "lang": "zh_CN" |
|
273 | + }, |
|
274 | + "model": "TestDataSource", |
|
275 | + "tag": "master" |
|
276 | + } |
|
277 | +} |
|
278 | +``` |
|
279 | + |
|
280 | +2.服务定义方式,根据自定义服务使用哪个分组校验。 |
|
281 | + |
|
282 | +```java |
|
283 | + @MethodService(description = "测试分组") |
|
284 | + public void testValidate(@Validate.Validated(value = TestDataSource.Api.class) TestDataSourceApi api) { |
|
285 | + |
|
286 | + |
|
287 | + } |
|
288 | +``` |
|
289 | + |
|
290 | + |
|
291 | + |
|
292 | +## 3.2.3**ER关系注解** |
|
293 | + |
|
294 | +#### @**OneToMany** |
|
295 | + |
|
296 | +采用@OneToMany对er关系一对多进行描述 |
|
297 | + |
|
298 | +``` |
|
299 | +@OneToMany(mappedBy = "org",cascade = { CascadeType.PERSIST,CascadeType.MERGE, CascadeType.DELETE }) |
|
300 | +private List<TestUser> userList; |
|
301 | +``` |
|
302 | + |
|
303 | +One2Many 一对多关系,不会写入表中。 |
|
304 | + |
|
305 | + |
|
306 | + |
|
307 | +| **字段名** | **中文名** | **类型** | **可选值** | |
|
308 | +| ------------- | ---------- | ----------- | ---------- | |
|
309 | +| targetEntity | | Class | | |
|
310 | +| cascade | 级联操作 | CascadeType | | |
|
311 | +| fetch | 延迟加载 | FetchType | | |
|
312 | +| mappedBy | 关系维护 | | | |
|
313 | +| orphanRemoval | | | | |
|
314 | + |
|
315 | + |
|
316 | + |
|
317 | +#### @**ManyToOne** |
|
318 | + |
|
319 | + 采用@ManyToOne对er关系多对一进行描述 |
|
320 | + |
|
321 | +``` |
|
322 | +1.同一个App内 |
|
323 | +@ManyToOne(displayName = "组织机构") |
|
324 | +@JoinColumn(name = "org_id", referencedProperty = "id") |
|
325 | +private TestOrg org; |
|
326 | + |
|
327 | +2.跨app引用 |
|
328 | +@ManyToOne(displayName = "申请人", targetModel = "rbac.rbac_user",targetProperty = "id",filter = "[[\"name\",\"=\",\"admin\"]]" ) |
|
329 | +@JoinColumn(name = "start_user_id",referencedProperty = "id" ) |
|
330 | +private Map<String, Object> startUserId; |
|
331 | +``` |
|
332 | + |
|
333 | +ManyToOne只用写关联表, 相对应的org_id字段会存表中,org不会存在表中。 |
|
334 | + |
|
335 | +| **字段名** | **中文名** | **类型** | 默认值 | 是否必填 | |
|
336 | +| -------------- | ------------------------------------------------------------ | ------------ | --------------------- | -------- | |
|
337 | +| displayName | 显示名字 | String | 属性英文名字 | 是 | |
|
338 | +| targetModel | one方模型,以app.model命名,<br />跨App引用才需要配置 | Class/String | TestOrg对象的模型名字 | 否 | |
|
339 | +| cascade | 级联操作:<br />DEL_SET_NULL:设置关联字段为空<br />DELETE:被级联删除<br />DEL_NO_PERMIT_RELATIVE:<br />存在记录则不允许主表删除 | CascadeType | DEL_SET_NULL | 否 | |
|
340 | +| targetProperty | one方关联属性名,跨App引用才需要配置 | String | id | 否 | |
|
341 | +| filter | 过滤条件,关联one方时带的默认过滤条件 | Filter | | 否 | |
|
342 | +| fetch | 延迟加载,暂时不支持 | FetchType | | 否 | |
|
343 | + |
|
344 | +@**JoinColumn** |
|
345 | + |
|
346 | +| 字段名 | 中文名 | 类型 | 默认值 | 是否必填 | |
|
347 | +| ------------------ | ------------------------------------------------------------ | ------ | ---------- | -------- | |
|
348 | +| name | 数据库表列名,当前表会添加一列org_id | String | | 是 | |
|
349 | +| referencedProperty | 默认会和one方的主键id关联,<br />如果不是通过id关联需要指定one的属性名 | String | id | 否 | |
|
350 | +| length | 列长度 | int | 主键的长度 | 否 | |
|
351 | + |
|
352 | + |
|
353 | + |
|
354 | +#### @**ManyToMany** |
|
355 | + |
|
356 | + 采用@ManyToMany对er关系多对多进行描述 |
|
357 | + |
|
358 | +``` |
|
359 | +@ManyToMany |
|
360 | +@JoinTable(name = "role_user", joinColumns = @JoinColumn(name = "role_id", nullable = false), inverseJoinColumns = @JoinColumn(name = "user_id", nullable = false)) |
|
361 | +private List<TestUser> userList; |
|
362 | +``` |
|
363 | + |
|
364 | + |
|
365 | + |
|
366 | +ManyToMany |
|
367 | + |
|
368 | + role与user是多对多的关系,这种情况关联字段不会存在表中,关联字段会存在创建的关系表中。 |
|
369 | + |
|
370 | +| **字段名** | **中文名** | **类型** | **可选值** | |
|
371 | +| ------------ | ---------- | ----------- | ---------- | |
|
372 | +| targetEntity | | Class | | |
|
373 | +| cascade | 级联操作 | CascadeType | | |
|
374 | +| fetch | 延迟加载 | FetchType | | |
|
375 | +| mappedBy | 关系维护 | String | | |
|
376 | +| | | | | |
|
377 | + |
|
378 | + |
|
379 | + |
|
380 | +## 3.2.4@**Selection注解** |
|
381 | + |
|
382 | +**1.1 单选** |
|
383 | + |
|
384 | +- **单选-常量枚举** |
|
385 | + |
|
386 | + ```java |
|
387 | + @Property(displayName = "1单选常量", defaultValue = "1",length = 256) |
|
388 | + @Selection(values = { @Option(label = "状态1", value = "1"), @Option(label = "禁用", value = "2"), @Option(label = "已删除", value = "3") }) |
|
389 | + private String status; |
|
390 | + ``` |
|
391 | + |
|
392 | + |
|
393 | + |
|
394 | +- **单选-选项值来自方法调用** |
|
395 | + |
|
396 | + ```java |
|
397 | + @Property(displayName = "selectMetod") |
|
398 | + @Selection(method = "selectMetod") |
|
399 | + private String selectMetod; |
|
400 | + |
|
401 | + /** |
|
402 | + * value为回显时候的值 |
|
403 | + */ |
|
404 | + @MethodService(description = "selectMetod") |
|
405 | + public List<Options> selectMetod(Object value) { |
|
406 | + String json = "[{\"code\":\"110101\",\"value\":\"东城区\"},{\"code\":\"110102\",\"value\":\"西城区\"},{\"code\":\"110105\",\"value\":\"朝阳区\"},{\"code\":\"110106\",\"value\":\"丰台区\"},{\"code\":\"110107\",\"value\":\"石景山区\"},{\"code\":\"110108\",\"value\":\"海淀区\"},{\"code\":\"110109\",\"value\":\"门头沟区\"},{\"code\":\"110111\",\"value\":\"房山区\"},{\"code\":\"110112\",\"value\":\"通州区\"},{\"code\":\"110113\",\"value\":\"顺义区\"},{\"code\":\"110114\",\"value\":\"昌平区\"},{\"code\":\"110115\",\"value\":\"大兴区\"},{\"code\":\"110116\",\"value\":\"怀柔区\"},{\"code\":\"110117\",\"value\":\"平谷区\"},{\"code\":\"110118\",\"value\":\"密云区\"},{\"code\":\"110119\",\"value\":\"延庆区\"}]"; |
|
407 | + List<Options> options = new ArrayList<Options>(); |
|
408 | + List<Options> provinceList = new ArrayList<Options>(); |
|
409 | + |
|
410 | + JSONArray proviceArray = JSONObject.parseArray(json); |
|
411 | + for (int i = 0; i < proviceArray.size(); i++) { |
|
412 | + JSONObject proviceObj = proviceArray.getJSONObject(i); |
|
413 | + provinceList.add(Options.of(proviceObj.getString("value"), proviceObj.getString("code"))); |
|
414 | + } |
|
415 | + |
|
416 | + //1.第一步:处理回显 |
|
417 | + String valueStr=Objects.toString(value, null); |
|
418 | + if (StringUtils.isNotBlank(valueStr)) { |
|
419 | + Optional<Options> optional = provinceList.stream().filter(p -> p.getValue().equals(valueStr)).findFirst(); |
|
420 | + if (optional.isPresent()) { |
|
421 | + options.add(optional.get()); |
|
422 | + } |
|
423 | + return options; |
|
424 | + } |
|
425 | + //2.第二步:返回下拉框所有值 |
|
426 | + options.addAll(provinceList); |
|
427 | + return options; |
|
428 | + } |
|
429 | + ``` |
|
430 | + |
|
431 | + |
|
432 | + |
|
433 | +- **单选-选项来自任意一个模型** |
|
434 | + |
|
435 | + properties是显示字段, id是保存的value值 |
|
436 | + |
|
437 | + ```java |
|
438 | + @Property(displayName = "4单选异步获取模型") |
|
439 | + @Selection(model = "TestOrg", properties = "name", orderBy = "name desc",filter = "[[\"login\",\"like\",\"admin%\"]]") |
|
440 | + private String selectModel; |
|
441 | + ``` |
|
442 | + |
|
443 | + |
|
444 | + |
|
445 | +**1.2 多选** |
|
446 | + |
|
447 | +- **多选常量** |
|
448 | + |
|
449 | + ```java |
|
450 | + @Property(displayName = "5-多选常量") |
|
451 | + @Selection(values = { @Option(label = "启用", value = "1"), @Option(label = "禁用", value = "2"),@Option(label = "已删除", value = "3") }, multiple = true) |
|
452 | + private String selectStatus; |
|
453 | + ``` |
|
454 | + |
|
455 | + |
|
456 | + |
|
457 | +- **多选-选项值来自方法调用** |
|
458 | + |
|
459 | + ```java |
|
460 | + @Property(displayName = "7-多选异步获取方法") |
|
461 | + @Selection(method = "selectMultipleProvince", multiple = true) |
|
462 | + private String[] selectMultipleProvince; |
|
463 | + |
|
464 | + /** |
|
465 | + * 多选选择省 |
|
466 | + */ |
|
467 | + @MethodService(description = "selectMultipleProvince") |
|
468 | + public List<Options> selectMultipleProvince(Object[] value) { |
|
469 | + |
|
470 | + String json = "[{\"code\":\"110101\",\"value\":\"东城区\"},{\"code\":\"110102\",\"value\":\"西城区\"},{\"code\":\"110105\",\"value\":\"朝阳区\"},{\"code\":\"110106\",\"value\":\"丰台区\"},{\"code\":\"110107\",\"value\":\"石景山区\"},{\"code\":\"110108\",\"value\":\"海淀区\"},{\"code\":\"110109\",\"value\":\"门头沟区\"},{\"code\":\"110111\",\"value\":\"房山区\"},{\"code\":\"110112\",\"value\":\"通州区\"},{\"code\":\"110113\",\"value\":\"顺义区\"},{\"code\":\"110114\",\"value\":\"昌平区\"},{\"code\":\"110115\",\"value\":\"大兴区\"},{\"code\":\"110116\",\"value\":\"怀柔区\"},{\"code\":\"110117\",\"value\":\"平谷区\"},{\"code\":\"110118\",\"value\":\"密云区\"},{\"code\":\"110119\",\"value\":\"延庆区\"}]"; |
|
471 | + List<Options> options = new ArrayList<Options>(); |
|
472 | + List<Options> provinceList = new ArrayList<Options>(); |
|
473 | + |
|
474 | + JSONArray proviceArray = JSONObject.parseArray(json); |
|
475 | + for (int i = 0; i < proviceArray.size(); i++) { |
|
476 | + JSONObject proviceObj = proviceArray.getJSONObject(i); |
|
477 | + provinceList.add(Options.of(proviceObj.getString("value"), proviceObj.getString("code"))); |
|
478 | + } |
|
479 | + |
|
480 | + // 1.处理回显 |
|
481 | + if (value != null && value.length > 0) { |
|
482 | + options = provinceList.stream().filter(p -> ArrayUtils.contains(value, p.getValue())).collect(Collectors.toList()); |
|
483 | + return options; |
|
484 | + } |
|
485 | + //2.返回选项列表 |
|
486 | + options.addAll(provinceList); |
|
487 | + return options; |
|
488 | + } |
|
489 | + |
|
490 | + ``` |
|
491 | + |
|
492 | +- **多选-many2many多选** |
|
493 | + |
|
494 | + ```java |
|
495 | + @ManyToMany |
|
496 | + @JoinTable(name = "role_user", joinColumns = @JoinColumn(name = "user_id", nullable = false), inverseJoinColumns = @JoinColumn(name = "role_id", nullable = false)) |
|
497 | + @Selection(multiple = true,properties = "roleName") |
|
498 | + private List<TestRole> roleList; |
|
499 | + ``` |
|
500 | + |
|
501 | + |
|
502 | + |
|
503 | +- **多选-选择来自任意模型** |
|
504 | + |
|
505 | + ```java |
|
506 | + @Property(displayName = "8多选异步获取模型") |
|
507 | + @Selection(model = "TestOrg", properties = "name", multiple = true) |
|
508 | + private List<TestOrg> selectMultipleModel; |
|
509 | + ``` |
|
510 | + |
|
511 | + |
|
512 | + |
|
513 | +**1.3 多级联动** |
|
514 | + |
|
515 | +1. 后端属性定义 |
|
516 | + |
|
517 | +```java |
|
518 | +linkageFields:联动的字段 |
|
519 | +method:调用方法名 |
|
520 | + |
|
521 | +@Property(displayName = "9-联动-省", toolTips = "请选择") |
|
522 | +@Selection(method = "selectProvince", linkageFields = {"city", "area" }) |
|
523 | +private String province; |
|
524 | + |
|
525 | +@Property(displayName = "9-联动-市", toolTips = "请选择",linkageFields = { "area" }) |
|
526 | +@Selection(method = "selectCity") |
|
527 | +private String city; |
|
528 | + |
|
529 | +@Property(displayName = "9-联动-区", toolTips = "请选择") |
|
530 | +@Selection(method = "selectArea") |
|
531 | +private String area; |
|
532 | + |
|
533 | +``` |
|
534 | + |
|
535 | +2. 后端定义联动的方法 |
|
536 | + |
|
537 | + ```java |
|
538 | + String json = "[{\"code\":\"110101\",\"value\":\"东城区\"},{\"code\":\"110102\",\"value\":\"西城区\"},{\"code\":\"110105\",\"value\":\"朝阳区\"},{\"code\":\"110106\",\"value\":\"丰台区\"},{\"code\":\"110107\",\"value\":\"石景山区\"},{\"code\":\"110108\",\"value\":\"海淀区\"},{\"code\":\"110109\",\"value\":\"门头沟区\"},{\"code\":\"110111\",\"value\":\"房山区\"},{\"code\":\"110112\",\"value\":\"通州区\"},{\"code\":\"110113\",\"value\":\"顺义区\"},{\"code\":\"110114\",\"value\":\"昌平区\"},{\"code\":\"110115\",\"value\":\"大兴区\"},{\"code\":\"110116\",\"value\":\"怀柔区\"},{\"code\":\"110117\",\"value\":\"平谷区\"},{\"code\":\"110118\",\"value\":\"密云区\"},{\"code\":\"110119\",\"value\":\"延庆区\"}]"; |
|
539 | + |
|
540 | + @MethodService(description = "selectProvince") |
|
541 | + public List<Options> selectProvince(Object value) { |
|
542 | + List<Options> options = new ArrayList<Options>(); |
|
543 | + List<Options> provinceList = new ArrayList<Options>(); |
|
544 | + |
|
545 | + JSONArray proviceArray = JSONObject.parseArray(json); |
|
546 | + for (int i = 0; i < proviceArray.size(); i++) { |
|
547 | + JSONObject proviceObj = proviceArray.getJSONObject(i); |
|
548 | + provinceList.add(Options.of(proviceObj.getString("value"), proviceObj.getString("code"))); |
|
549 | + } |
|
550 | + |
|
551 | + String valueStr = Objects.toString(value, null); |
|
552 | + //1.处理回显 |
|
553 | + if (StringUtils.isNotBlank(valueStr)) { |
|
554 | + Optional<Options> optional = provinceList.stream().filter(p -> p.getValue().equals(valueStr)).findFirst(); |
|
555 | + if (optional.isPresent()) { |
|
556 | + options.add(optional.get()); |
|
557 | + } |
|
558 | + |
|
559 | + return options; |
|
560 | + } |
|
561 | + //2.返回下拉列表 |
|
562 | + options.addAll(provinceList); |
|
563 | + return options; |
|
564 | + } |
|
565 | + |
|
566 | + |
|
567 | + /** |
|
568 | + * 选择市 |
|
569 | + */ |
|
570 | + @MethodService(description = "selectCity") |
|
571 | + public List<Options> selectCity(String provinceId, String value) { |
|
572 | + List<Options> options = new ArrayList<Options>(); |
|
573 | + List<Options> cityList = new ArrayList<Options>(); |
|
574 | + |
|
575 | + //1.处理回显 |
|
576 | + if (StringUtils.isNotBlank(value)) { |
|
577 | + JSONArray proviceArray = JSONObject.parseArray(json); |
|
578 | + for (int i = 0; i < proviceArray.size(); i++) { |
|
579 | + JSONObject proviceObj = proviceArray.getJSONObject(i); |
|
580 | + JSONArray children = proviceObj.getJSONArray("children"); |
|
581 | + for (int j = 0; j < children.size(); j++) { |
|
582 | + JSONObject cityObj = children.getJSONObject(j); |
|
583 | + String cityCode = cityObj.getString("code"); |
|
584 | + String cityName = cityObj.getString("value"); |
|
585 | + if (cityCode.equals(value)) { |
|
586 | + cityList.add(Options.of(cityName, cityCode)); |
|
587 | + } |
|
588 | + |
|
589 | + } |
|
590 | + } |
|
591 | + |
|
592 | + options.addAll(cityList); |
|
593 | + return options; |
|
594 | + } |
|
595 | + |
|
596 | + //2.返回列表 |
|
597 | + JSONArray proviceArray = JSONObject.parseArray(json); |
|
598 | + for (int i = 0; i < proviceArray.size(); i++) { |
|
599 | + JSONObject proviceObj = proviceArray.getJSONObject(i); |
|
600 | + String provinceCode = proviceObj.getString("code"); |
|
601 | + JSONArray children = proviceObj.getJSONArray("children"); |
|
602 | + if (!provinceCode.equals(provinceId)) { |
|
603 | + continue; |
|
604 | + } |
|
605 | + |
|
606 | + for (int j = 0; j < children.size(); j++) { |
|
607 | + JSONObject cityObj = children.getJSONObject(j); |
|
608 | + String cityCode = cityObj.getString("code"); |
|
609 | + String cityName = cityObj.getString("value"); |
|
610 | + cityList.add(Options.of(cityName, cityCode)); |
|
611 | + } |
|
612 | + } |
|
613 | + |
|
614 | + options.addAll(cityList); |
|
615 | + return options; |
|
616 | + |
|
617 | + } |
|
618 | + |
|
619 | + |
|
620 | + /** |
|
621 | + * 选择区 |
|
622 | + */ |
|
623 | + @MethodService(description = "selectArea") |
|
624 | + public List<Options> selectArea(String provinceId, String cityId, String value) { |
|
625 | + List<Options> options = new ArrayList<Options>(); |
|
626 | + List<Options> areaList = new ArrayList<Options>(); |
|
627 | + JSONArray proviceArray = JSONObject.parseArray(json); |
|
628 | + |
|
629 | + // 1.处理回显 |
|
630 | + if (StringUtils.isNotBlank(value)) { |
|
631 | + for (int i = 0; i < proviceArray.size(); i++) { |
|
632 | + JSONObject proviceObj = proviceArray.getJSONObject(i); |
|
633 | + JSONArray provinceChildren = proviceObj.getJSONArray("children"); |
|
634 | + for (int j = 0; j < provinceChildren.size(); j++) { |
|
635 | + JSONObject cityObj = provinceChildren.getJSONObject(j); |
|
636 | + JSONArray cityChildren = cityObj.getJSONArray("children"); |
|
637 | + for (int k = 0; k < cityChildren.size(); k++) { |
|
638 | + JSONObject areaObj = cityChildren.getJSONObject(k); |
|
639 | + String areaCode = areaObj.getString("code"); |
|
640 | + String areaName = areaObj.getString("value"); |
|
641 | + |
|
642 | + // 处理回显 |
|
643 | + if (areaCode.equals(value)) { |
|
644 | + areaList.add(Options.of(areaName, areaCode)); |
|
645 | + } |
|
646 | + |
|
647 | + } |
|
648 | + |
|
649 | + } |
|
650 | + } |
|
651 | + |
|
652 | + options.addAll(areaList); |
|
653 | + return options; |
|
654 | + } |
|
655 | + |
|
656 | + //2.返回列表 |
|
657 | + for (int i = 0; i < proviceArray.size(); i++) { |
|
658 | + JSONObject proviceObj = proviceArray.getJSONObject(i); |
|
659 | + String provinceCode = proviceObj.getString("code"); |
|
660 | + JSONArray provinceChildren = proviceObj.getJSONArray("children"); |
|
661 | + if (!provinceCode.equals(provinceId)) { |
|
662 | + continue; |
|
663 | + } |
|
664 | + |
|
665 | + for (int j = 0; j < provinceChildren.size(); j++) { |
|
666 | + JSONObject cityObj = provinceChildren.getJSONObject(j); |
|
667 | + JSONArray cityChildren = cityObj.getJSONArray("children"); |
|
668 | + String cityCode = cityObj.getString("code"); |
|
669 | + if (!cityCode.equals(cityId)) { |
|
670 | + continue; |
|
671 | + } |
|
672 | + |
|
673 | + for (int k = 0; k < cityChildren.size(); k++) { |
|
674 | + JSONObject areaObj = cityChildren.getJSONObject(k); |
|
675 | + String areaCode = areaObj.getString("code"); |
|
676 | + String areaName = areaObj.getString("value"); |
|
677 | + areaList.add(Options.of(areaName, areaCode)); |
|
678 | + } |
|
679 | + |
|
680 | + } |
|
681 | + } |
|
682 | + |
|
683 | + options.addAll(areaList); |
|
684 | + return options; |
|
685 | + |
|
686 | + } |
|
687 | + ``` |
|
688 | + |
|
689 | + 3. 后端定义视图 |
|
690 | + |
|
691 | + |
|
692 | + |
|
693 | +```json |
|
694 | + |
|
695 | +{ |
|
696 | + "body": { |
|
697 | + "columns": [ |
|
698 | + { |
|
699 | + "label": "省", |
|
700 | + "name": "province", |
|
701 | + "widget": "select", |
|
702 | + "linkage": { |
|
703 | + "params": {}, |
|
704 | + "children": [ |
|
705 | + "city", |
|
706 | + "area" |
|
707 | + ] |
|
708 | + } |
|
709 | + }, |
|
710 | + { |
|
711 | + "label": "市", |
|
712 | + "name": "city", |
|
713 | + "linkage": { |
|
714 | + "params": { |
|
715 | + "provinceId": "${province}" |
|
716 | + }, |
|
717 | + "children": [ |
|
718 | + "area" |
|
719 | + ] |
|
720 | + } |
|
721 | + }, |
|
722 | + { |
|
723 | + "label": "区", |
|
724 | + "name": "area", |
|
725 | + "linkage": { |
|
726 | + "params": { |
|
727 | + "provinceId": "${province}", |
|
728 | + "cityId": "${city}" |
|
729 | + } |
|
730 | + } |
|
731 | + }, |
|
732 | + //如果联动的是ManyToOne的lookup方法,那么查询参数应该放在filter里面 |
|
733 | + { |
|
734 | + "label": "组织", |
|
735 | + "name": "orgId", |
|
736 | + "linkage": { |
|
737 | + "type": "filter", |
|
738 | + "params": [ |
|
739 | + [ |
|
740 | + "id", |
|
741 | + "=", |
|
742 | + "${id}" |
|
743 | + ] |
|
744 | + ] |
|
745 | + } |
|
746 | + } |
|
747 | + ], |
|
748 | + "type": "form" |
|
749 | + }, |
|
750 | + "mode": "primary", |
|
751 | + "model": "TestUser", |
|
752 | + "name": "TestUser-表单", |
|
753 | + "type": "form" |
|
754 | +} |
|
755 | +``` |
|
756 | + |
|
757 | + |
|
758 | + |
|
759 | +4.视图显示效果 |
|
760 | + |
|
761 | + |
|
762 | + |
|
763 | + |
|
764 | + |
|
765 | +### Related**关联属性** |
|
766 | + |
|
767 | +关联属性主要用于带出ManyToOne关联模型的字段,关联字段默认不存储, related最多支持查询3层,比如org.parent.name |
|
768 | + |
|
769 | +``` |
|
770 | +@ManyToOne(displayName = "组织机构") |
|
771 | +@JoinColumn(name = "org_id", referencedProperty = "id") |
|
772 | +private TestOrg org; |
|
773 | + |
|
774 | +//related最多支持3层,比如org.parent.name |
|
775 | +@Property(displayName = "组织名称",related = "org.name") |
|
776 | +private String orgName; |
|
777 | +``` |
|
778 | + |
|
779 | + |
|
780 | + |
|
781 | +### Compute**计算属性** |
|
782 | + |
|
783 | +计算字段是指 一个字段的值,可以通过一个函数来动态计算出来 |
|
784 | + |
|
785 | +到目前为止,我们接触的字段都是存储在数据库中并直接从数据库检索。字段也可以通过计算获得。在这种情况下,字段的值不是直接检索自数据库,而是通过调用模型的方法来实时计算获得。要创建计算字段,需要设置它的computeMethod属性为方法名。 |
|
786 | + |
|
787 | +``` |
|
788 | +@Property(columnName = "method", computeMethod = "method", displayName = "计算方法") |
|
789 | +private String method; |
|
790 | + |
|
791 | +/** |
|
792 | + * 计算属性指定的方法 |
|
793 | + * @param map 当前行 |
|
794 | + * @return 返回计算后的值 |
|
795 | + */ |
|
796 | +public String method(Map<String, Object> map) { |
|
797 | + return "computer"; |
|
798 | +} |
|
799 | +``` |
|
800 | + |
|
801 | +计算脚本 |
|
802 | + |
|
803 | +``` |
|
804 | +@Property(columnName = "script", computeScript = " 3+100", displayName = "计算脚本") |
|
805 | +private String script; |
|
806 | +``` |
|
807 | + |
|
808 | + |
|
809 | + |
|
810 | +### **自动字段** |
|
811 | + |
|
812 | +自动字段由引擎进行全局维护,自动字段默认不显示,包括如下 |
|
813 | + |
|
814 | +- id是由雪花算法生成,然后转成16进制,不足13位字符串时以0左补齐 |
|
815 | + |
|
816 | +- create_user 创建人,ManyToOne类型 |
|
817 | + |
|
818 | +- create_date 创建时间 |
|
819 | + |
|
820 | +- update_user 最后一次更新人,ManyToOne类型 |
|
821 | + |
|
822 | +- update_date 最后一次更新时间 |
|
823 | + |
|
824 | +- tenant_id 租户id |
|
825 | + |
|
826 | + |
|
827 | + |
|
828 | +**自动字段默认是不显示的,如果需要前端显示的话,需要添加以下设置** |
|
829 | + |
|
830 | +```json |
|
831 | +{ |
|
832 | + "columns": [ |
|
833 | + { |
|
834 | + "displayName": "创建人", |
|
835 | + "name": "create_user", |
|
836 | + "custom": true, |
|
837 | + "display": true |
|
838 | + }, |
|
839 | + { |
|
840 | + "displayName": "创建时间", |
|
841 | + "name": "create_date", |
|
842 | + "custom": true, |
|
843 | + "display": true |
|
844 | + }, |
|
845 | + { |
|
846 | + "displayName": "更新人", |
|
847 | + "name": "update_user", |
|
848 | + "custom": true, |
|
849 | + "display": true |
|
850 | + }, |
|
851 | + { |
|
852 | + "displayName": "更新时间", |
|
853 | + "name": "update_date", |
|
854 | + "custom": true, |
|
855 | + "display": true |
|
856 | + } |
|
857 | + ] |
|
858 | +} |
|
859 | +``` |
|
860 | + |
|
861 | + |
|
862 | + |
|
863 | +## **3.2.5@Dict注解** |
|
864 | + |
|
865 | +代码演示如下 ( 代码来源Demo工程) |
|
866 | + |
|
867 | +```java |
|
868 | + @Property(displayName = "用户性别") |
|
869 | + @Dict(typeCode = "userSex", defaultValue = "0") |
|
870 | + private String test; |
|
871 | +``` |
|
872 | + |
|
873 | +- Dict 注解 |
|
874 | + |
|
875 | +| 英文 | 名称 | 可选值 | |
|
876 | +| ------------ | -------- | -------- | |
|
877 | +| typeCode | 字典类型 | 英文别名 | |
|
878 | +| defaultValue | 字典键值 | 0,1,2,3 | |
|
879 | +| multiple | 多选 | 可空 | |
|
880 | +| orderBy | | 可空 | |
|
881 | +| model | | 可空 | |
|
882 | +| label | | 可空 | |
|
883 | +| value | | 可空 | |
|
884 | +| type | | 可空 | |
|
885 | + |
|
886 | +- 界面维护字典数据 |
|
887 | + |
|
888 | +- 种子维护字典数据 |
|
889 | + |
|
890 | +系统内置了字典数据模型base_dict_value,字典类型模型base_dict_type |
|
891 | + |
|
892 | +```json |
|
893 | +{ |
|
894 | + "data": { |
|
895 | + "DictTypeSex": { |
|
896 | + "model": "base_dict_type", |
|
897 | + "properties": { |
|
898 | + "dictName": "用户性别", |
|
899 | + "dictType": "userSex", |
|
900 | + "status": 0 |
|
901 | + } |
|
902 | + }, |
|
903 | + "DictValueSexMan": { |
|
904 | + "model": "base_dict_value", |
|
905 | + "properties": { |
|
906 | + "dictLabel": "男", |
|
907 | + "dictValue": "0", |
|
908 | + "dictType": "userSex", |
|
909 | + "isDefault": true, |
|
910 | + "status": 0 |
|
911 | + } |
|
912 | + } |
|
913 | + } |
|
914 | +} |
|
915 | + |
|
916 | +``` |
|
917 | + |
|
918 | +## 3.2.6 特殊属性的查询 |
|
919 | + |
|
920 | +查询`@ManyToOne`、`@Selection`和`@Dict`注解标注的属性值时,会根据上下文参数`useDisplayForModel`返回两种不同格式的值。返回值格式一是数据库存储的值;格式二是Options或Options集合类型的值,方便界面显示。当useDisplayForModel为false或不存在时返回格式一,为true时返回格式二。 |
|
921 | + |
|
922 | +useDisplayForModel的值使用 `getMeta().addArgument(MetaConstant.USE_DISPLAY_FOR_MODEL, true)` 进行设置。 |
|
923 | + |
|
924 | +useDisplayForModel的值仅在第一次执行查询时有效,查询完毕会默认删除useDisplayForModel。如果传递的是`Arrays.asList("*")`,则useDisplayForModel不会生效,但查询完毕后依然会删除useDisplayForModel。 |
|
925 | + |
|
926 | +查询方法如下: |
|
927 | + |
|
928 | +| 方法 | 解释 | |
|
929 | +| ---------------------------------------- | -------------------------- | |
|
930 | +| select(Filter filter) | 查询Arrays.asList("*") | |
|
931 | +| select(Filter filter, String... columns) | 根据useDisplayForModel判定 | |
|
932 | +| selectById(String id) | 查询Arrays.asList("*") | |
|
933 | +| selectById(String id, String... columns) | 根据useDisplayForModel判定 | |
|
934 | +| search | 根据useDisplayForModel判定 | |
|
935 | +| read | 根据useDisplayForModel判定 | |
|
936 | + |
|
937 | +在前端调用查询方法时,会设置useDisplayForModel为true,其他情况不会设置该值。 |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/03.API\346\216\245\345\217\243\345\217\202\346\225\260\350\257\264\346\230\216.md
... | ... | @@ -0,0 +1,172 @@ |
1 | +---
|
|
2 | +title: API接口参数说明
|
|
3 | +date: 2023-09-25 17:31:35
|
|
4 | +permalink: /pages/b9a31b/
|
|
5 | +---
|
|
6 | +### 3.3.2 API接口请求参数和响应结果
|
|
7 | +
|
|
8 | +------
|
|
9 | +
|
|
10 | +#### 1. API请求参数
|
|
11 | +
|
|
12 | +```json
|
|
13 | +{
|
|
14 | + "id": "guid",
|
|
15 | + "jsonrpc": "2.0",
|
|
16 | + "method": "service",
|
|
17 | + "params": {
|
|
18 | + "args": {
|
|
19 | + "ids": [
|
|
20 | + "02254bc01kz5s"
|
|
21 | + ],
|
|
22 | + "values": {
|
|
23 | + "description": "设备",
|
|
24 | + "name": "iot_device",
|
|
25 | + "property_ids": [
|
|
26 | + [
|
|
27 | + 0,
|
|
28 | + 0,
|
|
29 | + {
|
|
30 | + "name": "ip",
|
|
31 | + "data_type": "String",
|
|
32 | + "display_name": "ip"
|
|
33 | + }
|
|
34 | + ]
|
|
35 | + ]
|
|
36 | + },
|
|
37 | + "valuesList": [
|
|
38 | + {
|
|
39 | + "tag": "master",
|
|
40 | + "app_ids": "021r397wz5fcw",
|
|
41 | + "property_ids": [
|
|
42 | + [
|
|
43 | + 0,
|
|
44 | + 0,
|
|
45 | + {
|
|
46 | + "name": "status",
|
|
47 | + "data_type": "Integer"
|
|
48 | + }
|
|
49 | + ]
|
|
50 | + ]
|
|
51 | + }
|
|
52 | + ],
|
|
53 | + "filter": [
|
|
54 | + "|",
|
|
55 | + [
|
|
56 | + "name",
|
|
57 | + "like",
|
|
58 | + "%admin"
|
|
59 | + ],
|
|
60 | + [
|
|
61 | + "email",
|
|
62 | + "like",
|
|
63 | + "%126"
|
|
64 | + ]
|
|
65 | + ],
|
|
66 | + "order": "",
|
|
67 | + "limit": 31,
|
|
68 | + "offset": 0,
|
|
69 | + "properties": [
|
|
70 | + "name",
|
|
71 | + "login",
|
|
72 | + "mobile"
|
|
73 | + ]
|
|
74 | + },
|
|
75 | + "context": {
|
|
76 | + "uid": "",
|
|
77 | + "lang": "zh_CN"
|
|
78 | + },
|
|
79 | + "useDisplayForModel": true,
|
|
80 | + "model": "rbac_user",
|
|
81 | + "tag": "master",
|
|
82 | + "service": "search",
|
|
83 | + "app": "base"
|
|
84 | + }
|
|
85 | +}
|
|
86 | +```
|
|
87 | +
|
|
88 | +
|
|
89 | +
|
|
90 | +#### 请求参数描述
|
|
91 | +
|
|
92 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
93 | +| ------------------ | ------- | ---- | ------------------------------------------------------------ | ------- |
|
|
94 | +| id | String | 否 | 请求Id,UUID格式 | |
|
|
95 | +| jsonrpc | String | 是 | jsonrpc版本,默认值:2.0 | 2.0 |
|
|
96 | +| method | String | 是 | 服务类型,默认值:service | service |
|
|
97 | +| model | String | 是 | 模型名称 | |
|
|
98 | +| service | String | 是 | 服务名称或者接口命名 | |
|
|
99 | +| tag | String | 是 | 版本号,默认值:master | master |
|
|
100 | +| app | String | 否 | 应用名称 | |
|
|
101 | +| context.uid | String | 否 | 用户id | |
|
|
102 | +| context.lang | String | 否 | 语言,默认值:zh_CN | zh_CN |
|
|
103 | +| properties | Array | 否 | 获取字段列表 | |
|
|
104 | +| limit | int | 否 | 分页limit,默认值:31 | |
|
|
105 | +| offset | int | 否 | 分页offset,默认值:0 | |
|
|
106 | +| order | String | 否 | 分页排序字段,示例:"id asc,create_date desc" | |
|
|
107 | +| filter | String | 否 | 查询过滤条件,示例:<br/>`["|",["name","like","%admin"],["email","like","%126"]]` | |
|
|
108 | +| useDisplayForModel | Boolean | 否 | 需要显示关联ManyToOne对象name时,设置为true | true |
|
|
109 | +| ids | Array | 否 | 主键id集合,示例: `["020uzt2d3sr9c"]` | |
|
|
110 | +| values | Object | 否 | service=update时,需要更新的对象字段 | |
|
|
111 | +| valuesList | Array | 否 | service=create时,需要创建的对象字段 | |
|
|
112 | +
|
|
113 | +
|
|
114 | +
|
|
115 | +
|
|
116 | +
|
|
117 | +#### 2.1 请求响应成功
|
|
118 | +
|
|
119 | +```json
|
|
120 | +{
|
|
121 | + "id": "guid",
|
|
122 | + "jsonrpc": "2.0",
|
|
123 | + "result": {
|
|
124 | + "data": [
|
|
125 | + {
|
|
126 | + "mobile": null,
|
|
127 | + "remark": null,
|
|
128 | + "login": "huangbaoming",
|
|
129 | + "name": "黄宝明",
|
|
130 | + "id": "02k94e932xgjk",
|
|
131 | + "email": null,
|
|
132 | + "status": "0"
|
|
133 | + }
|
|
134 | + ]
|
|
135 | + }
|
|
136 | +}
|
|
137 | +```
|
|
138 | +
|
|
139 | +#### 2.2 请求响应失败
|
|
140 | +
|
|
141 | +```json
|
|
142 | +{
|
|
143 | + "id": "guid",
|
|
144 | + "jsonrpc": "2.0",
|
|
145 | + "error": {
|
|
146 | + "code": 7100,
|
|
147 | + "message": "身份验证失败",
|
|
148 | + "data": {
|
|
149 | + "name": "java.lang.NullPointerException",
|
|
150 | + "debug": "java.lang.NullPointerException:com.sie.iidp.engine.container.Meta.setArguments(Meta.java:143)"
|
|
151 | + }
|
|
152 | + }
|
|
153 | +}
|
|
154 | +```
|
|
155 | +
|
|
156 | +
|
|
157 | +
|
|
158 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
159 | +| ---------- | ------ | ---- | -------------------- | ------ |
|
|
160 | +| id | String | 否 | 请求id | |
|
|
161 | +| jsonrpc | String | 是 | jsonrpc版本,默认:2.0 | |
|
|
162 | +| error | Object | 是 | 响应错误信息 | |
|
|
163 | +| code | int | 是 | 错误编码 | |
|
|
164 | +| data.name | String | 否 | 错写消息 | |
|
|
165 | +| data.debug | String | 否 | 异常详情 | |
|
|
166 | +
|
|
167 | +
|
|
168 | +
|
|
169 | +####
|
|
170 | +
|
|
171 | +
|
|
172 | +
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/04.\346\267\273\345\212\240\346\226\271\346\263\225\345\222\214\346\234\215\345\212\241.md
... | ... | @@ -0,0 +1,767 @@ |
1 | +---
|
|
2 | +title: 添加方法和服务
|
|
3 | +date: 2023-09-25 17:31:35
|
|
4 | +permalink: /pages/59895d/
|
|
5 | +---
|
|
6 | +### 3.3 添加方法和服务
|
|
7 | +
|
|
8 | +模型的方法在定义模型的java类中定义,但并不是类中所有的java方法都会成为模型的方法。满足以下规则的java方法才会被解析为模型的方法:
|
|
9 | +
|
|
10 | + ● 同一模型中方法名必须唯一。为了避免冲突,模型方法不支持重载,类似于Python的方法。
|
|
11 | +
|
|
12 | + ● 必须声明为public。模型类中定义的非public方法会被认为是辅助方法,不会解析为模型方法。
|
|
13 | +
|
|
14 | +模型的方法可以定义在多个扩展或多个被继承的模型中,它们可能在不同的java类中,并且这些java类没有继承关系。模型的方法可以在扩展或继承模型中重写。
|
|
15 | +
|
|
16 | +服务远程遵循 jsonrpc 2.0 协议,调用的地址:http(s)://ip:port/root/rpc/service,示例:
|
|
17 | +
|
|
18 | +清求:
|
|
19 | +
|
|
20 | +```json
|
|
21 | +{
|
|
22 | + "jsonrpc": "2.0",
|
|
23 | + "method": "read",
|
|
24 | + "id": "99BA5433-DF5F-A898-C8E0-78B8BA55F251",
|
|
25 | + "params": {
|
|
26 | + "args": {
|
|
27 | + "ids": [
|
|
28 | + "01ng9if6fve2o"
|
|
29 | + ],
|
|
30 | + "fields": [
|
|
31 | + "name",
|
|
32 | + "login"
|
|
33 | + ]
|
|
34 | + },
|
|
35 | + "model": "res.user",
|
|
36 | + "context": {
|
|
37 | + "tz": "",
|
|
38 | + "uid": "01ng9if6fve2o",
|
|
39 | + "lang": "zh_CN",
|
|
40 | + "ticket": "ticket",
|
|
41 | + "tenant": "tenant"
|
|
42 | + }
|
|
43 | + }
|
|
44 | +}
|
|
45 | +```
|
|
46 | +
|
|
47 | +响应:
|
|
48 | +
|
|
49 | +```json
|
|
50 | +{
|
|
51 | + "id": "99BA5433-DF5F-A898-C8E0-78B8BA55F251",
|
|
52 | + "result": {
|
|
53 | + "data": [
|
|
54 | + {
|
|
55 | + "name": "张三",
|
|
56 | + "id": "01no6a1ocg9hc",
|
|
57 | + "login": "zhangsan"
|
|
58 | + }
|
|
59 | + ],
|
|
60 | + "context": {
|
|
61 | + "token": "token"
|
|
62 | + }
|
|
63 | + },
|
|
64 | + "jsonrpc": "2.0"
|
|
65 | +}
|
|
66 | +```
|
|
67 | +
|
|
68 | +
|
|
69 | +
|
|
70 | +
|
|
71 | +
|
|
72 | +### 3.3.1. 服务声明
|
|
73 | +
|
|
74 | + 模型的方法只能在代码中调用,服务可以提供远程api调用。服务有两种方式声明:
|
|
75 | +
|
|
76 | + ● 在模型方法上使用@MethodService声明
|
|
77 | +
|
|
78 | + ● 在模型上使用@Service声明一个BaseService的子类提供服务方法
|
|
79 | +
|
|
80 | +在模型上使用@SDK.Service声明一个BaseService的子类提供服务方法该子类建议重写BaseService所有方法,包括before,execute,getArgsSpec,getResultSpec
|
|
81 | +
|
|
82 | +#### 3.3.1.1. @MethodService声明
|
|
83 | +
|
|
84 | +```java
|
|
85 | +@Model
|
|
86 | +public class TestUser extends BaseModel {
|
|
87 | +
|
|
88 | + @MethodService(description = "登录")
|
|
89 | + public Map<String, Object> login(RecordSet rec, String login, String password, boolean remember,
|
|
90 | + Map<String, Object> userAgent) {
|
|
91 | +
|
|
92 | + }
|
|
93 | +}
|
|
94 | +```
|
|
95 | +
|
|
96 | +
|
|
97 | +
|
|
98 | +#### 3.3.1.2. @Service声明并继承BaseService
|
|
99 | +
|
|
100 | +继承BaseService。
|
|
101 | +
|
|
102 | +```java
|
|
103 | +public class BuildPermissionService extends BaseService {
|
|
104 | + @Override
|
|
105 | + public void execute(Meta meta) {
|
|
106 | + DataAccessor da = meta.getDataAccessor();
|
|
107 | + String sql = "SELECT id, auth, model FROM rbac_permission WHERE active=%s";
|
|
108 | + da.execute(sql, Arrays.asList(true));
|
|
109 | + }
|
|
110 | +}
|
|
111 | +```
|
|
112 | +
|
|
113 | +
|
|
114 | +
|
|
115 | +模型上声明:
|
|
116 | +
|
|
117 | +```java
|
|
118 | +@Service(name = "load", path = BuildPermissionService.class)
|
|
119 | +@Model(name = "rbac_permission", displayName = "权限", orderBy = "model,name")
|
|
120 | +public class Permission extends Model {
|
|
121 | +}
|
|
122 | +```
|
|
123 | +
|
|
124 | +已知问题:如果模型扩展了另外一个应用模型,会导致无法加载该方法。
|
|
125 | +
|
|
126 | +
|
|
127 | +### 3.3.2. 通用模型服务
|
|
128 | +
|
|
129 | + 通用模型服务即每个模型都具备的服务。
|
|
130 | +
|
|
131 | +#### 3.3.2.1. create
|
|
132 | +
|
|
133 | +为模型创建新记录,请求:
|
|
134 | +
|
|
135 | +```json
|
|
136 | +{
|
|
137 | + "id": "guid",
|
|
138 | + "jsonrpc": "2.0",
|
|
139 | + "method": "service",
|
|
140 | + "params": {
|
|
141 | + "args": {
|
|
142 | + "valuesList": []
|
|
143 | + },
|
|
144 | + "service": "create",
|
|
145 | + "context": {
|
|
146 | + "uid": "",
|
|
147 | + "lang": "zh_CN"
|
|
148 | + },
|
|
149 | + "model": "meta_model",
|
|
150 | + "tag": "master"
|
|
151 | + }
|
|
152 | +}
|
|
153 | +```
|
|
154 | +
|
|
155 | +响应:
|
|
156 | +
|
|
157 | +```json
|
|
158 | +{
|
|
159 | + "id": "guid",
|
|
160 | + "jsonrpc": "2.0",
|
|
161 | + "result": {
|
|
162 | + "data": {},
|
|
163 | + "context": {}
|
|
164 | + }
|
|
165 | +}
|
|
166 | +```
|
|
167 | +
|
|
168 | +
|
|
169 | +
|
|
170 | +#### 3.3.2.2. delete
|
|
171 | +
|
|
172 | +删除当前集合的记录。
|
|
173 | +
|
|
174 | +请求:
|
|
175 | +
|
|
176 | +```json
|
|
177 | +{
|
|
178 | + "id": "guid",
|
|
179 | + "jsonrpc": "2.0",
|
|
180 | + "method": "service",
|
|
181 | + "params": {
|
|
182 | + "args": {
|
|
183 | + "ids": [
|
|
184 | + "id"
|
|
185 | + ]
|
|
186 | + },
|
|
187 | + "service": "delete",
|
|
188 | + "context": {
|
|
189 | + "uid": "",
|
|
190 | + "lang": "zh_CN"
|
|
191 | + },
|
|
192 | + "model": "meta_model",
|
|
193 | + "tag": "master"
|
|
194 | + }
|
|
195 | +}
|
|
196 | +```
|
|
197 | +
|
|
198 | +响应:
|
|
199 | +
|
|
200 | +```json
|
|
201 | +{
|
|
202 | + "id": "guid",
|
|
203 | + "jsonrpc": "2.0",
|
|
204 | + "result": {
|
|
205 | + "data": {},
|
|
206 | + "context": {}
|
|
207 | + }
|
|
208 | +}
|
|
209 | +```
|
|
210 | +
|
|
211 | +
|
|
212 | +
|
|
213 | +#### 3.3.2.3. update
|
|
214 | +
|
|
215 | +使用提供的值更新当前集中的所有记录。
|
|
216 | +
|
|
217 | +请求:
|
|
218 | +
|
|
219 | +```json
|
|
220 | +{
|
|
221 | + "id": "guid",
|
|
222 | + "jsonrpc": "2.0",
|
|
223 | + "method": "service",
|
|
224 | + "params": {
|
|
225 | + "args": {
|
|
226 | + "values": {},
|
|
227 | + "ids": [
|
|
228 | + "id"
|
|
229 | + ]
|
|
230 | + },
|
|
231 | + "service": "update",
|
|
232 | + "context": {
|
|
233 | + "uid": "",
|
|
234 | + "lang": "zh_CN"
|
|
235 | + },
|
|
236 | + "model": "meta_model",
|
|
237 | + "tag": "master"
|
|
238 | + }
|
|
239 | +}
|
|
240 | +```
|
|
241 | +
|
|
242 | +响应:
|
|
243 | +
|
|
244 | +```json
|
|
245 | +{
|
|
246 | + "id": "guid",
|
|
247 | + "jsonrpc": "2.0",
|
|
248 | + "result": {
|
|
249 | + "data": {},
|
|
250 | + "context": {}
|
|
251 | + }
|
|
252 | +}
|
|
253 | +```
|
|
254 | +
|
|
255 | +
|
|
256 | +
|
|
257 | +#### 3.3.2.4. read
|
|
258 | +
|
|
259 | +读取记录集指定字段的值。
|
|
260 | +
|
|
261 | +请求:
|
|
262 | +
|
|
263 | +```json
|
|
264 | +{
|
|
265 | + "id": "guid",
|
|
266 | + "jsonrpc": "2.0",
|
|
267 | + "method": "service",
|
|
268 | + "params": {
|
|
269 | + "args": {
|
|
270 | + "properties": []
|
|
271 | + },
|
|
272 | + "service": "read",
|
|
273 | + "context": {
|
|
274 | + "uid": "",
|
|
275 | + "lang": "zh_CN"
|
|
276 | + },
|
|
277 | + "model": "meta_model",
|
|
278 | + "tag": "master"
|
|
279 | + }
|
|
280 | +}
|
|
281 | +```
|
|
282 | +
|
|
283 | +响应:
|
|
284 | +
|
|
285 | +```json
|
|
286 | +{
|
|
287 | + "id": "guid",
|
|
288 | + "jsonrpc": "2.0",
|
|
289 | + "result": {
|
|
290 | + "data": {},
|
|
291 | + "context": {}
|
|
292 | + }
|
|
293 | +}
|
|
294 | +```
|
|
295 | +
|
|
296 | +#### 3.3.2.5. find
|
|
297 | +
|
|
298 | +根据参数搜索记录。
|
|
299 | +
|
|
300 | +请求:
|
|
301 | +
|
|
302 | +```json
|
|
303 | +{
|
|
304 | + "id": "guid",
|
|
305 | + "jsonrpc": "2.0",
|
|
306 | + "method": "service",
|
|
307 | + "params": {
|
|
308 | + "args": {
|
|
309 | + "filter": [],
|
|
310 | + "offset": 0,
|
|
311 | + "limit": 25,
|
|
312 | + "order": ""
|
|
313 | + },
|
|
314 | + "service": "find",
|
|
315 | + "context": {
|
|
316 | + "uid": "",
|
|
317 | + "lang": "zh_CN"
|
|
318 | + },
|
|
319 | + "model": "meta_model",
|
|
320 | + "tag": "master"
|
|
321 | + }
|
|
322 | +}
|
|
323 | +```
|
|
324 | +
|
|
325 | +响应:
|
|
326 | +
|
|
327 | +```json
|
|
328 | +{
|
|
329 | + "id": "guid",
|
|
330 | + "jsonrpc": "2.0",
|
|
331 | + "result": {
|
|
332 | + "data": {},
|
|
333 | + "context": {}
|
|
334 | + }
|
|
335 | +}
|
|
336 | +```
|
|
337 | +
|
|
338 | +
|
|
339 | +
|
|
340 | +#### 3.3.2.6. search
|
|
341 | +
|
|
342 | +搜索并读取记录集指定字段的值。
|
|
343 | +
|
|
344 | +请求:
|
|
345 | +
|
|
346 | +```json
|
|
347 | +{
|
|
348 | + "id": "guid",
|
|
349 | + "jsonrpc": "2.0",
|
|
350 | + "method": "service",
|
|
351 | + "params": {
|
|
352 | + "args": {
|
|
353 | + "filter": [],
|
|
354 | + "offset": 1,
|
|
355 | + "limit": 1,
|
|
356 | + "properties": [],
|
|
357 | + "order": ""
|
|
358 | + },
|
|
359 | + "service": "search",
|
|
360 | + "context": {
|
|
361 | + "uid": "",
|
|
362 | + "lang": "zh_CN"
|
|
363 | + },
|
|
364 | + "model": "meta_model",
|
|
365 | + "tag": "master"
|
|
366 | + }
|
|
367 | +}
|
|
368 | +```
|
|
369 | +
|
|
370 | +响应:
|
|
371 | +
|
|
372 | +```json
|
|
373 | +{
|
|
374 | + "id": "guid",
|
|
375 | + "jsonrpc": "2.0",
|
|
376 | + "result": {
|
|
377 | + "data": {},
|
|
378 | + "context": {}
|
|
379 | + }
|
|
380 | +}
|
|
381 | +```
|
|
382 | +
|
|
383 | +#### 3.3.2.7. count
|
|
384 | +
|
|
385 | +统计匹配条件的记录数。
|
|
386 | +
|
|
387 | +请求:
|
|
388 | +
|
|
389 | +```json
|
|
390 | +{
|
|
391 | + "id": "guid",
|
|
392 | + "jsonrpc": "2.0",
|
|
393 | + "method": "service",
|
|
394 | + "params": {
|
|
395 | + "args": {
|
|
396 | + "filter": []
|
|
397 | + },
|
|
398 | + "service": "count",
|
|
399 | + "context": {
|
|
400 | + "uid": "",
|
|
401 | + "lang": "zh_CN"
|
|
402 | + },
|
|
403 | + "model": "meta_model",
|
|
404 | + "tag": "master"
|
|
405 | + }
|
|
406 | +}
|
|
407 | +```
|
|
408 | +
|
|
409 | +响应:
|
|
410 | +
|
|
411 | +```json
|
|
412 | +{
|
|
413 | + "id": "guid",
|
|
414 | + "jsonrpc": "2.0",
|
|
415 | + "result": {
|
|
416 | + "data": {},
|
|
417 | + "context": {}
|
|
418 | + }
|
|
419 | +}
|
|
420 | +```
|
|
421 | +
|
|
422 | +#### 3.3.2.8. **exists**
|
|
423 | +
|
|
424 | +返回存在的记录
|
|
425 | +
|
|
426 | +请求
|
|
427 | +
|
|
428 | +```json
|
|
429 | +{
|
|
430 | + "id": "guid",
|
|
431 | + "jsonrpc": "2.0",
|
|
432 | + "method": "service",
|
|
433 | + "params": {
|
|
434 | + "args": {
|
|
435 | + "ids": [
|
|
436 | + "id"
|
|
437 | + ]
|
|
438 | + },
|
|
439 | + "service": "exists",
|
|
440 | + "context": {
|
|
441 | + "uid": "",
|
|
442 | + "lang": "zh_CN"
|
|
443 | + },
|
|
444 | + "model": "meta_app_dependency",
|
|
445 | + "tag": "master"
|
|
446 | + }
|
|
447 | +}
|
|
448 | +```
|
|
449 | +
|
|
450 | +响应
|
|
451 | +
|
|
452 | +```json
|
|
453 | +{
|
|
454 | + "id": "guid",
|
|
455 | + "jsonrpc": "2.0",
|
|
456 | + "result": {
|
|
457 | + "data": {},
|
|
458 | + "context": {}
|
|
459 | + }
|
|
460 | +}
|
|
461 | +```
|
|
462 | +
|
|
463 | +
|
|
464 | +
|
|
465 | +### 3.3.3. 自定义模型服务
|
|
466 | +
|
|
467 | +返回传入的参数
|
|
468 | +
|
|
469 | +
|
|
470 | +
|
|
471 | +请求
|
|
472 | +
|
|
473 | +```json
|
|
474 | +{
|
|
475 | + "id": "guid",
|
|
476 | + "jsonrpc": "2.0",
|
|
477 | + "method": "service",
|
|
478 | + "params": {
|
|
479 | + "args": {
|
|
480 | + "userName": "test"
|
|
481 | + },
|
|
482 | + "service": "find2",
|
|
483 | + "context": {
|
|
484 | + "uid": "",
|
|
485 | + "lang": "zh_CN"
|
|
486 | + },
|
|
487 | + "model": "mom_item",
|
|
488 | + "tag": "master"
|
|
489 | + }
|
|
490 | +}
|
|
491 | +```
|
|
492 | +
|
|
493 | +响应
|
|
494 | +
|
|
495 | +```json
|
|
496 | +{
|
|
497 | + "id": "guid",
|
|
498 | + "jsonrpc": "2.0",
|
|
499 | + "result": {
|
|
500 | + "data": {},
|
|
501 | + "context": {}
|
|
502 | + }
|
|
503 | +}
|
|
504 | +```
|
|
505 | +
|
|
506 | +### 3.3.4 API服务接口请求参数和响应结果
|
|
507 | +
|
|
508 | +#### 3.3.4.1 API请求参数
|
|
509 | +
|
|
510 | +```json
|
|
511 | +{
|
|
512 | + "id": "guid",
|
|
513 | + "jsonrpc": "2.0",
|
|
514 | + "method": "service",
|
|
515 | + "params": {
|
|
516 | + "args": {
|
|
517 | + "ids": [
|
|
518 | + "02254bc01kz5s"
|
|
519 | + ],
|
|
520 | + "values": {
|
|
521 | + "description": "设备",
|
|
522 | + "name": "iot_device",
|
|
523 | + "property_ids": [
|
|
524 | + [
|
|
525 | + 0,
|
|
526 | + 0,
|
|
527 | + {
|
|
528 | + "name": "ip",
|
|
529 | + "data_type": "String",
|
|
530 | + "display_name": "ip"
|
|
531 | + }
|
|
532 | + ]
|
|
533 | + ]
|
|
534 | + },
|
|
535 | + "valuesList": [
|
|
536 | + {
|
|
537 | + "tag": "master",
|
|
538 | + "app_ids": "021r397wz5fcw",
|
|
539 | + "property_ids": [
|
|
540 | + [
|
|
541 | + 0,
|
|
542 | + 0,
|
|
543 | + {
|
|
544 | + "name": "status",
|
|
545 | + "data_type": "Integer"
|
|
546 | + }
|
|
547 | + ]
|
|
548 | + ]
|
|
549 | + }
|
|
550 | + ],
|
|
551 | + "filter": [
|
|
552 | + "|",
|
|
553 | + [
|
|
554 | + "name",
|
|
555 | + "like",
|
|
556 | + "%admin"
|
|
557 | + ],
|
|
558 | + [
|
|
559 | + "email",
|
|
560 | + "like",
|
|
561 | + "%126"
|
|
562 | + ]
|
|
563 | + ],
|
|
564 | + "order": "",
|
|
565 | + "limit": 31,
|
|
566 | + "offset": 0,
|
|
567 | + "properties": [
|
|
568 | + "name",
|
|
569 | + "login",
|
|
570 | + "mobile"
|
|
571 | + ]
|
|
572 | + },
|
|
573 | + "context": {
|
|
574 | + "uid": "",
|
|
575 | + "lang": "zh_CN"
|
|
576 | + },
|
|
577 | + "useDisplayForModel": true,
|
|
578 | + "model": "rbac_user",
|
|
579 | + "tag": "master",
|
|
580 | + "service": "search",
|
|
581 | + "app": "base"
|
|
582 | + }
|
|
583 | +}
|
|
584 | +```
|
|
585 | +
|
|
586 | +
|
|
587 | +
|
|
588 | +#### 请求参数描述
|
|
589 | +
|
|
590 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
591 | +| ------------------ | ------- | ---- | ------------------------------------------------------------ | ------- |
|
|
592 | +| id | String | 否 | 请求Id,UUID格式 | |
|
|
593 | +| jsonrpc | String | 是 | jsonrpc版本,默认值:2.0 | 2.0 |
|
|
594 | +| method | String | 是 | 服务类型,默认值:service | service |
|
|
595 | +| model | String | 是 | 模型名称 | |
|
|
596 | +| service | String | 是 | 服务名称或者接口命名 | |
|
|
597 | +| tag | String | 是 | 版本号,默认值:master | master |
|
|
598 | +| app | String | 否 | 应用名称 | |
|
|
599 | +| context.uid | String | 否 | 用户id | |
|
|
600 | +| context.lang | String | 否 | 语言,默认值:zh_CN | zh_CN |
|
|
601 | +| properties | Array | 否 | 获取字段列表 | |
|
|
602 | +| limit | int | 否 | 分页limit,默认值:31 | |
|
|
603 | +| offset | int | 否 | 分页offset,默认值:0 | |
|
|
604 | +| order | String | 否 | 分页排序字段,示例:"id asc,create_date desc" | |
|
|
605 | +| filter | String | 否 | 查询过滤条件,示例:<br/>`["\|",["name","like","%admin"],["email","like","%126"]]` | |
|
|
606 | +| useDisplayForModel | Boolean | 否 | 需要显示关联ManyToOne对象name时,设置为true | true |
|
|
607 | +| ids | Array | 否 | 主键id集合,示例: `["020uzt2d3sr9c"]` | |
|
|
608 | +| values | Object | 否 | service=update时,需要更新的对象字段 | |
|
|
609 | +| valuesList | Array | 否 | service=create时,需要创建的对象字段 | |
|
|
610 | +
|
|
611 | +
|
|
612 | +
|
|
613 | +#### 3.3.4.2 请求响应成功
|
|
614 | +
|
|
615 | +```json
|
|
616 | +{
|
|
617 | + "id": "guid",
|
|
618 | + "jsonrpc": "2.0",
|
|
619 | + "result": {
|
|
620 | + "data": [
|
|
621 | + {
|
|
622 | + "mobile": null,
|
|
623 | + "remark": null,
|
|
624 | + "login": "huangbaoming",
|
|
625 | + "name": "黄宝明",
|
|
626 | + "id": "02k94e932xgjk",
|
|
627 | + "email": null,
|
|
628 | + "status": "0"
|
|
629 | + }
|
|
630 | + ]
|
|
631 | + }
|
|
632 | +}
|
|
633 | +```
|
|
634 | +
|
|
635 | +#### 3.3.4.3 请求响应失败
|
|
636 | +
|
|
637 | +```json
|
|
638 | +{
|
|
639 | + "id": "guid",
|
|
640 | + "jsonrpc": "2.0",
|
|
641 | + "error": {
|
|
642 | + "code": 7100,
|
|
643 | + "message": "身份验证失败",
|
|
644 | + "data": {
|
|
645 | + "name": "java.lang.NullPointerException",
|
|
646 | + "debug": "java.lang.NullPointerException:com.sie.iidp.engine.container.Meta.setArguments(Meta.java:143)"
|
|
647 | + }
|
|
648 | + }
|
|
649 | +}
|
|
650 | +```
|
|
651 | +
|
|
652 | +
|
|
653 | +
|
|
654 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
655 | +| ---------- | ------ | ---- | -------------------- | ------ |
|
|
656 | +| id | String | 否 | 请求id | |
|
|
657 | +| jsonrpc | String | 是 | jsonrpc版本,默认:2.0 | |
|
|
658 | +| error | Object | 是 | 响应错误信息 | |
|
|
659 | +| code | int | 是 | 错误编码 | |
|
|
660 | +| data.name | String | 否 | 错写消息 | |
|
|
661 | +| data.debug | String | 否 | 异常详情 | |
|
|
662 | +
|
|
663 | +
|
|
664 | +
|
|
665 | +
|
|
666 | +
|
|
667 | +### 3.3.5. 服务编排
|
|
668 | +
|
|
669 | +服务编排一般用来,对已有的服务进行组合编排,对外实现新的服务能力。
|
|
670 | +
|
|
671 | +#### 3.3.5.1. 声明新服务
|
|
672 | +
|
|
673 | +```java
|
|
674 | + @MethodService(name = "service3", auth = "read", description = "测试模型model2服务3", ServiceOrchestrateDefine = TestServiceOrchestrateDefine.class)
|
|
675 | + public String service3(String param3) {
|
|
676 | + System.out.println("model2_service3");
|
|
677 | + System.out.println("入口参数param3:" + param3);
|
|
678 | + return param3;
|
|
679 | + }
|
|
680 | +```
|
|
681 | +
|
|
682 | +
|
|
683 | +
|
|
684 | +#### 3.3.5.2. 指定编排逻辑
|
|
685 | +
|
|
686 | +通过@MethodService中ServiceOrchestrateDefine指定编排逻辑
|
|
687 | +```java
|
|
688 | +/**
|
|
689 | + * 服务编排测试,使用用户模型的服务进行测试
|
|
690 | + */
|
|
691 | +public class TestServiceOrchestrateDefine implements IServiceOrchestrateDefine {
|
|
692 | + @Override
|
|
693 | + public ServiceOrchestrate execute(String soName) {
|
|
694 | +
|
|
695 | + //有向图的数据结构
|
|
696 | + //服务编排组成元素定义
|
|
697 | + ServiceOrchestrateObjectFactory serviceOrchestrateObjectFactory = new ServiceOrchestrateObjectFactory();
|
|
698 | + BaseMeta start = serviceOrchestrateObjectFactory.createStart(soName);
|
|
699 | + List<BaseMeta> list = new ArrayList<>();
|
|
700 | + list.add(start);
|
|
701 | + ServiceOrchestrate serviceOrchestrate = ServiceOrchestrate.createServiceOrchestrate(soName, list);
|
|
702 | + /**
|
|
703 | + * start -> refModel1Service1 -> conditionGateway1 -> refModel2Service2 -> end
|
|
704 | + * -> condition1 -> refModel1Service2 -> refModel2Service1 -> end
|
|
705 | + * -> condition2 -> refModel1Service3 -> refModel2Service4 -> end
|
|
706 | + */
|
|
707 | +
|
|
708 | + //服务编排定义
|
|
709 | + //property\model Map<k,V>
|
|
710 | + serviceOrchestrate
|
|
711 | + .start()
|
|
712 | + .forwardService("refModel1Service1")
|
|
713 | + .forwardConditionGateway(
|
|
714 | + "conditionGateway1", "refModel2Service2",
|
|
715 | + "condition1", "condition2").end()
|
|
716 | + .append("conditionGateway1.condition1", "refModel1Service2").end()
|
|
717 | + .append("conditionGateway1.condition2", "refModel1Service3").end();
|
|
718 | + serviceOrchestrate.append("refModel1Service2", "refModel2Service1");
|
|
719 | + return serviceOrchestrate.append("refModel1Service3", "refModel2Service4").build();
|
|
720 | + }
|
|
721 | +}
|
|
722 | +```
|
|
723 | +#### 3.3.5.3. 新服务调用
|
|
724 | +
|
|
725 | +新定义编排服务,与常规服务调用方式一样,通过标准的api格式调用。
|
|
726 | +
|
|
727 | +### 3.3.6 异步执行
|
|
728 | +
|
|
729 | +#### 3.3.6.1模型开启新线程执行方法
|
|
730 | +
|
|
731 | +
|
|
732 | +
|
|
733 | +#### 3.3.6.2 第三方线程调用模型方法
|
|
734 | +
|
|
735 | +```java
|
|
736 | +
|
|
737 | + @Ignore
|
|
738 | + private static Meta meta1;
|
|
739 | +
|
|
740 | + /**
|
|
741 | + * 在该方法被执行时,缓存meta
|
|
742 | + * @param rs
|
|
743 | + */
|
|
744 | + public void start(RecordSet rs) {
|
|
745 | + meta1 = rs.getMeta();//在启动事件时(二选一即可)
|
|
746 | + meta1 = getMeta();// 服务被调用时(二选一即可)
|
|
747 | + }
|
|
748 | +
|
|
749 | + /**
|
|
750 | + * 只能在第三方线程调用该方法
|
|
751 | + * @param data
|
|
752 | + */
|
|
753 | + public static void test1(Map<String, Object> data) {
|
|
754 | + try (Meta meta = meta1.createNewMeta()) {
|
|
755 | + BaseContextHandler.setMeta(meta);
|
|
756 | +
|
|
757 | + //执行模型操作
|
|
758 | + Customer customer = new Customer();
|
|
759 | + customer.setName(Objects.toString(data.get("name")));
|
|
760 | + customer.create();
|
|
761 | +
|
|
762 | + BaseContextHandler.remove();
|
|
763 | + meta.flush();
|
|
764 | + }
|
|
765 | + }
|
|
766 | +```
|
|
767 | +
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/05.\346\267\273\345\212\240\350\247\204\345\210\231.md
... | ... | @@ -0,0 +1,134 @@ |
1 | +---
|
|
2 | +title: 添加规则
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/651934/
|
|
5 | +---
|
|
6 | +规则引擎由推理引擎发展而来,是一种嵌入在应用程序中的组件,实现了将业务决策从应用程序代码中分离出来,并使用预定义的语义模块编写业务决策。
|
|
7 | +
|
|
8 | +#### 3.4.1.1. 规则应用场景
|
|
9 | +
|
|
10 | +对界面和菜单项的访问,数据模型中实体和属性级别的访问,数据满足某些条件的访问,例如,用户所在部门的权限,开发者可以轻松配置高级访问控制规则,且确保规则完全透明。
|
|
11 | +
|
|
12 | +#### 3.4.1.2. 规则的定义
|
|
13 | +
|
|
14 | +一个规则满足说明条件执行上面动作拥有规则的名称,规则描述,以及规则优先级。Codition注解与Action注解是成对出现,必须要有条件动作规则名称。我们可以用规则注解去描述一段业务行为,例如控制权限,对用户以及视图,菜单,属性,数据的访问。
|
|
15 | +
|
|
16 | +#### 3.4.1.3. 什么是规则引擎
|
|
17 | +
|
|
18 | +使用注解定义规则,引擎提供了一下注解定义,首先,注解都是成对出现,其次,注解定义在类上。
|
|
19 | +
|
|
20 | +每个注解都有一个Name不能为空,作为唯一凭证。
|
|
21 | +
|
|
22 | +规则如下所示:
|
|
23 | +
|
|
24 | + ● Name :规则名称
|
|
25 | +
|
|
26 | + ● Description :规则描述
|
|
27 | +
|
|
28 | + ● Priority :规则执行优先级
|
|
29 | +
|
|
30 | + ● @Condition : 为了应用规则而必须满足的一组条件
|
|
31 | +
|
|
32 | + ● @Action : 当条件满足时执行的一组动作
|
|
33 | +
|
|
34 | +#### 3.4.1.4. **规则注解**
|
|
35 | +
|
|
36 | +##### Action注解
|
|
37 | +
|
|
38 | +| 字段名 | 中文名 | 可选值 | 描述 |
|
|
39 | +| --------- | -------- | --------------------------------------------------- | ------------------------------------------------------------ |
|
|
40 | +| name | 名称 | r01 | 规则名称 |
|
|
41 | +| resource | 静态资源 | @Resource 静态资源 | 菜单、实体、属性这些都属于静态资源 |
|
|
42 | +| filter | 过滤条件 | @Filter 过滤条件 | 条件组合 |
|
|
43 | +| method | 方法调用 | @Method 方法调用 | |
|
|
44 | +| condition | 复合条件 | & \| () & 同时满足执行 \| 任意满足执行 括号优先级 | 填@condition注解上面定义的名称,例如“r01”那么填入r01即可,如果需要多个条件可以用r02&r01即可如果两个条件任意生效可以是r01\|ro2 如果必须两边都要执行(r01&r02)\| (r03&r04) |
|
|
45 | +
|
|
46 | +代码示例:
|
|
47 | +
|
|
48 | +```java
|
|
49 | +@Rules.Condition(scope = ScopeEnum.USER, object = "02hej7bkhto1t", name = "r01")
|
|
50 | +@Rules.Action(name = "r11", condition = "r01", resources = @Rules.Resource(name = "r02", type = ResourceType.MENU, resource = "base_developer_center"))
|
|
51 | +```
|
|
52 | +
|
|
53 | +
|
|
54 | +
|
|
55 | +##### Resource注解
|
|
56 | +
|
|
57 | +| 字段名 | 中文名 | 可选值 |
|
|
58 | +| -------- | ------ | ---------------------- |
|
|
59 | +| type | 类型 | 菜单、视图、属性、服务 |
|
|
60 | +| action | 事件 | |
|
|
61 | +| resource | 资源 | meta_model_menu |
|
|
62 | +| effect | 允许 | 拒绝、允许 |
|
|
63 | +| name | 名称 | r01 |
|
|
64 | +
|
|
65 | +代码示例:
|
|
66 | +
|
|
67 | +```java
|
|
68 | +@Rules.Action(name = "r02", condition = "r01", resources = {@Rules.Resource(type = ResourceType.MENU, resource = "", name = "r03"), @Rules.Resource(type = ResourceType.MODEL, resource = "", name = "r04")})
|
|
69 | +```
|
|
70 | +
|
|
71 | +
|
|
72 | +
|
|
73 | +##### Method注解
|
|
74 | +
|
|
75 | +| 字段名 | 中文名 | 可选值 |
|
|
76 | +| --------- | ---------- | -------------- |
|
|
77 | +| name | 名称 | |
|
|
78 | +| model | 模型名 | rbac_user |
|
|
79 | +| parameter | 参数 | |
|
|
80 | +| BEA | 注入前后 | AFTER / BEFORE |
|
|
81 | +| methodSrc | 方法名 | 自定义服务名 |
|
|
82 | +| modelDst | 注入模型名 | rbac_user |
|
|
83 | +| methodDst | 注入服务名 | aa |
|
|
84 | +
|
|
85 | +代码示例:
|
|
86 | +
|
|
87 | +```java
|
|
88 | +@Rules.Action(name = "r02", condition = "r01", methods = {@Rules.Method(modelDst = "rbac_user", model = "rbac_user", methodSrc = "login", methodDst = "aa", name = "r03")})
|
|
89 | +```
|
|
90 | +
|
|
91 | +
|
|
92 | +
|
|
93 | +##### Filter注解
|
|
94 | +
|
|
95 | +| 字段名 | 中文名 | 可选值 |
|
|
96 | +| ------- | -------- | ------------------------- |
|
|
97 | +| name | 名称 | |
|
|
98 | +| model | 模型名 | rbac_user |
|
|
99 | +| filter | 过滤条件 | [["name", "like", "%a%"]] |
|
|
100 | +| context | 上下文 | |
|
|
101 | +
|
|
102 | +代码示例:
|
|
103 | +
|
|
104 | +```java
|
|
105 | +@Rules.Action(name = "r02", condition = "r01", filters = {@Rules.Filter(filter = "[['name', 'like', '%a%']]", model = "rbac_user", name = "r03")})
|
|
106 | +```
|
|
107 | +
|
|
108 | +
|
|
109 | +
|
|
110 | +###### Condition注解
|
|
111 | +
|
|
112 | +| 字段名 | 中文名 | 可选值 |
|
|
113 | +| ------ | ------ | ------------------------ |
|
|
114 | +| scope | 作用域 | 用户、全局、租户、自定义 |
|
|
115 | +| object | 对象 | {"admin"} |
|
|
116 | +| name | 名称 | r01 |
|
|
117 | +| script | 脚本 | 默认groovy脚本 |
|
|
118 | +
|
|
119 | +如下所示是一组规则,r02规则描述用户登录之前先执行xx方法
|
|
120 | +
|
|
121 | +```java
|
|
122 | +@Rules.Condition(scope = ScopeEnum.GLOBAL, name = "r02")
|
|
123 | +@Rules.Action(methods = @Rules.Method(methodDst = "rbac_user.login", methodSrc = "rbac_user.xx"), name = "r02")
|
|
124 | +```
|
|
125 | +
|
|
126 | +
|
|
127 | +
|
|
128 | +如下所示就是一组规则,r01规则描述admin用户不能访问开发者中心,其他菜单可以访问。一组规则必须包含规则名称,规则条件,规则执行动作。
|
|
129 | +
|
|
130 | +```java
|
|
131 | +@Rules.Condition(scope = ScopeEnum.USER, object = "admin", name = "r01")
|
|
132 | +@Rules.Action(resources = @Rules.Resource(type = ResourceType.MENU, resource = "base_developer_center"), name = "r01")
|
|
133 | +```
|
|
134 | +
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/06.\345\216\237\347\224\237sql\346\211\247\350\241\214.md
... | ... | @@ -0,0 +1,116 @@ |
1 | +---
|
|
2 | +title: 原生sql执行
|
|
3 | +date: 2023-09-25 17:31:35
|
|
4 | +permalink: /pages/77e0e8/
|
|
5 | +---
|
|
6 | +### 执行SQL
|
|
7 | +
|
|
8 | +遇到模型无法实现的复杂查询或者基于性能原因,可以通过BaseContextHandler获取上下文Meta,然后通过Meta的getDataAccessor方法获取当前数据库访问信息,允许直接执行SQL。
|
|
9 | +
|
|
10 | +```java
|
|
11 | + public void addManualModels(MetaContainer ctn) {
|
|
12 | + BussModelDataAccess bussModelDataAccess = (BussModelDataAccess) ModelDataAccessFactory.getDataAccess(ModelTypeEnum.Buss);
|
|
13 | + RelationDBAccessor cr = bussModelDataAccess.getRelationDBAccessor();
|
|
14 | + cr.execute("SELECT * FROM meta_model WHERE source=%s", Arrays.asList("manual"));
|
|
15 | + for (Map<String, Object> data : cr.fetchMapAll()) {
|
|
16 | + ModelMeta modelMeta = new ModelMeta();
|
|
17 | + modelMeta = BaseMetaUtil.convertModel(data);
|
|
18 | + ctn.addModel(modelMeta);
|
|
19 | + }
|
|
20 | + }
|
|
21 | +```
|
|
22 | +
|
|
23 | +
|
|
24 | +
|
|
25 | +执行自定义的`INSERT\UPDATE\DELETE` SQL后,需要调用Cache的`invalidate()`方法清除缓存以避免模型的数据不一致。
|
|
26 | +
|
|
27 | +```java
|
|
28 | +rs.getMeta().getStore().invalidate();
|
|
29 | +```
|
|
30 | +
|
|
31 | +
|
|
32 | +
|
|
33 | +#### 3.5.1.1. 多条件传参
|
|
34 | +
|
|
35 | +```java
|
|
36 | +cr.execute("SELECT * FROM meta_model WHERE field1=%s and field2=%s", Arrays.asList("field1Value","field2Value"));
|
|
37 | +```
|
|
38 | +
|
|
39 | +
|
|
40 | +
|
|
41 | +#### 3.5.1.2. in查询传参
|
|
42 | +
|
|
43 | +```java
|
|
44 | +cr.execute("SELECT * FROM meta_model WHERE field1 in %s ", Arrays.asList(Arrays.asList("field1Value")));
|
|
45 | +```
|
|
46 | +
|
|
47 | +#### 3.5.1.3. 示例代码
|
|
48 | +
|
|
49 | +```java
|
|
50 | +
|
|
51 | +@Model(name = "user", description = "用户")
|
|
52 | +public class User extends BaseModel {
|
|
53 | +
|
|
54 | + @Property(length = 128, displayName = "名称", displayForModel = true)
|
|
55 | + private String name;
|
|
56 | +
|
|
57 | + @Property(length = 128, displayName = "地址", displayForModel = true)
|
|
58 | + private String address;
|
|
59 | +
|
|
60 | +
|
|
61 | + /**
|
|
62 | + * 直接执行SQL
|
|
63 | + */
|
|
64 | + @MethodService(name = "fun1")
|
|
65 | + public void fun1() {
|
|
66 | + //获取数据库连接
|
|
67 | + BussModelDataAccess bussModelDataAccess = (BussModelDataAccess) ModelDataAccessFactory.getDataAccess(ModelTypeEnum.Buss);
|
|
68 | + RelationDBAccessor dataAccessor = bussModelDataAccess.getRelationDBAccessor();
|
|
69 | + //执行sql
|
|
70 | + dataAccessor.execute("SELECT * from User ");
|
|
71 | + //获取执行结果
|
|
72 | + List<Map<String, Object>> appMapList = dataAccessor.fetchMapAll();
|
|
73 | + for (Map<String, Object> stringObjectMap : appMapList) {
|
|
74 | + System.out.println(stringObjectMap.toString());
|
|
75 | + }
|
|
76 | + }
|
|
77 | +
|
|
78 | + /**
|
|
79 | + * 多条件传参
|
|
80 | + */
|
|
81 | + @MethodService(name = "fun2")
|
|
82 | + public void fun2() {
|
|
83 | + //获取数据库连接
|
|
84 | + BussModelDataAccess bussModelDataAccess = (BussModelDataAccess) ModelDataAccessFactory.getDataAccess(ModelTypeEnum.Buss);
|
|
85 | + RelationDBAccessor dataAccessor = bussModelDataAccess.getRelationDBAccessor();
|
|
86 | + //执行sql
|
|
87 | + dataAccessor.execute("SELECT * from User WHERE name=%s and address=%s", Arrays.asList("zs", "fs"));
|
|
88 | + //获取执行结果
|
|
89 | + List<Map<String, Object>> appMapList = dataAccessor.fetchMapAll();
|
|
90 | + for (Map<String, Object> stringObjectMap : appMapList) {
|
|
91 | + System.out.println(stringObjectMap.toString());
|
|
92 | + }
|
|
93 | + }
|
|
94 | +
|
|
95 | + /**
|
|
96 | + * in查询传参
|
|
97 | + */
|
|
98 | + @MethodService(name = "fun3")
|
|
99 | + public void fun3() {
|
|
100 | + //获取数据库连接
|
|
101 | + BussModelDataAccess bussModelDataAccess = (BussModelDataAccess) ModelDataAccessFactory.getDataAccess(ModelTypeEnum.Buss);
|
|
102 | + RelationDBAccessor dataAccessor = bussModelDataAccess.getRelationDBAccessor();
|
|
103 | + //执行sql
|
|
104 | + dataAccessor.execute("SELECT * from User WHERE name=%s and address=%s", Arrays.asList("zs", "fs"));
|
|
105 | + dataAccessor.execute("SELECT * from User WHERE name in %s ",
|
|
106 | + Arrays.asList(Arrays.asList("zs", "ls")));
|
|
107 | + //获取执行结果
|
|
108 | + List<Map<String, Object>> appMapList = dataAccessor.fetchMapAll();
|
|
109 | + for (Map<String, Object> stringObjectMap : appMapList) {
|
|
110 | + System.out.println(stringObjectMap.toString());
|
|
111 | + }
|
|
112 | + }
|
|
113 | +
|
|
114 | +}
|
|
115 | +```
|
|
116 | +
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/07.\345\205\250\345\261\200\346\234\215\345\212\241.md
... | ... | @@ -0,0 +1,6 @@ |
1 | +---
|
|
2 | +title: 全局服务
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/9d7a12/
|
|
5 | +---
|
|
6 | +待补充! |
|
... | ... | \ No newline at end of file |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/08.\345\244\226\351\203\250\346\234\215\345\212\241\345\257\271\346\216\245.md
... | ... | @@ -0,0 +1,6 @@ |
1 | +---
|
|
2 | +title: 外部服务对接
|
|
3 | +date: 2023-09-25 17:31:35
|
|
4 | +permalink: /pages/97be8b/
|
|
5 | +---
|
|
6 | +待补充! |
|
... | ... | \ No newline at end of file |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/09.\345\237\272\346\234\254\350\247\206\345\233\276.md
... | ... | @@ -0,0 +1,1265 @@ |
1 | +---
|
|
2 | +title: 基本视图
|
|
3 | +date: 2023-09-25 17:31:35
|
|
4 | +permalink: /pages/405eb0/
|
|
5 | +---
|
|
6 | +# 基本视图
|
|
7 | +
|
|
8 | +我们能够为给定模型生成默认视图。在实践中,业务应用程序永远不会接受默认视图。相反,我们至少应该以一种合乎逻辑的方式组织各个领域。
|
|
9 | +
|
|
10 | +视图在带有操作和菜单的 JSON 文件中定义。它们是 ui_view模型的实例。
|
|
11 | +
|
|
12 | +视图配置: app.json 中view节点配置视图的路径
|
|
13 | +
|
|
14 | +视图路径: 在resolved包下新建views目录,所有视图文件必须放在views目录
|
|
15 | +
|
|
16 | +data种子数据路径: 在resolved包下新建data目录,所有数据文件必须放在data目录
|
|
17 | +
|
|
18 | +
|
|
19 | +
|
|
20 | +```json
|
|
21 | +{
|
|
22 | + "name": "base",
|
|
23 | + "displayName": "base",
|
|
24 | + "company": "sie",
|
|
25 | + "category": "base",
|
|
26 | + "description": "基础模块",
|
|
27 | + "summary": "基础模块",
|
|
28 | + "type": "SDK",
|
|
29 | + "tag": "1.0",
|
|
30 | + "resolved": "com.sie.iidp.base",
|
|
31 | + "dependencies": [],
|
|
32 | + "license": "LGPL 3.0",
|
|
33 | + "view": [
|
|
34 | + "views/ui_view_view.json",
|
|
35 | + "views/menu_view.json"
|
|
36 | + ],
|
|
37 | + "data": [
|
|
38 | + "data/rbac_user.json"
|
|
39 | + ]
|
|
40 | +}
|
|
41 | +```
|
|
42 | +
|
|
43 | +## 3.8.1.1. 表单(form)
|
|
44 | +
|
|
45 | +### 表单视图(form)
|
|
46 | +
|
|
47 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
48 | +| ----- | ------ | --- | --- | ------- |
|
|
49 | +| name | String | 是 | 名称 | |
|
|
50 | +| model | String | 是 | 模型 | 模型名 |
|
|
51 | +| type | String | 否 | 类型 | form |
|
|
52 | +| mode | String | 是 | 模式 | primary |
|
|
53 | +| body | Object | | | |
|
|
54 | +| showEditBtn | Boolean | 否 | 详情页面是否显示编辑按钮 | |
|
|
55 | +
|
|
56 | +主要信息
|
|
57 | +
|
|
58 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
59 | +| ------- | ------ | --- | --- | ---------- |
|
|
60 | +| type | String | 是 | 类型 | |
|
|
61 | +| columns | String | 是 | 属性 | |
|
|
62 | +| tabs | Array | 否 | 切换 | 参考4.7.1.11 |
|
|
63 | +| tbar | Array | 否 | 工具栏 | 参考4.7.1.5 |
|
|
64 | +| showEditBtn | Boolean | 否 | 详情页面是否显示编辑按钮 | |
|
|
65 | +示例代码:
|
|
66 | +
|
|
67 | +注意:如果需要显示ER关联属性,必须在columns添加相关字段
|
|
68 | +
|
|
69 | +```json
|
|
70 | +"rbac_user_form": {
|
|
71 | + "name": "用户表单",
|
|
72 | + "model": "rbac_user",
|
|
73 | + "mode": "primary",
|
|
74 | + "body": {
|
|
75 | + "type": "form",
|
|
76 | + "columns": [
|
|
77 | + "name",
|
|
78 | + "login",
|
|
79 | + "mobile",
|
|
80 | + "email",
|
|
81 | + "role_ids" //如果需要显示ER关联属性,必须在columns添加相关字段
|
|
82 | + ],
|
|
83 | + "tabs": [
|
|
84 | + {
|
|
85 | + "header": "角色",
|
|
86 | + "tbar": [
|
|
87 | + {
|
|
88 | + "name": "添加",
|
|
89 | + "action": "addEr",
|
|
90 | + "auth": "update"
|
|
91 | + },
|
|
92 | + {
|
|
93 | + "name": "删除",
|
|
94 | + "action": "deleteEr",
|
|
95 | + "auth": "delete"
|
|
96 | + }
|
|
97 | + ],
|
|
98 | + "body": {
|
|
99 | + "type": "grid,form",
|
|
100 | + "field": "role_ids",
|
|
101 | + "columns": [
|
|
102 | + "name",
|
|
103 | + "is_admin"
|
|
104 | + ]
|
|
105 | + }
|
|
106 | + }
|
|
107 | + ],
|
|
108 | + "tbar": [
|
|
109 | + "@defaults"
|
|
110 | + ]
|
|
111 | + }
|
|
112 | + }
|
|
113 | +```
|
|
114 | +
|
|
115 | +### 分组表单视图(group)
|
|
116 | +
|
|
117 | +| 属性 | 类型 | 描述 | 可选值 |
|
|
118 | +| --------- | ------ | -------- | ------------------------------- |
|
|
119 | +| name | String | 显示名称 | |
|
|
120 | +| groupConf | Object | 分组对象 | `{ "name":"基本信息","id":"group1" }` |
|
|
121 | +| span | 占行宽度 | 占行宽度(24) | 6 |
|
|
122 | +| row | 所在行 | | 1 |
|
|
123 | +| rowSpan | 跨行展示 | | 2 |
|
|
124 | +
|
|
125 | +示例代码
|
|
126 | +
|
|
127 | +```json
|
|
128 | +{
|
|
129 | + "name": "code", // 表单字段
|
|
130 | + "groupConf": { // 有该字段表明是表单分组
|
|
131 | + "name": "基本信息", // 分组名称
|
|
132 | + "id": "group1" // 分组唯一id
|
|
133 | + },
|
|
134 | + "span": 6, // 该字段所占行宽度(总宽度为24)
|
|
135 | + "row": 1, // 该字段所在行,值不同即不同行
|
|
136 | + "rowSpan": 2 // 跨行展示,2表示该字段占两行
|
|
137 | +}
|
|
138 | +```
|
|
139 | +
|
|
140 | +页面展示:
|
|
141 | +
|
|
142 | +
|
|
143 | +
|
|
144 | +## 3.8.1.2. 表格(grid)
|
|
145 | +
|
|
146 | +表格视图(grid)注意事项(不填type则读取body中的type)
|
|
147 | +
|
|
148 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
149 | +| ----- | ------ | --- | --- | ----------------------- |
|
|
150 | +| name | String | 是 | 名称 | |
|
|
151 | +| model | String | 是 | 模型 | 模型名 |
|
|
152 | +| type | String | 否 | 类型 | grid |
|
|
153 | +| mode | String | 是 | 模式 | primary 主要 Extension 扩展 |
|
|
154 | +| body | Object | | | |
|
|
155 | +
|
|
156 | +主要信息
|
|
157 | +
|
|
158 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
159 | +| ------- | ------ | --- | --- | --- |
|
|
160 | +| type | String | 是 | 类型 | |
|
|
161 | +| columns | Array | 是 | 属性 | |
|
|
162 | +| buttons | Array | 是 | 按钮 | |
|
|
163 | +| tbar | Array | 是 | 工具栏 | |
|
|
164 | +
|
|
165 | +示例代码:
|
|
166 | +
|
|
167 | +```json
|
|
168 | +"rbac_user_grid": {
|
|
169 | + "name": "用户表格",
|
|
170 | + "model": "rbac_user",
|
|
171 | + "mode": "primary",
|
|
172 | + "body": {
|
|
173 | + "type": "grid",
|
|
174 | + "columns": [
|
|
175 | + "name",
|
|
176 | + "login",
|
|
177 | + "mobile",
|
|
178 | + "email"
|
|
179 | + ],
|
|
180 | + "buttons": [
|
|
181 | + {
|
|
182 | + "name": "编辑",
|
|
183 | + "action": "edit",
|
|
184 | + "auth": "update"
|
|
185 | + },
|
|
186 | + {
|
|
187 | + "name": "重置密码",
|
|
188 | + "auth": "resetPassword",
|
|
189 | + "service": "resetPassword",
|
|
190 | + "action": "openView",
|
|
191 | + "model": "rbac_user",
|
|
192 | + "views": "custom",
|
|
193 | + "params": [
|
|
194 | + "newPassword"
|
|
195 | + ]
|
|
196 | + }
|
|
197 | + ],
|
|
198 | + "tbar": [
|
|
199 | + {
|
|
200 | + "name": "新增",
|
|
201 | + "action": "create",
|
|
202 | + "auth": "create"
|
|
203 | + },
|
|
204 | + {
|
|
205 | + "name": "删除",
|
|
206 | + "action": "delete",
|
|
207 | + "auth": "delete"
|
|
208 | + }
|
|
209 | + ]
|
|
210 | + }
|
|
211 | +```
|
|
212 | +
|
|
213 | +
|
|
214 | +
|
|
215 | +## 3.8.1.3. 搜索(search)
|
|
216 | +
|
|
217 | +搜索(search)
|
|
218 | +
|
|
219 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
220 | +| ----- | ------ | --- | --- | --- |
|
|
221 | +| name | String | 是 | 名称 | |
|
|
222 | +| model | String | 是 | 模型 | |
|
|
223 | +| type | String | 否 | 类型 | |
|
|
224 | +| mode | String | 是 | 模式 | |
|
|
225 | +| body | Object | | | |
|
|
226 | +
|
|
227 | +body 内容
|
|
228 | +
|
|
229 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
230 | +| ------- | ------ | --- | --- | --- |
|
|
231 | +| type | String | 是 | 名称 | |
|
|
232 | +| columns | Array | 是 | 属性 | |
|
|
233 | +
|
|
234 | +示例代码:
|
|
235 | +
|
|
236 | +```json
|
|
237 | +"meta_app_search": {
|
|
238 | + "name": "应用查询",
|
|
239 | + "model": "meta_app",
|
|
240 | + "mode": "primary",
|
|
241 | + "body": {
|
|
242 | + "type": "search",
|
|
243 | + "columns": [
|
|
244 | + "name",
|
|
245 | + "category_ids",
|
|
246 | + "state",
|
|
247 | + "type"
|
|
248 | + ]
|
|
249 | + }
|
|
250 | + }
|
|
251 | +```
|
|
252 | +
|
|
253 | +
|
|
254 | +
|
|
255 | +##### 3.8.1.3.1 日期类型
|
|
256 | +
|
|
257 | +如果字段是日期、时间类型,可以指定 widget, 前端使用不同的日期组件进行渲染。
|
|
258 | +
|
|
259 | +目前支持的 widget
|
|
260 | +
|
|
261 | +1. year: 选择年份
|
|
262 | +2. month: 选择月份
|
|
263 | +3. date: 选择日期
|
|
264 | +4. datetime: 选择时间
|
|
265 | +5. monthrange: 月份范围
|
|
266 | +6. daterange: 日期范围
|
|
267 | +7. datetimerange: 时间范围
|
|
268 | +
|
|
269 | +```json
|
|
270 | +{
|
|
271 | + "views": {
|
|
272 | + "datedemo_grid": {
|
|
273 | + "body": {
|
|
274 | + "buttons": [
|
|
275 | + {
|
|
276 | + "action": "preview",
|
|
277 | + "auth": "read",
|
|
278 | + "name": "详情"
|
|
279 | + },
|
|
280 | + {
|
|
281 | + "action": "edit",
|
|
282 | + "auth": "update",
|
|
283 | + "name": "编辑"
|
|
284 | + }
|
|
285 | + ],
|
|
286 | + "columns": [
|
|
287 | + {
|
|
288 | + "label": "年",
|
|
289 | + "name": "year"
|
|
290 | + },
|
|
291 | + {
|
|
292 | + "label": "月",
|
|
293 | + "name": "month"
|
|
294 | + },
|
|
295 | + {
|
|
296 | + "label": "日",
|
|
297 | + "name": "date"
|
|
298 | + },
|
|
299 | + {
|
|
300 | + "label": "时间",
|
|
301 | + "name": "datetime"
|
|
302 | + },
|
|
303 | + {
|
|
304 | + "label": "月范围",
|
|
305 | + "name": "monthRange"
|
|
306 | + },
|
|
307 | + {
|
|
308 | + "label": "日范围",
|
|
309 | + "name": "dateRange"
|
|
310 | + },
|
|
311 | + {
|
|
312 | + "label": "时间范围",
|
|
313 | + "name": "datetimeRange"
|
|
314 | + }
|
|
315 | + ],
|
|
316 | + "tbar": [
|
|
317 | + {
|
|
318 | + "action": "create",
|
|
319 | + "auth": "create",
|
|
320 | + "name": "新增"
|
|
321 | + },
|
|
322 | + {
|
|
323 | + "action": "delete",
|
|
324 | + "auth": "delete",
|
|
325 | + "name": "删除"
|
|
326 | + }
|
|
327 | + ],
|
|
328 | + "type": "grid"
|
|
329 | + },
|
|
330 | + "mode": "primary",
|
|
331 | + "model": "DateDemo",
|
|
332 | + "name": "日期Demo-表格",
|
|
333 | + "type": "grid"
|
|
334 | + },
|
|
335 | + "datedemo_form": {
|
|
336 | + "body": {
|
|
337 | + "columns": [
|
|
338 | + {
|
|
339 | + "label": "年",
|
|
340 | + "name": "year",
|
|
341 | + "widget": "year"
|
|
342 | + },
|
|
343 | + {
|
|
344 | + "label": "月",
|
|
345 | + "name": "month",
|
|
346 | + "widget": "month"
|
|
347 | + },
|
|
348 | + {
|
|
349 | + "label": "日",
|
|
350 | + "name": "date",
|
|
351 | + "widget": "date"
|
|
352 | + },
|
|
353 | + {
|
|
354 | + "label": "时间",
|
|
355 | + "name": "datetime",
|
|
356 | + "widget": "datetime"
|
|
357 | + },
|
|
358 | + {
|
|
359 | + "label": "月范围",
|
|
360 | + "name": "monthRange",
|
|
361 | + "widget": "month"
|
|
362 | + },
|
|
363 | + {
|
|
364 | + "label": "日范围",
|
|
365 | + "name": "dateRange",
|
|
366 | + "widget": "date"
|
|
367 | + },
|
|
368 | + {
|
|
369 | + "label": "时间范围",
|
|
370 | + "name": "datetimeRange",
|
|
371 | + "widget": "datetime"
|
|
372 | + }
|
|
373 | + ],
|
|
374 | + "tabs": [],
|
|
375 | + "type": "form"
|
|
376 | + },
|
|
377 | + "mode": "primary",
|
|
378 | + "model": "DateDemo",
|
|
379 | + "name": "日期Demo-表单",
|
|
380 | + "type": "form"
|
|
381 | + },
|
|
382 | + "datedemo_search": {
|
|
383 | + "body": {
|
|
384 | + "columns": [
|
|
385 | + {
|
|
386 | + "label": "年",
|
|
387 | + "name": "year",
|
|
388 | + "widget": "year"
|
|
389 | + },
|
|
390 | + {
|
|
391 | + "label": "月",
|
|
392 | + "name": "month",
|
|
393 | + "widget": "month"
|
|
394 | + },
|
|
395 | + {
|
|
396 | + "label": "月范围",
|
|
397 | + "name": "monthRange",
|
|
398 | + "widget": "monthrange"
|
|
399 | + },
|
|
400 | + {
|
|
401 | + "label": "日",
|
|
402 | + "name": "date",
|
|
403 | + "widget": "date"
|
|
404 | + },
|
|
405 | + {
|
|
406 | + "label": "日范围",
|
|
407 | + "name": "dateRange",
|
|
408 | + "widget": "daterange"
|
|
409 | + },
|
|
410 | + {
|
|
411 | + "label": "时间",
|
|
412 | + "name": "datetime",
|
|
413 | + "widget": "datetime"
|
|
414 | + },
|
|
415 | + {
|
|
416 | + "label": "时间范围",
|
|
417 | + "name": "datetimeRange",
|
|
418 | + "widget": "datetimerange"
|
|
419 | + }
|
|
420 | + ],
|
|
421 | + "type": "search"
|
|
422 | + },
|
|
423 | + "mode": "primary",
|
|
424 | + "model": "DateDemo",
|
|
425 | + "name": "日期Demo-搜索",
|
|
426 | + "type": "search"
|
|
427 | + }
|
|
428 | + }
|
|
429 | +}
|
|
430 | +```
|
|
431 | +
|
|
432 | +## 3.8.1.4. 卡片(card)
|
|
433 | +
|
|
434 | +卡片(card) (type 不填则读取body中的type)
|
|
435 | +
|
|
436 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
437 | +| ----- | ------ | --- | --- | --- |
|
|
438 | +| name | String | 是 | 名称 | |
|
|
439 | +| model | String | 是 | 模型 | |
|
|
440 | +| type | String | 否 | 类型 | |
|
|
441 | +| mode | String | 是 | 模式 | |
|
|
442 | +| body | Object | | | |
|
|
443 | +
|
|
444 | +主要内容
|
|
445 | +
|
|
446 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
447 | +| ------- | ------ | --- | --- | --- |
|
|
448 | +| type | String | 是 | 名称 | |
|
|
449 | +| columns | Array | 是 | 属性 | |
|
|
450 | +
|
|
451 | +示例代码:
|
|
452 | +
|
|
453 | +```json
|
|
454 | +"rbac_user_card": {
|
|
455 | + "name": "用户卡片",
|
|
456 | + "model": "rbac_user",
|
|
457 | + "body": {
|
|
458 | + "type": "card",
|
|
459 | + "columns": [
|
|
460 | + "name",
|
|
461 | + "email",
|
|
462 | + "login",
|
|
463 | + "mobile"
|
|
464 | + ]
|
|
465 | + }
|
|
466 | + }
|
|
467 | +```
|
|
468 | +
|
|
469 | +## 3.8.1.5. 菜单(menu)
|
|
470 | +
|
|
471 | +菜单(menu)
|
|
472 | +
|
|
473 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
474 | +| ------------ | ------ | --- | --- | ----------------------- |
|
|
475 | +| name | String | 是 | 唯一 | |
|
|
476 | +| display_name | String | 是 | 名称 | |
|
|
477 | +| model | String | 是 | 模型 | |
|
|
478 | +| view | String | 是 | 视图 | grid ,form, search ,自定义 |
|
|
479 | +| sequence | Long | 否 | 排序 | |
|
|
480 | +| parent_ids | Object | | | @ref |
|
|
481 | +
|
|
482 | +1. 场景1
|
|
483 | +
|
|
484 | +树形菜单如何配置,如下所示
|
|
485 | +
|
|
486 | +```json
|
|
487 | +"base_developer_center": {
|
|
488 | + "sequence": "31",
|
|
489 | + "name": "base_developer_center",
|
|
490 | + "display_name": "开发者中心"
|
|
491 | + },
|
|
492 | + "meta_model_menu": {
|
|
493 | + "sequence": "5",
|
|
494 | + "view": "grid,form,search",
|
|
495 | + "name": "meta_model_menu",
|
|
496 | + "model": "meta_model",
|
|
497 | + "display_name": "模型",
|
|
498 | + "parent_ids": {
|
|
499 | + "@ref": "base_developer_center"
|
|
500 | + },
|
|
501 | + "meta_sub_model_menu": {
|
|
502 | + "sequence": "5",
|
|
503 | + "view": "grid,form,search",
|
|
504 | + "name": "meta_sub_model_menu",
|
|
505 | + "model": "meta_sub_model",
|
|
506 | + "display_name": "子模型",
|
|
507 | + "parent_ids": {
|
|
508 | + "@ref": "meta_model_menu"
|
|
509 | + }
|
|
510 | +}
|
|
511 | +```
|
|
512 | +
|
|
513 | +
|
|
514 | +
|
|
515 | +## 3.8.1.6. 工具栏(tbar)
|
|
516 | +
|
|
517 | +工具栏(tbar)
|
|
518 | +
|
|
519 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
520 | +| ------ | ------ | --- | --- | ------------------------------------------------------------------------------------------------- |
|
|
521 | +| name | String | 是 | 名称 | |
|
|
522 | +| action | String | 是 | 动作 | add添加delete删除save保存update修改createEr er创建addEr er增加按钮deleteEr er删除按钮updateEr er更新按钮@defaults 默认 |
|
|
523 | +| auth | String | 是 | 权限 | |
|
|
524 | +
|
|
525 | +示例代码:
|
|
526 | +
|
|
527 | +1. @defaults
|
|
528 | +
|
|
529 | + "tbar": ["@defaults"]
|
|
530 | +
|
|
531 | +a. type等于grid时,"@defaults等于默认添加 新增,删除按钮
|
|
532 | +
|
|
533 | +b. type等于form时,"@defaults等于默认添加 保存按钮
|
|
534 | +
|
|
535 | +c. type等于from并且存在er关系时,@defaults等于默认 添加 新增,删除按钮
|
|
536 | +
|
|
537 | +
|
|
538 | +
|
|
539 | +自定义tbar
|
|
540 | +
|
|
541 | +```json
|
|
542 | + "tbar": [
|
|
543 | + {
|
|
544 | + "name": "新增",
|
|
545 | + "action": "create",
|
|
546 | + "auth": "create"
|
|
547 | + },
|
|
548 | + {
|
|
549 | + "name": "删除",
|
|
550 | + "action": "delete",
|
|
551 | + "auth": "delete"
|
|
552 | + }, {
|
|
553 | + "name": "刷新应用",//自定义tbar
|
|
554 | + "model": "meta_app",
|
|
555 | + "service": "restartApp",//调用的服务名,默认参数是ids
|
|
556 | + "auth": "restartApp"
|
|
557 | + }
|
|
558 | + ]
|
|
559 | +```
|
|
560 | +
|
|
561 | +
|
|
562 | +
|
|
563 | +## 3.8.1.7. 按钮 (buttons)
|
|
564 | +
|
|
565 | +按钮(buttons)
|
|
566 | +
|
|
567 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
568 | +| ------- | ------ | --- | --- | ------ |
|
|
569 | +| name | String | 是 | 名称 | |
|
|
570 | +| action | String | 是 | 动作 | |
|
|
571 | +| auth | String | 是 | 权限 | |
|
|
572 | +| service | String | 否 | 服务 | |
|
|
573 | +| model | String | 否 | 模型 | |
|
|
574 | +| views | String | 否 | 视图 | custom |
|
|
575 | +| params | Array | 否 | 参数 | |
|
|
576 | +
|
|
577 | +示例代码:
|
|
578 | +
|
|
579 | + 1. @defaults 默认添加详情,编辑按钮
|
|
580 | +
|
|
581 | + "buttons": ["@defaults"]
|
|
582 | +
|
|
583 | +
|
|
584 | +
|
|
585 | +```json
|
|
586 | + "buttons": [
|
|
587 | + {
|
|
588 | + "name": "编辑",
|
|
589 | + "action": "edit",
|
|
590 | + "auth": "update"
|
|
591 | + },
|
|
592 | + {
|
|
593 | + "name": "安装",//自定义按钮
|
|
594 | + "model": "meta_app",//指定服务
|
|
595 | + "service": "installApp",//服务名
|
|
596 | + "auth": "installApp",//权限字符串
|
|
597 | + "enableCondition": "function condition(row) {return row && row[0] && row[0].state === 'installable';}"//按钮禁用条件
|
|
598 | + },
|
|
599 | + {
|
|
600 | + "name": "更新数据",
|
|
601 | + "model": "meta_app",
|
|
602 | + "service": "refreshApp",
|
|
603 | + "auth": "refreshApp"
|
|
604 | + }
|
|
605 | + ]
|
|
606 | +```
|
|
607 | +
|
|
608 | +
|
|
609 | +
|
|
610 | +## 3.8.1.8. 切换(tabs)
|
|
611 | +
|
|
612 | +切换(tabs)
|
|
613 | +
|
|
614 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
615 | +| ------- | ------ | --- | ------------ | --- |
|
|
616 | +| header | String | 是 | | |
|
|
617 | +| rowspan | String | 否 | | |
|
|
618 | +| tbar | Array | 否 | 参考4.7.1.5工具栏 | |
|
|
619 | +| body | Object | 否 | 一对多 多对多 内容 | |
|
|
620 | +
|
|
621 | +主要内容
|
|
622 | +
|
|
623 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
624 | +| ------- | ------ | --- | ---- | --------- |
|
|
625 | +| type | String | 是 | | grid,form |
|
|
626 | +| field | String | 否 | 关系字段 | |
|
|
627 | +| columns | Array | 否 | | |
|
|
628 | +
|
|
629 | +示例代码:
|
|
630 | +
|
|
631 | +```json
|
|
632 | +"tabs": [
|
|
633 | + {
|
|
634 | + "header": "应用",
|
|
635 | + "rowspan": 3,
|
|
636 | + "tbar": [
|
|
637 | + {
|
|
638 | + "name": "添加",
|
|
639 | + "action": "addEr"
|
|
640 | + },
|
|
641 | + {
|
|
642 | + "name": "删除",
|
|
643 | + "action": "deleteEr"
|
|
644 | + }
|
|
645 | + ],
|
|
646 | + "body": {
|
|
647 | + "type": "grid",
|
|
648 | + "field": "app_ids",
|
|
649 | + "columns": [
|
|
650 | + "name",
|
|
651 | + "tag",
|
|
652 | + "description",
|
|
653 | + "state"
|
|
654 | + ]
|
|
655 | + }
|
|
656 | + }
|
|
657 | + ]
|
|
658 | +```
|
|
659 | +
|
|
660 | +
|
|
661 | +
|
|
662 | +## 3.8.1.9. 关系(er)
|
|
663 | +
|
|
664 | + 1. 一对多: 示例:应用分类-应用列表(OneToMany)
|
|
665 | +
|
|
666 | + a. form里面columns需要指定OneToMany的属性:app_ids
|
|
667 | +
|
|
668 | + b. tab里面的field需要指定OneToMany的属性:app_ids
|
|
669 | +
|
|
670 | +
|
|
671 | +
|
|
672 | +```json
|
|
673 | +"meta_app_category_form": {
|
|
674 | + "name": "应用分类表单",
|
|
675 | + "model": "meta_app_category",
|
|
676 | + "mode": "primary",
|
|
677 | + "body": {
|
|
678 | + "type": "form",
|
|
679 | + "columns": [
|
|
680 | + "name",
|
|
681 | + "app_ids" //需要指定关联的属性:app_ids
|
|
682 | + ],
|
|
683 | + "tabs": [
|
|
684 | + {
|
|
685 | + "header": "应用",
|
|
686 | + "rowspan": 3,
|
|
687 | + "tbar": [
|
|
688 | + {
|
|
689 | + "name": "添加",
|
|
690 | + "action": "addEr"
|
|
691 | + },
|
|
692 | + {
|
|
693 | + "name": "删除",
|
|
694 | + "action": "deleteEr"
|
|
695 | + }
|
|
696 | + ],
|
|
697 | + "body": {
|
|
698 | + "type": "grid",
|
|
699 | + "field": "app_ids", //指定关联的属性:app_ids
|
|
700 | + "columns": [//关联表的属性
|
|
701 | + "name",
|
|
702 | + "tag",
|
|
703 | + "description",
|
|
704 | + "state"
|
|
705 | + ]
|
|
706 | + }
|
|
707 | + }
|
|
708 | + ]
|
|
709 | + }
|
|
710 | + }
|
|
711 | +```
|
|
712 | +
|
|
713 | + 2. 多对多 模型-属性(ManyToMany)
|
|
714 | +
|
|
715 | +```json
|
|
716 | + "meta_model_form": {
|
|
717 | + "name": "模型表单",
|
|
718 | + "model": "meta_model",
|
|
719 | + "mode": "primary",
|
|
720 | + "body": {
|
|
721 | + "type": "form",
|
|
722 | + "columns": [
|
|
723 | + "name",
|
|
724 | + "display_name",
|
|
725 | + "table_name",
|
|
726 | + "description",
|
|
727 | + "parents",
|
|
728 | + "tag",
|
|
729 | + "source",
|
|
730 | + "app_ids",
|
|
731 | + "is_abstract",
|
|
732 | + "auto_log",
|
|
733 | + "order_by",
|
|
734 | + "display_format",
|
|
735 | + "property_ids" //form必须包含关联的属性
|
|
736 | + ],
|
|
737 | + "tabs": [
|
|
738 | + {
|
|
739 | + "header": "属性",
|
|
740 | + "rowspan": 3,
|
|
741 | + "body": {
|
|
742 | + "type": "grid",
|
|
743 | + "field": "property_ids",//关联的属性必填
|
|
744 | + "columns": [
|
|
745 | + "name",
|
|
746 | + "display_name",
|
|
747 | + "data_type",
|
|
748 | + "required",
|
|
749 | + "read_only",
|
|
750 | + "store",
|
|
751 | + "db_index"
|
|
752 | + ],
|
|
753 | + "edit": { //form编辑
|
|
754 | + "type": "form",
|
|
755 | + "columns": [
|
|
756 | + "name",
|
|
757 | + "data_type",
|
|
758 | + "display_name",
|
|
759 | + "description",
|
|
760 | + "display",
|
|
761 | + "source",
|
|
762 | + "default_value",
|
|
763 | + "length",
|
|
764 | + "display_for_model",
|
|
765 | + "required",
|
|
766 | + "read_only",
|
|
767 | + "unique",
|
|
768 | + "store",
|
|
769 | + "db_index",
|
|
770 | + "property_type",
|
|
771 | + "compute_script"
|
|
772 | + ],
|
|
773 | + "buttons": [
|
|
774 | + {
|
|
775 | + "name": "编辑",
|
|
776 | + "action": "edit",
|
|
777 | + "auth": "update"
|
|
778 | + }
|
|
779 | + ]
|
|
780 | + }
|
|
781 | + },
|
|
782 | + "tbar": [
|
|
783 | + {
|
|
784 | + "name": "创建",
|
|
785 | + "action": "createEr", //ER关系tbar,button需要带后缀er
|
|
786 | + "auth": "create"
|
|
787 | + },
|
|
788 | + {
|
|
789 | + "name": "删除",
|
|
790 | + "action": "deleteEr",
|
|
791 | + "auth": "delete"
|
|
792 | + }
|
|
793 | + ]
|
|
794 | + }
|
|
795 | + ]
|
|
796 | + }
|
|
797 | + }
|
|
798 | +```
|
|
799 | +
|
|
800 | + 3. 多对一
|
|
801 | +
|
|
802 | +属性name设置isDisplayForModel
|
|
803 | +
|
|
804 | +```java
|
|
805 | +@Model(name = "meta_app_category", displayName = "应用分类")
|
|
806 | +public class MetaAppCategory extends Model {
|
|
807 | + public static Property name = Property.String().displayName("名称").tooltips("分类名称").isRequired().isDisplayForModel();
|
|
808 | +}
|
|
809 | +```
|
|
810 | +
|
|
811 | +在应用列表grid添加category字段,在应用列表会自动显示分类的name.
|
|
812 | +
|
|
813 | +```json
|
|
814 | +"meta_app_grid": {
|
|
815 | + "name": "应用列表",
|
|
816 | + "model": "meta_app",
|
|
817 | + "mode": "primary",
|
|
818 | + "body": {
|
|
819 | + "type": "grid",
|
|
820 | + "columns": [
|
|
821 | + "name",
|
|
822 | + "display_name",
|
|
823 | + "state",
|
|
824 | + "tag",
|
|
825 | + "product",
|
|
826 | + "category_ids",
|
|
827 | + "type",
|
|
828 | + "summary"
|
|
829 | + ],
|
|
830 | + "buttons": [
|
|
831 | + {
|
|
832 | + "name": "编辑",
|
|
833 | + "action": "edit",
|
|
834 | + "auth": "update"
|
|
835 | + }
|
|
836 | + ],
|
|
837 | + "tbar": [
|
|
838 | + {
|
|
839 | + "name": "新增",
|
|
840 | + "action": "create",
|
|
841 | + "auth": "create"
|
|
842 | + }
|
|
843 | + ]
|
|
844 | + }
|
|
845 | + }
|
|
846 | +```
|
|
847 | +
|
|
848 | +## 3.8.1.10. 视图扩展(extend)
|
|
849 | +
|
|
850 | +在视图中,表单,列表和搜索视图是使用json结构定义的。 如果要通过继承扩展原有视图,我们需要用一种方法来修改这个json。 这需要通过两步来实现,1、定位到视图json中某个界面元素的位置;2、然后在这个位置插入增补定义的视图。通过这两步就可以达到扩展视图的目的。
|
|
851 | +
|
|
852 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
853 | +| ----------- | ------ | --- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
854 | +| name | String | 是 | 名称 | |
|
|
855 | +| model | String | 是 | 模型 | |
|
|
856 | +| | String | | 模型 | |
|
|
857 | +| type | String | | 视图类型 | grid表格 form表单search搜索 |
|
|
858 | +| mode | String | 是 | 视图模式:扩展视图 | extension |
|
|
859 | +| inherit_ids | Object | 是 | 继承的视图id:格式: "@ref": "应用名.继承视图key".示例: "inherit_ids": {"@ref": "mom.mom_item_grid" } | |
|
|
860 | +| body | Object | 是 | {"jsonpath":[{"expr":"columns","position":"inside","body":["barcode","type"]}]} | |
|
|
861 | +| jsonpath | Array | 是 | 指定要替换的路径和内容 | |
|
|
862 | +| expr | String | 是 | columnscolumns.name | |
|
|
863 | +| position | String | 否 | 定位节点位置 | inside(默认值):匹配节点内的追加内容。after:将内容添加到父元素之中,匹配的节点之后。before:添加内容在匹配节点之前。replace:替换匹配的节点。如果使用空内容,它将删除该匹配的元素。attributes:修改匹配元素的XML属性。 |
|
|
864 | +| has_not | String | 否 | 如果节点不存在则创建,支持buttons,tabs,tars | |
|
|
865 | +| body | Object | 否 | | |
|
|
866 | +
|
|
867 | +示例代码:
|
|
868 | +
|
|
869 | +```json
|
|
870 | +{
|
|
871 | + "views": {
|
|
872 | + "rbac_user_grid_ext": {
|
|
873 | + "name": "用户表格扩展",
|
|
874 | + "model": "rbac_user",
|
|
875 | + "type": "grid",
|
|
876 | + "mode": "extension",
|
|
877 | + "inherit_ids": {
|
|
878 | + "@ref": "base.rbac_user_grid"
|
|
879 | + },
|
|
880 | + "body": {
|
|
881 | + "jsonpath": [
|
|
882 | + {
|
|
883 | + "expr": "columns.email",
|
|
884 | + "position": "after",
|
|
885 | + "body": [
|
|
886 | + "mobile"
|
|
887 | + ]
|
|
888 | + }
|
|
889 | + ]
|
|
890 | + }
|
|
891 | + },
|
|
892 | + "rbac_user_form_ext": {
|
|
893 | + "name": "用户表单扩展",
|
|
894 | + "model": "rbac_user",
|
|
895 | + "type": "form",
|
|
896 | + "mode": "extension",
|
|
897 | + "inherit_ids": {
|
|
898 | + "@ref": "base.rbac_user_form"
|
|
899 | + },
|
|
900 | + "body": {
|
|
901 | + "jsonpath": [
|
|
902 | + {
|
|
903 | + "expr": "columns.email",
|
|
904 | + "position": "before",
|
|
905 | + "body": [
|
|
906 | + "create_user"
|
|
907 | + ]
|
|
908 | + },
|
|
909 | + {
|
|
910 | + "has_not": "tabs",
|
|
911 | + "expr": "tabs",
|
|
912 | + "position": "inside",
|
|
913 | + "body": [
|
|
914 | + {
|
|
915 | + "header": "用户日志",
|
|
916 | + "tbar": [
|
|
917 | + {
|
|
918 | + "name": "添加",
|
|
919 | + "action": "addEr",
|
|
920 | + "icon": "add",
|
|
921 | + "auth": "create"
|
|
922 | + },
|
|
923 | + {
|
|
924 | + "name": "删除",
|
|
925 | + "action": "deleteEr",
|
|
926 | + "auth": "delete"
|
|
927 | + }
|
|
928 | + ],
|
|
929 | + "body": {
|
|
930 | + "type": "grid",
|
|
931 | + "field": "logs",
|
|
932 | + "columns": [
|
|
933 | + "ip",
|
|
934 | + "user_agent",
|
|
935 | + "url"
|
|
936 | + ]
|
|
937 | + }
|
|
938 | + }
|
|
939 | + ]
|
|
940 | + }
|
|
941 | + ]
|
|
942 | + }
|
|
943 | + },
|
|
944 | + "rbac_user_search_ext": {
|
|
945 | + "name": "用户搜索扩展",
|
|
946 | + "model": "rbac_user",
|
|
947 | + "mode": "extension",
|
|
948 | + "type": "search",
|
|
949 | + "inherit_ids": {
|
|
950 | + "@ref": "base.rbac_user_search"
|
|
951 | + },
|
|
952 | + "body": {
|
|
953 | + "jsonpath": [
|
|
954 | + {
|
|
955 | + "expr": "columns.email",
|
|
956 | + "position": "inside",
|
|
957 | + "body": [
|
|
958 | + "create_user"
|
|
959 | + ]
|
|
960 | + }
|
|
961 | + ]
|
|
962 | + }
|
|
963 | + }
|
|
964 | + }
|
|
965 | + }
|
|
966 | +```
|
|
967 | +
|
|
968 | +## 3.8.1.11. 种子数据 (data)
|
|
969 | +
|
|
970 | +种子数据(data)
|
|
971 | +
|
|
972 | +data种子数据路径:在resolved包下新建data目录,所有数据文件必须放在data目录
|
|
973 | +
|
|
974 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
975 | +| ---------- | ------ | --- | ----- | --------------------------- |
|
|
976 | +| model | String | 是 | 模型 | |
|
|
977 | +| properties | Object | | | |
|
|
978 | +| | String | | @eval | [4,@ref(rbac_role_admin),0] |
|
|
979 | +
|
|
980 | +属性(properties)
|
|
981 | +
|
|
982 | +示例代码:
|
|
983 | +
|
|
984 | +```json
|
|
985 | +{
|
|
986 | + "data": {
|
|
987 | + "rbac_role_admin": {
|
|
988 | + "model": "rbac_role",
|
|
989 | + "properties": {
|
|
990 | + "name": "管理员",
|
|
991 | + "is_admin": 1
|
|
992 | + }
|
|
993 | + },
|
|
994 | + "rbac_role_common": {
|
|
995 | + "model": "rbac_role",
|
|
996 | + "properties": {
|
|
997 | + "name": "普通用户",
|
|
998 | + "is_admin": 0
|
|
999 | + }
|
|
1000 | + },
|
|
1001 | +
|
|
1002 | +
|
|
1003 | + "rbac_user_admin": {
|
|
1004 | + "model": "rbac_user",
|
|
1005 | + "properties": {
|
|
1006 | + "name": "管理员",
|
|
1007 | + "login": "admin",
|
|
1008 | + "mobile": "18888888888",
|
|
1009 | + "password": "admin",
|
|
1010 | + "email": "1388888888@sie.com",
|
|
1011 | + "role_ids": [
|
|
1012 | + {
|
|
1013 | + "@eval": "[4, @ref(rbac_role_admin), 0]" //注意:此处使用了Many2Many指令集,在用户角色表中插入一条关联记录, @ref(rbac_role_admin)的意思是先获取rbac_role_admin生成的id,然后再插入到用户角色关联表里面
|
|
1014 | + }
|
|
1015 | + ]
|
|
1016 | + }
|
|
1017 | + },
|
|
1018 | + "rbac_user_lijun": {
|
|
1019 | + "model": "rbac_user",
|
|
1020 | + "properties": {
|
|
1021 | + "name": "李某",
|
|
1022 | + "login": "lijun",
|
|
1023 | + "mobile": "13600314629",
|
|
1024 | + "password": "lijun",
|
|
1025 | + "email": "lijun10@chinasie.com",
|
|
1026 | + "role_ids": [
|
|
1027 | + {
|
|
1028 | + "@eval": "[4, @ref(rbac_role_admin), 0]"
|
|
1029 | + }
|
|
1030 | + ]
|
|
1031 | + }
|
|
1032 | + }
|
|
1033 | + }
|
|
1034 | + }
|
|
1035 | +```
|
|
1036 | +
|
|
1037 | +## 3.8.1.12. 树 (tree)
|
|
1038 | +
|
|
1039 | +树 (tree)
|
|
1040 | +
|
|
1041 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
1042 | +| ----- | ------ | --- | ---- | ---- |
|
|
1043 | +| name | String | 是 | 名称 | |
|
|
1044 | +| model | String | 是 | 模型 | |
|
|
1045 | +| type | String | 是 | 类型 | tree |
|
|
1046 | +| mode | String | 是 | 视图模式 | |
|
|
1047 | +| body | Object | 是 | 内容 | |
|
|
1048 | +
|
|
1049 | +body内容
|
|
1050 | +
|
|
1051 | +| 参数 | 类型 | 必填 | 描述 | 可选值 |
|
|
1052 | +| ------- | ------ | --- | --- | ---- |
|
|
1053 | +| type | String | 是 | 类属 | tree |
|
|
1054 | +| model | String | 是 | 模型 | |
|
|
1055 | +| props | Object | 否 | 绑定 | |
|
|
1056 | +| tbar | Array | 否 | 工具栏 | |
|
|
1057 | +| buttons | Object | 否 | 按钮 | |
|
|
1058 | +| columns | Array | 否 | 字段 | |
|
|
1059 | +
|
|
1060 | +columns对象
|
|
1061 | +
|
|
1062 | +```json
|
|
1063 | +"columns": [
|
|
1064 | + "name",
|
|
1065 | + "parent_id",
|
|
1066 | + "children_ids",
|
|
1067 | + "description"
|
|
1068 | +]
|
|
1069 | +```
|
|
1070 | +
|
|
1071 | +props对象
|
|
1072 | +
|
|
1073 | +```json
|
|
1074 | +"props": {
|
|
1075 | + "children": "children",
|
|
1076 | + "parent": "parent",
|
|
1077 | + "label": "name",
|
|
1078 | + "showTree": false // 搜索结果是否为树状结构,默认为false
|
|
1079 | +}
|
|
1080 | +```
|
|
1081 | +
|
|
1082 | +tbar对象
|
|
1083 | +
|
|
1084 | +```json
|
|
1085 | +"tbar": [{
|
|
1086 | + "name": "单击",
|
|
1087 | + "auth": "click",
|
|
1088 | + "service": "click",
|
|
1089 | + "action": "openView",
|
|
1090 | + "model": "rbac_organization",
|
|
1091 | + "views": "custom",
|
|
1092 | + "params": []
|
|
1093 | +}]
|
|
1094 | +```
|
|
1095 | +
|
|
1096 | +buttons对象
|
|
1097 | +
|
|
1098 | +```json
|
|
1099 | +"buttons": [{
|
|
1100 | + "name": "新增",
|
|
1101 | + "auth": "create",
|
|
1102 | + "service": "create",
|
|
1103 | + "action": "openView",
|
|
1104 | + "model": "rbac_organization",
|
|
1105 | + "views": "custom",
|
|
1106 | + "params": []
|
|
1107 | +},{
|
|
1108 | + "name": "删除",
|
|
1109 | + "auth": "delete",
|
|
1110 | + "service": "delete",
|
|
1111 | + "action": "openView",
|
|
1112 | + "model": "rbac_organization",
|
|
1113 | + "views": "custom",
|
|
1114 | + "params": []
|
|
1115 | +}]
|
|
1116 | +```
|
|
1117 | +
|
|
1118 | +## 3.8.1.13.导入导出(buttons)
|
|
1119 | +
|
|
1120 | +------
|
|
1121 | +
|
|
1122 | +Import导入
|
|
1123 | +
|
|
1124 | +| 字段 | 名称 | 可选值 | 可空 |
|
|
1125 | +|:--------- | -------- | ------------------------ | --- |
|
|
1126 | +| action | 事件 | import | 否 |
|
|
1127 | +| model | 模型名 | base_test | 否 |
|
|
1128 | +| service | 服务名 | excelImport | 否 |
|
|
1129 | +| fileLimit | 文件限制 | | |
|
|
1130 | +| -ext | 文件类型 | .xls ,.xlsx 默认值.xls 可空 | 是 |
|
|
1131 | +| -maxSize | 文件大小单位MB | 默认1MB | 是 |
|
|
1132 | +
|
|
1133 | +前端视grid视图tbar配置
|
|
1134 | +
|
|
1135 | +```json
|
|
1136 | +{
|
|
1137 | + "action": "import",
|
|
1138 | + "model": "base_test",
|
|
1139 | + "service": "excelImport",
|
|
1140 | + "fileLimit": {
|
|
1141 | + "ext": ".xls,.xlsx",
|
|
1142 | + "maxSize": "2048"
|
|
1143 | + }
|
|
1144 | +}
|
|
1145 | +```
|
|
1146 | +
|
|
1147 | +| 字段 | 名称 | 可选值 | 可空 |
|
|
1148 | +| ---------- | --- | -------------- | --- |
|
|
1149 | +| action | 导出 | export | 否 |
|
|
1150 | +| source | 来源 | select | 否 |
|
|
1151 | +| properties | 属性 | ["name","age"] | 否 |
|
|
1152 | +| model | 模型 | base_test | 否 |
|
|
1153 | +| service | 服务 | excelExport | 否 |
|
|
1154 | +
|
|
1155 | +前端grid视图tbar工具栏配置
|
|
1156 | +
|
|
1157 | +```json
|
|
1158 | +{
|
|
1159 | + "action": "export",
|
|
1160 | + "source": "selected",
|
|
1161 | + "properties": ["name","sex"],
|
|
1162 | + "model": "base_test",
|
|
1163 | + "service": "excelExport"
|
|
1164 | +}
|
|
1165 | +```
|
|
1166 | +
|
|
1167 | +## 3.8.1.14.树(tree)
|
|
1168 | +
|
|
1169 | +1. 左边树为自己模型 (例子来源)
|
|
1170 | +
|
|
1171 | + ```json
|
|
1172 | + "mi_base_folder_tree": {
|
|
1173 | + "name": "文件目录-树",
|
|
1174 | + "model": "mi_base_folder",
|
|
1175 | + "type": "tree",
|
|
1176 | + "mode": "primary",
|
|
1177 | + "body": {
|
|
1178 | + "type": "tree",
|
|
1179 | + "model": "mi_base_folder",
|
|
1180 | + "columns": [
|
|
1181 | + "name",
|
|
1182 | + "parent",
|
|
1183 | + "children"
|
|
1184 | + ],
|
|
1185 | + "props": {
|
|
1186 | + "children": "children",
|
|
1187 | + "parent": "parent",
|
|
1188 | + "label": "name"
|
|
1189 | + },
|
|
1190 | + "search": {}
|
|
1191 | + }
|
|
1192 | + }
|
|
1193 | + ```
|
|
1194 | +
|
|
1195 | + 外面的Model与body里面的Model保持一致,左边树为自己模型。例如:组织功能,菜单功能
|
|
1196 | +
|
|
1197 | +2. 左边树为其他模型
|
|
1198 | +
|
|
1199 | + ```json
|
|
1200 | + "mi_base_file_tree": {
|
|
1201 | + "name": "文件-树",
|
|
1202 | + "model": "mi_base_file",
|
|
1203 | + "type": "tree",
|
|
1204 | + "mode": "primary",
|
|
1205 | + "body": {
|
|
1206 | + "type": "tree",
|
|
1207 | + "model": "mi_base_folder",
|
|
1208 | + "columns": [
|
|
1209 | + "name",
|
|
1210 | + "parent",
|
|
1211 | + "children"
|
|
1212 | + ],
|
|
1213 | + "props": {
|
|
1214 | + "children": "children",
|
|
1215 | + "parent": "parent",
|
|
1216 | + "label": "name",
|
|
1217 | + "subApp": "smi-base-asset",
|
|
1218 | + "subModel": "mi_base_file",
|
|
1219 | + "subViewType": "grid,search,form",
|
|
1220 | + "subViewFilter": "folder"
|
|
1221 | + },
|
|
1222 | + "search": {}
|
|
1223 | + }
|
|
1224 | + }
|
|
1225 | + ```
|
|
1226 | +
|
|
1227 | + body里面的的模型可以配置成其他模型model :文件夹,columns也是模型的属性。
|
|
1228 | +
|
|
1229 | + props操作内容 children 为子字段,parent为父字段,label为展示名。
|
|
1230 | +
|
|
1231 | + subApp为应用名,subModel为模型名,subViewType为视图类型,subViewFilter为过滤条件。
|
|
1232 | +
|
|
1233 | + 单击左边选中项请求sub的内容。
|
|
1234 | +
|
|
1235 | +## 3.8.1.15.字段(columns)
|
|
1236 | +
|
|
1237 | +对象结构的方式(详情参照文档)
|
|
1238 | +
|
|
1239 | +| 字段 | 描述 | 可选值 | 类型 |
|
|
1240 | +| ------ | ------------ | ---------- | ------- |
|
|
1241 | +| name | 属性名 | | Strin |
|
|
1242 | +| hide | 显示隐藏表单(form) | true/false | Boolean |
|
|
1243 | +| hidden | 显示隐藏表格(grid) | true/false | Boolean |
|
|
1244 | +| custom | 覆盖前端视图相同属性的值 | true/false | Boolean |
|
|
1245 | +
|
|
1246 | +代码示例:
|
|
1247 | +
|
|
1248 | +```json
|
|
1249 | + "columns": [
|
|
1250 | + {
|
|
1251 | + "label": "昵称",
|
|
1252 | + "name": "nickname",
|
|
1253 | + "sortable": true,
|
|
1254 | + "width": "80px"
|
|
1255 | + }
|
|
1256 | + ]
|
|
1257 | +```
|
|
1258 | +
|
|
1259 | +- 字段分组
|
|
1260 | +
|
|
1261 | +| | | |
|
|
1262 | +| --- | --- | --- |
|
|
1263 | +| | | |
|
|
1264 | +| | | |
|
|
1265 | +| | | |
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/10.Filter\350\277\207\346\273\244\346\235\241\344\273\266.md
... | ... | @@ -0,0 +1,159 @@ |
1 | +---
|
|
2 | +title: Filter过滤条件
|
|
3 | +date: 2023-09-25 17:31:35
|
|
4 | +permalink: /pages/107af7/
|
|
5 | +---
|
|
6 | +### Filter过滤条件
|
|
7 | +
|
|
8 | +------
|
|
9 | +
|
|
10 | +#### 1. filter表达式
|
|
11 | +
|
|
12 | +filter表达式:通常用来筛选数据记录。它们使用波兰表示法语法,以便于将它们解析后生成对应的SQL WHERE数据库筛选语句。
|
|
13 | +filter通常为一个数组,数组元素为过滤条件,每个条件是一个三元表达式,例如:`["&",["state","=","confirm"],["user_id","in",[1,2,3]]]`
|
|
14 | +filter多个条件的逻辑运算使用了“波兰表示法”,波兰表示法的特定是操作符置于操作数前,运算顺序为:从左至右读入表达式,遇到一个操作符后跟随两个操作数时,则计算之,然后将结果作为操作数替换这个操作符和两个操作数;重复此步骤,直至所有操作符处理完毕。
|
|
15 | +
|
|
16 | +
|
|
17 | +
|
|
18 | +#### 2. filter的写法
|
|
19 | +
|
|
20 | +filter表达式是一个条件列表,每个条件是一个形如`["field_name", "operator", value]`的数组。
|
|
21 | +
|
|
22 | +> filed_name 是需要筛选的字段,它可以使用点(.)来访问关系模块的字段。
|
|
23 | +> value 是一个表达式的值。它可以使用字符值,比如:字符串,数字,布尔值,或则列表、某个字段。
|
|
24 | +> operator 可以为:
|
|
25 | +> 常用的操作符:<,>,<=,>=,=,!=。
|
|
26 | +> "like"匹配一个"%value%"的字符串。"ilike"与此类似但不区分大小写。"not like"和"not ilike"也可以使用
|
|
27 | +> "child_of","parent_of"在层级关系中,筛选子集
|
|
28 | +> "in"和"not in"筛选是否在一个列表里面,所以,给的值应该是个list。当在"to-many"的关系字段中,"in"的作用和contains的作用一样
|
|
29 | +
|
|
30 | +示例:
|
|
31 | +
|
|
32 | +```json
|
|
33 | +[
|
|
34 | + "|",
|
|
35 | + [
|
|
36 | + "message_follower_ids",
|
|
37 | + "in",
|
|
38 | + [
|
|
39 | + "1",
|
|
40 | + "2",
|
|
41 | + "3"
|
|
42 | + ]
|
|
43 | + ],
|
|
44 | + "|",
|
|
45 | + [
|
|
46 | + "user_id",
|
|
47 | + "=",
|
|
48 | + "1000"
|
|
49 | + ],
|
|
50 | + [
|
|
51 | + "user_id",
|
|
52 | + "=",
|
|
53 | + false
|
|
54 | + ]
|
|
55 | +]
|
|
56 | +```
|
|
57 | +
|
|
58 | +
|
|
59 | +
|
|
60 | +#### 3. Filter的操作符
|
|
61 | +
|
|
62 | +比较运算符:条件的操作符主要有如下类型
|
|
63 | +
|
|
64 | +| 操作符 | 说明 |
|
|
65 | +| :------------- | :----------------------------------------------------------- |
|
|
66 | +| =,!=,>,>=,<,<= | 比较运算,等于,不等于,大于,大于等于,小于,小于等于 |
|
|
67 | +| like | 模糊匹配,可以使用通配符,,百分号“%”匹配零或者多个字符 |
|
|
68 | +| ilike | 类似like,不区分大小写 |
|
|
69 | +| not like | 模糊不匹配的 |
|
|
70 | +| in | 包含,判断值是否在元素的列表里面 |
|
|
71 | +| not in | 不包含,判断值是否不在元素的列表里面 |
|
|
72 | +| child_of | 判断是否value的子记录 |
|
|
73 | +| parent_of | 用于有 父子关系的模型,13版本开始使用。 在旧版本使用 parent_left, parent_right |
|
|
74 | +| | |
|
|
75 | +
|
|
76 | +逻辑运算符,主要用于多个条件处理,逻辑运算符链接。逻辑运算符作为前缀放置于条件前面。:
|
|
77 | +"|”(or)
|
|
78 | +"&" (and)
|
|
79 | +"!"(no)“
|
|
80 | +默认逻辑运算符为“&”
|
|
81 | +
|
|
82 | +
|
|
83 | +
|
|
84 | +#### 4. Filter使用的算法是波兰表达式
|
|
85 | +
|
|
86 | +计算的核心思想:运算波兰表达式时,无需记住运算的层次,只需要直接寻找第一个运算的操作符。以二元运算为例,从左至右读入表达式,
|
|
87 | +遇到一个操作符后跟随两个操作数时,则计算之,然后将结果作为操作数替换这个操作符和两个操作数;重复此步骤,直至所有操作符处理完毕。
|
|
88 | +简单来说,波兰表示法是一种操作符置于操作数前,并且不需要括号仍然能无歧义地解析表达的方法。
|
|
89 | +
|
|
90 | +举例:
|
|
91 | +
|
|
92 | +["|","&","|",a,b,c,"&",d,e]
|
|
93 | +
|
|
94 | +其中a,b,c,e,f,g分别是不带逻辑运算符的表达式,表达式的运算顺序:
|
|
95 | +
|
|
96 | +["|","&","|",a,b,c,"&",d,e]
|
|
97 | +
|
|
98 | +["|","&",[a| b],c,"&",d,e]
|
|
99 | +
|
|
100 | +["|",[[a | b] & c],"&",d,e]
|
|
101 | +
|
|
102 | +["|",[[a |b] & c],[d& e]]
|
|
103 | +
|
|
104 | +[[[[a| b] |c]| [d & e]]]
|
|
105 | +
|
|
106 | +逻辑运算符包括
|
|
107 | +
|
|
108 | +符号说明
|
|
109 | +
|
|
110 | +丨[or]或,二元运算
|
|
111 | +
|
|
112 | +&[and]与,二元运算
|
|
113 | +
|
|
114 | +![no]非,单目运算
|
|
115 | +
|
|
116 | +逻辑运算符默认是"与","与"运算符可以不写。
|
|
117 | +
|
|
118 | +实例
|
|
119 | +
|
|
120 | +“名字为 ABC”,就是一个最简单的单条件Domain。
|
|
121 | +
|
|
122 | +[["name","=","ABC"]]
|
|
123 | +
|
|
124 | +“名字为 ABC 而且 语言编码不为en_US”,Domain里条件默认逻辑关系就是and,所以如下。
|
|
125 | +
|
|
126 | +[["name","=","ABC"],
|
|
127 | +
|
|
128 | +["language.code","!=","en_US"]]
|
|
129 | +
|
|
130 | +“名字为 ABC 而且语言编码不为 en_US 而且国家的编码为 be 或者 de”。
|
|
131 | +
|
|
132 | +[["name","=","ABC"],
|
|
133 | +
|
|
134 | +["language.code","!=","en_US"],
|
|
135 | +
|
|
136 | +"|",["country_id.code","=","be"],
|
|
137 | +
|
|
138 | +["country_id.code","=","de"]]
|
|
139 | +
|
|
140 | +如果我们要做到这个效果
|
|
141 | +
|
|
142 | +A and (B or C)and D and E
|
|
143 | +
|
|
144 | +先从里面开始,把or提前
|
|
145 | +
|
|
146 | +A and (orB C) and D and E
|
|
147 | +
|
|
148 | +把里面的and提前,去掉括号
|
|
149 | +
|
|
150 | +and A or B C and D E
|
|
151 | +
|
|
152 | +所以最后的filter可以这样写
|
|
153 | +
|
|
154 | +A,"|", B,C,D,E
|
|
155 | +
|
|
156 | +当然了,我们为什么不写得让自己也容易看一点呢,如下:
|
|
157 | +
|
|
158 | +A,D,E,"|", B,C
|
|
159 | +
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/11.\346\226\207\344\273\266.md
... | ... | @@ -0,0 +1,259 @@ |
1 | +--- |
|
2 | +title: 文件 |
|
3 | +date: 2023-09-25 17:31:34 |
|
4 | +permalink: /pages/07ebd3/ |
|
5 | +--- |
|
6 | +# 文件 |
|
7 | + |
|
8 | +## 文件(files) |
|
9 | + |
|
10 | +引擎提供了文件上传/file/upload接口。 |
|
11 | + |
|
12 | +如果用到了文件上传功能。需要在apps目录下添加sie-iidp-file-1.0-SNAPSHOT.jar项目包 |
|
13 | + |
|
14 | +如果用到excel导入导出功能。也需要在apps目录下添加sie-iidp-file-1.0-SNAPSHOT.jar项目包 |
|
15 | + |
|
16 | +```properties |
|
17 | +#minio |
|
18 | +minio.endpoint=http://192.168.175.54:9000 |
|
19 | +minio.accessKey=snest |
|
20 | +minio.secretKey=12345678 |
|
21 | +minio.bucketName=apps |
|
22 | +``` |
|
23 | + |
|
24 | +文件上传接口 |
|
25 | + |
|
26 | +| 请求方式 | POST | |
|
27 | +| --------- | ------------ | |
|
28 | +| 请求地址 | /file/upload | |
|
29 | +| 请求参数1 | file | |
|
30 | +| 请求参数2 | userId | |
|
31 | + |
|
32 | +文件预览接口 |
|
33 | + |
|
34 | +| 请求方式 | GET | |
|
35 | +| -------- | ------------- | |
|
36 | +| 请求地址 | /file/preview | |
|
37 | +| 请求参数 | id | |
|
38 | + |
|
39 | +文件下载接口 |
|
40 | + |
|
41 | +| 请求方式 | POST/GET | |
|
42 | +| --------- | -------------- | |
|
43 | +| 请求地址 | /file/download | |
|
44 | +| 请求参数1 | id | |
|
45 | +| 请求头 | Authorization | |
|
46 | + |
|
47 | +文件上传自定义 |
|
48 | + |
|
49 | +| 请求方式 | POST | |
|
50 | +| --------- | -------------------- | |
|
51 | +| 请求地址 | /file/uploadForModel | |
|
52 | +| 请求参数1 | model_id | |
|
53 | +| 请求参数2 | user_id | |
|
54 | + |
|
55 | + |
|
56 | + |
|
57 | +### 表格(excel) |
|
58 | + |
|
59 | +1.基础用法模型导入导出 |
|
60 | + |
|
61 | +rbac_role继承base_excel模型,拥有了export,Import两个服务。所以只需要配置视图就可以直接访问导入导出功能。如果默认服务不支持你的需求,当然我们也支持自定义导入导出服务,自己编写服务,详情请看高阶用法。 |
|
62 | + |
|
63 | +```json |
|
64 | +@SDK.Model(name = "rbac_role", type = Buss, displayName = "角色", parent = "base_excel") |
|
65 | +public class Role extends Model { |
|
66 | + |
|
67 | +} |
|
68 | +``` |
|
69 | + |
|
70 | +1.1 tbar视图配置导出(export) |
|
71 | + |
|
72 | +```json |
|
73 | + { |
|
74 | + "name": "导出", |
|
75 | + "action": "export", |
|
76 | + "properties": ["name","code","is_admin","remark"], |
|
77 | + "model": "rbac_role", |
|
78 | + "service": "export" |
|
79 | + } |
|
80 | +``` |
|
81 | + |
|
82 | +1.2 tbar视图配置导入(Import) |
|
83 | + |
|
84 | +```json |
|
85 | +{ |
|
86 | + "name":"导入", |
|
87 | + "action": "import", |
|
88 | + "model": "rbac_role", |
|
89 | + "service": "Import", |
|
90 | + "fileLimit": { |
|
91 | + "ext": ".xls,.xlsx", |
|
92 | + "maxSize": "2048" |
|
93 | + } |
|
94 | +} |
|
95 | +``` |
|
96 | + |
|
97 | +2.高阶用法服务自定义 |
|
98 | + |
|
99 | +自定义export,Import服务。需要自己定制化导入导出可以重写这两个服务,或者可以自己命名服务名。 |
|
100 | + |
|
101 | +```java |
|
102 | + @MethodService(description = "Excel导出") |
|
103 | + public void export(RecordSet rs, Filter filter, List<String> properties, Integer limit, Integer offset, String order) { |
|
104 | + Meta meta = rs.getMeta(); |
|
105 | + |
|
106 | + Map<String, List<Map<String, Object>>> datas = new LinkedHashMap<>(); |
|
107 | + |
|
108 | + RecordSet rs1 = meta.get(meta.getModelName()); |
|
109 | + if (Objects.isNull(rs1)) { |
|
110 | + throw new ExceException(String.format("文件导出异常,RecordSet %s 空", meta.getModelName())); |
|
111 | + } |
|
112 | + |
|
113 | + List<Map<String, Object>> values = rs1.search(filter, properties, limit, offset, order); |
|
114 | + |
|
115 | + ModelMeta modelMeta = rs1.getModel(); |
|
116 | + if (Objects.isNull(modelMeta)) { |
|
117 | + throw new ExceException(String.format("文件导出异常,modelMeta %s 空", meta.getModelName())); |
|
118 | + } |
|
119 | + |
|
120 | + List<Map<String, Object>> newValues = new ArrayList<>(); |
|
121 | + for (Map<String, Object> value : values) { |
|
122 | + Map<String, Object> newV = new LinkedHashMap<>(); |
|
123 | + for (String p : properties) { |
|
124 | + PropertyMeta propertyMeta = modelMeta.getProperty(p); |
|
125 | + if (!Objects.isNull(propertyMeta)) { |
|
126 | + Object v = value.getOrDefault(p, ""); |
|
127 | + newV.put(propertyMeta.getDisplayName(), v); |
|
128 | + } |
|
129 | + } |
|
130 | + newValues.add(newV); |
|
131 | + } |
|
132 | + |
|
133 | + |
|
134 | + String modelName = StringUtils.isNotBlank(modelMeta.getDisplayName()) ? modelMeta.getDisplayName() : modelMeta.getName(); |
|
135 | + datas.put(modelName, newValues); |
|
136 | + |
|
137 | + |
|
138 | + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); |
|
139 | + String fileName = modelName + "_" + dateFormat.format(new Date()); |
|
140 | + String suffix = ".xlsx"; |
|
141 | + |
|
142 | + rs.getMeta().get("base_excel").call("fileExport", datas, fileName + suffix); |
|
143 | + |
|
144 | + } |
|
145 | + |
|
146 | + |
|
147 | + @MethodService(description = "Excel导入") |
|
148 | + public boolean Import(RecordSet rs, String fileId) { |
|
149 | + Meta meta = rs.getMeta(); |
|
150 | + |
|
151 | + RecordSet rs1 = meta.get(meta.getModelName()); |
|
152 | + if (Objects.isNull(rs1)) { |
|
153 | + throw new ExceException(String.format("文件导入异常,RecordSet %s 空", meta.getModelName())); |
|
154 | + } |
|
155 | + |
|
156 | + ModelMeta modelMeta = rs1.getModel(); |
|
157 | + if (Objects.isNull(modelMeta)) { |
|
158 | + throw new ExceException(String.format("文件导入异常,modelMeta %s 空", meta.getModelName())); |
|
159 | + } |
|
160 | + |
|
161 | + String modelName = StringUtils.isNotBlank(modelMeta.getDisplayName()) ? modelMeta.getDisplayName() : modelMeta.getName(); |
|
162 | + |
|
163 | + Map<String, List<Map<String, Object>>> fileMap = (Map<String, List<Map<String, Object>>>) rs.getMeta().get( |
|
164 | + "base_excel").call("fileImport", fileId); |
|
165 | + |
|
166 | + List<Map<String, Object>> sheet1 = fileMap.get(modelName); |
|
167 | + for (Map<String, Object> s : sheet1) { |
|
168 | + |
|
169 | + Map<String, Object> mV = new LinkedHashMap<>(); |
|
170 | + for (Map.Entry<String, Object> v : s.entrySet()) { |
|
171 | + PropertyMeta propertyMeta = modelMeta.getProperties().stream().filter(p -> p.getDisplayName().equals(v.getKey())).findFirst().orElse(null); |
|
172 | + if (!Objects.isNull(propertyMeta)) { |
|
173 | + mV.put(propertyMeta.getName(), v.getValue()); |
|
174 | + } |
|
175 | + } |
|
176 | + |
|
177 | + rs.create(mV); |
|
178 | + } |
|
179 | + return true; |
|
180 | + } |
|
181 | +``` |
|
182 | + |
|
183 | +自定义excelExport视图文件 |
|
184 | + |
|
185 | +```json |
|
186 | + { |
|
187 | + "name": "导出", |
|
188 | + "action": "export", |
|
189 | + "properties": ["name","code","is_admin","remark"], |
|
190 | + "model": "rbac_role", |
|
191 | + "service": "export" |
|
192 | + } |
|
193 | +``` |
|
194 | + |
|
195 | +自定义excelImport视图文件 |
|
196 | + |
|
197 | +```json |
|
198 | +{ |
|
199 | + "name":"导入", |
|
200 | + "action": "import", |
|
201 | + "model": "rbac_role", |
|
202 | + "service": "Import", |
|
203 | + "fileLimit": { |
|
204 | + "ext": ".xls,.xlsx", |
|
205 | + "maxSize": "2048" |
|
206 | + } |
|
207 | +} |
|
208 | +``` |
|
209 | + |
|
210 | +3. 名词解释 |
|
211 | + |
|
212 | + | 属性 | 定义 | |
|
213 | + | ---------- | ---------------------------------------------------------- | |
|
214 | + | action | 事件用于前端支持按钮类型,import导入按钮,export导出按钮。 | |
|
215 | + | service | 服务名,就是MethodService标注的方法。 | |
|
216 | + | name | 中文名称 | |
|
217 | + | fileLimit | 文件上传限制,文件大小,格式类型。 | |
|
218 | + | model | 模型名 | |
|
219 | + | properties | 指定需要导出的属性名 | |
|
220 | + |
|
221 | +文件(file)后端上传 |
|
222 | +```java |
|
223 | + FileInputStream fileInputStream; |
|
224 | + try { |
|
225 | + fileInputStream = new FileInputStream(new File("D:\\gitlab\\sie-snest-dev\\sie-snest-apps\\snest-apps\\mom\\target\\mom-1.0-SNAPSHOT.jar")); |
|
226 | + Map<String, Object> upload = (Map<String, Object>) recordSet.getMeta().get("meta_attachment").call("putObject", fileInputStream,"mom-1.0-SNAPSHOT.jar","apps"); |
|
227 | + System.out.println(upload); |
|
228 | + } catch (FileNotFoundException e) { |
|
229 | + // TODO Auto-generated catch block |
|
230 | + e.printStackTrace(); |
|
231 | + } |
|
232 | +``` |
|
233 | + |
|
234 | +#### 文件后端上传备注 |
|
235 | +若上传的文件流来自于URL,那么需要读取并转成ByteArrayInputStream |
|
236 | +```java |
|
237 | +URLConnection connection = new URL(url).openConnection(); |
|
238 | +InputStream inputStream = connection.getInputStream(); |
|
239 | +byte[] bytes = streamToByteArray(inputStream); |
|
240 | +ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); |
|
241 | +Map<String, Object> upload = (Map<String, Object>) myMeta.get("meta_attachment").call("putObject", byteArrayInputStream, originalFilename, "apps"); |
|
242 | +``` |
|
243 | +```java |
|
244 | +public static byte[] streamToByteArray(InputStream in) throws IOException { |
|
245 | + ByteArrayOutputStream output = new ByteArrayOutputStream(); |
|
246 | + byte[] buffer = new byte[4096]; |
|
247 | + int n; |
|
248 | + while (-1 != (n = in.read(buffer))) { |
|
249 | + output.write(buffer, 0, n); |
|
250 | + } |
|
251 | + return output.toByteArray(); |
|
252 | +} |
|
253 | +``` |
|
254 | + |
|
255 | +#### 获取文件流 |
|
256 | +```java |
|
257 | +String fileId = "meta_attacment 的 id"; |
|
258 | +InputStream is = rs.get("base_file").call("getInputStream", fileId); |
|
259 | +``` |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/12.\350\247\206\345\233\276\346\250\241\345\236\213.md
... | ... | @@ -0,0 +1,246 @@ |
1 | +--- |
|
2 | +title: 视图模型 |
|
3 | +date: 2023-09-25 17:31:35 |
|
4 | +permalink: /pages/87cb74/ |
|
5 | +--- |
|
6 | +### 概念 |
|
7 | + |
|
8 | +1. 视图模型vm是其他模型(一个或多个)的逻辑组合,通过vm及vm的属性记录组合规则。 |
|
9 | +2. vm不持久化,也不在内存存储数据。vm的查询、创建、更新、删除操作是通过调用其他模型的方法实现的。 |
|
10 | +3. 对前端来说,vm和普通模型没有区别。 |
|
11 | +4. vm支持查询操作,创建、更新、删除等尽量支持,用户可以重写所有操作。 |
|
12 | + |
|
13 | +使用到的注解: |
|
14 | + |
|
15 | +`@View.From` 指定要映射的模型 |
|
16 | + |
|
17 | +`@View.MapProperty` 指定要映射的属性,`filter`指定内部筛选条件(字段使用from模型的字段) |
|
18 | + |
|
19 | +```java |
|
20 | +@Model(type = Model.ModelType.View) |
|
21 | +@View.From("meta_app") |
|
22 | +public class MetaAppVm extends BaseModel<MetaAppVm> { |
|
23 | + /** |
|
24 | + * 映射meta_app模型的name属性,字符串类型 |
|
25 | + */ |
|
26 | + private String name; |
|
27 | + |
|
28 | + private String display_name; |
|
29 | + |
|
30 | + private String summary; |
|
31 | + |
|
32 | + /** |
|
33 | + * 映射meta_app模型的source属性,字符串类型 |
|
34 | + */ |
|
35 | + @View.MapProperty(value = "source", filter = "[[\"source\", \"=\", \"'base'\"]]") |
|
36 | + private String app_source; |
|
37 | + |
|
38 | + @View.MapProperty(value = "category_ids.name") |
|
39 | + private String categoryName; |
|
40 | + |
|
41 | + @View.MapProperty(value = "category_ids.description") |
|
42 | + private String categoryDescription; |
|
43 | + |
|
44 | + /** |
|
45 | + * 映射meta_app模型的dependency_ids属性,OneToMany |
|
46 | + */ |
|
47 | + @View.MapProperty("dependency_ids") |
|
48 | + private String dependency; |
|
49 | + |
|
50 | + /** |
|
51 | + * 映射meta_app模型的product属性,ManyToOne |
|
52 | + */ |
|
53 | + private String product; |
|
54 | + |
|
55 | + /** |
|
56 | + * 累加一对多的多的一侧的值,可以JOIN、SUM |
|
57 | + */ |
|
58 | + @View.MapProperty("model_ids.name") |
|
59 | + @View.MapFunction(value = SQLFunction.JOIN, args = ",") |
|
60 | + private String modelNames; |
|
61 | + |
|
62 | + /** |
|
63 | + * 只添加@Property属性,代表不映射 |
|
64 | + * 可重写search方法,自由赋值 |
|
65 | + */ |
|
66 | + @Property |
|
67 | + private String time; |
|
68 | +} |
|
69 | +``` |
|
70 | + |
|
71 | +当所有的`@View.MapProperty`映射的都是同一个模型的属性时,视图模型的增删改查都有效,如果不是,则只有查询生效。 |
|
72 | + |
|
73 | +### 配置模式 |
|
74 | + |
|
75 | +@View.From是必须的 |
|
76 | + |
|
77 | +字段上如果没有@View.MapProperty注解,则@View.MapProperty的值默认为字段名称,如下: |
|
78 | + |
|
79 | +```java |
|
80 | +private String name; |
|
81 | +``` |
|
82 | + |
|
83 | +#### Many2One的属性可以多级关联 |
|
84 | + |
|
85 | +如下,model_ids是Many2One |
|
86 | + |
|
87 | +```java |
|
88 | +@View.MapProperty("model_ids.name") |
|
89 | +private String model; |
|
90 | +``` |
|
91 | + |
|
92 | +也可以这样,此时model_ids和user_ids都是Many2One |
|
93 | + |
|
94 | +```java |
|
95 | +@View.MapProperty("model_ids.user_ids") |
|
96 | +private String user; |
|
97 | +``` |
|
98 | + |
|
99 | +#### One2Many和Many2Many只能关联一级 |
|
100 | + |
|
101 | +如下,model_ids是One2Many或Many2Many,这种情况是关联了model_ids对应的模型 |
|
102 | + |
|
103 | +```java |
|
104 | +@View.MapProperty("model_ids") |
|
105 | +private Object model; |
|
106 | +``` |
|
107 | + |
|
108 | +如下,model_ids是One2Many或Many2Many,此时是把model_ids对应的模型的所有name属性值,用join操作拼接在一起(以逗号分隔),类似于java的`String.join`方法 |
|
109 | + |
|
110 | +```java |
|
111 | +@View.MapProperty("model_ids.name") |
|
112 | +@View.MapFunction(value = SQLFunction.JOIN, args = ",") |
|
113 | +private Object model; |
|
114 | +``` |
|
115 | + |
|
116 | +### 聚合查询 |
|
117 | + |
|
118 | +vm除了支持属性名查询外,还支持聚合查询,目前支持SUM COUNT AVG MAX MIN JOIN。 |
|
119 | + |
|
120 | +查询时请注意类型是否支持。 |
|
121 | + |
|
122 | +例如如下是查询所有的名称,并拼接: |
|
123 | + |
|
124 | +```json |
|
125 | +{ |
|
126 | + "id": "guid", |
|
127 | + "jsonrpc": "2.0", |
|
128 | + "method": "service", |
|
129 | + "params": { |
|
130 | + "args": { |
|
131 | + "useDisplayForModel": false, |
|
132 | + "order": "", |
|
133 | + "filter": [], |
|
134 | + "limit": 31, |
|
135 | + "offset": 0, |
|
136 | + "properties": [ |
|
137 | + { |
|
138 | + "value":"JOIN", |
|
139 | + "args":[","], |
|
140 | + "alias":"names", |
|
141 | + "name":"name" |
|
142 | + } |
|
143 | + ] |
|
144 | + }, |
|
145 | + "context": { |
|
146 | + "uid": "", |
|
147 | + "lang": "zh_CN" |
|
148 | + }, |
|
149 | + "model": "demo_order_vm", |
|
150 | + "tag": "master", |
|
151 | + "service": "search", |
|
152 | + "app": "newSdkApp" |
|
153 | + } |
|
154 | +} |
|
155 | +``` |
|
156 | + |
|
157 | +结果如下: |
|
158 | + |
|
159 | +```json |
|
160 | +{ |
|
161 | + "id": "guid", |
|
162 | + "jsonrpc": "2.0", |
|
163 | + "result": { |
|
164 | + "data": [ |
|
165 | + { |
|
166 | + "names": "order1,order2,order3,order4,order5,order6" |
|
167 | + } |
|
168 | + ] |
|
169 | + } |
|
170 | +} |
|
171 | +``` |
|
172 | + |
|
173 | +还部分支持分组功能: |
|
174 | + |
|
175 | +如下: |
|
176 | + |
|
177 | +```json |
|
178 | +{ |
|
179 | + "id": "guid", |
|
180 | + "jsonrpc": "2.0", |
|
181 | + "method": "service", |
|
182 | + "params": { |
|
183 | + "args": { |
|
184 | + "useDisplayForModel": false, |
|
185 | + "order": "", |
|
186 | + "filter": [], |
|
187 | + "limit": 31, |
|
188 | + "offset": 0, |
|
189 | + "properties": [ |
|
190 | + "date", |
|
191 | + { |
|
192 | + "value":"JOIN", |
|
193 | + "args":[","], |
|
194 | + "alias":"names", |
|
195 | + "name":"name" |
|
196 | + } |
|
197 | + ] |
|
198 | + }, |
|
199 | + "context": { |
|
200 | + "uid": "", |
|
201 | + "lang": "zh_CN" |
|
202 | + }, |
|
203 | + "model": "demo_order_vm", |
|
204 | + "tag": "master", |
|
205 | + "service": "search", |
|
206 | + "app": "newSdkApp" |
|
207 | + } |
|
208 | +} |
|
209 | +``` |
|
210 | + |
|
211 | +结果: |
|
212 | + |
|
213 | +```json |
|
214 | +{ |
|
215 | + "id": "guid", |
|
216 | + "jsonrpc": "2.0", |
|
217 | + "result": { |
|
218 | + "data": [ |
|
219 | + { |
|
220 | + "date": "2021-10-10 18:00:00", |
|
221 | + "names": "order3" |
|
222 | + }, |
|
223 | + { |
|
224 | + "date": "2022-12-10 18:00:00", |
|
225 | + "names": "order1,order4,order5,order6" |
|
226 | + }, |
|
227 | + { |
|
228 | + "date": "2023-12-05 17:00:00", |
|
229 | + "names": "order2" |
|
230 | + } |
|
231 | + ] |
|
232 | + } |
|
233 | +} |
|
234 | +``` |
|
235 | + |
|
236 | +其他示例: |
|
237 | + |
|
238 | +```json |
|
239 | + { |
|
240 | + "value":"MAX",SUM COUNT AVG MAX MIN中任一个 |
|
241 | + "alias":"price", 别名 |
|
242 | + "name":"price" 名称 |
|
243 | + } |
|
244 | +``` |
|
245 | + |
|
246 | +代码调用可使用`ViewModelDataAccess.MapFunction` |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/13.\346\227\245\346\234\237\347\261\273\345\236\213.md
... | ... | @@ -0,0 +1,256 @@ |
1 | +---
|
|
2 | +title: 日期类型
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/edf963/
|
|
5 | +---
|
|
6 | +# 模型属性
|
|
7 | +
|
|
8 | +```java
|
|
9 | +package com.sie.app.newsdk.test.model.demo;
|
|
10 | +
|
|
11 | +import com.sie.iidp.sdk.BaseModel;
|
|
12 | +import com.sie.iidp.sdk.DataType;
|
|
13 | +import com.sie.iidp.sdk.annotation.meta.Model;
|
|
14 | +import com.sie.iidp.sdk.annotation.meta.Property;
|
|
15 | +import java.sql.Timestamp;
|
|
16 | +import java.util.Date;
|
|
17 | +
|
|
18 | +@Model(displayName = "日期Demo")
|
|
19 | +public class DateDemo extends BaseModel<DateDemo> {
|
|
20 | +
|
|
21 | + @Property(displayName = "年", dataType = DataType.DATE, dateFormat = "yyyy")
|
|
22 | + private Date year;
|
|
23 | +
|
|
24 | + @Property(displayName = "月", dataType = DataType.DATE, dateFormat = "yyyy-MM")
|
|
25 | + private Date month;
|
|
26 | +
|
|
27 | + @Property(displayName = "月范围", dataType = DataType.DATE, dateFormat = "yyyy-MM")
|
|
28 | + private Date monthRange;
|
|
29 | +
|
|
30 | + @Property(displayName = "日", dataType = DataType.DATE)
|
|
31 | + private Date date;
|
|
32 | +
|
|
33 | + @Property(displayName = "日范围", dataType = DataType.DATE)
|
|
34 | + private Date dateRange;
|
|
35 | +
|
|
36 | + @Property(displayName = "时间", dataType = DataType.DATE_TIME, dateFormat = "yyyy-MM-dd HH:mm:ss")
|
|
37 | + private Timestamp datetime;
|
|
38 | +
|
|
39 | + @Property(displayName = "时间范围", dataType = DataType.DATE_TIME, dateFormat = "yyyy-MM-dd "
|
|
40 | + + "HH:mm:ss")
|
|
41 | + private Timestamp datetimeRange;
|
|
42 | +
|
|
43 | + public Date getYear() {
|
|
44 | + return getDate("year");
|
|
45 | + }
|
|
46 | +
|
|
47 | + public Date getMonth() {
|
|
48 | + return getDate("month");
|
|
49 | + }
|
|
50 | +
|
|
51 | + public Date getDate() {
|
|
52 | + return getDate("date");
|
|
53 | + }
|
|
54 | +
|
|
55 | + public Timestamp getDatetime() {
|
|
56 | + return getTimestamp("datetime");
|
|
57 | + }
|
|
58 | +}
|
|
59 | +```
|
|
60 | +
|
|
61 | +注意
|
|
62 | +
|
|
63 | +1. 请使用 getDate 或 getTimestamp 从 BaseModel 中获取日期、时间属性
|
|
64 | +2. 年、月、时间,需要在 @Property 指定格式化。
|
|
65 | +
|
|
66 | +# 视图文件
|
|
67 | +
|
|
68 | +```json
|
|
69 | +{
|
|
70 | + "views": {
|
|
71 | + "datedemo_grid": {
|
|
72 | + "body": {
|
|
73 | + "buttons": [
|
|
74 | + {
|
|
75 | + "action": "preview",
|
|
76 | + "auth": "read",
|
|
77 | + "name": "详情"
|
|
78 | + },
|
|
79 | + {
|
|
80 | + "action": "edit",
|
|
81 | + "auth": "update",
|
|
82 | + "name": "编辑"
|
|
83 | + }
|
|
84 | + ],
|
|
85 | + "columns": [
|
|
86 | + {
|
|
87 | + "label": "年",
|
|
88 | + "name": "year"
|
|
89 | + },
|
|
90 | + {
|
|
91 | + "label": "月",
|
|
92 | + "name": "month"
|
|
93 | + },
|
|
94 | + {
|
|
95 | + "label": "日",
|
|
96 | + "name": "date"
|
|
97 | + },
|
|
98 | + {
|
|
99 | + "label": "时间",
|
|
100 | + "name": "datetime"
|
|
101 | + },
|
|
102 | + {
|
|
103 | + "label": "月范围",
|
|
104 | + "name": "monthRange"
|
|
105 | + },
|
|
106 | + {
|
|
107 | + "label": "日范围",
|
|
108 | + "name": "dateRange"
|
|
109 | + },
|
|
110 | + {
|
|
111 | + "label": "时间范围",
|
|
112 | + "name": "datetimeRange"
|
|
113 | + }
|
|
114 | + ],
|
|
115 | + "tbar": [
|
|
116 | + {
|
|
117 | + "action": "create",
|
|
118 | + "auth": "create",
|
|
119 | + "name": "新增"
|
|
120 | + },
|
|
121 | + {
|
|
122 | + "action": "delete",
|
|
123 | + "auth": "delete",
|
|
124 | + "name": "删除"
|
|
125 | + }
|
|
126 | + ],
|
|
127 | + "type": "grid"
|
|
128 | + },
|
|
129 | + "mode": "primary",
|
|
130 | + "model": "DateDemo",
|
|
131 | + "name": "日期Demo-表格",
|
|
132 | + "type": "grid"
|
|
133 | + },
|
|
134 | + "datedemo_form": {
|
|
135 | + "body": {
|
|
136 | + "columns": [
|
|
137 | + {
|
|
138 | + "label": "年",
|
|
139 | + "name": "year"
|
|
140 | + },
|
|
141 | + {
|
|
142 | + "label": "月",
|
|
143 | + "name": "month"
|
|
144 | + },
|
|
145 | + {
|
|
146 | + "label": "日",
|
|
147 | + "name": "date"
|
|
148 | + },
|
|
149 | + {
|
|
150 | + "label": "时间",
|
|
151 | + "name": "datetime"
|
|
152 | + },
|
|
153 | + {
|
|
154 | + "label": "月范围",
|
|
155 | + "name": "monthRange"
|
|
156 | + },
|
|
157 | + {
|
|
158 | + "label": "日范围",
|
|
159 | + "name": "dateRange"
|
|
160 | + },
|
|
161 | + {
|
|
162 | + "label": "时间范围",
|
|
163 | + "name": "datetimeRange"
|
|
164 | + }
|
|
165 | + ],
|
|
166 | + "tabs": [],
|
|
167 | + "type": "form"
|
|
168 | + },
|
|
169 | + "mode": "primary",
|
|
170 | + "model": "DateDemo",
|
|
171 | + "name": "日期Demo-表单",
|
|
172 | + "type": "form"
|
|
173 | + },
|
|
174 | + "datedemo_search": {
|
|
175 | + "body": {
|
|
176 | + "columns": [
|
|
177 | + {
|
|
178 | + "label": "年",
|
|
179 | + "name": "year",
|
|
180 | + "widget": "year"
|
|
181 | + },
|
|
182 | + {
|
|
183 | + "label": "月",
|
|
184 | + "name": "month",
|
|
185 | + "widget": "month"
|
|
186 | + },
|
|
187 | + {
|
|
188 | + "label": "月范围",
|
|
189 | + "name": "monthRange",
|
|
190 | + "widget": "monthrange"
|
|
191 | + },
|
|
192 | + {
|
|
193 | + "label": "日",
|
|
194 | + "name": "date",
|
|
195 | + "widget": "date"
|
|
196 | + },
|
|
197 | + {
|
|
198 | + "label": "日范围",
|
|
199 | + "name": "dateRange",
|
|
200 | + "widget": "daterange"
|
|
201 | + },
|
|
202 | + {
|
|
203 | + "label": "时间",
|
|
204 | + "name": "datetime",
|
|
205 | + "widget": "datetime"
|
|
206 | + },
|
|
207 | + {
|
|
208 | + "label": "时间范围",
|
|
209 | + "name": "datetimeRange",
|
|
210 | + "widget": "datetimerange"
|
|
211 | + }
|
|
212 | + ],
|
|
213 | + "type": "search"
|
|
214 | + },
|
|
215 | + "mode": "primary",
|
|
216 | + "model": "DateDemo",
|
|
217 | + "name": "日期Demo-搜索",
|
|
218 | + "type": "search"
|
|
219 | + }
|
|
220 | + }
|
|
221 | +}
|
|
222 | +```
|
|
223 | +
|
|
224 | +注意
|
|
225 | +
|
|
226 | +1. 月、日期、时间范围只能用于搜索条件。不能用于表单、表格。
|
|
227 | +
|
|
228 | +# 效果
|
|
229 | +
|
|
230 | +选择年份
|
|
231 | +
|
|
232 | +
|
|
233 | +
|
|
234 | +选择月份
|
|
235 | +
|
|
236 | +
|
|
237 | +
|
|
238 | +选择日期
|
|
239 | +
|
|
240 | +
|
|
241 | +
|
|
242 | +选择时间
|
|
243 | +
|
|
244 | +
|
|
245 | +
|
|
246 | +月份范围
|
|
247 | +
|
|
248 | +
|
|
249 | +
|
|
250 | +日期范围
|
|
251 | +
|
|
252 | +
|
|
253 | +
|
|
254 | +时间范围
|
|
255 | +
|
|
256 | +
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/14.\346\226\271\346\263\225\347\274\223\345\255\230.md
... | ... | @@ -0,0 +1,49 @@ |
1 | +---
|
|
2 | +title: 方法缓存
|
|
3 | +date: 2023-09-25 17:31:34
|
|
4 | +permalink: /pages/3189d9/
|
|
5 | +---
|
|
6 | +方法缓存就是缓存方法的执行结果。在下一次调用的时候,如果确认参数没有变化,且缓存未失效,则不调用方法,直接返回缓存。合理使用缓存,可以提升应用性能。
|
|
7 | +
|
|
8 | +方法缓存是通过在方法上加注解实现的,如下所示:
|
|
9 | +
|
|
10 | +```java
|
|
11 | + /**
|
|
12 | + * 缓存方法的执行结果
|
|
13 | + */
|
|
14 | + @Cache(name = "c1",key = "s{1} {2} {3} {4} {5}")
|
|
15 | + @Override
|
|
16 | + public List<CustomerVm> search(Filter filter, List<String> properties, Integer limit, Integer offset, String order) {}
|
|
17 | +
|
|
18 | +
|
|
19 | + /**
|
|
20 | + * 取消名称是c1,key以s开头的所有缓存
|
|
21 | + */
|
|
22 | + @CacheInvalidate(name = "c1", prefix = "s")
|
|
23 | + public Object test1() {
|
|
24 | + return "demo_customer test1";
|
|
25 | + }
|
|
26 | +```
|
|
27 | +
|
|
28 | +### @Cache
|
|
29 | +
|
|
30 | +缓存注解,在方法上标注
|
|
31 | +
|
|
32 | +| 属性 | 描述 |
|
|
33 | +| ------ | ------------------------------------------------------------ |
|
|
34 | +| name | 缓存名称,运行时模型所在的app内唯一 |
|
|
35 | +| key | 根据参数生成的key,比如,`sss{1} ff{2.value}s`,{1},{2}分别代表第一个参数,第二个参数 |
|
|
36 | +| expire | 过期时间(秒) |
|
|
37 | +
|
|
38 | +### @CacheInvalidate
|
|
39 | +
|
|
40 | +取消缓存注解,在方法上标注。
|
|
41 | +
|
|
42 | +如果prefix不为空,则prefix优先生效。否则,keys生效。如果prefix和keys都不存在,则所有的参数组成一个默认的key
|
|
43 | +
|
|
44 | +| 属性 | 描述 |
|
|
45 | +| ------ | -------------------------------------------- |
|
|
46 | +| name | 缓存名称,运行时模型所在的app内唯一 |
|
|
47 | +| keys | key的数组 |
|
|
48 | +| prefix | 前缀,如果配置的话,以该参数为前缀的全部清除 |
|
|
49 | +
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_219294_P2fHoxgXOo4quuKx_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_219294_P2fHoxgXOo4quuKx_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_219726_zJHx8sApCAuVxgBy_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_219726_zJHx8sApCAuVxgBy_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_219923_gOVgIiTlrm8cJdMK_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_219923_gOVgIiTlrm8cJdMK_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_220574_KlGvUq-T5NaocRZA_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_220574_KlGvUq-T5NaocRZA_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_223223_-0TDYaRO8rUAYRLX_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_223223_-0TDYaRO8rUAYRLX_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_223757_LnAxuQoh9Jr7nedX_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_223757_LnAxuQoh9Jr7nedX_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_224006_OL-IdKUa8rwLlx42_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_224006_OL-IdKUa8rwLlx42_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_225420_hdEH5b25afNs6Y5G_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_225420_hdEH5b25afNs6Y5G_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_227154_8xHFh7HYzQzTMMel_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_227154_8xHFh7HYzQzTMMel_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_227738_sOGBJnBhvWLQUTys_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_227738_sOGBJnBhvWLQUTys_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_232556_Sv-wgL1unBs2A1ox_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/MTY4ODg1NzU0NjMyMjY3Mw_232556_Sv-wgL1unBs2A1ox_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/aa7d8c0c087de2c6522defe77d67ca8.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/aa7d8c0c087de2c6522defe77d67ca8.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/scope_date.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/scope_date.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/scope_month.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/scope_month.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/scope_time.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/scope_time.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/select_date.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/select_date.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/select_month.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/select_month.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/select_time.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/select_time.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/select_year.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/select_year.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/\345\276\256\344\277\241\346\210\252\345\233\276_20230221140934.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/03.\346\250\241\345\236\213\345\274\200\345\217\221\350\257\264\346\230\216/images/\345\276\256\344\277\241\346\210\252\345\233\276_20230221140934.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/04.\346\211\251\345\261\225\350\203\275\345\212\233\350\257\264\346\230\216/01.\346\250\241\345\236\213\346\211\251\345\261\225.md
... | ... | @@ -0,0 +1,70 @@ |
1 | +---
|
|
2 | +title: 模型扩展
|
|
3 | +date: 2023-09-25 17:31:35
|
|
4 | +permalink: /pages/0fd75a/
|
|
5 | +---
|
|
6 | +## 4.1. **模型扩展**
|
|
7 | +
|
|
8 | + 平台最核心的能力就是扩展能力。平台可以通过模型扩展增强或改变原有模型内部定义的服务、属性,甚至模型自身的元数据。模型扩展有两种方式:
|
|
9 | +
|
|
10 | +### 4.1.1. **模型内部crud扩展**
|
|
11 | +
|
|
12 | + 常见场景说明:想在平台提供默认的crud能力的基础上,做一些业务操作。
|
|
13 | +
|
|
14 | + 通过在业务模型中定义与默认方法签名相同的方法,来重写默认逻辑
|
|
15 | +
|
|
16 | +新增
|
|
17 | +
|
|
18 | +```
|
|
19 | +public void create(@Spec(doc = "k v") List<Map<String, Object>> valuesList) {
|
|
20 | +```
|
|
21 | +
|
|
22 | +
|
|
23 | +
|
|
24 | +修改
|
|
25 | +
|
|
26 | +```java
|
|
27 | +public int update(@BaseService.Spec(doc = "k v") Map<String, Object> values) {
|
|
28 | + ...
|
|
29 | +}
|
|
30 | +```
|
|
31 | +
|
|
32 | +
|
|
33 | +
|
|
34 | + 删除
|
|
35 | +
|
|
36 | +```java
|
|
37 | +public boolean delete() {
|
|
38 | + ...
|
|
39 | +}
|
|
40 | +```
|
|
41 | +
|
|
42 | +
|
|
43 | +
|
|
44 | +查询
|
|
45 | +
|
|
46 | +```java
|
|
47 | +public List<Map<String, Object>> search(@Spec(doc = "过滤器") Filter filter,
|
|
48 | + @Spec(doc = "多个属性") List<String> properties,
|
|
49 | + @Spec(doc = "初始位置") Integer limit,
|
|
50 | + @Spec(doc = "记录数") Integer offset,
|
|
51 | + @Spec(doc = "排序") String order) {
|
|
52 | + ...
|
|
53 | +}
|
|
54 | +```
|
|
55 | +
|
|
56 | +
|
|
57 | +
|
|
58 | +统计数量
|
|
59 | +
|
|
60 | +```java
|
|
61 | +public long count(@Spec(doc = "过滤") Filter filter) {
|
|
62 | + ...
|
|
63 | +}
|
|
64 | +```
|
|
65 | +
|
|
66 | +
|
|
67 | +
|
|
68 | +### 4.1.2. 跨app模型扩展
|
|
69 | +
|
|
70 | + 待补充
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/04.\346\211\251\345\261\225\350\203\275\345\212\233\350\257\264\346\230\216/02.APP\345\212\240\350\275\275\345\216\237\347\220\206\346\246\202\350\277\260.md
... | ... | @@ -0,0 +1,31 @@ |
1 | +---
|
|
2 | +title: APP加载原理概述
|
|
3 | +date: 2023-09-25 17:31:35
|
|
4 | +permalink: /pages/a5e1e7/
|
|
5 | +---
|
|
6 | +在本平台,每一个app都是相互隔离的。app都依赖引擎和JDK。app中的类在加载时,首先加载app的jar包中的类,如果找不到则查找平台提供的包,最后再查找JDK的包,如果都找不到,则报告类加载不到异常。如下图所示:
|
|
7 | +
|
|
8 | +
|
|
9 | +
|
|
10 | +### 平台提供的类
|
|
11 | +
|
|
12 | +平台主要向用户提供了以下包及其子包的类:
|
|
13 | +
|
|
14 | +1. com.sie.snest
|
|
15 | +2. org.slf4j
|
|
16 | +3. com.alibaba.fastjson
|
|
17 | +4. com.fasterxml.jackson
|
|
18 | +
|
|
19 | +### app中的内容
|
|
20 | +
|
|
21 | +#### 用户开发的文件
|
|
22 | +
|
|
23 | +主要包括模型、json文件和配置文件
|
|
24 | +
|
|
25 | +#### sdk
|
|
26 | +
|
|
27 | +平台提供的sdk类库
|
|
28 | +
|
|
29 | +#### 私有类库
|
|
30 | +
|
|
31 | +app及sdk依赖的第三方类库
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/04.\346\211\251\345\261\225\350\203\275\345\212\233\350\257\264\346\230\216/03.\344\270\216\347\254\254\344\270\211\346\226\271\345\271\263\345\217\260\344\272\244\344\272\222.md
... | ... | @@ -0,0 +1,75 @@ |
1 | +---
|
|
2 | +title: 与第三方平台交互
|
|
3 | +date: 2023-09-25 17:31:35
|
|
4 | +permalink: /pages/4412f9/
|
|
5 | +---
|
|
6 | +通过**4.2 APP加载原理概述**可知,app如果需要第三方的平台功能,则将第三方的包依赖进来,在打包时会自动作为私有库打入app的jar中。
|
|
7 | +
|
|
8 | +以Springboot为例,说明如何与第三方平台交互,代码可参考snest-demo项目下的feignDemo app。
|
|
9 | +
|
|
10 | +### 调用Springboot
|
|
11 | +
|
|
12 | +在app的启动事件中启动Springboot,将Springboot的`ApplicationContext`缓存成**静态变量**,供平台模型的方法调用;在卸载事件中停止Springboot并清除。
|
|
13 | +
|
|
14 | +Springboot相关的配置在app的resources下面正常配置即可。
|
|
15 | +
|
|
16 | +```java
|
|
17 | +@SpringBootApplication
|
|
18 | +public class SpringApp {
|
|
19 | +
|
|
20 | + private final static Logger logger = LoggerFactory.getLogger(SpringApp.class);
|
|
21 | +
|
|
22 | + private static ConfigurableApplicationContext context;
|
|
23 | +
|
|
24 | + public static synchronized void start(String[] args) {
|
|
25 | + if (context != null) {
|
|
26 | + return;
|
|
27 | + }
|
|
28 | +
|
|
29 | + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
|
|
30 | + // 调整类加载器为当前APP的类加载器
|
|
31 | + Thread.currentThread().setContextClassLoader(SpringApp.class.getClassLoader());
|
|
32 | + try {
|
|
33 | + context = SpringApplication.run(SpringApp.class, args);
|
|
34 | +
|
|
35 | + } catch (Throwable e) {
|
|
36 | + logger.error(e.getMessage(), e);
|
|
37 | + } finally {
|
|
38 | + Thread.currentThread().setContextClassLoader(contextClassLoader);
|
|
39 | + }
|
|
40 | + }
|
|
41 | +
|
|
42 | + public static synchronized void stop() {
|
|
43 | + if (context == null) {
|
|
44 | + return;
|
|
45 | + }
|
|
46 | +
|
|
47 | + try {
|
|
48 | + context.stop();
|
|
49 | + } catch (Throwable e) {
|
|
50 | + logger.error(e.getMessage(), e);
|
|
51 | + } finally {
|
|
52 | + context = null;
|
|
53 | + }
|
|
54 | + }
|
|
55 | +
|
|
56 | + /**
|
|
57 | + * 通过该方法调用Springboot容器中的对象
|
|
58 | + * @return
|
|
59 | + */
|
|
60 | + public static synchronized ConfigurableApplicationContext getContext() {
|
|
61 | + return context;
|
|
62 | + }
|
|
63 | +}
|
|
64 | +```
|
|
65 | +
|
|
66 | +如果使用的IDE是Intellij IDEA的Ultimate版本,并报JMX相关的错误,需要调整这个配置。
|
|
67 | +
|
|
68 | +
|
|
69 | +
|
|
70 | +### Springboot调用模型
|
|
71 | +
|
|
72 | +Springboot调用模型的方法需要通过Meta实例。可以在app的启动事件里缓存Meta实例,在卸载事件里清除。
|
|
73 | +
|
|
74 | +具体可参考*3.3.6.2 第三方线程调用模型方法*
|
|
75 | +
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/04.\346\211\251\345\261\225\350\203\275\345\212\233\350\257\264\346\230\216/images/ThirdPlatformDepend.svg
... | ... | @@ -0,0 +1 @@ |
1 | +<svg width="645" height="315" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" overflow="hidden"><defs><clipPath id="clip0"><rect x="170" y="240" width="645" height="315"/></clipPath></defs><g clip-path="url(#clip0)" transform="translate(-170 -240)"><rect x="735.5" y="241.5" width="79" height="249" stroke="#005959" stroke-width="1.33333" stroke-miterlimit="8" fill="#007C7C"/><text fill="#FFFFFF" font-family="Arial,Arial_MSFontService,sans-serif" font-weight="400" font-size="24" transform="translate(751.747 374)">JDK</text><rect x="171.5" y="241.5" width="432" height="48" stroke="#005B88" stroke-width="1.33333" stroke-miterlimit="8" fill="#007EBA"/><text fill="#FFFFFF" font-family="Microsoft YaHei,Microsoft YaHei_MSFontService,sans-serif" font-weight="400" font-size="24" transform="translate(363.144 274)">平台</text><rect x="171.5" y="360.5" width="184" height="65" stroke="#B05F00" stroke-width="1.33333" stroke-miterlimit="8" fill="#F08300"/><text fill="#FFFFFF" font-family="Arial,Arial_MSFontService,sans-serif" font-weight="400" font-size="21" transform="translate(216.566 388)">app1+sdk</text><text fill="#FFFFFF" font-family="Arial,Arial_MSFontService,sans-serif" font-weight="400" font-size="21" transform="translate(219.566 413)">+</text><text fill="#FFFFFF" font-family="Microsoft YaHei,Microsoft YaHei_MSFontService,sans-serif" font-weight="400" font-size="21" transform="translate(232.066 413)">私有</text><text fill="#FFFFFF" font-family="Arial,Arial_MSFontService,sans-serif" font-weight="400" font-size="21" transform="translate(274.733 413)">libs</text><rect x="427.5" y="425.5" width="176" height="65" stroke="#B05F00" stroke-width="1.33333" stroke-miterlimit="8" fill="#F08300"/><text fill="#FFFFFF" font-family="Arial,Arial_MSFontService,sans-serif" font-weight="400" font-size="21" transform="translate(468.593 453)">app2+sdk</text><text fill="#FFFFFF" font-family="Arial,Arial_MSFontService,sans-serif" font-weight="400" font-size="21" transform="translate(471.593 478)">+</text><text fill="#FFFFFF" font-family="Microsoft YaHei,Microsoft YaHei_MSFontService,sans-serif" font-weight="400" font-size="21" transform="translate(484.093 478)">私有</text><text fill="#FFFFFF" font-family="Arial,Arial_MSFontService,sans-serif" font-weight="400" font-size="21" transform="translate(526.759 478)">libs</text><path d="M1-1.47447e-06 1.0001 64.5365-0.999905 64.5365-1 1.47447e-06ZM4.00009 63.2031 0.000104987 71.2031-3.99991 63.2032Z" fill="#95A2AA" transform="matrix(1 0 0 -1 263 360.203)"/><path d="M1-7.70004e-07 1.0001 129.679-0.9999 129.679-1 7.70004e-07ZM4.0001 128.346 0.000104987 136.346-3.9999 128.346Z" fill="#95A2AA" transform="matrix(1 0 0 -1 515 425.346)"/><path d="M0.00251116-0.999997 125.658-0.684455 125.653 1.31554-0.00251116 0.999997ZM124.332-3.68779 132.322 0.332283 124.312 4.31218Z" fill="#95A2AA" transform="matrix(1 0 0 -1 603 265.332)"/><path d="M355.003 392 728.815 393.061 728.809 395.061 354.997 394ZM727.489 390.058 735.478 394.08 727.467 398.057Z" fill="#95A2AA"/><path d="M7.93418e-07-1 125.655-0.9999 125.655 1.0001-7.93418e-07 1ZM124.322-3.9999 132.322 0.000104987 124.322 4.0001Z" fill="#95A2AA" transform="matrix(1 0 0 -1 603 458)"/><path d="M7.93418e-07-1 125.655-0.9999 125.655 1.0001-7.93418e-07 1ZM124.322-3.9999 132.322 0.000104987 124.322 4.0001Z" fill="#95A2AA" transform="matrix(1 0 0 -1 197 531)"/><text font-family="Microsoft YaHei,Microsoft YaHei_MSFontService,sans-serif" font-weight="400" font-size="21" transform="translate(345.638 537)">依赖</text></g></svg> |
|
... | ... | \ No newline at end of file |
01.\345\274\200\345\217\221\346\211\213\345\206\214/04.\346\211\251\345\261\225\350\203\275\345\212\233\350\257\264\346\230\216/images/qw_16793692632274.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/04.\346\211\251\345\261\225\350\203\275\345\212\233\350\257\264\346\230\216/images/qw_16793692632274.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/05.\346\225\260\346\215\256\345\272\223\346\224\257\346\214\201/01.\346\224\257\346\214\201\347\232\204\346\225\260\346\215\256\345\272\223\347\261\273\345\236\213.md
... | ... | @@ -0,0 +1,6 @@ |
1 | +---
|
|
2 | +title: 支持的数据库类型
|
|
3 | +date: 2023-09-25 17:31:35
|
|
4 | +permalink: /pages/f04a49/
|
|
5 | +---
|
|
6 | +目前支持MySQL8、Oracle数据库。 |
|
... | ... | \ No newline at end of file |
01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA.md
... | ... | @@ -0,0 +1,11 @@ |
1 | +[[06.常见问题QA/01.常见问题QA.md]] |
|
2 | + |
|
3 | +[[/常见问题/selection下拉组件数据过多分页问题处理.txt]] |
|
4 | + |
|
5 | +[[/常见问题/关于重新扩展引擎字段tenant_id注意.txt]] |
|
6 | + |
|
7 | +[[/常见问题/分页条数与表格总数不一致.md]] |
|
8 | + |
|
9 | +[[/常见问题/重写方法后出现argument-type-mismatch错误.md]] |
|
10 | + |
|
11 | +[[/模型如果声明isAutoLog=true属性,会自动生成创建时间,创建人,修改时间修改人四个字段,但是此四个字段无法在前端显示.md]] |
|
... | ... | \ No newline at end of file |
01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/01.\345\270\270\350\247\201\351\227\256\351\242\230QA.md
... | ... | @@ -0,0 +1,133 @@ |
1 | +---
|
|
2 | +title: 常见问题QA
|
|
3 | +date: 2023-09-25 17:31:35
|
|
4 | +permalink: /pages/c7b051/
|
|
5 | +---
|
|
6 | +# 常见问题QA
|
|
7 | +
|
|
8 | +## 问题1:为什么配置视图文件总是不生效?
|
|
9 | +
|
|
10 | +解答:我们系统视图分为默认视图,手动配置视图。如果是默认视图,我们是没有view.json文件的。如果是手动配置视图,那么在我们的工程项目中,建立views目录建立mode.json视图文件。menu.json菜单视图配置项目中,菜单view属性配置视图模型对应的key值。app.json文件中也一同配置访问视图地址。这个时候需要重新打包我们的工程,重新跑起来即可。
|
|
11 | +
|
|
12 | +
|
|
13 | +
|
|
14 | +## 问题2:为什么linux配置日志级别不生效?
|
|
15 | +
|
|
16 | +解答:logback日志文件默认读取顺序为logging.xml、application.properties、logging-spring.xml,检查项目application.properties是否指定了logback配置文件,检查logback配置文件名称是否对得上,将logback配置文件对上,重启即可。
|
|
17 | +
|
|
18 | +
|
|
19 | +
|
|
20 | +## 问题3:如何排查linux中jvm加载的是哪个日志配置文件?
|
|
21 | +
|
|
22 | +解答:使用arthas工具排查,通过打印类的相关命令:
|
|
23 | +
|
|
24 | +1.启动arthas,监控jvm,命令:./as.sh
|
|
25 | +
|
|
26 | +
|
|
27 | +
|
|
28 | +2.查看jvm加载的日志组件,命令:logger
|
|
29 | +
|
|
30 | +
|
|
31 | +
|
|
32 | +3.获取classLoaderHash,命令:sc -d com.sie.iiot.apps.alarm.model.AlarmManager
|
|
33 | +
|
|
34 | +
|
|
35 | +
|
|
36 | +4.查看类加载的logger信息,命令:ognl -c 10dba097 '@com.sie.iiot.apps.alarm.model.AlarmManager@logger'
|
|
37 | +
|
|
38 | +
|
|
39 | +
|
|
40 | +5.查看jvm加载的logger配置文件信息,命令:ognl -c 1be6f5c3 '#map1=@org.slf4j.LoggerFactory@getLogger("root").loggerContext.objectMap, #map1.get("CONFIGURATION_WATCH_LIST")'
|
|
41 | +
|
|
42 | +
|
|
43 | +
|
|
44 | +可以看到加载的是这个配置文件/root/java-iiot/jar/sie-iiot-server-1.0-SNAPSHOT.jar!/BOOT-INF/classes!/logback.xml
|
|
45 | +
|
|
46 | +## 问题4: 查询性能优化
|
|
47 | +
|
|
48 | +如果模型中的字段有 ER 关系。引擎自动会做懒加载,但是在查询的时候,使用懒加载可能会导致性能比较差。
|
|
49 | +
|
|
50 | +以下模型作为示例
|
|
51 | +
|
|
52 | +```java
|
|
53 | +@Model
|
|
54 | +public class Staff extends BaseModel<Staff> {
|
|
55 | +
|
|
56 | + @ManyToOne(displayName = "部门")
|
|
57 | + @JoinColumn
|
|
58 | + private Dept dept;
|
|
59 | +
|
|
60 | + @OneToMany(displayName = "学习记录")
|
|
61 | + private List<LearningRecord> learningRecords;
|
|
62 | +
|
|
63 | + @Property(displayName = "附件", dataType = DataType.FILE, multiple = true, length = 10)
|
|
64 | + private List<String> file;
|
|
65 | +
|
|
66 | + public List<LearningRecord> getLearningRecords() {
|
|
67 | + return (List<LearningRecord>) get("learningRecords");
|
|
68 | + }
|
|
69 | +}
|
|
70 | +```
|
|
71 | +
|
|
72 | +```java
|
|
73 | +@Model
|
|
74 | +public class LearningRecord extends BaseModel<LearningRecord> {
|
|
75 | +
|
|
76 | + @ManyToOne(displayName = "员工")
|
|
77 | + @JoinColumn
|
|
78 | + private Staff staff;
|
|
79 | +
|
|
80 | + @Property(displayName = "内容")
|
|
81 | + private String content;
|
|
82 | +}
|
|
83 | +```
|
|
84 | +
|
|
85 | +### 4.1 不要在循环中使用懒加载
|
|
86 | +
|
|
87 | +错误做法
|
|
88 | +
|
|
89 | +```java
|
|
90 | +for (Staff staff : staffList) {
|
|
91 | + List<LearningRecord> learningRecords = staff.getLearningRecords();
|
|
92 | +}
|
|
93 | +```
|
|
94 | +
|
|
95 | +如果 staffList 有 100 条数据,那么会执行 100 次查询 LearningRecord 的语句。
|
|
96 | +
|
|
97 | +正确做法应该是一次性查询所有相关的 LearningRecord,然后再进行下一步处理。
|
|
98 | +
|
|
99 | +```java
|
|
100 | +Set<String> staffIds = staffList.stream()
|
|
101 | +.map(BaseModel::getId)
|
|
102 | +.collect(Collectors.toSet());
|
|
103 | +
|
|
104 | +List<LearningRecord> learningRecords = new ArrayList<>();
|
|
105 | +if (CollectionUtil.isNotEmpty(staffIds)) {
|
|
106 | + learningRecords = new LearningRecord().search(Filter.in("staff", staffIds), Collections.singletonList("*"), 0, 0, null);
|
|
107 | +}
|
|
108 | +
|
|
109 | +for (Staff staff : staffList) {
|
|
110 | + List<LearningRecord> records = learningRecords.stream()
|
|
111 | + .filter(r -> staff.getId().equals(r.getOrDefault("staff", "")))
|
|
112 | + .collect(Collectors.toList());
|
|
113 | + // 其他处理逻辑
|
|
114 | +}
|
|
115 | +```
|
|
116 | +
|
|
117 | +### 4.2 尽量不查询类型为 File 的字段
|
|
118 | +
|
|
119 | +dataType = File 的字段跟 @OneToMany 类似。如果在做表格查询、导出等功能,那么不要去查询这些字段。
|
|
120 | +
|
|
121 | +错误做法
|
|
122 | +
|
|
123 | +```java
|
|
124 | +new Staff().search(null, Collections.singletonList("*"), 0, 0, null);
|
|
125 | +```
|
|
126 | +
|
|
127 | +引擎在处理上面的查询时,会发出大量的查询语句去查询 MetaAttachment 模型。
|
|
128 | +
|
|
129 | +正确的做法
|
|
130 | +
|
|
131 | +```java
|
|
132 | +new Staff().search(null, Arrays.asList("id", "dept"), 0, 0, null);
|
|
133 | +```
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/02.crud\346\234\215\345\212\241\351\207\215\345\206\231.md
... | ... | @@ -0,0 +1,46 @@ |
1 | +重写模型CRUD方法 |
|
2 | + |
|
3 | +一、扩展中RecordSet参数可以无缝替换为ids,但必须声明为数组,RecordSet比ids多携带了模型信息,相当于代理了ids和模型信息。 如:rs.call("xxx"),可以直接代替this.getMeta().get("模型名"),显得更为简单 |
|
4 | + |
|
5 | + |
|
6 | +二、扩展中的Map<String,Object>参数可以由模型类来代替,因为在iidp平台中,每个sdk声明的模型都是个Map |
|
7 | + |
|
8 | +创建扩展: |
|
9 | +```java |
|
10 | +public RecordSet create(RecordSet rs, @Spec(doc = "k v") List<Map<String, Object>> valuesList) { |
|
11 | +} |
|
12 | + |
|
13 | +public RecordSet create(String[] ids, @Spec(doc = "k v") List<XXModel> valuesList) { |
|
14 | +} |
|
15 | +``` |
|
16 | + |
|
17 | +更新扩展: |
|
18 | +```java |
|
19 | +public RecordSet update(RecordSet rs, @Spec(doc = "k v") Map<String, Object> values) { |
|
20 | + |
|
21 | + } |
|
22 | + |
|
23 | +public RecordSet update(String[] ids, Map<String, Object> values) { |
|
24 | + |
|
25 | + } |
|
26 | + |
|
27 | +public RecordSet update(String[] ids, xxx values) { |
|
28 | + |
|
29 | + } |
|
30 | +``` |
|
31 | + |
|
32 | +删除扩展: |
|
33 | +```java |
|
34 | +public boolean delete(RecordSet rs) {} |
|
35 | +public boolean delete(String[] ids) {} |
|
36 | +``` |
|
37 | + |
|
38 | +search扩展: |
|
39 | +```java |
|
40 | +public List<Map<String, Object>> search(RecordSet rs, |
|
41 | + @Spec(doc = "过滤器") Filter filter, |
|
42 | + @Spec(doc = "多个属性") List<String> properties, |
|
43 | + @Spec(doc = "记录数") Integer limit, |
|
44 | + @Spec(doc = "初始位置") Integer offset, |
|
45 | + @Spec(doc = "排序") String order) {} |
|
46 | +``` |
|
... | ... | \ No newline at end of file |
01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/03.meta\344\270\212\344\270\213\346\226\207\344\275\277\347\224\250\350\247\204\350\214\203.md
... | ... | @@ -0,0 +1,5 @@ |
1 | +1.Meta上下文是平台传参的核心,千万别滥用,业务系统不得自行new Meta(),如已使用,请检查后去掉,优先改掉启动事件方法里的代码 |
|
2 | + |
|
3 | +2.当同步方法中需要meta时,应该通过BaseContextHandle.getMeta()来获取上下文信息; |
|
4 | + |
|
5 | +3.当异步方法中需要meta时,应该调用rs.callAsync,不得自行创建线程 |
|
... | ... | \ No newline at end of file |
01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/images/image-20230322101850358.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/images/image-20230322101850358.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/images/image-20230322101945614.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/images/image-20230322101945614.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/images/image-20230322102008797.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/images/image-20230322102008797.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/images/image-20230322102055902.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/images/image-20230322102055902.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/images/image-20230322103208745.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/images/image-20230322103208745.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/images/image-20230322103222684.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/images/image-20230322103222684.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/07.API \345\205\254\345\205\261\351\224\231\350\257\257\347\240\201/01.api.md
... | ... | @@ -0,0 +1,34 @@ |
1 | +---
|
|
2 | +title: api
|
|
3 | +date: 2023-09-25 17:31:35
|
|
4 | +permalink: /pages/2e3e7e/
|
|
5 | +---
|
|
6 | +# API 公共错误码
|
|
7 | +
|
|
8 | +**注意:**
|
|
9 | +
|
|
10 | +- 下表为公共错误码。开发者在接入过程中遇到其他报错信息,可以参考所调用接口的 API 文档的 **业务错误码** 部分。
|
|
11 | +
|
|
12 | +- 服务权限不足问题请参考
|
|
13 | +
|
|
14 | + [权限配置说明]:
|
|
15 | +
|
|
16 | +| **code(返回码)** | **msg(返回码描述)** | **sub_code(明细返回码)** | **sub_msg(明细返回码描述)** | **解决方案** |
|
|
17 | +| --------------------------------- | ------------------------------------------------------------ | ---------------------------------------- | ------------------------------ | ------------------------------------ |
|
|
18 | +| 10000 | 接口调用成功,调用结果请参考具体的 API 所对应的业务返回参数。 | | | |
|
|
19 | +| 20000 | 服务不可用 | isp.unknow-error | 服务暂不可用(业务系统不可用) | 稍后重试 |
|
|
20 | +| aop.unknow-error | 服务暂不可用(网关自身的未知错误) | 稍后重试 | | |
|
|
21 | +| 20001 | 授权权限不足 | aop.invalid-auth-token | 无效的访问令牌 | 请刷新授权令牌或重新授权获取新的令牌 |
|
|
22 | +| aop.auth-token-time-out | 访问令牌已过期 | 请刷新授权令牌或重新授权获取新的令牌 | | |
|
|
23 | +| aop.invalid-app-auth-token | 无效的应用授权令牌 | 请刷新应用授权令牌或重新授权获取新的令牌 | | |
|
|
24 | +| aop.invalid-app-auth-token-no-api | 未授权当前接口 | 请重新授权获取新的应用授权令牌 | | |
|
|
25 | +| aop.app-auth-token-time-out | 应用授权令牌已过期 | 请刷新应用授权令牌或重新授权获取新的令牌 | | |
|
|
26 | +| | | | | |
|
|
27 | +| | | | | |
|
|
28 | +| | | | | |
|
|
29 | +| | | | | |
|
|
30 | +| | | | | |
|
|
31 | +| | | | | |
|
|
32 | +| | | | | |
|
|
33 | +| | | | | |
|
|
34 | +| | | | | | |
|
... | ... | \ No newline at end of file |
01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/01.\350\277\201\347\247\273.md
... | ... | @@ -0,0 +1,51 @@ |
1 | +---
|
|
2 | +title: 迁移
|
|
3 | +date: 2023-09-26 17:00:58
|
|
4 | +permalink: /pages/3cbbd0/
|
|
5 | +---
|
|
6 | +关于已有的开发文档迁移到新的文档系统的说明
|
|
7 | +已有的文档在这里: http://192.168.175.55:9888/TECH/tech-web-docs
|
|
8 | +新的文档系统在这里: http://192.168.175.55:9888/appDev/iidp-docs
|
|
9 | +
|
|
10 | +### 1、迁移的目的
|
|
11 | +主要有两个目的:一个是好看,另一个是方便搜索。
|
|
12 | +因为新的文档系统界面看起来比较美观,而且支持全文索引。
|
|
13 | +
|
|
14 | +
|
|
15 | +
|
|
16 | +
|
|
17 | +### 2、迁移的方式
|
|
18 | +
|
|
19 | +- 我们先看看已有的文档系统是如何做的?
|
|
20 | +
|
|
21 | +
|
|
22 | +
|
|
23 | +由上图可知,定位一个md文件是通过人工指定该md文件的目录路径。
|
|
24 | +
|
|
25 | +- 我们再看看新的文档系统是如何做的?
|
|
26 | +
|
|
27 | +
|
|
28 | +
|
|
29 | +
|
|
30 | +
|
|
31 | +
|
|
32 | +
|
|
33 | +由上图可知,定位一个md文件是通过人工指定该md文件的唯一标识,这个唯一标识是在md文件中自动生成的,
|
|
34 | +而且进入任何一个md文件,那么整个的目录结构都会显示出来,这样就可以方便的定位到任何一个md文件。
|
|
35 | +
|
|
36 | +整个的流程是:首页的开发文档 --> 快速上手 --> 正文。
|
|
37 | +相当于说在正文和首页间加了一个二级索引。
|
|
38 | +
|
|
39 | +- 最后我们预估一下迁移需要的工作量
|
|
40 | +
|
|
41 | +1. 将所有的原md文档复制粘贴到新的文档系统;
|
|
42 | +2. 将已有的目录命名方式统一修改成 01 02 03 ... 这种形式;
|
|
43 | +3. 将之前用目录区分的md文件统一放在一个文件夹下,比如说:02.快速上手,该文件夹下的都是关于快速上手的内容,不需要再建文件夹;
|
|
44 | +4. 重新调整链接图片的地址,因为旧的文档系统是直接 images/xxx.png 这种形式,需要修改成 ./images/xxx.png 这种形式;
|
|
45 | + 为什么要做这种修改呢?本质上是因为新旧文档系统是完全不同的。新的文档系统编译后的目录结构是这样的:
|
|
46 | + 
|
|
47 | +
|
|
48 | + 所以,新的文档系统正确的索引图片的方式是:/img/xxx.png
|
|
49 | + 但是为了减少迁移的工作量,且为了方便本编写md文件时可以预览,使用 ./images/xxxx.png 是可以的,也是我个人比较推荐的。
|
|
50 | + 另外,图片路径不要使用中文,否则build会报错。
|
|
51 | + 
|
01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_1.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_1.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_2.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_2.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_3.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_3.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_4.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_4.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_5.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_5.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_6.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_6.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_7.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_7.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_8.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/images/img_8.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/img.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/08.\350\277\201\347\247\273\347\233\270\345\205\263/img.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_174857_Nwmsn3qDYtdCAuWy_1672383326.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_174857_Nwmsn3qDYtdCAuWy_1672383326.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_24249_4VqgsiaLQPJJihwj_1672383968w=1280&h=273.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_24249_4VqgsiaLQPJJihwj_1672383968w=1280&h=273.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_566526_T7xPlCNMYko_d7bx_1672383887w=1280&h=647.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_566526_T7xPlCNMYko_d7bx_1672383887w=1280&h=647.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_622945_zN9R0Rg0h85FbSCo_1672379476w=1280&h=414.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_622945_zN9R0Rg0h85FbSCo_1672379476w=1280&h=414.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_633679_tHXBuGttoeRrJQrV_1672381187.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_633679_tHXBuGttoeRrJQrV_1672381187.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_707527_QUEYKsDbMw8lddKj_1672381143.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_707527_QUEYKsDbMw8lddKj_1672381143.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_741522_yvNZnpYufycL5nrh_1672383741w=1280&h=625.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_741522_yvNZnpYufycL5nrh_1672383741w=1280&h=625.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_861905_B1WZZPeB5n82aRJj_1672383351.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_861905_B1WZZPeB5n82aRJj_1672383351.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_935953_uF69TGR02M2xDXJr_1672379524w=1280&h=119.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_935953_uF69TGR02M2xDXJr_1672379524w=1280&h=119.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_95145_2-ny1r4XSeGyQglN_1672380830.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_95145_2-ny1r4XSeGyQglN_1672380830.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_964603_wfon-VgSfQ6GGHcD_1672384019w=1280&h=330.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDExNTYwMTY5MjA_964603_wfon-VgSfQ6GGHcD_1672384019w=1280&h=330.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDMwNDMxMjA1MTQ_769720_WiwuW6ZChFpNuvgF_1672718142w=1280&h=583.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDMwNDMxMjA1MTQ_769720_WiwuW6ZChFpNuvgF_1672718142w=1280&h=583.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDMwNDMxMjA1MTQ_895868_Bas-UY2qx9IP_eh__1672717798w=1280&h=475-1674994709695-3.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDMwNDMxMjA1MTQ_895868_Bas-UY2qx9IP_eh__1672717798w=1280&h=475-1674994709695-3.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDMwNDMxMjA1MTQ_895868_Bas-UY2qx9IP_eh__1672717798w=1280&h=475.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTMxMDI3MDMwNDMxMjA1MTQ_895868_Bas-UY2qx9IP_eh__1672717798w=1280&h=475.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_219294_P2fHoxgXOo4quuKx_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_219294_P2fHoxgXOo4quuKx_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_219726_zJHx8sApCAuVxgBy_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_219726_zJHx8sApCAuVxgBy_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_219923_gOVgIiTlrm8cJdMK_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_219923_gOVgIiTlrm8cJdMK_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_220574_KlGvUq-T5NaocRZA_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_220574_KlGvUq-T5NaocRZA_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_223223_-0TDYaRO8rUAYRLX_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_223223_-0TDYaRO8rUAYRLX_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_223482_WtefS9JuEMu-t79j_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_223482_WtefS9JuEMu-t79j_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_223757_LnAxuQoh9Jr7nedX_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_223757_LnAxuQoh9Jr7nedX_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_224006_OL-IdKUa8rwLlx42_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_224006_OL-IdKUa8rwLlx42_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_224121_xT6GpOEWFHvEMdMj_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_224121_xT6GpOEWFHvEMdMj_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_225420_hdEH5b25afNs6Y5G_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_225420_hdEH5b25afNs6Y5G_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_227154_8xHFh7HYzQzTMMel_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_227154_8xHFh7HYzQzTMMel_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_227738_sOGBJnBhvWLQUTys_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_227738_sOGBJnBhvWLQUTys_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_232556_Sv-wgL1unBs2A1ox_1672361879.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/MTY4ODg1NzU0NjMyMjY3Mw_232556_Sv-wgL1unBs2A1ox_1672361879.png" differ |
01.\345\274\200\345\217\221\346\211\213\345\206\214/images/\345\276\256\344\277\241\346\210\252\345\233\276_20230221140934.png
... | ... | Binary files /dev/null and "a/01.\345\274\200\345\217\221\346\211\213\345\206\214/images/\345\276\256\344\277\241\346\210\252\345\233\276_20230221140934.png" differ |
111.md
... | ... | @@ -0,0 +1 @@ |
1 | +11 |
|
... | ... | \ No newline at end of file |
Hazelcast\345\210\206\345\270\203\345\274\217\345\206\205\345\255\230\345\220\214\346\255\245\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md
... | ... | @@ -0,0 +1,864 @@ |
1 | +### 1,需求分析 |
|
2 | + |
|
3 | +整个平台如果在单机版的情况下,因为所有的数据都在同一个进程或者pod中, |
|
4 | +那么是不存在分布式数据同步的问题,但是如果是在分布式部署情况下,由于多副本,以及app和meta之间的关联性 |
|
5 | +(比如说app1和app2都包含meta1,那么meta1变动,需要同步给其他所有关联的app1和app2) |
|
6 | +那么此时就需要分布式同步的功能,必须保证所有的pod和所有的app都能获取到最新的更新, |
|
7 | +这样才能保证对外是统一的一致性的表现。 |
|
8 | + |
|
9 | +在已有的分布式环境,不同的app安装在独立的容器中,且没有多副本,所以说本质上跟单机版是一样的,只是对于整个软件系统来说它通过分片的方式, |
|
10 | +将各个应用隔离开来,各自的内容数据也只用于各自的容器中。 |
|
11 | + |
|
12 | +但是对于多租户app来说,它在分布式环境中是必须所有的app都需要的,那么自然涉及到不同容器中的内存同步。 |
|
13 | + |
|
14 | +同理,对于多副本来说更是需要内存的同步。 |
|
15 | + |
|
16 | +### 2,方案选型 |
|
17 | + |
|
18 | +- ~~基于redis stream的时间发布订阅同步~~ |
|
19 | + ~~这是早期的版本,由于各种问题(尤其是并发写)现在已经废弃。~~ |
|
20 | + |
|
21 | + |
|
22 | + |
|
23 | +- 基于 Hazelcast |
|
24 | + Hazelcast 是由Hazelcast公司开发的一款开源的分布式内存级别的缓存数据库,可以为基于JVM环境运行的各种应用提供分布式集群和分布式缓存服务。 |
|
25 | + 1. 我们需要什么? |
|
26 | + 通过在实际的项目中,我们的模型、菜单、策略等信息都是保持在一个map中,所以我们需要的是一个分布式map, |
|
27 | + 在一个节点操作,会自动同步给所有的节点(对于可分片的数据结构,比如map,实际实现不是全部同步,而是基于分片的形式,但是在用户看来跟同步效果是一样的)。另外,我们对于一些websocket消息,还需要一个分布式queue来统一分发。 |
|
28 | + |
|
29 | + 2. Hazelcast 能提供什么? |
|
30 | + 它能提供分布式map,分布式queue。而且它实现了java标准库中Map的所有接口,对于使用者来说之前怎么用map的,现在依然可以使用, |
|
31 | + 不需要修改任何方法,这对已有的代码是非常友好的。另外,它提供了嵌入式的使用方式,这是我认为最有价值的地方,分布式缓存在市面上已经有很多了, |
|
32 | + 比如 ignite redis 等等,但是它们都不支持嵌入式的使用方式,需要额外独立的部署第三方服务,这对一些使用场景来说显得有点复杂了。 |
|
33 | + 此时,Hazelcast的嵌入式使用体验就非常友好,只需要引入一个jar包即可,每个节点即是客户端也是服务端,自动服务注册和发现(比如多播,比如基于k8s等),自动组网, |
|
34 | + 对于用户来说非常方便。 |
|
35 | + |
|
36 | +### 3,功能实现 |
|
37 | +- 基于Hazelcast 提供一个distributed map |
|
38 | +```java |
|
39 | +public class DMap { |
|
40 | + private static boolean distributed = WebMode.of(ConfigConstant.ENGINE_RUN_MODE) != WebMode.DISTRIBUTED; |
|
41 | + |
|
42 | + static private String mapName = "hazelcast-map"; |
|
43 | + static private HazelcastInstance instance; |
|
44 | + |
|
45 | + static { |
|
46 | + if (distributed) { |
|
47 | + SystemInfo systemInfo = new SystemInfo(); |
|
48 | + String id = IdGenerator.getDeviceId(systemInfo); |
|
49 | + String instanceName = "hazelInstance_" + id; |
|
50 | + String clusterName = "iidp-hazelcast-cluster"; |
|
51 | + Config config = new Config(); |
|
52 | + config.setClusterName(clusterName); |
|
53 | + config.setInstanceName(instanceName); |
|
54 | + config.getJetConfig().setEnabled(false); |
|
55 | + config.setIntegrityCheckerConfig(new IntegrityCheckerConfig().setEnabled(false)); |
|
56 | + config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false); |
|
57 | + config.getNetworkConfig().getJoin().getKubernetesConfig().setEnabled(true); |
|
58 | + // map config |
|
59 | + // config.getMapConfig(). |
|
60 | + instance = Hazelcast.newHazelcastInstance(config); |
|
61 | + } |
|
62 | + } |
|
63 | + |
|
64 | + // 如果用户想直接hazelcast提供的方法可以在获取instance后直接使用 |
|
65 | + static public HazelcastInstance getInstance() { |
|
66 | + return instance; |
|
67 | + } |
|
68 | + |
|
69 | + static public <K, V> Map<K, V> getMap() { |
|
70 | + if (!distributed) { |
|
71 | + return new HashMap<>(); |
|
72 | + } |
|
73 | + |
|
74 | + return instance.getMap(mapName); |
|
75 | + } |
|
76 | + |
|
77 | + static public <K, V> Map<K, V> getMap(String name) { |
|
78 | + if (!distributed) { |
|
79 | + return new HashMap<>(); |
|
80 | + } |
|
81 | + |
|
82 | + return instance.getMap(name); |
|
83 | + } |
|
84 | +} |
|
85 | + |
|
86 | +``` |
|
87 | + |
|
88 | +- 在业务代码中,将需要分布式map的地方替换成DMap |
|
89 | +由于DMap实现了标准库中的Map接口,就可以将DMap当作普通的map使用了。 |
|
90 | +```java |
|
91 | +private Map<String, MenuMeta> menuMetaMap = DMap.getMap(); |
|
92 | +``` |
|
93 | +但是实际使用场景中,比较复杂,主要体现在序列化问题,在内存中可以通过引用的方式修改map 的value等问题,需要业务自行调整和适配。 |
|
94 | + |
|
95 | +### 4,性能测试 |
|
96 | +#### 1、简单的map put 操作 |
|
97 | + |
|
98 | +``` |
|
99 | +mp.Put(ctx, "foo", "bar") |
|
100 | +``` |
|
101 | + |
|
102 | +结果: |
|
103 | + |
|
104 | +- 1个并发,共10个请求 |
|
105 | + |
|
106 | +``` |
|
107 | +time="2023-11-29T15:15:39+08:00" level=info msg="total: 10 concurrency: 1 requests per client: 10" |
|
108 | +time="2023-11-29T15:15:39+08:00" level=info msg="took 27 ms for 10 requests" |
|
109 | +time="2023-11-29T15:15:39+08:00" level=info msg="sent requests : 10" |
|
110 | +time="2023-11-29T15:15:39+08:00" level=info msg="received requests : 10" |
|
111 | +time="2023-11-29T15:15:39+08:00" level=info msg="received requests_OK : 10" |
|
112 | +time="2023-11-29T15:15:39+08:00" level=info msg="throughput (TPS) : 370" |
|
113 | +time="2023-11-29T15:15:39+08:00" level=info msg="mean: 2772220 ns, median: 1824400 ns, max: 9850800 ns, min: 1627700 ns, p99.9: 6775050 ns" |
|
114 | +time="2023-11-29T15:15:39+08:00" level=info msg="mean: 2 ms, median: 1 ms, max: 9 ms, min: 1 ms, p99.9: 6 ms" |
|
115 | + |
|
116 | +``` |
|
117 | + |
|
118 | +- 10个并发,共100个请求 |
|
119 | + |
|
120 | +``` |
|
121 | +time="2023-11-29T15:17:57+08:00" level=info msg="total: 100 concurrency: 10 requests per client: 10" |
|
122 | +time="2023-11-29T15:17:57+08:00" level=info msg="took 84 ms for 100 requests" |
|
123 | +time="2023-11-29T15:17:57+08:00" level=info msg="sent requests : 100" |
|
124 | +time="2023-11-29T15:17:57+08:00" level=info msg="received requests : 100" |
|
125 | +time="2023-11-29T15:17:57+08:00" level=info msg="received requests_OK : 100" |
|
126 | +time="2023-11-29T15:17:57+08:00" level=info msg="throughput (TPS) : 1190" |
|
127 | +time="2023-11-29T15:17:57+08:00" level=info msg="mean: 8230900 ns, median: 5983800 ns, max: 28240200 ns, min: 1665600 ns, p99.9: 28240200 ns" |
|
128 | +time="2023-11-29T15:17:57+08:00" level=info msg="mean: 8 ms, median: 5 ms, max: 28 ms, min: 1 ms, p99.9: 28 ms" |
|
129 | + |
|
130 | +``` |
|
131 | + |
|
132 | +- 10个并发,共1000个请求 |
|
133 | + |
|
134 | +``` |
|
135 | +time="2023-11-29T15:19:25+08:00" level=info msg="total: 1000 concurrency: 10 requests per client: 100" |
|
136 | +time="2023-11-29T15:19:26+08:00" level=info msg="took 836 ms for 1000 requests" |
|
137 | +time="2023-11-29T15:19:26+08:00" level=info msg="sent requests : 1000" |
|
138 | +time="2023-11-29T15:19:26+08:00" level=info msg="received requests : 1000" |
|
139 | +time="2023-11-29T15:19:26+08:00" level=info msg="received requests_OK : 1000" |
|
140 | +time="2023-11-29T15:19:26+08:00" level=info msg="throughput (TPS) : 1196" |
|
141 | +time="2023-11-29T15:19:26+08:00" level=info msg="mean: 8338478 ns, median: 6197100 ns, max: 25382100 ns, min: 1508800 ns, p99.9: 25382100 ns" |
|
142 | +time="2023-11-29T15:19:26+08:00" level=info msg="mean: 8 ms, median: 6 ms, max: 25 ms, min: 1 ms, p99.9: 25 ms" |
|
143 | + |
|
144 | +``` |
|
145 | + |
|
146 | +- 10个并发,共10000个请求 |
|
147 | + |
|
148 | +``` |
|
149 | +time="2023-11-29T15:22:55+08:00" level=info msg="total: 10000 concurrency: 10 requests per client: 1000" |
|
150 | +time="2023-11-29T15:23:08+08:00" level=info msg="took 13574 ms for 10000 requests" |
|
151 | +time="2023-11-29T15:23:08+08:00" level=info msg="sent requests : 10000" |
|
152 | +time="2023-11-29T15:23:08+08:00" level=info msg="received requests : 10000" |
|
153 | +time="2023-11-29T15:23:08+08:00" level=info msg="received requests_OK : 10000" |
|
154 | +time="2023-11-29T15:23:08+08:00" level=info msg="throughput (TPS) : 736" |
|
155 | +time="2023-11-29T15:23:08+08:00" level=info msg="mean: 13561955 ns, median: 9066300 ns, max: 652868300 ns, min: 1509600 ns, p99.9: 619792100 ns" |
|
156 | +time="2023-11-29T15:23:08+08:00" level=info msg="mean: 13 ms, median: 9 ms, max: 652 ms, min: 1 ms, p99.9: 619 ms" |
|
157 | + |
|
158 | +``` |
|
159 | + |
|
160 | +- 50个并发,共100个请求 |
|
161 | + |
|
162 | +``` |
|
163 | +time="2023-11-29T15:24:14+08:00" level=info msg="total: 100 concurrency: 50 requests per client: 2" |
|
164 | +time="2023-11-29T15:24:14+08:00" level=info msg="took 20 ms for 100 requests" |
|
165 | +time="2023-11-29T15:24:14+08:00" level=info msg="sent requests : 100" |
|
166 | +time="2023-11-29T15:24:14+08:00" level=info msg="received requests : 100" |
|
167 | +time="2023-11-29T15:24:14+08:00" level=info msg="received requests_OK : 100" |
|
168 | +time="2023-11-29T15:24:14+08:00" level=info msg="throughput (TPS) : 5000" |
|
169 | +time="2023-11-29T15:24:14+08:00" level=info msg="mean: 7575184 ns, median: 4312200 ns, max: 16251400 ns, min: 2274700 ns, p99.9: 16251400 ns" |
|
170 | +time="2023-11-29T15:24:14+08:00" level=info msg="mean: 7 ms, median: 4 ms, max: 16 ms, min: 2 ms, p99.9: 16 ms" |
|
171 | + |
|
172 | +``` |
|
173 | + |
|
174 | +- 50个并发,共1000个请求 |
|
175 | + |
|
176 | +``` |
|
177 | +time="2023-11-29T15:25:37+08:00" level=info msg="total: 1000 concurrency: 50 requests per client: 20" |
|
178 | +time="2023-11-29T15:25:37+08:00" level=info msg="took 322 ms for 1000 requests" |
|
179 | +time="2023-11-29T15:25:37+08:00" level=info msg="sent requests : 1000" |
|
180 | +time="2023-11-29T15:25:37+08:00" level=info msg="received requests : 1000" |
|
181 | +time="2023-11-29T15:25:37+08:00" level=info msg="received requests_OK : 1000" |
|
182 | +time="2023-11-29T15:25:37+08:00" level=info msg="throughput (TPS) : 3105" |
|
183 | +time="2023-11-29T15:25:37+08:00" level=info msg="mean: 15954920 ns, median: 15421600 ns, max: 31742900 ns, min: 1670800 ns, p99.9: 31742900 ns" |
|
184 | +time="2023-11-29T15:25:37+08:00" level=info msg="mean: 15 ms, median: 15 ms, max: 31 ms, min: 1 ms, p99.9: 31 ms" |
|
185 | + |
|
186 | +``` |
|
187 | + |
|
188 | +- 50个并发,共10000个请求 |
|
189 | + |
|
190 | +``` |
|
191 | +time="2023-11-29T15:26:10+08:00" level=info msg="total: 10000 concurrency: 50 requests per client: 200" |
|
192 | +time="2023-11-29T15:26:13+08:00" level=info msg="took 2959 ms for 10000 requests" |
|
193 | +time="2023-11-29T15:26:13+08:00" level=info msg="sent requests : 10000" |
|
194 | +time="2023-11-29T15:26:13+08:00" level=info msg="received requests : 10000" |
|
195 | +time="2023-11-29T15:26:13+08:00" level=info msg="received requests_OK : 10000" |
|
196 | +time="2023-11-29T15:26:13+08:00" level=info msg="throughput (TPS) : 3379" |
|
197 | +time="2023-11-29T15:26:13+08:00" level=info msg="mean: 14783029 ns, median: 12242400 ns, max: 265030700 ns, min: 1512100 ns, p99.9: 233556500 ns" |
|
198 | +time="2023-11-29T15:26:13+08:00" level=info msg="mean: 14 ms, median: 12 ms, max: 265 ms, min: 1 ms, p99.9: 233 ms" |
|
199 | +``` |
|
200 | + |
|
201 | +- 100个并发,共10000个请求 |
|
202 | + |
|
203 | +``` |
|
204 | +time="2023-11-29T15:26:49+08:00" level=info msg="total: 10000 concurrency: 100 requests per client: 100" |
|
205 | +time="2023-11-29T15:26:51+08:00" level=info msg="took 1861 ms for 10000 requests" |
|
206 | +time="2023-11-29T15:26:51+08:00" level=info msg="sent requests : 10000" |
|
207 | +time="2023-11-29T15:26:51+08:00" level=info msg="received requests : 10000" |
|
208 | +time="2023-11-29T15:26:51+08:00" level=info msg="received requests_OK : 10000" |
|
209 | +time="2023-11-29T15:26:51+08:00" level=info msg="throughput (TPS) : 5373" |
|
210 | +time="2023-11-29T15:26:51+08:00" level=info msg="mean: 18531453 ns, median: 16275300 ns, max: 61866200 ns, min: 1028600 ns, p99.9: 61866200 ns" |
|
211 | +time="2023-11-29T15:26:51+08:00" level=info msg="mean: 18 ms, median: 16 ms, max: 61 ms, min: 1 ms, p99.9: 61 ms" |
|
212 | + |
|
213 | +``` |
|
214 | + |
|
215 | +- 1000个并发,共10000个请求 |
|
216 | + |
|
217 | +``` |
|
218 | +time="2023-11-29T15:27:34+08:00" level=info msg="total: 10000 concurrency: 1000 requests per client: 10" |
|
219 | +time="2023-11-29T15:27:34+08:00" level=info msg="took 447 ms for 10000 requests" |
|
220 | +time="2023-11-29T15:27:34+08:00" level=info msg="sent requests : 10000" |
|
221 | +time="2023-11-29T15:27:34+08:00" level=info msg="received requests : 10000" |
|
222 | +time="2023-11-29T15:27:34+08:00" level=info msg="received requests_OK : 10000" |
|
223 | +time="2023-11-29T15:27:34+08:00" level=info msg="throughput (TPS) : 22371" |
|
224 | +time="2023-11-29T15:27:34+08:00" level=info msg="mean: 43340988 ns, median: 32804700 ns, max: 112154200 ns, min: 3652300 ns, p99.9: 108302100 ns" |
|
225 | +time="2023-11-29T15:27:34+08:00" level=info msg="mean: 43 ms, median: 32 ms, max: 112 ms, min: 3 ms, p99.9: 108 ms" |
|
226 | + |
|
227 | +``` |
|
228 | + |
|
229 | +- 10000个并发,共20000个请求 |
|
230 | + |
|
231 | +``` |
|
232 | +time="2023-11-29T15:28:32+08:00" level=info msg="total: 20000 concurrency: 10000 requests per client: 2" |
|
233 | +time="2023-11-29T15:28:34+08:00" level=info msg="took 1440 ms for 20000 requests" |
|
234 | +time="2023-11-29T15:28:34+08:00" level=info msg="sent requests : 20000" |
|
235 | +time="2023-11-29T15:28:34+08:00" level=info msg="received requests : 20000" |
|
236 | +time="2023-11-29T15:28:34+08:00" level=info msg="received requests_OK : 20000" |
|
237 | +time="2023-11-29T15:28:34+08:00" level=info msg="throughput (TPS) : 13888" |
|
238 | +time="2023-11-29T15:28:34+08:00" level=info msg="mean: 606367409 ns, median: 591710500 ns, max: 1158019400 ns, min: 6242300 ns, p99.9: 1154830950 ns" |
|
239 | +time="2023-11-29T15:28:34+08:00" level=info msg="mean: 606 ms, median: 591 ms, max: 1158 ms, min: 6 ms, p99.9: 1154 ms" |
|
240 | + |
|
241 | +``` |
|
242 | + |
|
243 | +#### 2、元模型的map put |
|
244 | +```golang |
|
245 | + |
|
246 | +const key = `test_meta_model_11111122222333333` |
|
247 | +const val = ` |
|
248 | +{ |
|
249 | + "appDataInfo": { |
|
250 | + "id": null, |
|
251 | + "storeAppId": null, |
|
252 | + "name": "base", |
|
253 | + "tag": "master", |
|
254 | + "displayName": "基础模块", |
|
255 | + "category": "base", |
|
256 | + "categoryDesc": "基础模块", |
|
257 | + "description": "基础模块", |
|
258 | + "application": false, |
|
259 | + "type": "SDK", |
|
260 | + "loaderType": "SDK", |
|
261 | + "company": "sie", |
|
262 | + "product": "base", |
|
263 | + "productIcon": null, |
|
264 | + "productDesc": "工业互联网平台", |
|
265 | + "summary": "基础模块", |
|
266 | + "source": "base", |
|
267 | + "resolved": "sie-snest-base-1.0-SNAPSHOT.jar", |
|
268 | + "sdkScanPkgPath": "com.sie.snest.base", |
|
269 | + "jarFile": "sie-snest-base-1.0-SNAPSHOT.jar", |
|
270 | + "jarPath": "C:\\Users\\29662\\IdeaProjects\\sie-snest\\apps\\sie-snest-base-1.0-SNAPSHOT.jar", |
|
271 | + "jarFileId": null, |
|
272 | + "md5": "57d51247cfd631a1171539dd0b3a8b80", |
|
273 | + "viewFileId": null, |
|
274 | + "viewFile": null, |
|
275 | + "viewFileMd5": null, |
|
276 | + "state": null, |
|
277 | + "storeJarFileId": null, |
|
278 | + "storeMd5": null, |
|
279 | + "dependencies": [], |
|
280 | + "icon": null, |
|
281 | + "license": "LGPL 3.0", |
|
282 | + "jsonFilePath": "com/sie/snest/base/", |
|
283 | + "jsonObject": null, |
|
284 | + "appJsonObject": { |
|
285 | + "summary": "基础模块", |
|
286 | + "product": "base", |
|
287 | + "displayName": "基础模块", |
|
288 | + "description": "基础模块", |
|
289 | + "type": "SDK", |
|
290 | + "version": "0.0.1", |
|
291 | + "categoryDesc": "基础模块", |
|
292 | + "dependencies": [], |
|
293 | + "productDesc": "工业互联网平台", |
|
294 | + "license": "LGPL 3.0", |
|
295 | + "name": "base", |
|
296 | + "company": "sie", |
|
297 | + "tag": "master", |
|
298 | + "category": "base", |
|
299 | + "events": { |
|
300 | + "startUp": [ |
|
301 | + "auth_check_job::start", |
|
302 | + "highavailable_init_menu::start" |
|
303 | + ] |
|
304 | + }, |
|
305 | + "resolved": "com.sie.snest.base" |
|
306 | + }, |
|
307 | + "author": null, |
|
308 | + "weight": 1.01, |
|
309 | + "models": null, |
|
310 | + "events": { |
|
311 | + "startUp": [ |
|
312 | + "auth_check_job::start", |
|
313 | + "highavailable_init_menu::start" |
|
314 | + ] |
|
315 | + }, |
|
316 | + "kind": "unStateful", |
|
317 | + "replicas": 1, |
|
318 | + "appInstallHosts": null, |
|
319 | + "svcName": null, |
|
320 | + "extServiceModels": null, |
|
321 | + "globalConfig": null, |
|
322 | + "appConfig": null, |
|
323 | + "delete": false, |
|
324 | + "newApp": false, |
|
325 | + "nameTag": "base.master", |
|
326 | + "baseApp": true, |
|
327 | + "primary": false |
|
328 | + }, |
|
329 | + "appMeta": null, |
|
330 | + "metas": {}, |
|
331 | + "menuMetaMap": {}, |
|
332 | + "models": {}, |
|
333 | + "refIdTreeMap": {}, |
|
334 | + "modelNameViewMetaIdMap": {} |
|
335 | +} |
|
336 | +` |
|
337 | +``` |
|
338 | + |
|
339 | + |
|
340 | +- 1个并发,共10个请求 |
|
341 | + |
|
342 | +``` |
|
343 | +time="2023-11-29T15:43:59+08:00" level=info msg="total: 10 concurrency: 1 requests per client: 10" |
|
344 | +time="2023-11-29T15:43:59+08:00" level=info msg="took 36 ms for 10 requests" |
|
345 | +time="2023-11-29T15:43:59+08:00" level=info msg="sent requests : 10" |
|
346 | +time="2023-11-29T15:43:59+08:00" level=info msg="received requests : 10" |
|
347 | +time="2023-11-29T15:43:59+08:00" level=info msg="received requests_OK : 10" |
|
348 | +time="2023-11-29T15:43:59+08:00" level=info msg="throughput (TPS) : 277" |
|
349 | +time="2023-11-29T15:43:59+08:00" level=info msg="mean: 3567850 ns, median: 2957000 ns, max: 8734000 ns, min: 2061200 ns, p99.9: 6508750 ns" |
|
350 | +time="2023-11-29T15:43:59+08:00" level=info msg="mean: 3 ms, median: 2 ms, max: 8 ms, min: 2 ms, p99.9: 6 ms" |
|
351 | + |
|
352 | +``` |
|
353 | + |
|
354 | +- 10个并发,共100个请求 |
|
355 | + |
|
356 | +``` |
|
357 | +time="2023-11-29T15:44:38+08:00" level=info msg="total: 100 concurrency: 10 requests per client: 10" |
|
358 | +time="2023-11-29T15:44:38+08:00" level=info msg="took 76 ms for 100 requests" |
|
359 | +time="2023-11-29T15:44:38+08:00" level=info msg="sent requests : 100" |
|
360 | +time="2023-11-29T15:44:38+08:00" level=info msg="received requests : 100" |
|
361 | +time="2023-11-29T15:44:38+08:00" level=info msg="received requests_OK : 100" |
|
362 | +time="2023-11-29T15:44:38+08:00" level=info msg="throughput (TPS) : 1315" |
|
363 | +time="2023-11-29T15:44:38+08:00" level=info msg="mean: 7293158 ns, median: 5833600 ns, max: 16503800 ns, min: 3412900 ns, p99.9: 16503800 ns" |
|
364 | +time="2023-11-29T15:44:38+08:00" level=info msg="mean: 7 ms, median: 5 ms, max: 16 ms, min: 3 ms, p99.9: 16 ms" |
|
365 | + |
|
366 | +``` |
|
367 | + |
|
368 | +- 10个并发,共1000个请求 |
|
369 | + |
|
370 | +``` |
|
371 | +time="2023-11-29T15:45:06+08:00" level=info msg="total: 1000 concurrency: 10 requests per client: 100" |
|
372 | +time="2023-11-29T15:45:07+08:00" level=info msg="took 863 ms for 1000 requests" |
|
373 | +time="2023-11-29T15:45:07+08:00" level=info msg="sent requests : 1000" |
|
374 | +time="2023-11-29T15:45:07+08:00" level=info msg="received requests : 1000" |
|
375 | +time="2023-11-29T15:45:07+08:00" level=info msg="received requests_OK : 1000" |
|
376 | +time="2023-11-29T15:45:07+08:00" level=info msg="throughput (TPS) : 1158" |
|
377 | +time="2023-11-29T15:45:07+08:00" level=info msg="mean: 8593066 ns, median: 6355050 ns, max: 32560400 ns, min: 1412800 ns, p99.9: 32560400 ns" |
|
378 | +time="2023-11-29T15:45:07+08:00" level=info msg="mean: 8 ms, median: 6 ms, max: 32 ms, min: 1 ms, p99.9: 32 ms" |
|
379 | + |
|
380 | +``` |
|
381 | + |
|
382 | +- 10个并发,共10000个请求 |
|
383 | + |
|
384 | +``` |
|
385 | +time="2023-11-29T15:45:36+08:00" level=info msg="total: 10000 concurrency: 10 requests per client: 1000" |
|
386 | +time="2023-11-29T15:45:56+08:00" level=info msg="took 20853 ms for 10000 requests" |
|
387 | +time="2023-11-29T15:45:56+08:00" level=info msg="sent requests : 10000" |
|
388 | +time="2023-11-29T15:45:56+08:00" level=info msg="received requests : 10000" |
|
389 | +time="2023-11-29T15:45:56+08:00" level=info msg="received requests_OK : 10000" |
|
390 | +time="2023-11-29T15:45:56+08:00" level=info msg="throughput (TPS) : 479" |
|
391 | +time="2023-11-29T15:45:56+08:00" level=info msg="mean: 20829690 ns, median: 14514300 ns, max: 438134100 ns, min: 1987800 ns, p99.9: 414818550 ns" |
|
392 | +time="2023-11-29T15:45:56+08:00" level=info msg="mean: 20 ms, median: 14 ms, max: 438 ms, min: 1 ms, p99.9: 414 ms" |
|
393 | + |
|
394 | +``` |
|
395 | + |
|
396 | +- 50个并发,共100个请求 |
|
397 | + |
|
398 | +``` |
|
399 | +time="2023-11-29T15:24:14+08:00" level=info msg="total: 100 concurrency: 50 requests per client: 2" |
|
400 | +time="2023-11-29T15:24:14+08:00" level=info msg="took 20 ms for 100 requests" |
|
401 | +time="2023-11-29T15:24:14+08:00" level=info msg="sent requests : 100" |
|
402 | +time="2023-11-29T15:24:14+08:00" level=info msg="received requests : 100" |
|
403 | +time="2023-11-29T15:24:14+08:00" level=info msg="received requests_OK : 100" |
|
404 | +time="2023-11-29T15:24:14+08:00" level=info msg="throughput (TPS) : 5000" |
|
405 | +time="2023-11-29T15:24:14+08:00" level=info msg="mean: 7575184 ns, median: 4312200 ns, max: 16251400 ns, min: 2274700 ns, p99.9: 16251400 ns" |
|
406 | +time="2023-11-29T15:24:14+08:00" level=info msg="mean: 7 ms, median: 4 ms, max: 16 ms, min: 2 ms, p99.9: 16 ms" |
|
407 | + |
|
408 | +``` |
|
409 | + |
|
410 | +- 50个并发,共1000个请求 |
|
411 | + |
|
412 | +``` |
|
413 | +time="2023-11-29T15:46:48+08:00" level=info msg="total: 1000 concurrency: 50 requests per client: 20" |
|
414 | +time="2023-11-29T15:46:50+08:00" level=info msg="took 1373 ms for 1000 requests" |
|
415 | +time="2023-11-29T15:46:50+08:00" level=info msg="sent requests : 1000" |
|
416 | +time="2023-11-29T15:46:50+08:00" level=info msg="received requests : 1000" |
|
417 | +time="2023-11-29T15:46:50+08:00" level=info msg="received requests_OK : 1000" |
|
418 | +time="2023-11-29T15:46:50+08:00" level=info msg="throughput (TPS) : 728" |
|
419 | +time="2023-11-29T15:46:50+08:00" level=info msg="mean: 68348635 ns, median: 59982300 ns, max: 197308000 ns, min: 3298100 ns, p99.9: 197308000 ns" |
|
420 | +time="2023-11-29T15:46:50+08:00" level=info msg="mean: 68 ms, median: 59 ms, max: 197 ms, min: 3 ms, p99.9: 197 ms" |
|
421 | + |
|
422 | +``` |
|
423 | + |
|
424 | +- 50个并发,共10000个请求 |
|
425 | + |
|
426 | +``` |
|
427 | + |
|
428 | +time="2023-11-29T15:47:22+08:00" level=info msg="total: 10000 concurrency: 50 requests per client: 200" |
|
429 | +time="2023-11-29T15:47:34+08:00" level=info msg="took 12819 ms for 10000 requests" |
|
430 | +time="2023-11-29T15:47:34+08:00" level=info msg="sent requests : 10000" |
|
431 | +time="2023-11-29T15:47:34+08:00" level=info msg="received requests : 10000" |
|
432 | +time="2023-11-29T15:47:34+08:00" level=info msg="received requests_OK : 10000" |
|
433 | +time="2023-11-29T15:47:34+08:00" level=info msg="throughput (TPS) : 780" |
|
434 | +time="2023-11-29T15:47:34+08:00" level=info msg="mean: 64058473 ns, median: 37056100 ns, max: 1242320700 ns, min: 1734200 ns, p99.9: 1218236450 ns" |
|
435 | +time="2023-11-29T15:47:34+08:00" level=info msg="mean: 64 ms, median: 37 ms, max: 1242 ms, min: 1 ms, p99.9: 1218 ms" |
|
436 | + |
|
437 | +``` |
|
438 | + |
|
439 | +- 100个并发,共10000个请求 |
|
440 | + |
|
441 | +``` |
|
442 | +time="2023-11-29T15:48:04+08:00" level=info msg="total: 10000 concurrency: 100 requests per client: 100" |
|
443 | +time="2023-11-29T15:48:13+08:00" level=info msg="took 9619 ms for 10000 requests" |
|
444 | +time="2023-11-29T15:48:13+08:00" level=info msg="sent requests : 10000" |
|
445 | +time="2023-11-29T15:48:13+08:00" level=info msg="received requests : 10000" |
|
446 | +time="2023-11-29T15:48:13+08:00" level=info msg="received requests_OK : 10000" |
|
447 | +time="2023-11-29T15:48:13+08:00" level=info msg="throughput (TPS) : 1039" |
|
448 | +time="2023-11-29T15:48:13+08:00" level=info msg="mean: 95861576 ns, median: 84699850 ns, max: 353457500 ns, min: 6367600 ns, p99.9: 346699700 ns" |
|
449 | +time="2023-11-29T15:48:13+08:00" level=info msg="mean: 95 ms, median: 84 ms, max: 353 ms, min: 6 ms, p99.9: 346 ms" |
|
450 | + |
|
451 | +``` |
|
452 | + |
|
453 | +- 1000个并发,共10000个请求 |
|
454 | + |
|
455 | +``` |
|
456 | +time="2023-11-29T15:48:44+08:00" level=info msg="total: 10000 concurrency: 1000 requests per client: 10" |
|
457 | +time="2023-11-29T15:48:55+08:00" level=info msg="took 11480 ms for 10000 requests" |
|
458 | +time="2023-11-29T15:48:55+08:00" level=info msg="sent requests : 10000" |
|
459 | +time="2023-11-29T15:48:55+08:00" level=info msg="received requests : 10000" |
|
460 | +time="2023-11-29T15:48:55+08:00" level=info msg="received requests_OK : 10000" |
|
461 | +time="2023-11-29T15:48:55+08:00" level=info msg="throughput (TPS) : 871" |
|
462 | +time="2023-11-29T15:48:55+08:00" level=info msg="mean: 1102012743 ns, median: 988037750 ns, max: 2135727900 ns, min: 2662200 ns, p99.9: 2129425900 ns" |
|
463 | +time="2023-11-29T15:48:55+08:00" level=info msg="mean: 1102 ms, median: 988 ms, max: 2135 ms, min: 2 ms, p99.9: 2129 ms" |
|
464 | + |
|
465 | +``` |
|
466 | + |
|
467 | +- 10000个并发,共20000个请求 |
|
468 | + |
|
469 | +``` |
|
470 | +time="2023-11-29T15:49:32+08:00" level=info msg="total: 20000 concurrency: 10000 requests per client: 2" |
|
471 | +time="2023-11-29T15:49:53+08:00" level=info msg="took 20497 ms for 20000 requests" |
|
472 | +time="2023-11-29T15:49:53+08:00" level=info msg="sent requests : 20000" |
|
473 | +time="2023-11-29T15:49:53+08:00" level=info msg="received requests : 20000" |
|
474 | +time="2023-11-29T15:49:53+08:00" level=info msg="received requests_OK : 20000" |
|
475 | +time="2023-11-29T15:49:53+08:00" level=info msg="throughput (TPS) : 975" |
|
476 | +time="2023-11-29T15:49:53+08:00" level=info msg="mean: 9629394346 ns, median: 9241691750 ns, max: 18458848300 ns, min: 3947500 ns, p99.9: 18437869200 ns" |
|
477 | +time="2023-11-29T15:49:53+08:00" level=info msg="mean: 9629 ms, median: 9241 ms, max: 18458 ms, min: 3 ms, p99.9: 18437 ms" |
|
478 | + |
|
479 | +``` |
|
480 | +#### 3、元模型的map get |
|
481 | + |
|
482 | + |
|
483 | +- 10个并发,共100个请求 |
|
484 | + |
|
485 | +``` |
|
486 | +time="2023-11-29T15:51:20+08:00" level=info msg="total: 100 concurrency: 10 requests per client: 10" |
|
487 | +time="2023-11-29T15:51:20+08:00" level=info msg="took 82 ms for 100 requests" |
|
488 | +time="2023-11-29T15:51:20+08:00" level=info msg="sent requests : 100" |
|
489 | +time="2023-11-29T15:51:20+08:00" level=info msg="received requests : 100" |
|
490 | +time="2023-11-29T15:51:20+08:00" level=info msg="received requests_OK : 100" |
|
491 | +time="2023-11-29T15:51:20+08:00" level=info msg="throughput (TPS) : 1219" |
|
492 | +time="2023-11-29T15:51:20+08:00" level=info msg="mean: 7894017 ns, median: 6708500 ns, max: 22592800 ns, min: 2172000 ns, p99.9: 21619400 ns" |
|
493 | +time="2023-11-29T15:51:20+08:00" level=info msg="mean: 7 ms, median: 6 ms, max: 22 ms, min: 2 ms, p99.9: 21 ms" |
|
494 | + |
|
495 | +``` |
|
496 | + |
|
497 | +- 10个并发,共1000个请求 |
|
498 | + |
|
499 | +``` |
|
500 | +time="2023-11-29T15:52:56+08:00" level=info msg="total: 1000 concurrency: 10 requests per client: 100" |
|
501 | +time="2023-11-29T15:52:56+08:00" level=info msg="took 816 ms for 1000 requests" |
|
502 | +time="2023-11-29T15:52:56+08:00" level=info msg="sent requests : 1000" |
|
503 | +time="2023-11-29T15:52:56+08:00" level=info msg="received requests : 1000" |
|
504 | +time="2023-11-29T15:52:56+08:00" level=info msg="received requests_OK : 1000" |
|
505 | +time="2023-11-29T15:52:56+08:00" level=info msg="throughput (TPS) : 1225" |
|
506 | +time="2023-11-29T15:52:56+08:00" level=info msg="mean: 8093208 ns, median: 5498600 ns, max: 37198100 ns, min: 1601400 ns, p99.9: 35485450 ns" |
|
507 | +time="2023-11-29T15:52:56+08:00" level=info msg="mean: 8 ms, median: 5 ms, max: 37 ms, min: 1 ms, p99.9: 35 ms" |
|
508 | + |
|
509 | +``` |
|
510 | + |
|
511 | +- 10个并发,共10000个请求 |
|
512 | + |
|
513 | +``` |
|
514 | +time="2023-11-29T15:53:21+08:00" level=info msg="total: 10000 concurrency: 10 requests per client: 1000" |
|
515 | +time="2023-11-29T15:53:31+08:00" level=info msg="took 10059 ms for 10000 requests" |
|
516 | +time="2023-11-29T15:53:31+08:00" level=info msg="sent requests : 10000" |
|
517 | +time="2023-11-29T15:53:31+08:00" level=info msg="received requests : 10000" |
|
518 | +time="2023-11-29T15:53:31+08:00" level=info msg="received requests_OK : 10000" |
|
519 | +time="2023-11-29T15:53:31+08:00" level=info msg="throughput (TPS) : 994" |
|
520 | +time="2023-11-29T15:53:31+08:00" level=info msg="mean: 10034711 ns, median: 5877000 ns, max: 851575200 ns, min: 1510200 ns, p99.9: 838274550 ns" |
|
521 | +time="2023-11-29T15:53:31+08:00" level=info msg="mean: 10 ms, median: 5 ms, max: 851 ms, min: 1 ms, p99.9: 838 ms" |
|
522 | + |
|
523 | +``` |
|
524 | + |
|
525 | +- 50个并发,共100个请求 |
|
526 | + |
|
527 | +``` |
|
528 | +time="2023-11-29T15:53:57+08:00" level=info msg="total: 100 concurrency: 50 requests per client: 2" |
|
529 | +time="2023-11-29T15:53:57+08:00" level=info msg="took 33 ms for 100 requests" |
|
530 | +time="2023-11-29T15:53:57+08:00" level=info msg="sent requests : 100" |
|
531 | +time="2023-11-29T15:53:57+08:00" level=info msg="received requests : 100" |
|
532 | +time="2023-11-29T15:53:57+08:00" level=info msg="received requests_OK : 100" |
|
533 | +time="2023-11-29T15:53:57+08:00" level=info msg="throughput (TPS) : 3030" |
|
534 | +time="2023-11-29T15:53:57+08:00" level=info msg="mean: 13962681 ns, median: 12463350 ns, max: 22122800 ns, min: 2119500 ns, p99.9: 22122800 ns" |
|
535 | +time="2023-11-29T15:53:57+08:00" level=info msg="mean: 13 ms, median: 12 ms, max: 22 ms, min: 2 ms, p99.9: 22 ms" |
|
536 | + |
|
537 | +``` |
|
538 | + |
|
539 | +- 50个并发,共1000个请求 |
|
540 | + |
|
541 | +``` |
|
542 | +time="2023-11-29T15:54:17+08:00" level=info msg="total: 1000 concurrency: 50 requests per client: 20" |
|
543 | +time="2023-11-29T15:54:17+08:00" level=info msg="took 384 ms for 1000 requests" |
|
544 | +time="2023-11-29T15:54:17+08:00" level=info msg="sent requests : 1000" |
|
545 | +time="2023-11-29T15:54:17+08:00" level=info msg="received requests : 1000" |
|
546 | +time="2023-11-29T15:54:17+08:00" level=info msg="received requests_OK : 1000" |
|
547 | +time="2023-11-29T15:54:17+08:00" level=info msg="throughput (TPS) : 2604" |
|
548 | +time="2023-11-29T15:54:17+08:00" level=info msg="mean: 19014493 ns, median: 20007500 ns, max: 39160200 ns, min: 2248800 ns, p99.9: 39160200 ns" |
|
549 | +time="2023-11-29T15:54:17+08:00" level=info msg="mean: 19 ms, median: 20 ms, max: 39 ms, min: 2 ms, p99.9: 39 ms" |
|
550 | + |
|
551 | +``` |
|
552 | + |
|
553 | +- 50个并发,共10000个请求 |
|
554 | + |
|
555 | +``` |
|
556 | +time="2023-11-29T15:54:41+08:00" level=info msg="total: 10000 concurrency: 50 requests per client: 200" |
|
557 | +time="2023-11-29T15:54:44+08:00" level=info msg="took 3075 ms for 10000 requests" |
|
558 | +time="2023-11-29T15:54:44+08:00" level=info msg="sent requests : 10000" |
|
559 | +time="2023-11-29T15:54:44+08:00" level=info msg="received requests : 10000" |
|
560 | +time="2023-11-29T15:54:44+08:00" level=info msg="received requests_OK : 10000" |
|
561 | +time="2023-11-29T15:54:44+08:00" level=info msg="throughput (TPS) : 3252" |
|
562 | +time="2023-11-29T15:54:44+08:00" level=info msg="mean: 15350080 ns, median: 13936100 ns, max: 74815300 ns, min: 1637300 ns, p99.9: 74309900 ns" |
|
563 | +time="2023-11-29T15:54:44+08:00" level=info msg="mean: 15 ms, median: 13 ms, max: 74 ms, min: 1 ms, p99.9: 74 ms" |
|
564 | + |
|
565 | +``` |
|
566 | + |
|
567 | +- 100个并发,共10000个请求 |
|
568 | + |
|
569 | +``` |
|
570 | +time="2023-11-29T15:55:08+08:00" level=info msg="total: 10000 concurrency: 100 requests per client: 100" |
|
571 | +time="2023-11-29T15:55:11+08:00" level=info msg="took 3017 ms for 10000 requests" |
|
572 | +time="2023-11-29T15:55:11+08:00" level=info msg="sent requests : 10000" |
|
573 | +time="2023-11-29T15:55:11+08:00" level=info msg="received requests : 10000" |
|
574 | +time="2023-11-29T15:55:11+08:00" level=info msg="received requests_OK : 10000" |
|
575 | +time="2023-11-29T15:55:11+08:00" level=info msg="throughput (TPS) : 3314" |
|
576 | +time="2023-11-29T15:55:11+08:00" level=info msg="mean: 30073157 ns, median: 20134900 ns, max: 942773300 ns, min: 3710100 ns, p99.9: 942152800 ns" |
|
577 | +time="2023-11-29T15:55:11+08:00" level=info msg="mean: 30 ms, median: 20 ms, max: 942 ms, min: 3 ms, p99.9: 942 ms" |
|
578 | + |
|
579 | +``` |
|
580 | + |
|
581 | +- 1000个并发,共10000个请求 |
|
582 | + |
|
583 | +``` |
|
584 | +time="2023-11-29T15:55:40+08:00" level=info msg="total: 10000 concurrency: 1000 requests per client: 10" |
|
585 | +time="2023-11-29T15:55:42+08:00" level=info msg="took 2423 ms for 10000 requests" |
|
586 | +time="2023-11-29T15:55:42+08:00" level=info msg="sent requests : 10000" |
|
587 | +time="2023-11-29T15:55:42+08:00" level=info msg="received requests : 10000" |
|
588 | +time="2023-11-29T15:55:42+08:00" level=info msg="received requests_OK : 10000" |
|
589 | +time="2023-11-29T15:55:42+08:00" level=info msg="throughput (TPS) : 4127" |
|
590 | +time="2023-11-29T15:55:42+08:00" level=info msg="mean: 231318655 ns, median: 223177700 ns, max: 350379500 ns, min: 4207800 ns, p99.9: 349300600 ns" |
|
591 | +time="2023-11-29T15:55:42+08:00" level=info msg="mean: 231 ms, median: 223 ms, max: 350 ms, min: 4 ms, p99.9: 349 ms" |
|
592 | + |
|
593 | +``` |
|
594 | + |
|
595 | +- 10000个并发,共20000个请求 |
|
596 | + |
|
597 | +``` |
|
598 | +time="2023-11-29T15:56:08+08:00" level=info msg="total: 20000 concurrency: 10000 requests per client: 2" |
|
599 | +time="2023-11-29T15:56:13+08:00" level=info msg="took 5116 ms for 20000 requests" |
|
600 | +time="2023-11-29T15:56:13+08:00" level=info msg="sent requests : 20000" |
|
601 | +time="2023-11-29T15:56:13+08:00" level=info msg="received requests : 20000" |
|
602 | +time="2023-11-29T15:56:13+08:00" level=info msg="received requests_OK : 20000" |
|
603 | +time="2023-11-29T15:56:13+08:00" level=info msg="throughput (TPS) : 3909" |
|
604 | +time="2023-11-29T15:56:13+08:00" level=info msg="mean: 2087396474 ns, median: 2025976800 ns, max: 3294130100 ns, min: 20420700 ns, p99.9: 3268514400 ns" |
|
605 | +time="2023-11-29T15:56:13+08:00" level=info msg="mean: 2087 ms, median: 2025 ms, max: 3294 ms, min: 20 ms, p99.9: 3268 ms" |
|
606 | + |
|
607 | +``` |
|
608 | + |
|
609 | +#### 4、源码 |
|
610 | + |
|
611 | +```golang |
|
612 | + |
|
613 | +package main |
|
614 | + |
|
615 | +import ( |
|
616 | + "context" |
|
617 | + "flag" |
|
618 | + "fmt" |
|
619 | + "sync" |
|
620 | + "sync/atomic" |
|
621 | + "time" |
|
622 | + |
|
623 | + "github.com/hazelcast/hazelcast-go-client" |
|
624 | + log "github.com/sirupsen/logrus" |
|
625 | + "go.uber.org/ratelimit" |
|
626 | +) |
|
627 | + |
|
628 | +const key = `test_meta_model_11111122222333333` |
|
629 | +const val = ` |
|
630 | +{ |
|
631 | + "appDataInfo": { |
|
632 | + "id": null, |
|
633 | + "storeAppId": null, |
|
634 | + "name": "base", |
|
635 | + "tag": "master", |
|
636 | + "displayName": "基础模块", |
|
637 | + "category": "base", |
|
638 | + "categoryDesc": "基础模块", |
|
639 | + "description": "基础模块", |
|
640 | + "application": false, |
|
641 | + "type": "SDK", |
|
642 | + "loaderType": "SDK", |
|
643 | + "company": "sie", |
|
644 | + "product": "base", |
|
645 | + "productIcon": null, |
|
646 | + "productDesc": "工业互联网平台", |
|
647 | + "summary": "基础模块", |
|
648 | + "source": "base", |
|
649 | + "resolved": "sie-snest-base-1.0-SNAPSHOT.jar", |
|
650 | + "sdkScanPkgPath": "com.sie.snest.base", |
|
651 | + "jarFile": "sie-snest-base-1.0-SNAPSHOT.jar", |
|
652 | + "jarPath": "C:\\Users\\29662\\IdeaProjects\\sie-snest\\apps\\sie-snest-base-1.0-SNAPSHOT.jar", |
|
653 | + "jarFileId": null, |
|
654 | + "md5": "57d51247cfd631a1171539dd0b3a8b80", |
|
655 | + "viewFileId": null, |
|
656 | + "viewFile": null, |
|
657 | + "viewFileMd5": null, |
|
658 | + "state": null, |
|
659 | + "storeJarFileId": null, |
|
660 | + "storeMd5": null, |
|
661 | + "dependencies": [], |
|
662 | + "icon": null, |
|
663 | + "license": "LGPL 3.0", |
|
664 | + "jsonFilePath": "com/sie/snest/base/", |
|
665 | + "jsonObject": null, |
|
666 | + "appJsonObject": { |
|
667 | + "summary": "基础模块", |
|
668 | + "product": "base", |
|
669 | + "displayName": "基础模块", |
|
670 | + "description": "基础模块", |
|
671 | + "type": "SDK", |
|
672 | + "version": "0.0.1", |
|
673 | + "categoryDesc": "基础模块", |
|
674 | + "dependencies": [], |
|
675 | + "productDesc": "工业互联网平台", |
|
676 | + "license": "LGPL 3.0", |
|
677 | + "name": "base", |
|
678 | + "company": "sie", |
|
679 | + "tag": "master", |
|
680 | + "category": "base", |
|
681 | + "events": { |
|
682 | + "startUp": [ |
|
683 | + "auth_check_job::start", |
|
684 | + "highavailable_init_menu::start" |
|
685 | + ] |
|
686 | + }, |
|
687 | + "resolved": "com.sie.snest.base" |
|
688 | + }, |
|
689 | + "author": null, |
|
690 | + "weight": 1.01, |
|
691 | + "models": null, |
|
692 | + "events": { |
|
693 | + "startUp": [ |
|
694 | + "auth_check_job::start", |
|
695 | + "highavailable_init_menu::start" |
|
696 | + ] |
|
697 | + }, |
|
698 | + "kind": "unStateful", |
|
699 | + "replicas": 1, |
|
700 | + "appInstallHosts": null, |
|
701 | + "svcName": null, |
|
702 | + "extServiceModels": null, |
|
703 | + "globalConfig": null, |
|
704 | + "appConfig": null, |
|
705 | + "delete": false, |
|
706 | + "newApp": false, |
|
707 | + "nameTag": "base.master", |
|
708 | + "baseApp": true, |
|
709 | + "primary": false |
|
710 | + }, |
|
711 | + "appMeta": null, |
|
712 | + "metas": {}, |
|
713 | + "menuMetaMap": {}, |
|
714 | + "models": {}, |
|
715 | + "refIdTreeMap": {}, |
|
716 | + "modelNameViewMetaIdMap": {} |
|
717 | +} |
|
718 | +` |
|
719 | + |
|
720 | +var ( |
|
721 | + concurrency = flag.Int("c", 1, "concurrency") |
|
722 | + total = flag.Int("n", 10, "total requests for all clients") |
|
723 | + host = flag.String("s", "127.0.0.1:8972", "server ip and port") |
|
724 | + pool = flag.Int("pool", 10, " shared grpc clients") |
|
725 | + rate = flag.Int("r", 0, "throughputs") |
|
726 | + client = flag.String("type", "kim", "client type") |
|
727 | +) |
|
728 | + |
|
729 | +func main() { |
|
730 | + flag.Parse() |
|
731 | + |
|
732 | + ctx := context.TODO() |
|
733 | + |
|
734 | + cfg := hazelcast.Config{} |
|
735 | + cfg.Cluster.Name = "hazelcast-benchmark" |
|
736 | + cfg.Cluster.Network.Addresses = []string{"192.168.168.176:5701"} |
|
737 | + |
|
738 | + hz, err := hazelcast.StartNewClientWithConfig(ctx, cfg) |
|
739 | + if err != nil { |
|
740 | + panic(fmt.Errorf("starting the client with config: %w", err)) |
|
741 | + } |
|
742 | + mp, err := hz.GetMap(ctx, "my-distributed-map") |
|
743 | + if err != nil { |
|
744 | + panic(fmt.Errorf("trying to get a map: %w", err)) |
|
745 | + } |
|
746 | + |
|
747 | + var rl ratelimit.Limiter |
|
748 | + if *rate > 0 { |
|
749 | + rl = ratelimit.New(*rate) |
|
750 | + } |
|
751 | + |
|
752 | + // 并发goroutine数.模拟客户端 |
|
753 | + n := *concurrency |
|
754 | + // 每个客户端需要发送的请求数 |
|
755 | + m := *total / n |
|
756 | + log.Infof("total: %d concurrency: %d requests per client: %d", *total, n, m) |
|
757 | + |
|
758 | + // 等待所有测试完成 |
|
759 | + var wg sync.WaitGroup |
|
760 | + wg.Add(n * m) |
|
761 | + |
|
762 | + // 总请求数 |
|
763 | + var trans uint64 |
|
764 | + // 返回正常的总请求数 |
|
765 | + var transOK uint64 |
|
766 | + |
|
767 | + // 每个goroutine的耗时记录 |
|
768 | + d := make([][]int64, n, n) |
|
769 | + |
|
770 | + // 栅栏,控制客户端同时开始测试 |
|
771 | + var startWg sync.WaitGroup |
|
772 | + startWg.Add(n + 1) // +1 是因为有一个goroutine用来记录开始时间 |
|
773 | + |
|
774 | + // 创建客户端 goroutine 并进行测试 |
|
775 | + startTime := time.Now().UnixNano() |
|
776 | + go func() { |
|
777 | + startWg.Done() |
|
778 | + startWg.Wait() |
|
779 | + startTime = time.Now().UnixNano() |
|
780 | + }() |
|
781 | + |
|
782 | + for i := 0; i < n; i++ { |
|
783 | + dt := make([]int64, 0, m) |
|
784 | + d = append(d, dt) |
|
785 | + |
|
786 | + go func(i int) { |
|
787 | + for j := 0; j < m; j++ { |
|
788 | + // 限流,这里不把限流的时间计算到等待耗时中 |
|
789 | + if rl != nil { |
|
790 | + rl.Take() |
|
791 | + } |
|
792 | + |
|
793 | + t := time.Now().UnixNano() |
|
794 | + _, err = mp.Get(ctx, key) |
|
795 | + if err != nil { |
|
796 | + panic(fmt.Errorf("trying to put to map: %w", err)) |
|
797 | + } |
|
798 | + |
|
799 | + t = time.Now().UnixNano() - t // 等待时间+服务时间,等待时间是客户端调度的等待时间以及服务端读取请求、调度的时间,服务时间是请求被服务处理的实际时间 |
|
800 | + |
|
801 | + d[i] = append(d[i], t) |
|
802 | + |
|
803 | + if err == nil { |
|
804 | + atomic.AddUint64(&transOK, 1) |
|
805 | + } |
|
806 | + |
|
807 | + atomic.AddUint64(&trans, 1) |
|
808 | + wg.Done() |
|
809 | + } |
|
810 | + }(i) |
|
811 | + |
|
812 | + } |
|
813 | + |
|
814 | + wg.Wait() |
|
815 | + |
|
816 | + // 统计 |
|
817 | + Stats(startTime, *total, d, trans, transOK) |
|
818 | +} |
|
819 | + |
|
820 | + |
|
821 | +// Stats 统计结果. |
|
822 | +func Stats(startTime int64, totalRequests int, tookTimes [][]int64, trans, transOK uint64) { |
|
823 | + // 测试总耗时 |
|
824 | + totalTInNano := time.Now().UnixNano() - startTime |
|
825 | + totalT := totalTInNano / 1000000 |
|
826 | + log.Infof("took %d ms for %d requests", totalT, totalRequests) |
|
827 | + |
|
828 | + // 汇总每个请求的耗时 |
|
829 | + totalD := make([]int64, 0, totalRequests) |
|
830 | + for _, k := range tookTimes { |
|
831 | + totalD = append(totalD, k...) |
|
832 | + } |
|
833 | + // 将int64数组转换成float64数组,以便分析 |
|
834 | + totalD2 := make([]float64, 0, totalRequests) |
|
835 | + for _, k := range totalD { |
|
836 | + totalD2 = append(totalD2, float64(k)) |
|
837 | + } |
|
838 | + |
|
839 | + // 计算各个指标 |
|
840 | + mean, _ := stats.Mean(totalD2) |
|
841 | + median, _ := stats.Median(totalD2) |
|
842 | + max, _ := stats.Max(totalD2) |
|
843 | + min, _ := stats.Min(totalD2) |
|
844 | + p999, _ := stats.Percentile(totalD2, 99.9) |
|
845 | + |
|
846 | + // 输出结果 |
|
847 | + log.Infof("sent requests : %d", totalRequests) |
|
848 | + log.Infof("received requests : %d", trans) |
|
849 | + log.Infof("received requests_OK : %d", transOK) |
|
850 | + if totalT == 0 { |
|
851 | + log.Infof("throughput (TPS) : %d", int64(totalRequests)*1000*1000000/totalTInNano) |
|
852 | + } else { |
|
853 | + log.Infof("throughput (TPS) : %d", int64(totalRequests)*1000/totalT) |
|
854 | + } |
|
855 | + |
|
856 | + log.Infof("mean: %.f ns, median: %.f ns, max: %.f ns, min: %.f ns, p99.9: %.f ns", mean, median, max, min, p999) |
|
857 | + log.Infof("mean: %d ms, median: %d ms, max: %d ms, min: %d ms, p99.9: %d ms", int64(mean/1000000), int64(median/1000000), int64(max/1000000), int64(min/1000000), int64(p999/1000000)) |
|
858 | +} |
|
859 | + |
|
860 | + |
|
861 | +``` |
|
862 | + |
|
863 | +### 5,问题 |
|
864 | +- 序列化 |
|
... | ... | \ No newline at end of file |
Hazelcast\346\200\247\350\203\275\346\265\213\350\257\225.md
... | ... | @@ -0,0 +1,766 @@ |
1 | +### 1、简单的map put 操作 |
|
2 | + |
|
3 | +``` |
|
4 | +mp.Put(ctx, "foo", "bar") |
|
5 | +``` |
|
6 | + |
|
7 | +结果: |
|
8 | + |
|
9 | +- 1个并发,共10个请求 |
|
10 | + |
|
11 | +``` |
|
12 | +time="2023-11-29T15:15:39+08:00" level=info msg="total: 10 concurrency: 1 requests per client: 10" |
|
13 | +time="2023-11-29T15:15:39+08:00" level=info msg="took 27 ms for 10 requests" |
|
14 | +time="2023-11-29T15:15:39+08:00" level=info msg="sent requests : 10" |
|
15 | +time="2023-11-29T15:15:39+08:00" level=info msg="received requests : 10" |
|
16 | +time="2023-11-29T15:15:39+08:00" level=info msg="received requests_OK : 10" |
|
17 | +time="2023-11-29T15:15:39+08:00" level=info msg="throughput (TPS) : 370" |
|
18 | +time="2023-11-29T15:15:39+08:00" level=info msg="mean: 2772220 ns, median: 1824400 ns, max: 9850800 ns, min: 1627700 ns, p99.9: 6775050 ns" |
|
19 | +time="2023-11-29T15:15:39+08:00" level=info msg="mean: 2 ms, median: 1 ms, max: 9 ms, min: 1 ms, p99.9: 6 ms" |
|
20 | + |
|
21 | +``` |
|
22 | + |
|
23 | +- 10个并发,共100个请求 |
|
24 | + |
|
25 | +``` |
|
26 | +time="2023-11-29T15:17:57+08:00" level=info msg="total: 100 concurrency: 10 requests per client: 10" |
|
27 | +time="2023-11-29T15:17:57+08:00" level=info msg="took 84 ms for 100 requests" |
|
28 | +time="2023-11-29T15:17:57+08:00" level=info msg="sent requests : 100" |
|
29 | +time="2023-11-29T15:17:57+08:00" level=info msg="received requests : 100" |
|
30 | +time="2023-11-29T15:17:57+08:00" level=info msg="received requests_OK : 100" |
|
31 | +time="2023-11-29T15:17:57+08:00" level=info msg="throughput (TPS) : 1190" |
|
32 | +time="2023-11-29T15:17:57+08:00" level=info msg="mean: 8230900 ns, median: 5983800 ns, max: 28240200 ns, min: 1665600 ns, p99.9: 28240200 ns" |
|
33 | +time="2023-11-29T15:17:57+08:00" level=info msg="mean: 8 ms, median: 5 ms, max: 28 ms, min: 1 ms, p99.9: 28 ms" |
|
34 | + |
|
35 | +``` |
|
36 | + |
|
37 | +- 10个并发,共1000个请求 |
|
38 | + |
|
39 | +``` |
|
40 | +time="2023-11-29T15:19:25+08:00" level=info msg="total: 1000 concurrency: 10 requests per client: 100" |
|
41 | +time="2023-11-29T15:19:26+08:00" level=info msg="took 836 ms for 1000 requests" |
|
42 | +time="2023-11-29T15:19:26+08:00" level=info msg="sent requests : 1000" |
|
43 | +time="2023-11-29T15:19:26+08:00" level=info msg="received requests : 1000" |
|
44 | +time="2023-11-29T15:19:26+08:00" level=info msg="received requests_OK : 1000" |
|
45 | +time="2023-11-29T15:19:26+08:00" level=info msg="throughput (TPS) : 1196" |
|
46 | +time="2023-11-29T15:19:26+08:00" level=info msg="mean: 8338478 ns, median: 6197100 ns, max: 25382100 ns, min: 1508800 ns, p99.9: 25382100 ns" |
|
47 | +time="2023-11-29T15:19:26+08:00" level=info msg="mean: 8 ms, median: 6 ms, max: 25 ms, min: 1 ms, p99.9: 25 ms" |
|
48 | + |
|
49 | +``` |
|
50 | + |
|
51 | +- 10个并发,共10000个请求 |
|
52 | + |
|
53 | +``` |
|
54 | +time="2023-11-29T15:22:55+08:00" level=info msg="total: 10000 concurrency: 10 requests per client: 1000" |
|
55 | +time="2023-11-29T15:23:08+08:00" level=info msg="took 13574 ms for 10000 requests" |
|
56 | +time="2023-11-29T15:23:08+08:00" level=info msg="sent requests : 10000" |
|
57 | +time="2023-11-29T15:23:08+08:00" level=info msg="received requests : 10000" |
|
58 | +time="2023-11-29T15:23:08+08:00" level=info msg="received requests_OK : 10000" |
|
59 | +time="2023-11-29T15:23:08+08:00" level=info msg="throughput (TPS) : 736" |
|
60 | +time="2023-11-29T15:23:08+08:00" level=info msg="mean: 13561955 ns, median: 9066300 ns, max: 652868300 ns, min: 1509600 ns, p99.9: 619792100 ns" |
|
61 | +time="2023-11-29T15:23:08+08:00" level=info msg="mean: 13 ms, median: 9 ms, max: 652 ms, min: 1 ms, p99.9: 619 ms" |
|
62 | + |
|
63 | +``` |
|
64 | + |
|
65 | +- 50个并发,共100个请求 |
|
66 | + |
|
67 | +``` |
|
68 | +time="2023-11-29T15:24:14+08:00" level=info msg="total: 100 concurrency: 50 requests per client: 2" |
|
69 | +time="2023-11-29T15:24:14+08:00" level=info msg="took 20 ms for 100 requests" |
|
70 | +time="2023-11-29T15:24:14+08:00" level=info msg="sent requests : 100" |
|
71 | +time="2023-11-29T15:24:14+08:00" level=info msg="received requests : 100" |
|
72 | +time="2023-11-29T15:24:14+08:00" level=info msg="received requests_OK : 100" |
|
73 | +time="2023-11-29T15:24:14+08:00" level=info msg="throughput (TPS) : 5000" |
|
74 | +time="2023-11-29T15:24:14+08:00" level=info msg="mean: 7575184 ns, median: 4312200 ns, max: 16251400 ns, min: 2274700 ns, p99.9: 16251400 ns" |
|
75 | +time="2023-11-29T15:24:14+08:00" level=info msg="mean: 7 ms, median: 4 ms, max: 16 ms, min: 2 ms, p99.9: 16 ms" |
|
76 | + |
|
77 | +``` |
|
78 | + |
|
79 | +- 50个并发,共1000个请求 |
|
80 | + |
|
81 | +``` |
|
82 | +time="2023-11-29T15:25:37+08:00" level=info msg="total: 1000 concurrency: 50 requests per client: 20" |
|
83 | +time="2023-11-29T15:25:37+08:00" level=info msg="took 322 ms for 1000 requests" |
|
84 | +time="2023-11-29T15:25:37+08:00" level=info msg="sent requests : 1000" |
|
85 | +time="2023-11-29T15:25:37+08:00" level=info msg="received requests : 1000" |
|
86 | +time="2023-11-29T15:25:37+08:00" level=info msg="received requests_OK : 1000" |
|
87 | +time="2023-11-29T15:25:37+08:00" level=info msg="throughput (TPS) : 3105" |
|
88 | +time="2023-11-29T15:25:37+08:00" level=info msg="mean: 15954920 ns, median: 15421600 ns, max: 31742900 ns, min: 1670800 ns, p99.9: 31742900 ns" |
|
89 | +time="2023-11-29T15:25:37+08:00" level=info msg="mean: 15 ms, median: 15 ms, max: 31 ms, min: 1 ms, p99.9: 31 ms" |
|
90 | + |
|
91 | +``` |
|
92 | + |
|
93 | +- 50个并发,共10000个请求 |
|
94 | + |
|
95 | +``` |
|
96 | +time="2023-11-29T15:26:10+08:00" level=info msg="total: 10000 concurrency: 50 requests per client: 200" |
|
97 | +time="2023-11-29T15:26:13+08:00" level=info msg="took 2959 ms for 10000 requests" |
|
98 | +time="2023-11-29T15:26:13+08:00" level=info msg="sent requests : 10000" |
|
99 | +time="2023-11-29T15:26:13+08:00" level=info msg="received requests : 10000" |
|
100 | +time="2023-11-29T15:26:13+08:00" level=info msg="received requests_OK : 10000" |
|
101 | +time="2023-11-29T15:26:13+08:00" level=info msg="throughput (TPS) : 3379" |
|
102 | +time="2023-11-29T15:26:13+08:00" level=info msg="mean: 14783029 ns, median: 12242400 ns, max: 265030700 ns, min: 1512100 ns, p99.9: 233556500 ns" |
|
103 | +time="2023-11-29T15:26:13+08:00" level=info msg="mean: 14 ms, median: 12 ms, max: 265 ms, min: 1 ms, p99.9: 233 ms" |
|
104 | +``` |
|
105 | + |
|
106 | +- 100个并发,共10000个请求 |
|
107 | + |
|
108 | +``` |
|
109 | +time="2023-11-29T15:26:49+08:00" level=info msg="total: 10000 concurrency: 100 requests per client: 100" |
|
110 | +time="2023-11-29T15:26:51+08:00" level=info msg="took 1861 ms for 10000 requests" |
|
111 | +time="2023-11-29T15:26:51+08:00" level=info msg="sent requests : 10000" |
|
112 | +time="2023-11-29T15:26:51+08:00" level=info msg="received requests : 10000" |
|
113 | +time="2023-11-29T15:26:51+08:00" level=info msg="received requests_OK : 10000" |
|
114 | +time="2023-11-29T15:26:51+08:00" level=info msg="throughput (TPS) : 5373" |
|
115 | +time="2023-11-29T15:26:51+08:00" level=info msg="mean: 18531453 ns, median: 16275300 ns, max: 61866200 ns, min: 1028600 ns, p99.9: 61866200 ns" |
|
116 | +time="2023-11-29T15:26:51+08:00" level=info msg="mean: 18 ms, median: 16 ms, max: 61 ms, min: 1 ms, p99.9: 61 ms" |
|
117 | + |
|
118 | +``` |
|
119 | + |
|
120 | +- 1000个并发,共10000个请求 |
|
121 | + |
|
122 | +``` |
|
123 | +time="2023-11-29T15:27:34+08:00" level=info msg="total: 10000 concurrency: 1000 requests per client: 10" |
|
124 | +time="2023-11-29T15:27:34+08:00" level=info msg="took 447 ms for 10000 requests" |
|
125 | +time="2023-11-29T15:27:34+08:00" level=info msg="sent requests : 10000" |
|
126 | +time="2023-11-29T15:27:34+08:00" level=info msg="received requests : 10000" |
|
127 | +time="2023-11-29T15:27:34+08:00" level=info msg="received requests_OK : 10000" |
|
128 | +time="2023-11-29T15:27:34+08:00" level=info msg="throughput (TPS) : 22371" |
|
129 | +time="2023-11-29T15:27:34+08:00" level=info msg="mean: 43340988 ns, median: 32804700 ns, max: 112154200 ns, min: 3652300 ns, p99.9: 108302100 ns" |
|
130 | +time="2023-11-29T15:27:34+08:00" level=info msg="mean: 43 ms, median: 32 ms, max: 112 ms, min: 3 ms, p99.9: 108 ms" |
|
131 | + |
|
132 | +``` |
|
133 | + |
|
134 | +- 10000个并发,共20000个请求 |
|
135 | + |
|
136 | +``` |
|
137 | +time="2023-11-29T15:28:32+08:00" level=info msg="total: 20000 concurrency: 10000 requests per client: 2" |
|
138 | +time="2023-11-29T15:28:34+08:00" level=info msg="took 1440 ms for 20000 requests" |
|
139 | +time="2023-11-29T15:28:34+08:00" level=info msg="sent requests : 20000" |
|
140 | +time="2023-11-29T15:28:34+08:00" level=info msg="received requests : 20000" |
|
141 | +time="2023-11-29T15:28:34+08:00" level=info msg="received requests_OK : 20000" |
|
142 | +time="2023-11-29T15:28:34+08:00" level=info msg="throughput (TPS) : 13888" |
|
143 | +time="2023-11-29T15:28:34+08:00" level=info msg="mean: 606367409 ns, median: 591710500 ns, max: 1158019400 ns, min: 6242300 ns, p99.9: 1154830950 ns" |
|
144 | +time="2023-11-29T15:28:34+08:00" level=info msg="mean: 606 ms, median: 591 ms, max: 1158 ms, min: 6 ms, p99.9: 1154 ms" |
|
145 | + |
|
146 | +``` |
|
147 | + |
|
148 | +### 2、元模型的map put |
|
149 | +```golang |
|
150 | + |
|
151 | +const key = `test_meta_model_11111122222333333` |
|
152 | +const val = ` |
|
153 | +{ |
|
154 | + "appDataInfo": { |
|
155 | + "id": null, |
|
156 | + "storeAppId": null, |
|
157 | + "name": "base", |
|
158 | + "tag": "master", |
|
159 | + "displayName": "基础模块", |
|
160 | + "category": "base", |
|
161 | + "categoryDesc": "基础模块", |
|
162 | + "description": "基础模块", |
|
163 | + "application": false, |
|
164 | + "type": "SDK", |
|
165 | + "loaderType": "SDK", |
|
166 | + "company": "sie", |
|
167 | + "product": "base", |
|
168 | + "productIcon": null, |
|
169 | + "productDesc": "工业互联网平台", |
|
170 | + "summary": "基础模块", |
|
171 | + "source": "base", |
|
172 | + "resolved": "sie-snest-base-1.0-SNAPSHOT.jar", |
|
173 | + "sdkScanPkgPath": "com.sie.snest.base", |
|
174 | + "jarFile": "sie-snest-base-1.0-SNAPSHOT.jar", |
|
175 | + "jarPath": "C:\\Users\\29662\\IdeaProjects\\sie-snest\\apps\\sie-snest-base-1.0-SNAPSHOT.jar", |
|
176 | + "jarFileId": null, |
|
177 | + "md5": "57d51247cfd631a1171539dd0b3a8b80", |
|
178 | + "viewFileId": null, |
|
179 | + "viewFile": null, |
|
180 | + "viewFileMd5": null, |
|
181 | + "state": null, |
|
182 | + "storeJarFileId": null, |
|
183 | + "storeMd5": null, |
|
184 | + "dependencies": [], |
|
185 | + "icon": null, |
|
186 | + "license": "LGPL 3.0", |
|
187 | + "jsonFilePath": "com/sie/snest/base/", |
|
188 | + "jsonObject": null, |
|
189 | + "appJsonObject": { |
|
190 | + "summary": "基础模块", |
|
191 | + "product": "base", |
|
192 | + "displayName": "基础模块", |
|
193 | + "description": "基础模块", |
|
194 | + "type": "SDK", |
|
195 | + "version": "0.0.1", |
|
196 | + "categoryDesc": "基础模块", |
|
197 | + "dependencies": [], |
|
198 | + "productDesc": "工业互联网平台", |
|
199 | + "license": "LGPL 3.0", |
|
200 | + "name": "base", |
|
201 | + "company": "sie", |
|
202 | + "tag": "master", |
|
203 | + "category": "base", |
|
204 | + "events": { |
|
205 | + "startUp": [ |
|
206 | + "auth_check_job::start", |
|
207 | + "highavailable_init_menu::start" |
|
208 | + ] |
|
209 | + }, |
|
210 | + "resolved": "com.sie.snest.base" |
|
211 | + }, |
|
212 | + "author": null, |
|
213 | + "weight": 1.01, |
|
214 | + "models": null, |
|
215 | + "events": { |
|
216 | + "startUp": [ |
|
217 | + "auth_check_job::start", |
|
218 | + "highavailable_init_menu::start" |
|
219 | + ] |
|
220 | + }, |
|
221 | + "kind": "unStateful", |
|
222 | + "replicas": 1, |
|
223 | + "appInstallHosts": null, |
|
224 | + "svcName": null, |
|
225 | + "extServiceModels": null, |
|
226 | + "globalConfig": null, |
|
227 | + "appConfig": null, |
|
228 | + "delete": false, |
|
229 | + "newApp": false, |
|
230 | + "nameTag": "base.master", |
|
231 | + "baseApp": true, |
|
232 | + "primary": false |
|
233 | + }, |
|
234 | + "appMeta": null, |
|
235 | + "metas": {}, |
|
236 | + "menuMetaMap": {}, |
|
237 | + "models": {}, |
|
238 | + "refIdTreeMap": {}, |
|
239 | + "modelNameViewMetaIdMap": {} |
|
240 | +} |
|
241 | +` |
|
242 | +``` |
|
243 | + |
|
244 | + |
|
245 | +- 1个并发,共10个请求 |
|
246 | + |
|
247 | +``` |
|
248 | +time="2023-11-29T15:43:59+08:00" level=info msg="total: 10 concurrency: 1 requests per client: 10" |
|
249 | +time="2023-11-29T15:43:59+08:00" level=info msg="took 36 ms for 10 requests" |
|
250 | +time="2023-11-29T15:43:59+08:00" level=info msg="sent requests : 10" |
|
251 | +time="2023-11-29T15:43:59+08:00" level=info msg="received requests : 10" |
|
252 | +time="2023-11-29T15:43:59+08:00" level=info msg="received requests_OK : 10" |
|
253 | +time="2023-11-29T15:43:59+08:00" level=info msg="throughput (TPS) : 277" |
|
254 | +time="2023-11-29T15:43:59+08:00" level=info msg="mean: 3567850 ns, median: 2957000 ns, max: 8734000 ns, min: 2061200 ns, p99.9: 6508750 ns" |
|
255 | +time="2023-11-29T15:43:59+08:00" level=info msg="mean: 3 ms, median: 2 ms, max: 8 ms, min: 2 ms, p99.9: 6 ms" |
|
256 | + |
|
257 | +``` |
|
258 | + |
|
259 | +- 10个并发,共100个请求 |
|
260 | + |
|
261 | +``` |
|
262 | +time="2023-11-29T15:44:38+08:00" level=info msg="total: 100 concurrency: 10 requests per client: 10" |
|
263 | +time="2023-11-29T15:44:38+08:00" level=info msg="took 76 ms for 100 requests" |
|
264 | +time="2023-11-29T15:44:38+08:00" level=info msg="sent requests : 100" |
|
265 | +time="2023-11-29T15:44:38+08:00" level=info msg="received requests : 100" |
|
266 | +time="2023-11-29T15:44:38+08:00" level=info msg="received requests_OK : 100" |
|
267 | +time="2023-11-29T15:44:38+08:00" level=info msg="throughput (TPS) : 1315" |
|
268 | +time="2023-11-29T15:44:38+08:00" level=info msg="mean: 7293158 ns, median: 5833600 ns, max: 16503800 ns, min: 3412900 ns, p99.9: 16503800 ns" |
|
269 | +time="2023-11-29T15:44:38+08:00" level=info msg="mean: 7 ms, median: 5 ms, max: 16 ms, min: 3 ms, p99.9: 16 ms" |
|
270 | + |
|
271 | +``` |
|
272 | + |
|
273 | +- 10个并发,共1000个请求 |
|
274 | + |
|
275 | +``` |
|
276 | +time="2023-11-29T15:45:06+08:00" level=info msg="total: 1000 concurrency: 10 requests per client: 100" |
|
277 | +time="2023-11-29T15:45:07+08:00" level=info msg="took 863 ms for 1000 requests" |
|
278 | +time="2023-11-29T15:45:07+08:00" level=info msg="sent requests : 1000" |
|
279 | +time="2023-11-29T15:45:07+08:00" level=info msg="received requests : 1000" |
|
280 | +time="2023-11-29T15:45:07+08:00" level=info msg="received requests_OK : 1000" |
|
281 | +time="2023-11-29T15:45:07+08:00" level=info msg="throughput (TPS) : 1158" |
|
282 | +time="2023-11-29T15:45:07+08:00" level=info msg="mean: 8593066 ns, median: 6355050 ns, max: 32560400 ns, min: 1412800 ns, p99.9: 32560400 ns" |
|
283 | +time="2023-11-29T15:45:07+08:00" level=info msg="mean: 8 ms, median: 6 ms, max: 32 ms, min: 1 ms, p99.9: 32 ms" |
|
284 | + |
|
285 | +``` |
|
286 | + |
|
287 | +- 10个并发,共10000个请求 |
|
288 | + |
|
289 | +``` |
|
290 | +time="2023-11-29T15:45:36+08:00" level=info msg="total: 10000 concurrency: 10 requests per client: 1000" |
|
291 | +time="2023-11-29T15:45:56+08:00" level=info msg="took 20853 ms for 10000 requests" |
|
292 | +time="2023-11-29T15:45:56+08:00" level=info msg="sent requests : 10000" |
|
293 | +time="2023-11-29T15:45:56+08:00" level=info msg="received requests : 10000" |
|
294 | +time="2023-11-29T15:45:56+08:00" level=info msg="received requests_OK : 10000" |
|
295 | +time="2023-11-29T15:45:56+08:00" level=info msg="throughput (TPS) : 479" |
|
296 | +time="2023-11-29T15:45:56+08:00" level=info msg="mean: 20829690 ns, median: 14514300 ns, max: 438134100 ns, min: 1987800 ns, p99.9: 414818550 ns" |
|
297 | +time="2023-11-29T15:45:56+08:00" level=info msg="mean: 20 ms, median: 14 ms, max: 438 ms, min: 1 ms, p99.9: 414 ms" |
|
298 | + |
|
299 | +``` |
|
300 | + |
|
301 | +- 50个并发,共100个请求 |
|
302 | + |
|
303 | +``` |
|
304 | +time="2023-11-29T15:24:14+08:00" level=info msg="total: 100 concurrency: 50 requests per client: 2" |
|
305 | +time="2023-11-29T15:24:14+08:00" level=info msg="took 20 ms for 100 requests" |
|
306 | +time="2023-11-29T15:24:14+08:00" level=info msg="sent requests : 100" |
|
307 | +time="2023-11-29T15:24:14+08:00" level=info msg="received requests : 100" |
|
308 | +time="2023-11-29T15:24:14+08:00" level=info msg="received requests_OK : 100" |
|
309 | +time="2023-11-29T15:24:14+08:00" level=info msg="throughput (TPS) : 5000" |
|
310 | +time="2023-11-29T15:24:14+08:00" level=info msg="mean: 7575184 ns, median: 4312200 ns, max: 16251400 ns, min: 2274700 ns, p99.9: 16251400 ns" |
|
311 | +time="2023-11-29T15:24:14+08:00" level=info msg="mean: 7 ms, median: 4 ms, max: 16 ms, min: 2 ms, p99.9: 16 ms" |
|
312 | + |
|
313 | +``` |
|
314 | + |
|
315 | +- 50个并发,共1000个请求 |
|
316 | + |
|
317 | +``` |
|
318 | +time="2023-11-29T15:46:48+08:00" level=info msg="total: 1000 concurrency: 50 requests per client: 20" |
|
319 | +time="2023-11-29T15:46:50+08:00" level=info msg="took 1373 ms for 1000 requests" |
|
320 | +time="2023-11-29T15:46:50+08:00" level=info msg="sent requests : 1000" |
|
321 | +time="2023-11-29T15:46:50+08:00" level=info msg="received requests : 1000" |
|
322 | +time="2023-11-29T15:46:50+08:00" level=info msg="received requests_OK : 1000" |
|
323 | +time="2023-11-29T15:46:50+08:00" level=info msg="throughput (TPS) : 728" |
|
324 | +time="2023-11-29T15:46:50+08:00" level=info msg="mean: 68348635 ns, median: 59982300 ns, max: 197308000 ns, min: 3298100 ns, p99.9: 197308000 ns" |
|
325 | +time="2023-11-29T15:46:50+08:00" level=info msg="mean: 68 ms, median: 59 ms, max: 197 ms, min: 3 ms, p99.9: 197 ms" |
|
326 | + |
|
327 | +``` |
|
328 | + |
|
329 | +- 50个并发,共10000个请求 |
|
330 | + |
|
331 | +``` |
|
332 | + |
|
333 | +time="2023-11-29T15:47:22+08:00" level=info msg="total: 10000 concurrency: 50 requests per client: 200" |
|
334 | +time="2023-11-29T15:47:34+08:00" level=info msg="took 12819 ms for 10000 requests" |
|
335 | +time="2023-11-29T15:47:34+08:00" level=info msg="sent requests : 10000" |
|
336 | +time="2023-11-29T15:47:34+08:00" level=info msg="received requests : 10000" |
|
337 | +time="2023-11-29T15:47:34+08:00" level=info msg="received requests_OK : 10000" |
|
338 | +time="2023-11-29T15:47:34+08:00" level=info msg="throughput (TPS) : 780" |
|
339 | +time="2023-11-29T15:47:34+08:00" level=info msg="mean: 64058473 ns, median: 37056100 ns, max: 1242320700 ns, min: 1734200 ns, p99.9: 1218236450 ns" |
|
340 | +time="2023-11-29T15:47:34+08:00" level=info msg="mean: 64 ms, median: 37 ms, max: 1242 ms, min: 1 ms, p99.9: 1218 ms" |
|
341 | + |
|
342 | +``` |
|
343 | + |
|
344 | +- 100个并发,共10000个请求 |
|
345 | + |
|
346 | +``` |
|
347 | +time="2023-11-29T15:48:04+08:00" level=info msg="total: 10000 concurrency: 100 requests per client: 100" |
|
348 | +time="2023-11-29T15:48:13+08:00" level=info msg="took 9619 ms for 10000 requests" |
|
349 | +time="2023-11-29T15:48:13+08:00" level=info msg="sent requests : 10000" |
|
350 | +time="2023-11-29T15:48:13+08:00" level=info msg="received requests : 10000" |
|
351 | +time="2023-11-29T15:48:13+08:00" level=info msg="received requests_OK : 10000" |
|
352 | +time="2023-11-29T15:48:13+08:00" level=info msg="throughput (TPS) : 1039" |
|
353 | +time="2023-11-29T15:48:13+08:00" level=info msg="mean: 95861576 ns, median: 84699850 ns, max: 353457500 ns, min: 6367600 ns, p99.9: 346699700 ns" |
|
354 | +time="2023-11-29T15:48:13+08:00" level=info msg="mean: 95 ms, median: 84 ms, max: 353 ms, min: 6 ms, p99.9: 346 ms" |
|
355 | + |
|
356 | +``` |
|
357 | + |
|
358 | +- 1000个并发,共10000个请求 |
|
359 | + |
|
360 | +``` |
|
361 | +time="2023-11-29T15:48:44+08:00" level=info msg="total: 10000 concurrency: 1000 requests per client: 10" |
|
362 | +time="2023-11-29T15:48:55+08:00" level=info msg="took 11480 ms for 10000 requests" |
|
363 | +time="2023-11-29T15:48:55+08:00" level=info msg="sent requests : 10000" |
|
364 | +time="2023-11-29T15:48:55+08:00" level=info msg="received requests : 10000" |
|
365 | +time="2023-11-29T15:48:55+08:00" level=info msg="received requests_OK : 10000" |
|
366 | +time="2023-11-29T15:48:55+08:00" level=info msg="throughput (TPS) : 871" |
|
367 | +time="2023-11-29T15:48:55+08:00" level=info msg="mean: 1102012743 ns, median: 988037750 ns, max: 2135727900 ns, min: 2662200 ns, p99.9: 2129425900 ns" |
|
368 | +time="2023-11-29T15:48:55+08:00" level=info msg="mean: 1102 ms, median: 988 ms, max: 2135 ms, min: 2 ms, p99.9: 2129 ms" |
|
369 | + |
|
370 | +``` |
|
371 | + |
|
372 | +- 10000个并发,共20000个请求 |
|
373 | + |
|
374 | +``` |
|
375 | +time="2023-11-29T15:49:32+08:00" level=info msg="total: 20000 concurrency: 10000 requests per client: 2" |
|
376 | +time="2023-11-29T15:49:53+08:00" level=info msg="took 20497 ms for 20000 requests" |
|
377 | +time="2023-11-29T15:49:53+08:00" level=info msg="sent requests : 20000" |
|
378 | +time="2023-11-29T15:49:53+08:00" level=info msg="received requests : 20000" |
|
379 | +time="2023-11-29T15:49:53+08:00" level=info msg="received requests_OK : 20000" |
|
380 | +time="2023-11-29T15:49:53+08:00" level=info msg="throughput (TPS) : 975" |
|
381 | +time="2023-11-29T15:49:53+08:00" level=info msg="mean: 9629394346 ns, median: 9241691750 ns, max: 18458848300 ns, min: 3947500 ns, p99.9: 18437869200 ns" |
|
382 | +time="2023-11-29T15:49:53+08:00" level=info msg="mean: 9629 ms, median: 9241 ms, max: 18458 ms, min: 3 ms, p99.9: 18437 ms" |
|
383 | + |
|
384 | +``` |
|
385 | +### 3、元模型的map get |
|
386 | + |
|
387 | + |
|
388 | +- 10个并发,共100个请求 |
|
389 | + |
|
390 | +``` |
|
391 | +time="2023-11-29T15:51:20+08:00" level=info msg="total: 100 concurrency: 10 requests per client: 10" |
|
392 | +time="2023-11-29T15:51:20+08:00" level=info msg="took 82 ms for 100 requests" |
|
393 | +time="2023-11-29T15:51:20+08:00" level=info msg="sent requests : 100" |
|
394 | +time="2023-11-29T15:51:20+08:00" level=info msg="received requests : 100" |
|
395 | +time="2023-11-29T15:51:20+08:00" level=info msg="received requests_OK : 100" |
|
396 | +time="2023-11-29T15:51:20+08:00" level=info msg="throughput (TPS) : 1219" |
|
397 | +time="2023-11-29T15:51:20+08:00" level=info msg="mean: 7894017 ns, median: 6708500 ns, max: 22592800 ns, min: 2172000 ns, p99.9: 21619400 ns" |
|
398 | +time="2023-11-29T15:51:20+08:00" level=info msg="mean: 7 ms, median: 6 ms, max: 22 ms, min: 2 ms, p99.9: 21 ms" |
|
399 | + |
|
400 | +``` |
|
401 | + |
|
402 | +- 10个并发,共1000个请求 |
|
403 | + |
|
404 | +``` |
|
405 | +time="2023-11-29T15:52:56+08:00" level=info msg="total: 1000 concurrency: 10 requests per client: 100" |
|
406 | +time="2023-11-29T15:52:56+08:00" level=info msg="took 816 ms for 1000 requests" |
|
407 | +time="2023-11-29T15:52:56+08:00" level=info msg="sent requests : 1000" |
|
408 | +time="2023-11-29T15:52:56+08:00" level=info msg="received requests : 1000" |
|
409 | +time="2023-11-29T15:52:56+08:00" level=info msg="received requests_OK : 1000" |
|
410 | +time="2023-11-29T15:52:56+08:00" level=info msg="throughput (TPS) : 1225" |
|
411 | +time="2023-11-29T15:52:56+08:00" level=info msg="mean: 8093208 ns, median: 5498600 ns, max: 37198100 ns, min: 1601400 ns, p99.9: 35485450 ns" |
|
412 | +time="2023-11-29T15:52:56+08:00" level=info msg="mean: 8 ms, median: 5 ms, max: 37 ms, min: 1 ms, p99.9: 35 ms" |
|
413 | + |
|
414 | +``` |
|
415 | + |
|
416 | +- 10个并发,共10000个请求 |
|
417 | + |
|
418 | +``` |
|
419 | +time="2023-11-29T15:53:21+08:00" level=info msg="total: 10000 concurrency: 10 requests per client: 1000" |
|
420 | +time="2023-11-29T15:53:31+08:00" level=info msg="took 10059 ms for 10000 requests" |
|
421 | +time="2023-11-29T15:53:31+08:00" level=info msg="sent requests : 10000" |
|
422 | +time="2023-11-29T15:53:31+08:00" level=info msg="received requests : 10000" |
|
423 | +time="2023-11-29T15:53:31+08:00" level=info msg="received requests_OK : 10000" |
|
424 | +time="2023-11-29T15:53:31+08:00" level=info msg="throughput (TPS) : 994" |
|
425 | +time="2023-11-29T15:53:31+08:00" level=info msg="mean: 10034711 ns, median: 5877000 ns, max: 851575200 ns, min: 1510200 ns, p99.9: 838274550 ns" |
|
426 | +time="2023-11-29T15:53:31+08:00" level=info msg="mean: 10 ms, median: 5 ms, max: 851 ms, min: 1 ms, p99.9: 838 ms" |
|
427 | + |
|
428 | +``` |
|
429 | + |
|
430 | +- 50个并发,共100个请求 |
|
431 | + |
|
432 | +``` |
|
433 | +time="2023-11-29T15:53:57+08:00" level=info msg="total: 100 concurrency: 50 requests per client: 2" |
|
434 | +time="2023-11-29T15:53:57+08:00" level=info msg="took 33 ms for 100 requests" |
|
435 | +time="2023-11-29T15:53:57+08:00" level=info msg="sent requests : 100" |
|
436 | +time="2023-11-29T15:53:57+08:00" level=info msg="received requests : 100" |
|
437 | +time="2023-11-29T15:53:57+08:00" level=info msg="received requests_OK : 100" |
|
438 | +time="2023-11-29T15:53:57+08:00" level=info msg="throughput (TPS) : 3030" |
|
439 | +time="2023-11-29T15:53:57+08:00" level=info msg="mean: 13962681 ns, median: 12463350 ns, max: 22122800 ns, min: 2119500 ns, p99.9: 22122800 ns" |
|
440 | +time="2023-11-29T15:53:57+08:00" level=info msg="mean: 13 ms, median: 12 ms, max: 22 ms, min: 2 ms, p99.9: 22 ms" |
|
441 | + |
|
442 | +``` |
|
443 | + |
|
444 | +- 50个并发,共1000个请求 |
|
445 | + |
|
446 | +``` |
|
447 | +time="2023-11-29T15:54:17+08:00" level=info msg="total: 1000 concurrency: 50 requests per client: 20" |
|
448 | +time="2023-11-29T15:54:17+08:00" level=info msg="took 384 ms for 1000 requests" |
|
449 | +time="2023-11-29T15:54:17+08:00" level=info msg="sent requests : 1000" |
|
450 | +time="2023-11-29T15:54:17+08:00" level=info msg="received requests : 1000" |
|
451 | +time="2023-11-29T15:54:17+08:00" level=info msg="received requests_OK : 1000" |
|
452 | +time="2023-11-29T15:54:17+08:00" level=info msg="throughput (TPS) : 2604" |
|
453 | +time="2023-11-29T15:54:17+08:00" level=info msg="mean: 19014493 ns, median: 20007500 ns, max: 39160200 ns, min: 2248800 ns, p99.9: 39160200 ns" |
|
454 | +time="2023-11-29T15:54:17+08:00" level=info msg="mean: 19 ms, median: 20 ms, max: 39 ms, min: 2 ms, p99.9: 39 ms" |
|
455 | + |
|
456 | +``` |
|
457 | + |
|
458 | +- 50个并发,共10000个请求 |
|
459 | + |
|
460 | +``` |
|
461 | +time="2023-11-29T15:54:41+08:00" level=info msg="total: 10000 concurrency: 50 requests per client: 200" |
|
462 | +time="2023-11-29T15:54:44+08:00" level=info msg="took 3075 ms for 10000 requests" |
|
463 | +time="2023-11-29T15:54:44+08:00" level=info msg="sent requests : 10000" |
|
464 | +time="2023-11-29T15:54:44+08:00" level=info msg="received requests : 10000" |
|
465 | +time="2023-11-29T15:54:44+08:00" level=info msg="received requests_OK : 10000" |
|
466 | +time="2023-11-29T15:54:44+08:00" level=info msg="throughput (TPS) : 3252" |
|
467 | +time="2023-11-29T15:54:44+08:00" level=info msg="mean: 15350080 ns, median: 13936100 ns, max: 74815300 ns, min: 1637300 ns, p99.9: 74309900 ns" |
|
468 | +time="2023-11-29T15:54:44+08:00" level=info msg="mean: 15 ms, median: 13 ms, max: 74 ms, min: 1 ms, p99.9: 74 ms" |
|
469 | + |
|
470 | +``` |
|
471 | + |
|
472 | +- 100个并发,共10000个请求 |
|
473 | + |
|
474 | +``` |
|
475 | +time="2023-11-29T15:55:08+08:00" level=info msg="total: 10000 concurrency: 100 requests per client: 100" |
|
476 | +time="2023-11-29T15:55:11+08:00" level=info msg="took 3017 ms for 10000 requests" |
|
477 | +time="2023-11-29T15:55:11+08:00" level=info msg="sent requests : 10000" |
|
478 | +time="2023-11-29T15:55:11+08:00" level=info msg="received requests : 10000" |
|
479 | +time="2023-11-29T15:55:11+08:00" level=info msg="received requests_OK : 10000" |
|
480 | +time="2023-11-29T15:55:11+08:00" level=info msg="throughput (TPS) : 3314" |
|
481 | +time="2023-11-29T15:55:11+08:00" level=info msg="mean: 30073157 ns, median: 20134900 ns, max: 942773300 ns, min: 3710100 ns, p99.9: 942152800 ns" |
|
482 | +time="2023-11-29T15:55:11+08:00" level=info msg="mean: 30 ms, median: 20 ms, max: 942 ms, min: 3 ms, p99.9: 942 ms" |
|
483 | + |
|
484 | +``` |
|
485 | + |
|
486 | +- 1000个并发,共10000个请求 |
|
487 | + |
|
488 | +``` |
|
489 | +time="2023-11-29T15:55:40+08:00" level=info msg="total: 10000 concurrency: 1000 requests per client: 10" |
|
490 | +time="2023-11-29T15:55:42+08:00" level=info msg="took 2423 ms for 10000 requests" |
|
491 | +time="2023-11-29T15:55:42+08:00" level=info msg="sent requests : 10000" |
|
492 | +time="2023-11-29T15:55:42+08:00" level=info msg="received requests : 10000" |
|
493 | +time="2023-11-29T15:55:42+08:00" level=info msg="received requests_OK : 10000" |
|
494 | +time="2023-11-29T15:55:42+08:00" level=info msg="throughput (TPS) : 4127" |
|
495 | +time="2023-11-29T15:55:42+08:00" level=info msg="mean: 231318655 ns, median: 223177700 ns, max: 350379500 ns, min: 4207800 ns, p99.9: 349300600 ns" |
|
496 | +time="2023-11-29T15:55:42+08:00" level=info msg="mean: 231 ms, median: 223 ms, max: 350 ms, min: 4 ms, p99.9: 349 ms" |
|
497 | + |
|
498 | +``` |
|
499 | + |
|
500 | +- 10000个并发,共20000个请求 |
|
501 | + |
|
502 | +``` |
|
503 | +time="2023-11-29T15:56:08+08:00" level=info msg="total: 20000 concurrency: 10000 requests per client: 2" |
|
504 | +time="2023-11-29T15:56:13+08:00" level=info msg="took 5116 ms for 20000 requests" |
|
505 | +time="2023-11-29T15:56:13+08:00" level=info msg="sent requests : 20000" |
|
506 | +time="2023-11-29T15:56:13+08:00" level=info msg="received requests : 20000" |
|
507 | +time="2023-11-29T15:56:13+08:00" level=info msg="received requests_OK : 20000" |
|
508 | +time="2023-11-29T15:56:13+08:00" level=info msg="throughput (TPS) : 3909" |
|
509 | +time="2023-11-29T15:56:13+08:00" level=info msg="mean: 2087396474 ns, median: 2025976800 ns, max: 3294130100 ns, min: 20420700 ns, p99.9: 3268514400 ns" |
|
510 | +time="2023-11-29T15:56:13+08:00" level=info msg="mean: 2087 ms, median: 2025 ms, max: 3294 ms, min: 20 ms, p99.9: 3268 ms" |
|
511 | + |
|
512 | +``` |
|
513 | + |
|
514 | +### 4、源码 |
|
515 | + |
|
516 | +```golang |
|
517 | + |
|
518 | +package main |
|
519 | + |
|
520 | +import ( |
|
521 | + "context" |
|
522 | + "flag" |
|
523 | + "fmt" |
|
524 | + "sync" |
|
525 | + "sync/atomic" |
|
526 | + "time" |
|
527 | + |
|
528 | + "github.com/hazelcast/hazelcast-go-client" |
|
529 | + log "github.com/sirupsen/logrus" |
|
530 | + "go.uber.org/ratelimit" |
|
531 | +) |
|
532 | + |
|
533 | +const key = `test_meta_model_11111122222333333` |
|
534 | +const val = ` |
|
535 | +{ |
|
536 | + "appDataInfo": { |
|
537 | + "id": null, |
|
538 | + "storeAppId": null, |
|
539 | + "name": "base", |
|
540 | + "tag": "master", |
|
541 | + "displayName": "基础模块", |
|
542 | + "category": "base", |
|
543 | + "categoryDesc": "基础模块", |
|
544 | + "description": "基础模块", |
|
545 | + "application": false, |
|
546 | + "type": "SDK", |
|
547 | + "loaderType": "SDK", |
|
548 | + "company": "sie", |
|
549 | + "product": "base", |
|
550 | + "productIcon": null, |
|
551 | + "productDesc": "工业互联网平台", |
|
552 | + "summary": "基础模块", |
|
553 | + "source": "base", |
|
554 | + "resolved": "sie-snest-base-1.0-SNAPSHOT.jar", |
|
555 | + "sdkScanPkgPath": "com.sie.snest.base", |
|
556 | + "jarFile": "sie-snest-base-1.0-SNAPSHOT.jar", |
|
557 | + "jarPath": "C:\\Users\\29662\\IdeaProjects\\sie-snest\\apps\\sie-snest-base-1.0-SNAPSHOT.jar", |
|
558 | + "jarFileId": null, |
|
559 | + "md5": "57d51247cfd631a1171539dd0b3a8b80", |
|
560 | + "viewFileId": null, |
|
561 | + "viewFile": null, |
|
562 | + "viewFileMd5": null, |
|
563 | + "state": null, |
|
564 | + "storeJarFileId": null, |
|
565 | + "storeMd5": null, |
|
566 | + "dependencies": [], |
|
567 | + "icon": null, |
|
568 | + "license": "LGPL 3.0", |
|
569 | + "jsonFilePath": "com/sie/snest/base/", |
|
570 | + "jsonObject": null, |
|
571 | + "appJsonObject": { |
|
572 | + "summary": "基础模块", |
|
573 | + "product": "base", |
|
574 | + "displayName": "基础模块", |
|
575 | + "description": "基础模块", |
|
576 | + "type": "SDK", |
|
577 | + "version": "0.0.1", |
|
578 | + "categoryDesc": "基础模块", |
|
579 | + "dependencies": [], |
|
580 | + "productDesc": "工业互联网平台", |
|
581 | + "license": "LGPL 3.0", |
|
582 | + "name": "base", |
|
583 | + "company": "sie", |
|
584 | + "tag": "master", |
|
585 | + "category": "base", |
|
586 | + "events": { |
|
587 | + "startUp": [ |
|
588 | + "auth_check_job::start", |
|
589 | + "highavailable_init_menu::start" |
|
590 | + ] |
|
591 | + }, |
|
592 | + "resolved": "com.sie.snest.base" |
|
593 | + }, |
|
594 | + "author": null, |
|
595 | + "weight": 1.01, |
|
596 | + "models": null, |
|
597 | + "events": { |
|
598 | + "startUp": [ |
|
599 | + "auth_check_job::start", |
|
600 | + "highavailable_init_menu::start" |
|
601 | + ] |
|
602 | + }, |
|
603 | + "kind": "unStateful", |
|
604 | + "replicas": 1, |
|
605 | + "appInstallHosts": null, |
|
606 | + "svcName": null, |
|
607 | + "extServiceModels": null, |
|
608 | + "globalConfig": null, |
|
609 | + "appConfig": null, |
|
610 | + "delete": false, |
|
611 | + "newApp": false, |
|
612 | + "nameTag": "base.master", |
|
613 | + "baseApp": true, |
|
614 | + "primary": false |
|
615 | + }, |
|
616 | + "appMeta": null, |
|
617 | + "metas": {}, |
|
618 | + "menuMetaMap": {}, |
|
619 | + "models": {}, |
|
620 | + "refIdTreeMap": {}, |
|
621 | + "modelNameViewMetaIdMap": {} |
|
622 | +} |
|
623 | +` |
|
624 | + |
|
625 | +var ( |
|
626 | + concurrency = flag.Int("c", 1, "concurrency") |
|
627 | + total = flag.Int("n", 10, "total requests for all clients") |
|
628 | + host = flag.String("s", "127.0.0.1:8972", "server ip and port") |
|
629 | + pool = flag.Int("pool", 10, " shared grpc clients") |
|
630 | + rate = flag.Int("r", 0, "throughputs") |
|
631 | + client = flag.String("type", "kim", "client type") |
|
632 | +) |
|
633 | + |
|
634 | +func main() { |
|
635 | + flag.Parse() |
|
636 | + |
|
637 | + ctx := context.TODO() |
|
638 | + |
|
639 | + cfg := hazelcast.Config{} |
|
640 | + cfg.Cluster.Name = "hazelcast-benchmark" |
|
641 | + cfg.Cluster.Network.Addresses = []string{"192.168.168.176:5701"} |
|
642 | + |
|
643 | + hz, err := hazelcast.StartNewClientWithConfig(ctx, cfg) |
|
644 | + if err != nil { |
|
645 | + panic(fmt.Errorf("starting the client with config: %w", err)) |
|
646 | + } |
|
647 | + mp, err := hz.GetMap(ctx, "my-distributed-map") |
|
648 | + if err != nil { |
|
649 | + panic(fmt.Errorf("trying to get a map: %w", err)) |
|
650 | + } |
|
651 | + |
|
652 | + var rl ratelimit.Limiter |
|
653 | + if *rate > 0 { |
|
654 | + rl = ratelimit.New(*rate) |
|
655 | + } |
|
656 | + |
|
657 | + // 并发goroutine数.模拟客户端 |
|
658 | + n := *concurrency |
|
659 | + // 每个客户端需要发送的请求数 |
|
660 | + m := *total / n |
|
661 | + log.Infof("total: %d concurrency: %d requests per client: %d", *total, n, m) |
|
662 | + |
|
663 | + // 等待所有测试完成 |
|
664 | + var wg sync.WaitGroup |
|
665 | + wg.Add(n * m) |
|
666 | + |
|
667 | + // 总请求数 |
|
668 | + var trans uint64 |
|
669 | + // 返回正常的总请求数 |
|
670 | + var transOK uint64 |
|
671 | + |
|
672 | + // 每个goroutine的耗时记录 |
|
673 | + d := make([][]int64, n, n) |
|
674 | + |
|
675 | + // 栅栏,控制客户端同时开始测试 |
|
676 | + var startWg sync.WaitGroup |
|
677 | + startWg.Add(n + 1) // +1 是因为有一个goroutine用来记录开始时间 |
|
678 | + |
|
679 | + // 创建客户端 goroutine 并进行测试 |
|
680 | + startTime := time.Now().UnixNano() |
|
681 | + go func() { |
|
682 | + startWg.Done() |
|
683 | + startWg.Wait() |
|
684 | + startTime = time.Now().UnixNano() |
|
685 | + }() |
|
686 | + |
|
687 | + for i := 0; i < n; i++ { |
|
688 | + dt := make([]int64, 0, m) |
|
689 | + d = append(d, dt) |
|
690 | + |
|
691 | + go func(i int) { |
|
692 | + for j := 0; j < m; j++ { |
|
693 | + // 限流,这里不把限流的时间计算到等待耗时中 |
|
694 | + if rl != nil { |
|
695 | + rl.Take() |
|
696 | + } |
|
697 | + |
|
698 | + t := time.Now().UnixNano() |
|
699 | + _, err = mp.Get(ctx, key) |
|
700 | + if err != nil { |
|
701 | + panic(fmt.Errorf("trying to put to map: %w", err)) |
|
702 | + } |
|
703 | + |
|
704 | + t = time.Now().UnixNano() - t // 等待时间+服务时间,等待时间是客户端调度的等待时间以及服务端读取请求、调度的时间,服务时间是请求被服务处理的实际时间 |
|
705 | + |
|
706 | + d[i] = append(d[i], t) |
|
707 | + |
|
708 | + if err == nil { |
|
709 | + atomic.AddUint64(&transOK, 1) |
|
710 | + } |
|
711 | + |
|
712 | + atomic.AddUint64(&trans, 1) |
|
713 | + wg.Done() |
|
714 | + } |
|
715 | + }(i) |
|
716 | + |
|
717 | + } |
|
718 | + |
|
719 | + wg.Wait() |
|
720 | + |
|
721 | + // 统计 |
|
722 | + Stats(startTime, *total, d, trans, transOK) |
|
723 | +} |
|
724 | + |
|
725 | + |
|
726 | +// Stats 统计结果. |
|
727 | +func Stats(startTime int64, totalRequests int, tookTimes [][]int64, trans, transOK uint64) { |
|
728 | + // 测试总耗时 |
|
729 | + totalTInNano := time.Now().UnixNano() - startTime |
|
730 | + totalT := totalTInNano / 1000000 |
|
731 | + log.Infof("took %d ms for %d requests", totalT, totalRequests) |
|
732 | + |
|
733 | + // 汇总每个请求的耗时 |
|
734 | + totalD := make([]int64, 0, totalRequests) |
|
735 | + for _, k := range tookTimes { |
|
736 | + totalD = append(totalD, k...) |
|
737 | + } |
|
738 | + // 将int64数组转换成float64数组,以便分析 |
|
739 | + totalD2 := make([]float64, 0, totalRequests) |
|
740 | + for _, k := range totalD { |
|
741 | + totalD2 = append(totalD2, float64(k)) |
|
742 | + } |
|
743 | + |
|
744 | + // 计算各个指标 |
|
745 | + mean, _ := stats.Mean(totalD2) |
|
746 | + median, _ := stats.Median(totalD2) |
|
747 | + max, _ := stats.Max(totalD2) |
|
748 | + min, _ := stats.Min(totalD2) |
|
749 | + p999, _ := stats.Percentile(totalD2, 99.9) |
|
750 | + |
|
751 | + // 输出结果 |
|
752 | + log.Infof("sent requests : %d", totalRequests) |
|
753 | + log.Infof("received requests : %d", trans) |
|
754 | + log.Infof("received requests_OK : %d", transOK) |
|
755 | + if totalT == 0 { |
|
756 | + log.Infof("throughput (TPS) : %d", int64(totalRequests)*1000*1000000/totalTInNano) |
|
757 | + } else { |
|
758 | + log.Infof("throughput (TPS) : %d", int64(totalRequests)*1000/totalT) |
|
759 | + } |
|
760 | + |
|
761 | + log.Infof("mean: %.f ns, median: %.f ns, max: %.f ns, min: %.f ns, p99.9: %.f ns", mean, median, max, min, p999) |
|
762 | + log.Infof("mean: %d ms, median: %d ms, max: %d ms, min: %d ms, p99.9: %d ms", int64(mean/1000000), int64(median/1000000), int64(max/1000000), int64(min/1000000), int64(p999/1000000)) |
|
763 | +} |
|
764 | + |
|
765 | + |
|
766 | +``` |
|
... | ... | \ No newline at end of file |
Home.md
... | ... | @@ -0,0 +1,38 @@ |
1 | +<!----> |
|
2 | +**欢迎来到 IIDP wiki 文档中心.** |
|
3 | + |
|
4 | +[[/uploads/Home/logo.png]] |
|
5 | + |
|
6 | +<!--<video width="100%" height="100%" src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4" controls="true" />--> |
|
7 | + |
|
8 | +### [升级指引](upgrad) |
|
9 | + |
|
10 | +### [版本发布](版本发布/前后端版本更新信息) |
|
11 | + |
|
12 | +### [[常见问题]] |
|
13 | + |
|
14 | +### [[IIDP开发手册]] |
|
15 | + |
|
16 | +### [[IIDP开发规范]] |
|
17 | + |
|
18 | +### [IIDP前端开发规范](web-front-standard) |
|
19 | + |
|
20 | +### [[方案与设计]] |
|
21 | + |
|
22 | +### [[iidp-plugin]] |
|
23 | + |
|
24 | +### [工作流视频](workflow-video) |
|
25 | + |
|
26 | + |
|
27 | +### [[基础APP]] |
|
28 | + |
|
29 | + <!--[[grant]] --> |
|
30 | + |
|
31 | +### [前端学习资料](web-front-information) |
|
32 | + |
|
33 | +### [后端学习资料](web-backend-information) |
|
34 | + |
|
35 | +### [[demo]] |
|
36 | + |
|
37 | + |
|
38 | + |
IIDP Tutorials.md
... | ... | @@ -0,0 +1,2 @@ |
1 | +- [01-初始化项目](IIDP Tutorials/01-Initialize-Project) |
|
2 | +- [02-第一个APP](IIDP Tutorials/02-First-App) |
|
... | ... | \ No newline at end of file |
IIDP Tutorials/01-Initialize-Project.md
... | ... | @@ -0,0 +1,159 @@ |
1 | +# 目标 |
|
2 | + |
|
3 | +本文带你配置 IIDP 的开发环境,并运行后端服务。 |
|
4 | + |
|
5 | +# 开始前准备 |
|
6 | + |
|
7 | +1. git |
|
8 | +2. Java 8 |
|
9 | +3. IDEA |
|
10 | +4. maven |
|
11 | +5. MySQL 8.0+ |
|
12 | + |
|
13 | +# 拉取项目 |
|
14 | + |
|
15 | +使用 git 拉取 [snest-demo](http://192.168.175.55:9888/snest-public/snest-demo) 到本地,并使用 IDEA 打开。 |
|
16 | + |
|
17 | +# 配置 maven |
|
18 | + |
|
19 | +打开 IDEA 配置 - Build,Execution,Deployment - Build Tools - Maven。使用自己的 maven 配置覆盖 IDEA 自带 maven 的配置。 |
|
20 | + |
|
21 | +修改 maven.settings.xml 添加仓库配置。 |
|
22 | + |
|
23 | +```xml |
|
24 | +<?xml version="1.0" encoding="UTF-8"?> |
|
25 | +<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" |
|
26 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> |
|
27 | + <localRepository>${user.home}/.m2/repository</localRepository> |
|
28 | + |
|
29 | + <mirrors> |
|
30 | + <mirror> |
|
31 | + <id>nexus</id> |
|
32 | + <mirrorOf>central</mirrorOf> |
|
33 | + <url>http://192.168.168.156:8081/repository/maven-public/</url> |
|
34 | + </mirror> |
|
35 | + <mirror> |
|
36 | + <id>nexus-aliyun</id> |
|
37 | + <mirrorOf>central</mirrorOf> |
|
38 | + <name>Nexus aliyun</name> |
|
39 | + <url>http://maven.aliyun.com/nexus/content/groups/public</url> |
|
40 | + </mirror> |
|
41 | + </mirrors> |
|
42 | + |
|
43 | + <profiles> |
|
44 | + <profile> |
|
45 | + <id>nexus</id> |
|
46 | + <repositories> |
|
47 | + <repository> |
|
48 | + <id>nexus</id> |
|
49 | + <name>Nexus</name> |
|
50 | + <url>http://192.168.168.156:8081/repository/maven-public/</url> |
|
51 | + <releases> |
|
52 | + <enabled>true</enabled> |
|
53 | + <!--<always>true</always>--> |
|
54 | + <updatePolicy>always</updatePolicy> |
|
55 | + </releases> |
|
56 | + <snapshots> |
|
57 | + <enabled>true</enabled> |
|
58 | + <!--<always>true</always>--> |
|
59 | + </snapshots> |
|
60 | + </repository> |
|
61 | + </repositories> |
|
62 | + <pluginRepositories> |
|
63 | + <pluginRepository> |
|
64 | + <id>nexus</id> |
|
65 | + <name>Nexus</name> |
|
66 | + <url>http://192.168.168.156:8081/repository/maven-snapshots/</url> |
|
67 | + <releases> |
|
68 | + <enabled>true</enabled> |
|
69 | + <!--<always>true</always>--> |
|
70 | + </releases> |
|
71 | + <snapshots> |
|
72 | + <enabled>true</enabled> |
|
73 | + <!--<always>true</always>--> |
|
74 | + </snapshots> |
|
75 | + </pluginRepository> |
|
76 | + </pluginRepositories> |
|
77 | + </profile> |
|
78 | + |
|
79 | + <profile> |
|
80 | + <id>nexus-aliyun</id> |
|
81 | + <repositories> |
|
82 | + <repository> |
|
83 | + <id>nexus-aliyun</id> |
|
84 | + <name>nexus-aliyun</name> |
|
85 | + <url>https://maven.aliyun.com/repository/public</url> |
|
86 | + <releases> |
|
87 | + <enabled>true</enabled> |
|
88 | + </releases> |
|
89 | + <snapshots> |
|
90 | + <enabled>true</enabled> |
|
91 | + </snapshots> |
|
92 | + </repository> |
|
93 | + </repositories> |
|
94 | + <pluginRepositories> |
|
95 | + <pluginRepository> |
|
96 | + <id>nexus-aliyun</id> |
|
97 | + <name>nexus-aliyun</name> |
|
98 | + <url>https://maven.aliyun.com/repository/public/</url> |
|
99 | + <releases> |
|
100 | + <enabled>true</enabled> |
|
101 | + </releases> |
|
102 | + <snapshots> |
|
103 | + <enabled>true</enabled> |
|
104 | + </snapshots> |
|
105 | + </pluginRepository> |
|
106 | + </pluginRepositories> |
|
107 | + </profile> |
|
108 | + </profiles> |
|
109 | + |
|
110 | + <activeProfiles> |
|
111 | + <activeProfile>nexus-aliyun</activeProfile> |
|
112 | + <activeProfile>nexus</activeProfile> |
|
113 | + </activeProfiles> |
|
114 | +</settings> |
|
115 | +``` |
|
116 | + |
|
117 | +# 创建数据库 |
|
118 | + |
|
119 | +创建一个新的数据库,字符集选择 utf8mb4,排序字符集选择 utf8mb4-bin。 |
|
120 | + |
|
121 | +修改 snest-demo-server/src/main/resources/dbcp.properties 里面的数据库连接。 |
|
122 | + |
|
123 | +# 启动项目 |
|
124 | + |
|
125 | +先运行 maven 的 clean package。 |
|
126 | + |
|
127 | +跟 SpringBoot 项目类似,启动 snest-demo-server/src/main/java/com/sie/demo/server/Server.java |
|
128 | + |
|
129 | +打开浏览器访问 http://localhost:8060/root/api/master 可以看到以下页面。 |
|
130 | + |
|
131 | +[[/uploads/IIDP Tutorials/01-Initialize-Project/api-doc.png]] |
|
132 | + |
|
133 | +# 项目结构 |
|
134 | + |
|
135 | +[[/uploads/IIDP Tutorials/01-Initialize-Project/project-struct.png]] |
|
136 | + |
|
137 | +- apps 目录 |
|
138 | + - apps.json 内置应用列表 |
|
139 | + - sie-snest-base-1.0-SNAPSHOT.jar |
|
140 | + - sie-snest-file-1.0-SNAPSHOT.jar |
|
141 | + - sie-snest-dict-1.0-SNAPSHOT.jar |
|
142 | +- snest-apps 模块 |
|
143 | + - 需要开发的 APP |
|
144 | +- snest-server 引擎服务 |
|
145 | + - application.properties 引擎配置信息 |
|
146 | + - dbcp.properties 数据库配置信息 |
|
147 | + |
|
148 | +启动逻辑如下 |
|
149 | + |
|
150 | +1. 引擎启动的时候,会读取 apps/apps.json 文件。 |
|
151 | +2. 然后根据 apps.json 的内容,先加载 apps 目录下的内置应用。 |
|
152 | +3. 根据数据库的数据,加载其他方式安装的 app。 |
|
153 | +4. 启动 Spring 容器,暴露服务 |
|
154 | + |
|
155 | +# 常见问题 |
|
156 | + |
|
157 | +## application.properties 指定了不存在的 apps 目录 |
|
158 | + |
|
159 | +在 application.properties 或 application-dev.properties 文件里,有一个配置项 app.install.jar.dir。如果指定了一个不存在的路径,会导致无法加载内置应用。 |
IIDP Tutorials/02-First-App.md
IIDP\345\211\215\347\253\257\345\274\200\345\217\221\350\247\204\350\214\203.md
... | ... | @@ -0,0 +1,160 @@ |
1 | +<!--[[_TOC_]]--> |
|
2 | +# IIDP前端开发规范 |
|
3 | + |
|
4 | +[[/uploads/IIDP-png/logo.png]] |
|
5 | + |
|
6 | +# 前言 |
|
7 | + |
|
8 | +希望IIDP开发者通过阅读本手册,能够充分利用IIDP平台进行开发,避免常见的错误和陷阱,写出高质量的代码,提高开发效率。让我们一起码出高效、码出质量的软件系统。 |
|
9 | + |
|
10 | +# 开发规范 |
|
11 | + |
|
12 | +## 命名规范 |
|
13 | +1.【强制】 组件命名规范,组件名称必须唯一 |
|
14 | +说明:在IIDP平台中,确保组件名称的唯一性,便于识别和管理。 |
|
15 | + |
|
16 | +正例:tech-button / tech-dialog / tech-upload / tech-custom-multi-input tech-作为名称前缀,具体的组件名作为后缀名称。 |
|
17 | + |
|
18 | +反例:custom-multi-input 没有tech-作为名称前缀,会导致使用时type:custom-multi-input不显示这个组件 |
|
19 | + |
|
20 | + |
|
21 | +### 常用的命名规范: |
|
22 | +【推荐】小驼峰式命名法首字母小写,大驼峰式命名法首字母大写,短横线连接式,下划线连接式 |
|
23 | + |
|
24 | +正例:camelCase/PascalCase/kebab-case/snake_case |
|
25 | + |
|
26 | +反例:formconfiglabelposition 单词直接全小写拼接可读性差 |
|
27 | + |
|
28 | +1、项目文件命名 |
|
29 | + |
|
30 | +【推荐】命名方法:kebab-case 字母小写短横线连接 |
|
31 | + |
|
32 | +正例:whclould-smart-port / my-project-name |
|
33 | + |
|
34 | +反例:My-Project |
|
35 | +2、目录名 |
|
36 | + |
|
37 | +【推荐】kebab-case,有复数结构时,要采用复数命名法 |
|
38 | + |
|
39 | +正例:assets、components、directives、mixins、utils、views |
|
40 | +3、单文件组件名 |
|
41 | +【推荐】单文件组件名应该始终是单词大写开头 (PascalCase),大驼峰式命名法。 |
|
42 | + |
|
43 | +正例:MyComponent.vue |
|
44 | + |
|
45 | +反例:my-Component.vue |
|
46 | + |
|
47 | +4、全局变量,全局方法名称使用应用名称+功能名称 |
|
48 | + |
|
49 | + 正例: |
|
50 | +```js |
|
51 | + const IOT_THEME_OPTIONS = ['blue', 'red', 'green'] |
|
52 | + |
|
53 | + function iotGetSearchParams(params){ |
|
54 | + console.log(params) |
|
55 | + } |
|
56 | +``` |
|
57 | +反例: const MY_TE = ['blue', 'red', 'green'] 可读性差维护成本高 |
|
58 | +## 代码规范 |
|
59 | +### 代码参数命名 |
|
60 | +【推荐】在声明 prop 的时候,其命名应该始终使用小驼峰式命名法camelCase |
|
61 | + |
|
62 | +正例:camelCase |
|
63 | + |
|
64 | +反例:camel-Case |
|
65 | +## 注释规范 |
|
66 | +注释的原则:提高代码的可读性,从而提高代码的可维护性 |
|
67 | +如无必要,勿增注释 ( As short as possible ) |
|
68 | +如有必要,尽量详尽 ( As long as necessary ) |
|
69 | + |
|
70 | +## 扩展规范 |
|
71 | +1.【推荐】扩展名称使用应用名称+菜单名+功能名称 |
|
72 | + |
|
73 | +正例:iot_iiot_program_menu_search_extend |
|
74 | + |
|
75 | +反例:unit_test_extend_view 不明确生效的位置和功能 |
|
76 | +2、关于扩展选择的节点id |
|
77 | +【强制】在选择扩展的节点id时,对于id中包含undefined的节点,可能是平台的异常,请咨询平台人员 |
|
78 | + |
|
79 | +3、样式扩展 |
|
80 | + |
|
81 | +【强制】对框架使用的Element的样式修改时,使用局部作用域,以免影响全局样式 |
|
82 | + |
|
83 | +4、主题扩展 |
|
84 | + |
|
85 | +对页面顶部,侧边栏,菜单等公共模块的扩展 |
|
86 | +需独立新增app内扩展,可以单独自行安装卸载,以免影响全局 |
|
87 | +# 版本管理规范 |
|
88 | +## git commit规范 |
|
89 | +1. feat从master拉出,分支保留至release发版 |
|
90 | + |
|
91 | +2. fix分支修复哪个rel就从哪个rel拉出,从哪个rel分支拉的,合并到哪个rel |
|
92 | + |
|
93 | +3. feat新功能开发,自测成功前每天将master分支合并到feat_xxx分支 自测完成后,到gitlab提合并申请到dev分支 |
|
94 | + |
|
95 | +4. 分支创建规则: |
|
96 | + |
|
97 | + |
|
98 | +## 关于版本更新 |
|
99 | +为了确保线上使用的依赖版本与开发时的版本一致,有以下几项注意事项: |
|
100 | + |
|
101 | +1、执行npm run update:tech、npm run update:beta、npm update等命令升级后依赖版本中会带有 ^ 等符号,如果需要使用固定版本,需要手动修删除 ^ 符号,使用固定的具体的版本号,并执行npm run init:tech重新安装指定版本 |
|
102 | + |
|
103 | +2、为了保证打包稳定,请直接使用当前开发环境所安装的依赖,不需要执行 npm run update:tech 或者 npm run update:beta |
|
104 | +# 工程结构规范 |
|
105 | + |
|
106 | +1、工程结构说明 |
|
107 | +```js |
|
108 | +|— apps |
|
109 | + |— base // 业务App 根据实际情况命名 |
|
110 | + |— common // 公共总扩展,一般情况不用操作,后面会展开讲解 |
|
111 | + |— common - 公共总扩展 |
|
112 | + |— assetImport.js - 内部导入连接资源扩展入口 |
|
113 | + |— asset.json - 外部url连接资源扩展入口 |
|
114 | + |— common.js - 应用公共配置扩展入口 |
|
115 | + |— comps.js - 定制组件扩展入口 |
|
116 | + |— extendView.js - 合并视图扩展入口 |
|
117 | + |— hook.js - 功能钩子扩展入口 |
|
118 | + |— schema.js - 初始视图结构扩展入口 |
|
119 | + |— index.js - 公共扩展总入口 |
|
120 | + |— views // 纯js格式编辑视图,通过视图的扩展能力合并的主视图中 |
|
121 | + |— rbacUser // 业务定制的扩展视图,名称根据实际情况定义 |
|
122 | + |— tview__base__rbac_user.js // 扩展文件,后面会展开讲解 命名规则:[自定义业务名].js |
|
123 | + |— ... |
|
124 | + |— index.js // 扩展视图的入口 |
|
125 | + |— config // 当前App的额外配置,如全局变量 |
|
126 | + |— app.json |
|
127 | + |— resource // 语言包,皮肤 等资源 |
|
128 | + |— static-resource // 静态资源 |
|
129 | + |— index.js // 扩展引入入口 |
|
130 | + |— component // 公共业务组件 |
|
131 | +|— config |
|
132 | + |— nginx |
|
133 | + |— apps.json |
|
134 | +|— build // 各种环境的打包入口 |
|
135 | +``` |
|
136 | + |
|
137 | +2、工程运行起来后,以下文件夹是工程临时生成文件不用处理: |
|
138 | +resource、static-resouorce、views、dist、distApp、distTmp |
|
139 | +# 测试规范 |
|
140 | +单元测试 |
|
141 | + |
|
142 | +1.【强制】编写独立、可重复执行的单元测试。 说明:单元测试应该是独立的、不依赖外部资源的测试,可以在任何环境下重复执行。确保每个单元测试之间相互独立,不会相互影响,提高测试的可靠性和可维护性。 |
|
143 | + |
|
144 | +2.【强制】 测试覆盖率达到预定目标。 说明:测试覆盖率是衡量测试代码覆盖业务代码的程度的指标。根据项目的需求和复杂度,设定合理的测试覆盖率目标,并确保单元测试覆盖率达到或超过这个目标。 |
|
145 | + |
|
146 | +3.【强制】 每个单元测试应该只测试一个功能点或场景。 说明:每个单元测试应该聚焦于测试一个特定的功能点或场景,避免将多个功能点或场景混合在一个测试中。这样可以提高测试的可读性和可维护性,并能更准确地定位问题。 |
|
147 | + |
|
148 | +4.【强制】 使用合适的测试数据进行测试。 说明:在编写单元测试时,应该使用合适的测试数据来覆盖不同的情况和边界条件,以验证代码在各种情况下的正确性。包括正常情况、边界情况、异常情况等。 |
|
149 | + |
|
150 | +5.【强制】验证预期的行为和结果。 说明:每个单元测试应该明确验证预期的行为和结果,通过断言来判断测试是否通过。确保测试代码能够准确地验证被测试代码的行为和结果。 |
|
151 | + |
|
152 | +功能测试 |
|
153 | + |
|
154 | +1.【强制】验证系统的用户界面(UI)功能。 说明:功能测试应该验证系统的用户界面功能是否符合预期,包括页面布局、交互操作、表单验证等。通过功能测试可以确保用户界面的可用性和易用性。 |
|
155 | +# 部署规范 |
|
156 | +1、底座的更新只能通过更新服务器中前端工程。 |
|
157 | + |
|
158 | +2、app 的更新可通过应用市场更新,可也以通过服务器部署更新 |
|
159 | + |
|
160 | +3、打包外部依赖 app 和本地开发的 app 到./dist/umdComps中。适用于通过应用市场上传安装 |
IIDP\345\274\200\345\217\221\346\211\213\345\206\214.md
... | ... | @@ -0,0 +1,14 @@ |
1 | +1. [[概述|01.开发手册/01.概述/01.概述.md]] |
|
2 | +2. 入门教程 |
|
3 | + 1. [[后端服务搭建|guide/init-project]] |
|
4 | + 2. [[前端服务搭建|guide/init-web-project]] |
|
5 | + 3. [[创建模型|guide/model]] |
|
6 | + 4. [[生成视图|guide/create-view]] |
|
7 | + 5. [[批量更新|guide/service/updateByFilter]] |
|
8 | +3. [[常见问题/向平台人员提问之前的准备建议.md]] |
|
9 | +4. [[01.开发手册/06.常见问题QA/02.crud服务重写.md]] |
|
10 | +5. [[01.开发手册/06.常见问题QA/03.meta上下文使用规范.md]] |
|
11 | +6. [[常见问题/日志问题:通过将重要日志写入文件解决idea日志被覆盖的问题.md]] |
|
12 | +7. [[常见问题/种子数据中明明配置了菜单,用管理员登陆在页面中菜单找不到.md]] |
|
13 | +8. [[常见问题/视图问题:菜单能点击,但返回空白页.md]] |
|
14 | +9. [[01.开发手册/06.常见问题QA]] |
|
... | ... | \ No newline at end of file |
IIDP\345\274\200\345\217\221\350\247\204\350\214\203.md
... | ... | @@ -0,0 +1,448 @@ |
1 | +<!--[[_TOC_]]--> |
|
2 | +# IIDP开发规范 |
|
3 | + |
|
4 | +[[IIDP开发规范.pdf|/uploads/IIDP-png/IIDP开发规范.pdf]] |
|
5 | + |
|
6 | +[[/uploads/IIDP-png/logo.png]] |
|
7 | + |
|
8 | + |
|
9 | +# 前言 |
|
10 | + |
|
11 | +《IIDP开发规范》是赛意工业软件及物联子公司技术平台研发团队,基于IIDP低代码平台设计、开发和测试过程中,汇集团队内部和外部交付团队集体的智慧结晶和开发经验总结,经历了从零开始自研IIDP低代码平台,多次大规模一线实战的检验及不断的完善,系统化地整理成册,回馈给开发者的一份开发规范。现代软件行业的高速发展对开发者的综合素质要求越来越高,因为不仅是编程知识点,其它维度的知识点也会影响到软件的最终交付质量。企业数字化转型,需要部署大量企业级应用,随着业务的发展,需求无法得到及时响应,大大增加了数字化转型的成本,这也是我们开发IIDP的初衷,极大地给开发者带来了很多开发方面的便利和快捷,同时为了提高开发者编码的质量和可靠性,我们也约定了很多规范,比如:对象命名的混乱带来代码的不好维护;模型设计的不规范带来从一开始就导致设计上的混乱给;数据库的表结构和索引设计缺陷可能带来软件上的架构缺陷或性能风险;工程结构混乱导致后续维护艰难;没有鉴权的漏洞代码易被黑客攻击等等。所以本手册以IIDP开发者为中心视角,划分为编程规范、测试规范、授权管理规范、版本管理规范、工程结构规范、部署规范等几个维度,再根据内容特征,细分成若干二级子目录。根据约束力强弱及故障敏感性,规约依次分为【强制】、【推荐】、【参考】三大类。对于规约条目的延伸信息中,“说明”对内容做了适当扩展和解释;“正例”提倡什么样的编码和实现方式;“反例”说明需要提防的雷区,以及真实的错误案例。 |
|
12 | + |
|
13 | + 现代软件架构都需要协同开发完成,高效协作即降低协同成本,提升沟通效率,所谓无规矩不成方圆,无规范不能协作。众所周知,制订交通法规表面上是要限制行车权,实际上是保障公众的人身安全。试想如果没有限速,没有红绿灯,谁还敢上路行驶。对软件来说,适当的规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率。代码的字里行间流淌的是软件生命中的血液,质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升质量意识。 |
|
14 | + |
|
15 | +希望IIDP开发者通过阅读本手册,能够充分利用IIDP平台进行开发,避免常见的错误和陷阱,写出高质量的代码,提高开发效率。让我们一起码出高效、码出质量的软件系统。 |
|
16 | + |
|
17 | +# 编程规范 |
|
18 | +### API调用规范 |
|
19 | +1.<font color=red>【强制】</font>前端调用后端api必须指定app(分布式部署场景下) |
|
20 | +说明:平台交付最小单元就是app,接口调用时必须指定app的名字 |
|
21 | +正例:传参中包含app,model,service,其中app是model所在的app |
|
22 | +反例:传参中只有model和service,未提供app或app错误 |
|
23 | + |
|
24 | +### 命名规范 |
|
25 | +1.<font color=red>【强制】</font> app命名规范。应用名称必须唯一,必须在app.json中定义,使用业务含义的应 用名称作为前缀。(长度,允许包含哪些字符,开头。保留关键字,平台预留) |
|
26 | + |
|
27 | +说明:在IIDP平台中,app是名称唯一标识,确保应用名称的唯一性,便于识别和管理。 |
|
28 | +正例:iot_net / iot_base / smi_base / smi_redis / snest_tenant / snest_log / snest_base 等大的项目名称作为前缀,具体的业务场景作为后缀名称,具有业务含义,也能唯一区分。 |
|
29 | +反例:net / base / log 等模糊或不具有业务含义的名称很容易重名,且不知道是具体哪个项目的名称。 |
|
30 | + |
|
31 | +2.【强制】模型命名规范。模型名称必须唯一,并在注解中定义。推荐使用业务含义的模型名称作为前缀。 |
|
32 | +说明:在IIDP平台中,模型名称是名称唯一标识,对应着数据库中的表名称,使用下划线拼接,控制字符串长度,确保应用名称的唯一性,便于识别和管理。 |
|
33 | +正例:tenant_action_rule / tenant_rbac_organization 等大的项目名称作为前缀,具体的业务场景作为后缀名称。 |
|
34 | +反例:net_model / base_model / logModel 等,不需要加model后缀,不需要采用驼峰拼接,且不知道是具体哪个项目的名称。 |
|
35 | + |
|
36 | + |
|
37 | +### 常量定义 |
|
38 | + |
|
39 | +1.【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。 |
|
40 | + |
|
41 | +正例:通过枚举值定义模型类型 |
|
42 | + |
|
43 | +```java |
|
44 | +public static enum ModelType { |
|
45 | + Default, |
|
46 | + Define, |
|
47 | + Buss, |
|
48 | + Memory, |
|
49 | + Data, |
|
50 | + Cache, |
|
51 | + Config, |
|
52 | + Tree, |
|
53 | + Template, |
|
54 | + View; |
|
55 | + |
|
56 | + private ModelType() {} |
|
57 | +} |
|
58 | +``` |
|
59 | + |
|
60 | +反例:模型名称用字符串,调用方法也是字符串。导致其他地方使用这个模型名称时,需要重新再写一遍字符串,如果修改了一处,另外一处不会自动同步修改。 |
|
61 | + |
|
62 | +```java |
|
63 | +this.getMeta().get("rbac_tenant").callSuper(Tenant.class, "create", valuesList); |
|
64 | +``` |
|
65 | + |
|
66 | + |
|
67 | +### 模型规范 |
|
68 | + |
|
69 | +1.【参考】基于元模型驱动的平台核心思想:一切皆模型、模型可扩展可继承。 |
|
70 | + 说明:关于平台的几点说明: |
|
71 | + 1、每个元模型都有唯一身份证; |
|
72 | + 2、平台的模型不仅提供属性、服务等能力,模型之间还可以方便进行继承、扩展; |
|
73 | + 3、模型还分了业务模型(缓存与db的存储与访问能力)、数据模型(内存存储和访问能力)、树状模型(行级存储与访问能力)等; |
|
74 | + 4、业务模型根据是否抽象来确定是否需要建立表; |
|
75 | + |
|
76 | +### 在线IDE使用规范 |
|
77 | +1.【参考】基于元模型驱动的平台核心思想:一切皆模型、模型可扩展可继承。 |
|
78 | + |
|
79 | +### SDK使用规范 |
|
80 | + |
|
81 | +1.熟悉SDK文档和功能: |
|
82 | + 在开始使用SDK之前,仔细阅读官方文档,了解SDK提供的功能和用法。 |
|
83 | + 理解SDK的设计理念和工作原理,以便正确地使用和集成SDK。 |
|
84 | + |
|
85 | +2.遵循SDK的最佳实践: |
|
86 | + 官方文档已提供一些最佳实践和推荐的使用方式,尽量遵循这些指南。 |
|
87 | + 遵循SDK的设计模式和约定,以便与其他开发者更好地协作。 |
|
88 | + |
|
89 | +3.使用适当的错误处理机制: |
|
90 | + 当使用SDK的方法或者函数时,要注意捕获和处理可能发生的异常。 |
|
91 | + 根据SDK提供的错误码或者异常类型,进行适当的错误处理,例如记录日志、返回错误信息等。 |
|
92 | + |
|
93 | +4.避免滥用SDK: |
|
94 | + 只使用SDK提供的必要功能,避免滥用SDK的高级功能,以免增加复杂性和性能开销。 |
|
95 | + 不要过度依赖SDK,尽量保持代码的灵活性和可扩展性。 |
|
96 | + |
|
97 | +5.使用SDK提供的扩展点: |
|
98 | + 如果SDK提供了扩展点或者插件机制,可以使用这些机制来自定义功能或者扩展SDK的行为。 |
|
99 | + |
|
100 | +6.更新和升级SDK: |
|
101 | + 定期检查SDK的新版本和更新,了解新功能、修复的问题和性能改进。 |
|
102 | + 在合适的时机,考虑升级到新版本的SDK,以获得更好的功能和性能。 |
|
103 | + |
|
104 | +7.与SDK开发者保持互动: |
|
105 | + 如果SDK有相关的群聊或者社区,可以积极参与其中,与其他开发者交流经验和解决问题。提交反馈和建议,帮助SDK的改进和发展。(外网可访问的在线文档,反馈和建议的途径) |
|
106 | +8.IIDP SDK使用规范 |
|
107 | +api调用 |
|
108 | +filter |
|
109 | +crud |
|
110 | +与前端页面交互 |
|
111 | + 总之,SDK使用规范的核心是熟悉SDK的功能和用法,并遵循官方文档中的建议和最佳实践。合理地使用SDK,并与其他团队成员保持一致的使用方式,可以提高代码的可读性、可维护性和可扩展性。 |
|
112 | + |
|
113 | + |
|
114 | +### 日志规范 |
|
115 | + |
|
116 | +1.【强制】IIDP引擎日志规范。 |
|
117 | +说明:IIDP引擎对通用的日志接口进行封装,规范日志打印格式,自动追加引擎相关日志信息,比如自动最佳model service等信息,自动可知道访问的是哪个model哪个service,基于这种结构化的日志,便于后续的日志分析。 |
|
118 | + |
|
119 | +2.【强制】在日志输出中包含有用的上下文信息。 |
|
120 | +说明:日志应该包含有助于定位问题的上下文信息,例如时间戳、线程ID、请求ID、关键参数值等。 |
|
121 | + |
|
122 | +### 后端视图规范 |
|
123 | + |
|
124 | +1.<font color=red>【强制】</font>同一个后端视图内按钮的 auth 要求唯一 |
|
125 | +说明:后端视图,例如 grid 视图,buttons 和 tabr 可以声明多个按钮。如果是调用自定义服务的按钮,auth 必须唯一,并且不能与默认权限 `read`、`update`、`create`、`delete` 相同。否则会导致不能单独对某个按钮进行授权。 |
|
126 | + |
|
127 | +反例 |
|
128 | + |
|
129 | +```json |
|
130 | +"buttons": [ |
|
131 | + { |
|
132 | + "action": "edit", |
|
133 | + "auth": "update", |
|
134 | + "name": "编辑" |
|
135 | + }, |
|
136 | + { |
|
137 | + "name": "发布", |
|
138 | + "auth": "update", |
|
139 | + "service": "updateApp", |
|
140 | + "model": "meta_app" |
|
141 | + }, |
|
142 | + { |
|
143 | + "name": "生成视图", |
|
144 | + "auth": "update", |
|
145 | + "service": "buildDefaultViews", |
|
146 | + "model": "meta_model" |
|
147 | + } |
|
148 | +] |
|
149 | +``` |
|
150 | + |
|
151 | +正例 |
|
152 | + |
|
153 | +```json |
|
154 | +"buttons": [ |
|
155 | + { |
|
156 | + "action": "edit", |
|
157 | + "auth": "update", |
|
158 | + "name": "编辑" |
|
159 | + }, |
|
160 | + { |
|
161 | + "name": "发布", |
|
162 | + "auth": "updateApp", |
|
163 | + "service": "updateApp", |
|
164 | + "model": "meta_app" |
|
165 | + }, |
|
166 | + { |
|
167 | + "name": "生成视图", |
|
168 | + "auth": "buildDefaultViews", |
|
169 | + "service": "buildDefaultViews", |
|
170 | + "model": "meta_model" |
|
171 | + } |
|
172 | +] |
|
173 | +``` |
|
174 | + |
|
175 | +# 测试规范 |
|
176 | + |
|
177 | +### 单元测试 |
|
178 | +1.【强制】编写独立、可重复执行的单元测试。 |
|
179 | + 说明:单元测试应该是独立的、不依赖外部资源的测试,可以在任何环境下重复执行。确保每个单元测试之间相互独立,不会相互影响,提高测试的可靠性和可维护性。 |
|
180 | + |
|
181 | +2.【强制】使用适当的测试框架和断言库。 |
|
182 | + 说明:选择适合项目的测试框架(如JUnit、TestNG等)和断言库(如AssertJ、Hamcrest等),以便编写清晰、简洁的测试代码,并提供丰富的断言方法来验证测试结果的正确性。 |
|
183 | + |
|
184 | +3.【强制】 测试覆盖率达到预定目标。 |
|
185 | + 说明:测试覆盖率是衡量测试代码覆盖业务代码的程度的指标。根据项目的需求和复杂度,设定合理的测试覆盖率目标,并确保单元测试覆盖率达到或超过这个目标。 |
|
186 | + |
|
187 | +4.【强制】 每个单元测试应该只测试一个功能点或场景。 |
|
188 | + 说明:每个单元测试应该聚焦于测试一个特定的功能点或场景,避免将多个功能点或场景混合在一个测试中。这样可以提高测试的可读性和可维护性,并能更准确地定位问题。 |
|
189 | + |
|
190 | +5.【强制】 使用合适的测试数据进行测试。 |
|
191 | + 说明:在编写单元测试时,应该使用合适的测试数据来覆盖不同的情况和边界条件,以验证代码在各种情况下的正确性。包括正常情况、边界情况、异常情况等。 |
|
192 | + |
|
193 | +6.【强制】验证预期的行为和结果。 |
|
194 | + 说明:每个单元测试应该明确验证预期的行为和结果,通过断言来判断测试是否通过。确保测试代码能够准确地验证被测试代码的行为和结果。 |
|
195 | + |
|
196 | +### 功能测试 |
|
197 | + |
|
198 | +1.【强制】验证系统的用户界面(UI)功能。 |
|
199 | + 说明:功能测试应该验证系统的用户界面功能是否符合预期,包括页面布局、交互操作、表单验证等。通过功能测试可以确保用户界面的可用性和易用性。 |
|
200 | + |
|
201 | + |
|
202 | +### 集成测试 |
|
203 | + |
|
204 | +### 性能测试 |
|
205 | + |
|
206 | +【推荐】性能测试是评估系统在不同负载条件下的性能表现的过程。为了确保性能测试的准确性和可重复性,需要遵循一套规范和最佳实践。 |
|
207 | +以下是性能测试规范的一些要点: |
|
208 | + |
|
209 | +(1)目标定义:明确性能测试的目标和需求。确定要测试的系统组件、功能或场景,以及期望的性能指标,如响应时间、吞吐量、并发用户数等。 |
|
210 | +(2)测试环境:建立逼近真实生产环境的测试环境。包括硬件、网络、操作系统、数据库等方面的配置和设置。确保测试环境与生产环境的相似性,以便更准确地模拟实际情况。 |
|
211 | + |
|
212 | +(3)测试数据:使用真实、多样化的测试数据进行性能测试。测试数据应该涵盖典型场景和边界情况,并具有一定的数据量和变化。 |
|
213 | + |
|
214 | +(4)测试脚本和工具:编写清晰、可重复执行的性能测试脚本。选择适当的性能测试工具,如JMeter、LoadRunner等,用于执行和监控性能测试。 |
|
215 | +(5)负载模拟:根据实际使用情况和预期负载,设计和模拟不同负载条件下的测试场景。包括正常负载、峰值负载、压力测试等。确保测试覆盖了系统的不同使用情况和负载情况。 |
|
216 | + |
|
217 | +(6)测试监控和度量:监控系统在测试过程中的各项性能指标,如响应时间、CPU利用率、内存使用等。使用合适的监控工具和指标,以便及时发现性能瓶颈和问题。 |
|
218 | + |
|
219 | +(7)结果分析和报告:对性能测试结果进行分析和总结。识别性能瓶颈、性能优化的潜力和建议等。生成详尽的测试报告,包括测试配置、执行过程、结果数据和结论。 |
|
220 | + |
|
221 | +(8)迭代和持续测试:性能测试应该是一个迭代的过程,随着系统的演化和变化,不断进行性能测试和优化。同时,建立持续性能测试的机制,确保系统在每次发布和部署后的性能稳定性。 |
|
222 | + |
|
223 | +通过遵循性能测试规范,可以提高性能测试的准确性、可重复性和可靠性。这有助于发现和解决系统的性能问题,提升系统的性能和可扩展性,提供更好的用户体验。 |
|
224 | + |
|
225 | +### 自动化测试 |
|
226 | + |
|
227 | +元模型自动化测试 |
|
228 | +自动化测试工具 |
|
229 | + |
|
230 | +### 回归测试 |
|
231 | +1.【强制】所有代码必须进行单元测试,确保功能的正确性和稳定性。 |
|
232 | +说明:单元测试是保证代码质量的重要手段,通过编写针对各个模块和函数的测试用例,可以验证代码的正确性和稳定性。所有代码的提交前必须通过相应的单元测试。 |
|
233 | + |
|
234 | + |
|
235 | +# 授权管理规范 |
|
236 | + |
|
237 | +### 平台授权 |
|
238 | +1.【强制】dev开发环境不需要授权,但Dev无法使用应用市场。 |
|
239 | + |
|
240 | +2.【强制】在启动iidp平台之前,必须进行授权操作。 |
|
241 | +说明:为了确保iidp平台的安全性和合规性,必须在启动之前进行授权操作。授权可以包括但不限于身份验证、访问权限控制等措施,以确保只有经过授权的用户可以访问和使用iidp平台。具体的授权方式可以根据实际情况进行选择和实施。 |
|
242 | + |
|
243 | +### 多租户 |
|
244 | +1.【强制】多租户授权 |
|
245 | + 说明:每一个租户的创建都必须进行授权,包括租户能够使用的应用和数量,防止大规模使用多租户功能。 |
|
246 | + |
|
247 | +2.【强制】行列权限控制规范 |
|
248 | + 说明:平台基于元模型进行行、列权限控制,在模型处理逻辑一致的情况下,可以通过权限控制实现千人千面效果。 |
|
249 | + |
|
250 | +# 版本管理规范 |
|
251 | + |
|
252 | +### GIT使用规范 |
|
253 | + |
|
254 | +1.【建议】不建议使用rebase。 |
|
255 | + 说明:虽然rebase能够使得提交的commit线很整洁,但这并不是实际的提交记录的真正反应,而且由于rebase会重新生成commit id,可能会导致很多冲突的情况。 |
|
256 | + |
|
257 | +2.【强制】使用版本管理工具Git进行代码版本管理。 |
|
258 | + 说明:Git是一种分布式版本控制系统,可以有效地管理代码的版本和变更历史。通过使用Git,可以轻松地进行代码的协作开发、版本回退、分支管理等操作,提高团队协作效率和代码质量。 |
|
259 | + |
|
260 | +3.【强制】使用合适的分支策略进行代码开发和管理。 |
|
261 | + 说明:合适的分支策略可以有效地组织代码的开发和管理流程,常见的分支策略包括主分支(master/main)、开发分支(develop)、特性分支(feature)、发布分支(release)、修复分支(hotfix)等。根据具体项目和团队的需求,选择合适的分支策略进行代码管理。 |
|
262 | + |
|
263 | +4.【强制】提交代码前进行代码审查。 |
|
264 | + 说明:代码审查是保证代码质量和一致性的重要环节。通过代码审查,可以发现潜在的问题和错误,提高代码的可读性和可维护性。在提交代码之前,应邀请其他团队成员进行代码审查,并根据审查结果进行相应的修改和优化。 |
|
265 | + |
|
266 | +5.【强制】使用有意义的提交消息。 |
|
267 | + 说明:提交消息是对代码变更的简要描述,应该清晰、有意义且符合规范。提交消息应该包含变更的目的、内容和影响等信息,方便其他团队成员理解和追踪代码变更历史。 |
|
268 | + 正例: |
|
269 | + ``` |
|
270 | + feat: 添加用户注册功能 |
|
271 | + |
|
272 | + 添加了用户注册功能,包括表单验证、数据存储和页面跳转等功能。 |
|
273 | + ``` |
|
274 | + 反例: |
|
275 | + ``` |
|
276 | + fix: 修复bug |
|
277 | + |
|
278 | + 修复了一个bug。 |
|
279 | + ``` |
|
280 | + |
|
281 | +6.【强制】遵循代码合并流程,确保代码的一致性和稳定性。 |
|
282 | + 说明:代码合并是将不同分支上的代码合并到一起的过程,应该遵循合适的合并流程,包括解决冲突、测试合并后的代码等步骤,确保合并后的代码一致性和稳定性。 |
|
283 | + |
|
284 | +7.【强制】使用标签管理发布版本。 |
|
285 | + |
|
286 | + 说明:标签是对特定版本的代码进行命名和标记,方便追踪和发布。在每次发布稳定版本时,应该创建相应的标签,并注明版本号和发布日期等信息。 |
|
287 | + |
|
288 | +8.【建议】使用Git Hooks进行代码质量检查。 |
|
289 | + 说明:Git Hooks是Git提供的钩子机制,可以在特定的Git操作触发相应的脚本。通过使用Git Hooks,可以在代码提交、合并等操作前进行代码质量检查,例如代码格式化、静态代码分析等,提高代码的质量和一致性。 |
|
290 | + |
|
291 | +9.【建议】使用Git Flow等工具辅助分支管理。 |
|
292 | + 说明:Git Flow是一种流行的Git分支管理工具,可以简化分支管理流程,提供命令行工具和图形界面工具来支持分支的创建、合并、发布等操作。使用Git Flow等工具可以提高分支管理的效率和可靠性。 |
|
293 | + |
|
294 | +10.【建议】定期进行代码仓库的维护和清理。 |
|
295 | + 说明:定期进行代码仓库的维护和清理可以减少仓库的冗余和混乱,提高代码仓库的性能和可用性。包括删除不再需要的分支、清理过期的标签、整理和优化仓库结构等操作。 |
|
296 | + |
|
297 | +11.【建议】使用Git相关的工具和服务进行代码托管和协作开发。 |
|
298 | + 说明:Git相关的工具和服务提供了丰富的功能和便捷的操作,可以方便地进行代码托管、协作开发、问题追踪等工作。常见的Git工具和服务包括GitHub、GitLab、Bitbucket等,根据团队的需求选择合适的工具和服务进行使用。 |
|
299 | + |
|
300 | +以上是使用Git的一些常见规范,包括分支管理、代码审查、提交消息、合并流程等方面根据具体项目和团队的需求,可以进行相应的调整和补充 |
|
301 | + |
|
302 | +### CICD规范 |
|
303 | +1.【强制】 使用CI/CD工具进行自动化的代码构建、测试和部署。 |
|
304 | +说明:CI/CD(持续集成/持续部署)是一种软件开发实践,通过自动化的流程来构建、测试和部署代码,以提高开发效率和软件质量。选择合适的CI/CD工具(如Jenkins、GitLab CI、Travis CI等)来配置和管理CI/CD流程,确保代码的自动化构建、测试和部署。 |
|
305 | + |
|
306 | +2.【强制】 将CI/CD配置文件纳入版本控制。 |
|
307 | +说明:CI/CD配置文件是定义CI/CD流程的文件,应该将其纳入版本控制,与代码一起进行管理。这样可以确保CI/CD配置与代码版本的一致性,方便团队成员查看和修改CI/CD配置。 |
|
308 | + |
|
309 | +3.【强制】 在CI/CD流程中包含代码编译、单元测试和静态代码分析等步骤。 |
|
310 | +说明:CI/CD流程应该包含代码的编译、单元测试和静态代码分析等步骤,以确保代码的质量和稳定性。在编译阶段,将源代码编译成可执行的程序或库;在单元测试阶段,运行针对代码的单元测试,验证代码的正确性;在静态代码分析阶段,使用工具对代码进行静态分析,检查潜在的问题和代码质量。 |
|
311 | + |
|
312 | +4.【强制】 使用环境变量管理敏感信息。 |
|
313 | +说明:敏感信息(如数据库密码、API密钥等)应该通过环境变量进行管理,而不应直接写入代码或配置文件中。在CI/CD流程中,使用环境变量来获取敏感信息,并确保在不同环境中使用不同的敏感信息。 |
|
314 | + |
|
315 | +5.【强制】 在CI/CD流程中进行集成测试和部署到测试环境。 |
|
316 | +说明:在CI/CD流程的后续阶段,应该进行集成测试和部署到测试环境。集成测试是对不同模块或服务的集成进行测试,验证系统的整体功能和兼容性;部署到测试环境是将代码部署到与生产环境相似的测试环境中,以进行更全面的测试和验证。 |
|
317 | + |
|
318 | +6.【强制】 使用持续集成服务器进行自动化构建和测试。 |
|
319 | +说明:持续集成服务器是用于执行CI/CD流程的服务器,可以自动触发代码构建、测试和部署。通过配置持续集成服务器,可以实现代码的自动化构建和测试,提高开发效率和代码质量。 |
|
320 | + |
|
321 | +7.【强制】 使用容器化技术进行部署。 |
|
322 | +说明:容器化技术(如Docker)可以将应用程序及其依赖项打包成独立的容器,提供了一致的运行环境,方便部署和扩展。在CI/CD流程中,可以使用容器化技术将应用程序打包成镜像,并在部署阶段使用这些镜像进行快速部署和回滚。 |
|
323 | + |
|
324 | +8.【建议】 使用自动化测试工具进行端到端测试。 |
|
325 | +说明:自动化测试工具可以模拟用户的行为,对整个应用程序进行端到端的测试,以验证系统的功能和性能。在CI/CD流程中,可以使用自动化测试工具进行端到端测试,提高测试的覆盖范围和准确性。 |
|
326 | + |
|
327 | +9.【建议】 使用日志和监控工具进行应用程序的监控和故障排查。 |
|
328 | +说明:日志和监控工具可以帮助监控应用程序的运行状态和性能指标,并及时发现和排查故障。在CI/CD流程中,可以配置日志和监控工具,以便在部署后及时获取应用程序的运行情况,并进行故障排查和优化。 |
|
329 | + |
|
330 | +10.【建议】 定期审查和优化CI/CD流程。 |
|
331 | +说明:CI/CD流程是一个持续演进的过程,应该定期审查和优化流程,以适应项目的需求和变化。通过审查和优化CI/CD流程,可以提高开发效率和代码质量,减少部署和发布的风险 |
|
332 | + |
|
333 | +### 发版规范 |
|
334 | + |
|
335 | +### APP版本规范 |
|
336 | + |
|
337 | +1.App的名称,版本,描述,产品线(分类,业务领域,不好约定,业务行为) |
|
338 | +2.行业,领域,分类的方式体现 |
|
339 | + |
|
340 | +# 工程结构规范 |
|
341 | + |
|
342 | +### APP设计规范 |
|
343 | + |
|
344 | +3.【推荐】app的拆分推荐考虑基于商业角度、复用性、职责、性能、部署场景等要素综合考虑后进行拆分,业务的变化是复杂的,要基于具体的业务场景来进行设计,这点非常非常重要,关系到后续的具体业务开发的方向。 |
|
345 | +说明:具体app的拆分规范主要包括以下几个方面: |
|
346 | +1、对app进行分层 |
|
347 | + app层级越低则通用性越强,层级越高则有更多的通用app选择,可以拿来即用或通过扩展使用。随着app层级清晰且越来越多,可以逐步形成app货架: |
|
348 | +(1)L1:平台通用app |
|
349 | + 包括运维、权限、中间件集成、api集成、数据流处理等相关的app |
|
350 | +(2)L2:业务通用app |
|
351 | +包括通知、告警、文件、字典、审批流、打印、主数据等业务通用app |
|
352 | +(3)L3:产品通用app |
|
353 | +包括Iot、smi、qms等产品通用app |
|
354 | +(4)L4:行业通用app |
|
355 | +包括电子套件、pcb、光伏等行业app |
|
356 | +(5)L5:定制app |
|
357 | +包括各类企业定制的app |
|
358 | + |
|
359 | +2、按商业模式划分app |
|
360 | +哪些应用、模块、功能是打算独立销售的,可以将这些功能封装到独立app中。 |
|
361 | + |
|
362 | +3、按耦合程度划分app |
|
363 | +可参考DDD,识别领域模型、聚合根等,将高内聚的模型封装在一个app。 |
|
364 | + |
|
365 | +4、识别并分离不变与变化app |
|
366 | +将不变的或很少变化的模型封装在一个app,将变化频繁的模型、或扩展模型封装在另外的app。 |
|
367 | + |
|
368 | +5、识别高并发场景 |
|
369 | +将对性能要求非常苛刻的模型封装到独立app中。 |
|
370 | + |
|
371 | + |
|
372 | +4.【推荐】app调用关系规范 |
|
373 | +说明:appA方法或服务调用到另外一个appB的方法或服务,这是调用关系: |
|
374 | +(1)调用关系相关方app具备安装顺序无关性; |
|
375 | +(2)调用关系会影响到运行时的方法或服务调用,如指定appB的方法或服务找不到. |
|
376 | + |
|
377 | +5.【推荐】app依赖关系规范 |
|
378 | +说明:appA与另外appB的属性之间具备ER关系,或模型之间具有继承、扩展关系,这是依赖关系,我们应梳理好依赖关系后再进行建模,以免导致循环依赖: |
|
379 | +(1)A继承/扩展了B的模型,必须要先装B,再安装A; |
|
380 | +(2)A与B的模型之间可能存在1:N、N:1、N:N三种ER关系,N:1与N:N支持单边关系, |
|
381 | + |
|
382 | +6.【推荐】单边关系与双边关系使用规范 |
|
383 | +说明:可以独立安装,1:N不支持单边关系需要N方先安装,因此在使用1:N时需要尽 量避免循环依赖。平台支持N:1、N:N的单边关系,特别适用于跨App扩展ER关系的 场景,在保证不改动原App的情况下,扩展ER关系: |
|
384 | +1、双边关系 |
|
385 | +模型双方建立ER关系,可以双向获取对端的数据 |
|
386 | +2、单边关系 |
|
387 | + 模型A建立关联模型B的ER关系,B不需要配置与A的关系,适用于通过A获取B,但是不需要通过B获取A。如:业务模型关联码表,码表不需要关联业务模型。 |
|
388 | + |
|
389 | +7.【推荐】跨模型方法调用规范 |
|
390 | +说明:平台为了保证扩展能力,所有的模型、属性、服务等都是可以动态扩展的,因此 本质上每个元模型都是Map,而不是具体的Class: |
|
391 | +1、不建议在平台中用new模型的方式创建对象 |
|
392 | +2、调用方法也应该采用统一的call方法 |
|
393 | +get set方法建议采用平台提供的模板(基于idea模版) |
|
394 | + |
|
395 | +### pom引入规范 |
|
396 | + |
|
397 | +1.【强制】禁止引入hutool包。 |
|
398 | +2.第三方的包,谨慎引入,防止app打包体积过大 |
|
399 | +3.pom编写规范 |
|
400 | + |
|
401 | + |
|
402 | + |
|
403 | +### 工程结构规范 |
|
404 | + |
|
405 | +代码目录结构,怎么安排,命名,约定大于配置 |
|
406 | +Model、app.json、views、service 必须在同一级目录 |
|
407 | +Views里面的文件内容字段命名和model里面的字段有关系。 |
|
408 | +字符串关联,编译插件来识别并提示。 |
|
409 | +运行阶段,提示字符串相关问题,可以考虑通过静态分析来进行处理。 |
|
410 | +由于引擎是一个jar包,在开发环境中,通过一个引擎jar包来加载正在开发的apps,对于调试非常不友好,建议通过新建一个server项目依赖引擎jar包,以及必要的配置(如端口配置)来启动引擎,加载正在开发的apps进行调试 |
|
411 | + |
|
412 | +### 公共错误码规范 |
|
413 | + |
|
414 | +【推荐】在开发和测试过程中,定义和使用一套统一的公共错误码规范可以提高代码的可读性、可维护性和可测试性。公共错误码规范定义了一系列标准错误码及其对应的错误信息,使得开发人员和测试人员能够快速理解和处理错误情况。 |
|
415 | +具体要求和建议如下: |
|
416 | +(1)错误码命名规范:定义一套统一的错误码命名规范,例如使用大写字母和下划线分隔的形式,例如:INVALID_PARAMETER。 |
|
417 | +(2)错误码分类:根据错误类型或模块进行分类,例如将参数相关的错误码放在一个范围内,将数据库相关的错误码放在另一个范围内。这样可以更好地组织和管理错误码。 |
|
418 | +(3)错误码取值范围:为每个错误码定义一个取值范围,以便后续的扩展和维护。例如,可以为参数相关的错误码分配范围为 1000-1999,数据库相关的错误码分配范围为 2000-2999。 |
|
419 | +(4)错误码文档化:为每个错误码提供清晰的错误信息和解释,以便开发人员和测试人员能够快速理解错误的含义和处理方式。这些信息可以在代码注释、文档或错误码定义文件中进行记录。 |
|
420 | +(5)错误码使用范围:明确错误码的使用范围,例如哪些模块或功能应该使用哪些错误码。这样可以避免错误码的混乱和重复使用。 |
|
421 | +(6)错误码的处理和返回:在代码中正确处理错误码,并根据错误码返回适当的错误信息给用户或调用方。错误信息应该清晰、准确地描述错误的原因,帮助用户或调用方理解和解决问题。 |
|
422 | +通过制定和遵守公共错误码规范,可以提高代码的可维护性和可测试性,减少错误处理的复杂性,并提供更好的用户体验。同时,还能够促进团队之间的协作和沟通,减少因错误处理不一致而引起的问题。 |
|
423 | + |
|
424 | +# 部署规范 |
|
425 | +### 前端部署 |
|
426 | +1.【强制】获取真实IP时,需要在Nginx配置中添加以下配置项: |
|
427 | + |
|
428 | +反例:未添加上述配置项,导致后端服务器无法获取到真实IP地址。 |
|
429 | +根据上述新增规范,应在Nginx的配置文件中添加配置项proxy_set_header X-Real-IP remote_addr 和 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for,以确保能够获取到客户端的真实IP地址。 |
|
430 | + |
|
431 | +### 后端部署 |
|
432 | + |
|
433 | +1.【强制】部署模式,确定是单机版部署还是分布式模式部署。 |
|
434 | +说明:由配置文件中的engine.run.mode=SINGLE 配置项确定。 |
|
435 | +2.【推荐】用linux,不要用windows |
|
436 | +说明:在开发和部署过程中,选择适合的服务器操作系统对于项目的稳定性和性能至关重要。Linux 作为一种开源操作系统,具有广泛的支持和强大的性能优势,尤其在服务器领域表现出色。相比之下,Windows 服务器在某些方面可能不如 Linux 服务器稳定和高效。 |
|
437 | +正例:使用 Linux 服务器可以获得更好的性能和稳定性。它提供了强大的命令行工具和灵活的配置选项,适用于各种服务器应用和开发环境。 |
|
438 | +反例:使用 Windows 服务器可能会面临一些限制和性能瓶颈。Windows 操作系统相对较重,可能需要更多的系统资源,并且在某些情况下可能不够稳定。 |
|
439 | +请注意,选择适合的服务器操作系统应根据具体项目需求和技术栈来决定。在某些特定情况下,使用 Windows 服务器可能是必要的,比如需要与 Microsoft 技术栈紧密集成的项目。然而,总体而言,推荐使用 Linux 服务器可以获得更好的性能和稳定性。 |
|
440 | + |
|
441 | +3.【强制】开发环境和部署环境要保持一致 |
|
442 | +说明:为了确保代码在不同环境中的一致性和可靠性,开发环境和部署环境应该尽可能保持一致。这包括操作系统、软件版本、配置文件等方面的一致性。 |
|
443 | +正例:在开发过程中,使用与目标部署环境相同的操作系统和软件版本进行开发和测试。确保开发团队和部署团队使用相同的工具链和依赖项,以减少环境差异可能带来的问题。 |
|
444 | +反例:在开发过程中使用不同于目标部署环境的操作系统或软件版本,导致在部署时出现兼容性问题或意外的行为差异。 |
|
445 | +通过保持开发环境和部署环境的一致性,可以最大程度地减少因环境差异而引起的问题。这样可以更好地预测和管理应用程序的行为,并提高部署过程的可靠性和效率。同时,也方便开发团队和运维团队之间的协作和沟通,减少因环境差异而导致的沟通和排查问题的成本。 |
|
446 | +需要注意的是,有时候在开发环境和部署环境之间可能存在一些差异,比如数据库的配置、外部服务的依赖等。在这种情况下,应该及时通知和协调相关团队,确保环境差异被妥善处理,并进行必要的测试和验证,以确保代码在部署环境中的正常运行 |
|
447 | + |
|
448 | + |
JUnit5\345\215\225\345\205\203\346\265\213\350\257\225.md
... | ... | @@ -0,0 +1,305 @@ |
1 | +### JUnit |
|
2 | + |
|
3 | +JUnit是一个开源的Java语言的单元测试框架,专门针对Java设计,使用最广泛。JUnit是事实上的单元测试的标准框架,任何Java开发者都应当学习并使用JUnit编写单元测试。 |
|
4 | + |
|
5 | +使用JUnit编写单元测试的好处在于,我们可以非常简单地组织测试代码,并随时运行它们,JUnit就会给出成功的测试和失败的测试,还可以生成测试报告,不仅包含测试的成功率,还可以统计测试的代码覆盖率,即被测试的代码本身有多少经过了测试。对于高质量的代码来说,测试覆盖率应该在80%以上。 |
|
6 | + |
|
7 | +此外,几乎所有的IDE工具都集成了JUnit,这样我们就可以直接在IDE中编写并运行JUnit测试。JUnit目前最新版本是5,也是这里要介绍的JUnit版本。 |
|
8 | +使用JUnit很方便,选中需要单测的方法,点击右键 Generate -> Tests 弹出下图JUnit5来创建测试代码。 |
|
9 | + |
|
10 | + |
|
11 | + |
|
12 | +什么是单元测试呢?单元测试是针对最小的功能单元编写测试代码。Java程序最小的功能单元是方法,因此,对Java程序进行单元测试就是针对单个Java方法的测试。 |
|
13 | + |
|
14 | +单元测试有什么好处呢?在学习单元测试前,我们可以先了解一下测试驱动开发。 |
|
15 | + |
|
16 | +所谓测试驱动开发,是指先编写接口,紧接着编写测试。编写完测试后,我们才开始真正编写实现代码。在编写实现代码的过程中,一边写,一边测,什么时候测试全部通过了,那就表示编写的实现完成了: |
|
17 | + |
|
18 | + 编写接口 |
|
19 | + │ |
|
20 | + ▼ |
|
21 | + 编写测试 |
|
22 | + │ |
|
23 | + ▼ |
|
24 | + ┌─> 编写实现 |
|
25 | + │ │ |
|
26 | + │ N ▼ |
|
27 | + └── 运行测试 |
|
28 | + │ Y |
|
29 | + ▼ |
|
30 | + 任务完成 |
|
31 | + |
|
32 | +这就是我们通常所说的TDD。当然,这是一种理想情况。大部分情况是我们已经编写了实现代码,需要对已有的代码进行测试。 |
|
33 | + |
|
34 | +单元测试可以确保单个方法按照正确预期运行,如果修改了某个方法的代码,只需确保其对应的单元测试通过,即可认为改动正确。此外,测试代码本身就可以作为示例代码,用来演示如何调用该方法。 |
|
35 | + |
|
36 | +使用JUnit进行单元测试,我们可以使用断言(Assertion)来测试期望结果,可以方便地组织和运行测试,并方便地查看测试结果。此外,JUnit既可以直接在IDE中运行,也可以方便地集成到Maven这些自动化工具中运行。 |
|
37 | + |
|
38 | +在编写单元测试的时候,我们要遵循一定的规范: |
|
39 | + |
|
40 | + - 单元测试代码本身必须非常简单,能一下看明白,决不能再为测试代码编写测试,这样套娃的做法; |
|
41 | + - 每个单元测试应当互相独立,不依赖运行的顺序; |
|
42 | + - 测试时不但要覆盖常用测试用例,还要特别注意测试边界条件,例如输入为0,null,空字符串""等情况。 |
|
43 | + |
|
44 | +不要对单元测试存在如下误解: |
|
45 | + |
|
46 | + - 那是测试同学干的事情,实际上单元测试与开发同学强相关的; |
|
47 | + - 单元测试代码是多余的。系统的整体功能与各单元部件的测试正常与否是强相关的; |
|
48 | + - 单元测试代码不需要维护。一年半载后,那么单元测试几乎处于废弃状态; |
|
49 | + - 单元测试与线上故障没有辩证关系。好的单元测试能够最大限度地规避线上故障。 |
|
50 | + |
|
51 | + |
|
52 | +### 编写JUnit测试 |
|
53 | + |
|
54 | +我们编写了一个计算阶乘的类,它只有一个静态方法来计算阶乘: |
|
55 | + |
|
56 | +``` |
|
57 | +n! = 1 × 2 × 3 × ...× n |
|
58 | + |
|
59 | +``` |
|
60 | + |
|
61 | +代码如下: |
|
62 | +```java |
|
63 | +public class Factorial { |
|
64 | + public static long fact(long n) { |
|
65 | + if (n < 0) { |
|
66 | + throw new IllegalArgumentException(); |
|
67 | + } |
|
68 | + long r = 1; |
|
69 | + for (long i = 1; i <= n; i++) { |
|
70 | + r = r * i; |
|
71 | + } |
|
72 | + return r; |
|
73 | + } |
|
74 | +} |
|
75 | +``` |
|
76 | +然后按照上述的方式点击右键,生成对应的测试类 FactorialTest.java : |
|
77 | +```java |
|
78 | +public class FactorialTest { |
|
79 | + |
|
80 | + @Test |
|
81 | + void testFact() { |
|
82 | + assertEquals(1, Factorial.fact(1)); |
|
83 | + assertEquals(2, Factorial.fact(2)); |
|
84 | + assertEquals(6, Factorial.fact(3)); |
|
85 | + assertEquals(3628800, Factorial.fact(10)); |
|
86 | + assertEquals(2432902008176640000L, Factorial.fact(20)); |
|
87 | + } |
|
88 | + |
|
89 | + @Disabled |
|
90 | + @Test |
|
91 | + void testNegative() { |
|
92 | + assertThrows(IllegalArgumentException.class, () -> { |
|
93 | + Factorial.fact(-1); |
|
94 | + }); |
|
95 | + } |
|
96 | +} |
|
97 | + |
|
98 | +``` |
|
99 | +执行这个单元测试,在testFact中,我们给出了入参和期待的值,并断言是否成功。 |
|
100 | +除了常规的测试用例以外,可以发现上面的例子对异常情况也有测试,testNegative. |
|
101 | +在Java程序中,异常处理是非常重要的。我们自己编写的方法,也经常抛出各种异常。对于可能抛出的异常进行测试,本身就是测试的重要环节。因此,在编写JUnit测试的时候,除了正常的输入输出,我们还要特别针对可能导致异常的情况进行测试。 |
|
102 | + |
|
103 | + |
|
104 | + |
|
105 | + |
|
106 | +我个人对于程序的理解除了完成正常的逻辑外,绝大部分时间都是在处理异常,逻辑的复杂性绝大部分都是异常带来的, |
|
107 | +正常的逻辑可以梳理清楚,但是异常太多太多了,程序在执行过程中有可能随时产生异常,比如io异常、参数错误、超时等等,各种情况下都有可能产生异常。 |
|
108 | +我个人觉得异常应该作为一种程序结构集成在我们的代码中,因为这是客观存在的,我不太喜欢那种大统一的无差别try然后catch打印日志。 |
|
109 | +我更倾向于在执行逻辑过程中随时随地的判断异常,当然带来的坏处是编写过程很繁琐,但我个人觉得无非是多打几个字而已,换来的是心里的踏实和程序的健壮。 |
|
110 | + |
|
111 | + |
|
112 | +回到异常情况下的单元测试,JUnit提供了assertThrows()来期望捕获一个指定的异常。第二个参数Executable封装了我们要执行的会产生异常的代码。当我们执行Factorial.fact(-1)时,必定抛出IllegalArgumentException。assertThrows()在捕获到指定异常时表示通过测试,未捕获到异常,或者捕获到的异常类型不对,均表示测试失败。 |
|
113 | + |
|
114 | +经过思考,观察Factorial.fact()方法,注意到由于long型整数有范围限制,当我们传入参数21时,得到的结果是-4249290049419214848,而不是期望的51090942171709440000,因此,当传入参数大于20时,Factorial.fact()方法应当抛出ArithmeticException。而且已有的单元测试用例已经覆盖了所有的代码行和分支。 |
|
115 | +这只是一个简单的求阶乘的算法,就有很多异常情况,更何况复杂的算法和业务逻辑,所以说写一个健壮的程序是多么不容易啊,但我们还是需要向这个目标前进,不断地完善我们单元测试用例, |
|
116 | +至少能保证做到,凡是通过我们编写的单元测试,至少保证目前为止我们认为的健壮性,如果后续发现有问题,就继续补充单元测试,完善功能。 |
|
117 | + |
|
118 | +### jacoco 单元覆盖率测试 |
|
119 | + |
|
120 | +JUnit5是Java编程语言的单元测试框架。覆盖率是指代码被测试覆盖的程度,即代码中被测试覆盖的语句、分支、条件等的比例。JUnit5可以通过集成代码覆盖率工具来测量代码覆盖率。 |
|
121 | + |
|
122 | +如果已经编写了完善的单元测试用例,那么使用jacoco进行代码覆盖率统计就很容易了。 |
|
123 | + |
|
124 | +在pom.xml中加入jacoco插件 |
|
125 | +```xml |
|
126 | +<plugin> |
|
127 | + <groupId>org.jacoco</groupId> |
|
128 | + <artifactId>jacoco-maven-plugin</artifactId> |
|
129 | + <version>0.8.6</version> |
|
130 | + <executions> |
|
131 | + <execution> |
|
132 | + <id>prepare-agent</id> |
|
133 | + <goals> |
|
134 | + <goal>prepare-agent</goal> |
|
135 | + </goals> |
|
136 | + </execution> |
|
137 | + <execution> |
|
138 | + <id>report</id> |
|
139 | + <phase>test</phase> |
|
140 | + <goals> |
|
141 | + <goal>report</goal> |
|
142 | + </goals> |
|
143 | + |
|
144 | + <configuration> |
|
145 | + <!--定义输出的文件夹--> |
|
146 | + <outputDirectory>target/jacoco-report</outputDirectory> |
|
147 | + <!--执行数据的文件--> |
|
148 | + <dataFile>${project.build.directory}/jacoco.exec</dataFile> |
|
149 | + <!--要从报告中排除的类文件列表,支持通配符(*和?)。如果未指定则不会排除任何内容--> |
|
150 | +<!-- <excludes>**/test/*.class</excludes>--> |
|
151 | +<!-- https://www.baeldung.com/jacoco-report-exclude--> |
|
152 | + <excludes> |
|
153 | + <exclude>com/example/demo/*</exclude> |
|
154 | + </excludes> |
|
155 | + <!--包含生成报告的文件列表,支持通配符(*和?)。如果未指定则包含所有内容--> |
|
156 | + <includes> |
|
157 | + <include>com/example/demo2/*</include> |
|
158 | + </includes> |
|
159 | + <!--HTML 报告页面中使用的页脚文本。--> |
|
160 | + <footer></footer> |
|
161 | + <!--生成报告的文件类型,HTML(默认)、XML、CSV--> |
|
162 | +<!-- <formats>HTML</formats>--> |
|
163 | + <!--生成报告的编码格式,默认UTF-8--> |
|
164 | + <outputEncoding>UTF-8</outputEncoding> |
|
165 | + <!--抑制执行的标签--> |
|
166 | + <skip></skip> |
|
167 | + <!--源文件编码--> |
|
168 | + <sourceEncoding>UTF-8</sourceEncoding> |
|
169 | + <!--HTML报告的标题--> |
|
170 | + <title>${project.name}</title> |
|
171 | + </configuration> |
|
172 | + |
|
173 | + </execution> |
|
174 | + </executions> |
|
175 | + </plugin> |
|
176 | +``` |
|
177 | +在执行 mvn test 后在target/jacoco-report目录中自动生成 index.html 等页面相关文件。 |
|
178 | + |
|
179 | + |
|
180 | + |
|
181 | +点击 index.html 在浏览器中展示,即可看到具体的单测结果的覆盖率。 |
|
182 | + |
|
183 | + |
|
184 | + |
|
185 | +由上图可知,指令覆盖率是85%,分支覆盖率是75% |
|
186 | +继续查看具体的覆盖情况 |
|
187 | + |
|
188 | + |
|
189 | + |
|
190 | +标红的代码行是单元测试没有覆盖的,标绿说明单元测试已经覆盖。 |
|
191 | + |
|
192 | +我们继续增加单元测试 |
|
193 | +```java |
|
194 | + @Test |
|
195 | + void testNegative() { |
|
196 | + assertThrows(IllegalArgumentException.class, () -> { |
|
197 | + Factorial.fact(-1); |
|
198 | + }); |
|
199 | + } |
|
200 | +``` |
|
201 | +重新生成测试覆盖统计: |
|
202 | + |
|
203 | + |
|
204 | + |
|
205 | +可见已经达到了100%覆盖。 |
|
206 | + |
|
207 | + |
|
208 | +### 引擎例子 |
|
209 | +- 启动引擎 |
|
210 | +```java |
|
211 | +public class SieEngineTestExtension implements BeforeEachCallback, Extension { |
|
212 | + |
|
213 | + private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create("com.sie.engine"); |
|
214 | + |
|
215 | + @Override |
|
216 | + public void beforeEach(ExtensionContext context) throws Exception { |
|
217 | + startTestEngine(context); |
|
218 | + } |
|
219 | + |
|
220 | + public static void startTestEngine(ExtensionContext context) { |
|
221 | + // 启动容器的逻辑 |
|
222 | + TestEngineStart.start(); |
|
223 | + storeTestEngineStarted(context); |
|
224 | + } |
|
225 | + |
|
226 | + public static void storeTestEngineStarted(ExtensionContext context) { |
|
227 | + context.getStore(NAMESPACE).put("testEngineStarted", true); |
|
228 | + } |
|
229 | + |
|
230 | + public static boolean isTestEngineStarted(ExtensionContext context) { |
|
231 | + return context.getStore(NAMESPACE).get("testEngineStarted", Boolean.class); |
|
232 | + } |
|
233 | +} |
|
234 | +``` |
|
235 | +自定义一个 SieEngineTestExtension 类,该类会启动引擎,初始化测试环境。 |
|
236 | + |
|
237 | +- 使用 |
|
238 | +```java |
|
239 | +@ExtendWith(SieEngineTestExtension.class) // 执行测试前先启动引擎 |
|
240 | +class RecordSetTest { |
|
241 | + @Test |
|
242 | + void call() { |
|
243 | + Meta meta = BaseContextHandler.getMeta(); |
|
244 | + System.out.println("userId = " + meta.getUserId()); |
|
245 | + System.out.println("tenantId = " + meta.getTenantId()); |
|
246 | + assertNotNull(meta); |
|
247 | + assertNotNull(meta.get("rbac_user")); |
|
248 | + Object object = meta.get("rbac_user").search(new Filter(), null, null, null, null); |
|
249 | + System.out.println(object); |
|
250 | + |
|
251 | + System.out.println("===========测试完成==========="); |
|
252 | + } |
|
253 | +} |
|
254 | +``` |
|
255 | + |
|
256 | +使用 @ExtendWith(SieEngineTestExtension.class) 注解来标识该测试类启动前会先启动引擎,那么就可以在引擎中进行后续的测试。 |
|
257 | +还是在上面的代码示例,就可以测试meta相应的方法,比如 meta.get("rbac_user").search(new Filter(), null, null, null, null); |
|
258 | + |
|
259 | +执行mvn test 生成jacoco覆盖率图,比如下图: |
|
260 | + |
|
261 | + |
|
262 | + |
|
263 | +### 单元测试规范 |
|
264 | + |
|
265 | +1. 【强制】好的单元测试必须遵守AIR原则。 |
|
266 | + <br><span style="color:orange">说明</span>:单元测试在线上运行时,感觉像空气(AIR)一样并不存在,但在测试质量的保障上,却是非常关键的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。 |
|
267 | + - A:Automatic(自动化) |
|
268 | + - I:Independent(独立性) |
|
269 | + - R:Repeatable(可重复) |
|
270 | +2. 【强制】单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用System.out来进行人肉验证,必须使用assert来验证。 |
|
271 | +3. 【强制】保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。 <br><span style="color:red">反例</span>:method2需要依赖method1的执行,将执行结果作为method2的输入。 |
|
272 | +4. 【强制】单元测试是可以重复执行的,不能受到外界环境的影响。 |
|
273 | + <br><span style="color:orange">说明</span>:单元测试通常会被放到持续集成中,每次有代码check in时单元测试都会被执行。如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。 <br><span style="color:green">正例</span>:为了不受外界环境影响,要求设计代码时就把SUT的依赖改成注入,在测试时用spring 这样的DI框架注入一个本地(内存)实现或者Mock实现。 |
|
274 | +5. 【强制】对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别。 |
|
275 | + <br><span style="color:orange">说明</span>:只有测试粒度小才能在出错时尽快定位到出错位置。单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域。 |
|
276 | +6. 【强制】核心业务、核心应用、核心模块的增量代码确保单元测试通过。 |
|
277 | + <br><span style="color:orange">说明</span>:新增代码及时补充单元测试,如果新增代码影响了原有单元测试,请及时修正。 |
|
278 | +7. 【强制】单元测试代码必须写在如下工程目录:src/test/java,不允许写在业务代码目录下。 |
|
279 | + <br><span style="color:orange">说明</span>:源码构建时会跳过此目录,而单元测试框架默认是扫描此目录。 |
|
280 | +8. 【推荐】单元测试的基本目标:语句覆盖率达到70%;核心模块的语句覆盖率和分支覆盖率都要达到100% |
|
281 | + <br><span style="color:orange">说明</span>:在工程规约的应用分层中提到的DAO层,Manager层,可重用度高的Service,都应该进行单元测试。 |
|
282 | +9. 【推荐】编写单元测试代码遵守BCDE原则,以保证被测试模块的交付质量。 |
|
283 | + - B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。 |
|
284 | + - C:Correct,正确的输入,并得到预期的结果。 |
|
285 | + - D:Design,与设计文档相结合,来编写单元测试。 |
|
286 | + - E:Error,强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),并得到预期的结果。 |
|
287 | +10. 【推荐】对于数据库相关的查询,更新,删除等操作,不能假设数据库里的数据是存在的,或者直接操作数据库把数据插入进去,请使用程序插入或者导入数据的方式来准备数据。 <br><span style="color:red">反例</span>:删除某一行数据的单元测试,在数据库中,先直接手动增加一行作为删除目标,但是这一行新增数据并不符合业务插入规则,导致测试结果异常。 |
|
288 | +11. 【推荐】和数据库相关的单元测试,可以设定自动回滚机制,不给数据库造成脏数据。或者对单元测试产生的数据有明确的前后缀标识。 <br><span style="color:green">正例</span>:在RDC内部单元测试中,使用RDC_UNIT_TEST_的前缀标识数据。 |
|
289 | +12. 【推荐】对于不可测的代码建议做必要的重构,使代码变得可测,避免为了达到测试要求而书写不规范测试代码。 |
|
290 | +13. 【推荐】在设计评审阶段,开发人员需要和测试人员一起确定单元测试范围,单元测试最好覆盖所有测试用例(UC)。 |
|
291 | +14. 【推荐】单元测试作为一种质量保障手段,不建议项目发布后补充单元测试用例,建议在项目提测前完成单元测试。 |
|
292 | +15. 【参考】为了更方便地进行单元测试,业务代码应避免以下情况: |
|
293 | + - 构造方法中做的事情过多。 |
|
294 | + - 存在过多的全局变量和静态方法。 |
|
295 | + - 存在过多的外部依赖。 |
|
296 | + - 存在过多的条件语句。 |
|
297 | + <br><span style="color:orange">说明</span>:多层条件语句建议使用卫语句、策略模式、状态模式等方式重构。 |
|
298 | +16. 【参考】不要对单元测试存在如下误解: |
|
299 | + - 那是测试同学干的事情。本文是开发手册,凡是本文内容都是与开发同学强相关的。 |
|
300 | + - 单元测试代码是多余的。汽车的整体功能与各单元部件的测试正常与否是强相关的。 |
|
301 | + - 单元测试代码不需要维护。一年半载后,那么单元测试几乎处于废弃状态。 |
|
302 | + - 单元测试与线上故障没有辩证关系。好的单元测试能够最大限度地规避线上故障。 |
|
303 | + |
|
304 | +### cicd |
|
305 | + |
New Page.md
ORM\345\242\236\345\274\272\350\257\255\346\263\225\345\212\237\350\203\275\350\256\276\350\256\241.md
... | ... | @@ -0,0 +1,718 @@ |
1 | + |
|
2 | +# ORM增强,增删改查语法糖 |
|
3 | + |
|
4 | +注意:调用业务实现的方法不能用this.xxx,否则扩展模型无法正确的调用方法。 |
|
5 | + |
|
6 | +规范:模型里使用this去操作,业务类才使用ModelUtils或DbUtils |
|
7 | + |
|
8 | +解决痛点: |
|
9 | +1. 优化完善数据操作方法、重载提高效率 |
|
10 | +2. 完善常用方法强类型输入 |
|
11 | +3. super差异与影响扩展问题 |
|
12 | +4. 统一规范写法。 |
|
13 | +5. 详细操作的文档 |
|
14 | + |
|
15 | +## 获取模型 |
|
16 | +返回RecordSet |
|
17 | +- ModelUtils.getModel(clazz): 根据模型类获取模型 |
|
18 | +- ModelUtils.getModel(modelName):根据模型名称获取模型 |
|
19 | + |
|
20 | +- this.getModel():获取当前模型 |
|
21 | +- this.getModel(modelName): 指定名字获取模型 |
|
22 | +- this.getModel(clazz): |
|
23 | + |
|
24 | +## 新增数据 |
|
25 | +- this.create(): 插入一条数据,相当于call("create") |
|
26 | +- this.create(data): 插入一条数据 |
|
27 | +- this.create(dataLis): 插入多条数据 |
|
28 | + |
|
29 | +- this.superCreate(): 执行父模型插入一条数据,相当于callSuper("create") |
|
30 | +- this.superCreate(data): 执行父模型插入一条数据,相当于callSuper("create") |
|
31 | +- this.superCreate(dataLis): 执行父模型插入多条数据,相当于callSuper("create") |
|
32 | + |
|
33 | +- ModelUtils.getModel(modelName).create(data): 获取模型并根据数据集合插入 |
|
34 | +- ModelUtils.getModel(modelName).create(dataLis): 获取模型并根据数据集合插入 |
|
35 | +- ModelUtils.getModel(clazz).create(dataLis): 获取模型并根据数据集合插入 |
|
36 | + |
|
37 | +- ModelUtils.getModel(clazz).superCreate(): 获取模型的父模型插入一条数据,相当于callSuper("create") |
|
38 | +- |
|
39 | +## 删除数据 |
|
40 | +- this.delete(): 删除当前模型 |
|
41 | +- this.delete(id): 根据id删除 |
|
42 | +- this.delete(ids): 根据id集合删除 |
|
43 | +- this.delete(recordSet): 根据数据集合删除 |
|
44 | +- this.delete(filter): 根据条件过滤删除 |
|
45 | + |
|
46 | +- this.superDelete(): 执行父模型删除当前模型,相当于callSuper("delete") |
|
47 | +- this.superDelete(id): 执行父模型,根据id删除,相当于callSuper("delete") |
|
48 | +- this.superDelete(ids): 执行父模型,根据ids删除,相当于callSuper("delete") |
|
49 | +- this.superDelete(recordSet): 执行父模型,根据数据集合删除,相当于callSuper("delete") |
|
50 | +- this.superDelete(filter): 执行父模型,根据条件过滤删除,相当于callSuper("delete") |
|
51 | + |
|
52 | +- ModelUtils.getModel(modelName).delete(id): 获取模型并根据id删除 |
|
53 | +- ModelUtils.getModel(clazz).delete(ids): 获取模型并根据ids删除 |
|
54 | +- ModelUtils.getModel(clazz).delete(filter): 获取模型并根据filter条件删除 |
|
55 | + |
|
56 | +- ModelUtils.getModel(clazz).superDelete(filter): 获取模型的父模型根据filter条件删除 |
|
57 | + |
|
58 | +- DbUtils.delete(id,clazz): 并根据id删除 |
|
59 | +- DbUtils.delete(modelName,id): 并根据id删除 |
|
60 | +- DbUtils.delete(modelName,ids): 并根据id删除 |
|
61 | +- DbUtils.delete(modelName,recordSet): 并根据id删除 |
|
62 | +- DbUtils.delete(modelName,filter): 并根据filter条件删除 |
|
63 | + |
|
64 | +- DbUtils.superDelete(modelName,filter): 模型的父模型根据filter条件删除 |
|
65 | + |
|
66 | +## 修改数据 |
|
67 | +- this.update(data): 根据当前模型更新指定列数据 |
|
68 | +- this.update(id,data): 根据id更新指定数据 |
|
69 | +- this.update(id,data,ignoreNulls): 根据id更新指定数据,是否忽略值为null的字段 |
|
70 | +- this.update(ids,data): 根据id集合更新指定数据 |
|
71 | +- this.update(ids,data,ignoreNulls): 根据id集合更新指定数据,是否忽略值为null的字段 |
|
72 | +- this.update(filter):根据过滤条件更新当前模型数据 |
|
73 | +- this.update(filter,data):根据过滤条件更新数据 |
|
74 | +- this.update(filter,data,,ignoreNulls):根据过滤条件更新数据,是否忽略值为null的字段 |
|
75 | + |
|
76 | +- this.superUpdate(data): 执行父模型,根据当前模型更新指定列数据 |
|
77 | +- this.superUpdate(id,data): 执行父模型,根据id更新指定数据 |
|
78 | +- this.superUpdate(id,data,ignoreNulls): 执行父模型,根据id更新指定数据,是否忽略值为null的字段 |
|
79 | +- this.superUpdate(ids,data): |
|
80 | +- this.superUpdate(ids,data,ignoreNulls): |
|
81 | +- this.superUpdate(filter): |
|
82 | +- this.superUpdate(filter,data): |
|
83 | +- this.superUpdate(filter,data,ignoreNulls): |
|
84 | + |
|
85 | +- ModelUtils.getModel(modelName).update(data): 获取模型并根据id更新数据 |
|
86 | +- ModelUtils.getModel(modelName).update(filter,data): 获取模型并根据id更新数据 |
|
87 | + |
|
88 | +- ModelUtils.getModel(modelName).superUpdate(filter,data): |
|
89 | + |
|
90 | +## 查询数据 |
|
91 | +- this.searchOneById(id):根据主键查询数据。 |
|
92 | +- this.searchOneById(id,properties):根据主键查询数据,并指定查询字段。 |
|
93 | +- this.searchOneByFilter(filter):根据条件列表查询数据 |
|
94 | +- this.searchOneByFilter(filter,properties):根据条件列表查询数据,并指定查询字段。 |
|
95 | +- this.searchListByIds(ids):根据主键列表查询数据。 |
|
96 | +- this.searchListByIds(ids,properties):根据主键列表查询数据,并指定查询字段。 |
|
97 | +- this.searchListByIds(ids):根据主键列表查询数据。 |
|
98 | + |
|
99 | +- this.searchAll(): 查询所有数据 |
|
100 | +- this.searchAll(properties): 查询所有数据,并指定查询字段 |
|
101 | +- this.search(filter): 根据过滤条件查询数据 |
|
102 | +- this.search(filter,properties): 根据过滤条件查询数据,并指定查询字段 |
|
103 | +- this.search(filter,limit,offset,order): 根据过滤条件查询数据,并分页 |
|
104 | +- this.search(filter,properties,limit,offset): 根据过滤条件查询数据,并分页 |
|
105 | +- this.search(filter,properties,limit,offset,order): 根据过滤条件查询数据,并分页 |
|
106 | + |
|
107 | +- this.superSearchOneById(id): |
|
108 | +- this.superSearchOneById(id,properties): |
|
109 | +- this.superSearchOneByFilter(filter):根据条件列表查询数据 |
|
110 | +- this.superSearchOneByFilter(filter,properties):根据条件列表查询数据,并指定查询字段。 |
|
111 | +- this.superSearchListByIds(ids): |
|
112 | +- this.superSearchListByIds(ids,properties): |
|
113 | +- this.superSearchAll(): |
|
114 | +- this.superSearchAll(properties): |
|
115 | +- this.superSearch(filter): |
|
116 | +- this.superSearch(filter,properties): |
|
117 | +- this.superSearch(filter,limit,offset,order): |
|
118 | +- this.superSearch(filter,properties,limit,offset): |
|
119 | +- this.superSearch(filter,properties,limit,offset,order): |
|
120 | + |
|
121 | +- ModelUtils.getModel(modelName).searchOneById(id):指定模型根据主键查询数据。 |
|
122 | +- ModelUtils.getModel(modelName).superSearchOneById(id): |
|
123 | + |
|
124 | +说明:返回Optional<> |
|
125 | + |
|
126 | +## 查找数据 |
|
127 | +- this.findOneById(id):根据主键查询数据。 |
|
128 | +- this.findByIds(ids):根据主键集查询数据。 |
|
129 | +- this.find(filter):根据过滤条件查询数据。 |
|
130 | + |
|
131 | +- this.superFindOneById(id) |
|
132 | +- this.superFindByIds(id) |
|
133 | +- this.superFind(filter) |
|
134 | + |
|
135 | +- ModelUtils.getModel(modelName).findOneById(id): |
|
136 | + |
|
137 | +## 查询数据数量 |
|
138 | +- this.count(): 查询数据数量 |
|
139 | +- this.count(filter): 根据过滤条件查询数据 |
|
140 | +- this.exist(filter): 要据条件判断数据是否存在 |
|
141 | +- this.exist(id): 要据条件判断数据是否存在 |
|
142 | +- this.exist(ids): 要据条件判断数据是否存在 |
|
143 | + |
|
144 | +- this.superCount(): |
|
145 | +- this.superCount(filter): |
|
146 | +- this.superExist(filter): |
|
147 | + |
|
148 | + |
|
149 | + |
|
150 | +# 链式操作 |
|
151 | + |
|
152 | +### 查询示例 |
|
153 | + |
|
154 | +> 创建一条数据 |
|
155 | + |
|
156 | +```java |
|
157 | +DbChain.app("aps").model("Account") |
|
158 | +.setId(RowKey.AUTO) |
|
159 | +.set("user_name", "zhang san") |
|
160 | +.set("age", 18) |
|
161 | +.set("birthday", new Date()) |
|
162 | +.create(); |
|
163 | +``` |
|
164 | + |
|
165 | + |
|
166 | +> 更新数据 |
|
167 | + |
|
168 | +```java |
|
169 | +DbChain.app("aps").model("Account") |
|
170 | +.setId(RowKey.AUTO) |
|
171 | +.set("user_name", "zhang san") |
|
172 | +.set("age", 18) |
|
173 | +.set("birthday", new Date()) |
|
174 | +.update(); |
|
175 | +``` |
|
176 | + |
|
177 | +> 查询一条数据 |
|
178 | + |
|
179 | +```java |
|
180 | +DbChain.app("aps").model("Account") |
|
181 | +.select(ARTICLE.ALL_COLUMNS) |
|
182 | +.where(ARTICLE.ID.ge(100)) |
|
183 | +.limit(1) |
|
184 | +.list(); |
|
185 | + |
|
186 | +DbChain.app("aps").model("Account") |
|
187 | +.select(ARTICLE.ALL_COLUMNS) |
|
188 | +.where(ARTICLE.ID.ge(100)) |
|
189 | +.one(); |
|
190 | +``` |
|
191 | + |
|
192 | +> 查询多条数据 |
|
193 | + |
|
194 | +```java |
|
195 | +DbChain.app("aps").model("Account") |
|
196 | +.select(properties) |
|
197 | +.where(ARTICLE.ID.ge(100)) |
|
198 | +.or(ACCOUNT.USER_NAME.like("michael")) |
|
199 | +.list(); |
|
200 | +``` |
|
201 | + |
|
202 | +> 常用扩展方法 |
|
203 | + |
|
204 | +- one():获取一条数据 |
|
205 | +- list():获取多条数据 |
|
206 | +- page():分页查询 |
|
207 | +- obj():当 SQL 查询只返回 1 列数据的时候,且只有 1 条数据时,可以使用此方法 |
|
208 | +- objList():当 SQL 查询只返回 1 列数据的时候,可以使用此方法 |
|
209 | +- count():查询数据条数 |
|
210 | +- exists():是否存在,判断 count 是否大于 0 |
|
211 | + |
|
212 | +## group by |
|
213 | + |
|
214 | +```java |
|
215 | +DbChain.app("aps").model("Account") |
|
216 | +.select(properties) |
|
217 | +.groupBy("name"); |
|
218 | +``` |
|
219 | +## having |
|
220 | +```java |
|
221 | +DbChain.app("aps").model("Account") |
|
222 | +.select(properties) |
|
223 | +.having(ACCOUNT.AGE.between(18,25)); |
|
224 | +``` |
|
225 | + |
|
226 | +## orderBy |
|
227 | + |
|
228 | +```java |
|
229 | +DbChain.app("aps").model("Account") |
|
230 | +.select(properties) |
|
231 | +.orderBy("age","asc", "name","desc"); |
|
232 | +``` |
|
233 | + |
|
234 | + |
|
235 | + |
|
236 | +## SQL 函数支持 |
|
237 | + |
|
238 | + |
|
239 | + |
|
240 | +```java |
|
241 | +DbChain.app("aps").model("Account") |
|
242 | +.select(properties) |
|
243 | +.where(ARTICLE.ID.ge(100)) |
|
244 | +.or(ACCOUNT.USER_NAME.like("michael")) |
|
245 | +.list(); |
|
246 | +``` |
|
247 | + |
|
248 | +# 批量操作 |
|
249 | + |
|
250 | +提供了许多批量操作(批量插入、批量更新等)的方法,当有多个方法的时候,会经常导致误用的情况。 |
|
251 | + |
|
252 | +### createBatch 方法 |
|
253 | + |
|
254 | +```java |
|
255 | +List<Account> accounts = .... |
|
256 | +this.createBatch(accounts); |
|
257 | +``` |
|
258 | + |
|
259 | +组装一个如下的 SQL: |
|
260 | + |
|
261 | +```sql |
|
262 | +insert into tb_account(id,nickname, .....) values |
|
263 | + (100,"miachel100", ....), |
|
264 | + (101,"miachel101", ....), |
|
265 | + (102,"miachel102", ....), |
|
266 | + (103,"miachel103", ....), |
|
267 | + (104,"miachel104", ....), |
|
268 | + (105,"miachel105", ....); |
|
269 | +``` |
|
270 | + |
|
271 | +# 条件过滤 |
|
272 | + |
|
273 | +### where |
|
274 | + |
|
275 | +```java |
|
276 | +Filter filter=Filter.create() |
|
277 | + .and(ACCOUNT.ID.ge(100)) |
|
278 | + .or(ACCOUNT.USER_NAME.like("michael")) |
|
279 | +``` |
|
280 | + |
|
281 | +```sql |
|
282 | +WHERE id >= ? |
|
283 | +AND user_name LIKE ? |
|
284 | +``` |
|
285 | + |
|
286 | +### and (...) or (...) |
|
287 | + |
|
288 | + |
|
289 | +```java |
|
290 | +Filter filter=Filter.create() |
|
291 | + .where(ACCOUNT.ID.ge(100)) |
|
292 | + .and(ACCOUNT.SEX.eq(1).or(ACCOUNT.SEX.eq(2))) |
|
293 | + .or(ACCOUNT.AGE.in(18,19,20).and(ACCOUNT.USER_NAME.like("michael"))); |
|
294 | +``` |
|
295 | +其查询生成的 Sql 如下: |
|
296 | +```sql |
|
297 | +WHERE id >= ? |
|
298 | +AND (sex = ? OR sex = ? ) |
|
299 | +OR (age IN (?,?,?) AND user_name LIKE ? ) |
|
300 | +``` |
|
301 | + |
|
302 | +### 动态条件 |
|
303 | + |
|
304 | +```java |
|
305 | +Filter filter=Filter.create() |
|
306 | + .where(ACCOUNT.ID.ge(100).when(flag)) //flag为false,忽略该条件 |
|
307 | + .and(ACCOUNT.USER_NAME.like("michael")); |
|
308 | +``` |
|
309 | + |
|
310 | +使用重载方法 |
|
311 | + |
|
312 | +```java |
|
313 | +Filter filter=Filter.create() |
|
314 | +.and(ACCOUNT.USER_NAME.like("michael", flag)); //flag为false,忽略该条件 |
|
315 | +``` |
|
316 | + |
|
317 | +```java |
|
318 | +Filter filter=Filter.create() |
|
319 | + .where(ACCOUNT.USER_NAME.like(name, StringUtil::isNotBlank)); //name为空字符串,忽略该条件 |
|
320 | +``` |
|
321 | + |
|
322 | + |
|
323 | +# 模型事件 |
|
324 | + |
|
325 | +> 模型事件是指在进行模型的查询和写入操作的时候触发的操作行为。 |
|
326 | + |
|
327 | +模型支持如下事件: |
|
328 | + |
|
329 | +| 事件 | 描述 | 事件方法名| |
|
330 | +|--------------| -------- | ------- | |
|
331 | +| afterRead | 查询后 | onAfterRead | |
|
332 | +| beforeInsert | 新增前 | onBeforeInsert | |
|
333 | +| afterInsert | 新增后 | onAfterInsert | |
|
334 | +| beforeUpdate | 更新前 | onBeforeUpdate | |
|
335 | +| afterUpdate | 更新后 | onAfterUpdate | |
|
336 | +| beforeDelete | 删除前 | onBeforeDelete | |
|
337 | +| afterDelete | 删除后 | onAfterDelete | |
|
338 | +| beforeWrite | 写入前 | onBeforeWrite | |
|
339 | +| afterWrite | 写入后 | onAfterWrite | |
|
340 | + |
|
341 | + |
|
342 | +### 写入事件 |
|
343 | +onBeforeWrite和onAfterWrite事件会在新增操作和更新操作都会触发. |
|
344 | +> 注意:模型的新增或更新是自动判断的. |
|
345 | + |
|
346 | +具体的触发顺序: |
|
347 | +```java |
|
348 | +// 执行 onBeforeWrite |
|
349 | +// 如果事件没有返回`false`,那么继续执行 |
|
350 | +// 执行新增或更新操作(onBeforeInsert/onAfterInsert或onBeforeUpdate/onAfterUpdate) |
|
351 | +// 新增或更新执行成功 |
|
352 | +// 执行 onAfterWrite |
|
353 | +``` |
|
354 | + |
|
355 | + |
|
356 | +### 模型事件定义 |
|
357 | +> 最简单的方式是在模型类里面定义静态方法来定义模型的相关事件响应。 |
|
358 | +```java |
|
359 | +class User extends BaseModel<User> |
|
360 | +{ |
|
361 | + public boolean onBeforeUpdate(RecordSet rs, Map<String, Object> value) |
|
362 | + { |
|
363 | + return false; |
|
364 | + } |
|
365 | + |
|
366 | + public boolean onAfterDelete(RecordSet rs) |
|
367 | + { |
|
368 | + return false; |
|
369 | + } |
|
370 | +} |
|
371 | +``` |
|
372 | +参考资料:https://doc.thinkphp.cn/v8_0/model_event.html |
|
373 | + |
|
374 | +# 模型监听器 |
|
375 | + |
|
376 | +## onInsert |
|
377 | + |
|
378 | +用于监听 Entity 实体类数据被新增到数据库,我们可以在实体类被新增时做一些前置操作。比如: |
|
379 | + |
|
380 | + - 数据填充。 |
|
381 | + - 数据修改。 |
|
382 | + |
|
383 | +示例代码如下: |
|
384 | +```java |
|
385 | +//配置 onInsert = MyInsertListener.class |
|
386 | +@Model(tableName = "tb_account", onInsert = MyInsertListener.class) |
|
387 | +public class Account extends BaseModel<Account>{ |
|
388 | + |
|
389 | +} |
|
390 | +``` |
|
391 | + |
|
392 | +```java |
|
393 | +public class MyInsertListener implements InsertListener { |
|
394 | + |
|
395 | + @Override |
|
396 | + public void onInsert(RecordSet entity,List<Map<String, Object>> valuesList) { |
|
397 | + Account account = (Account)entity; |
|
398 | + |
|
399 | + //设置 account 被新增时的一些默认数据 |
|
400 | + account.setInsertTime(new Date()); |
|
401 | + account.setInsertUserId("..."); |
|
402 | + |
|
403 | + } |
|
404 | +} |
|
405 | +``` |
|
406 | + |
|
407 | + |
|
408 | +## onUpdate |
|
409 | +使用方式同 onInsert 一致,用于在数据被更新的时候,设置一些默认数据。 |
|
410 | + |
|
411 | +## onSet |
|
412 | + |
|
413 | +onSet 可以用于配置:查询数据 数据模型 (或者 模型 列表、分页等)时,对 模型 的属性设置的监听,可以用于如下的场景。 |
|
414 | + |
|
415 | +- 场景1:字段权限,不同的用户或者角色可以查询不同的字段内容。 |
|
416 | +- 场景2:字典回写,模型中定义许多业务字段,当数据库字段赋值时,主动去设置业务字段。 |
|
417 | +- 场景3:一对多,一对一查询,模型中定义关联实体,在监听到字段赋值时,主动去查询关联表赋值。 |
|
418 | +- 场景4:字段加密,监听到内容被赋值时,对内容进行加密处理。 |
|
419 | +- 场景5:字段脱敏,出字段内容进行脱敏处理 |
|
420 | + |
|
421 | +示例代码如下: |
|
422 | +```java |
|
423 | +//配置 onSet = MySetListener.class |
|
424 | +@Model(tableName = "tb_account", onSet = MySetListener.class) |
|
425 | +public class Account extends BaseModel<Account> { |
|
426 | + |
|
427 | +} |
|
428 | +``` |
|
429 | + |
|
430 | +```java |
|
431 | +public class MySetListener implements SetListener { |
|
432 | + |
|
433 | + @Override |
|
434 | + public Object onSet(RecordSet rs, String property, Object value){ |
|
435 | + |
|
436 | + |
|
437 | + //场景1:模型中可能定义某个业务值 |
|
438 | + // 当监听到某个字段被赋值了,这里可以主动去给另外的其他字段赋值 |
|
439 | + |
|
440 | + //场景2:内容转换和二次加工,对 value 值进行修改后返回 |
|
441 | + |
|
442 | + return value; |
|
443 | + } |
|
444 | +} |
|
445 | +``` |
|
446 | + |
|
447 | +## 全局设置 |
|
448 | +```java |
|
449 | +//为模型 Account1 和 Account2 注册 insertListner |
|
450 | +config.registerInsertListener(insertListener, Account1.class, Account2.class); |
|
451 | + |
|
452 | + |
|
453 | +//为模型 Account1 和 Account2 注册 updateListener |
|
454 | +config.registerUpdateListener(updateListener, Account1.class, Account2.class); |
|
455 | + |
|
456 | + |
|
457 | +//为模型 Account1 和 Account2 注册 setListener |
|
458 | +config.registerSetListener(setListener, Account1.class, Account2.class); |
|
459 | + |
|
460 | +``` |
|
461 | + |
|
462 | +# 字段加密 |
|
463 | + |
|
464 | +字段加密,指的是数据库在存入了明文内容,但是当我们进行查询时,返回的内容为加密内容,而非明文内容。 |
|
465 | + |
|
466 | +step 1: 为实体类编写一个 set 监听器(SetListener) |
|
467 | + |
|
468 | +```java |
|
469 | + |
|
470 | + |
|
471 | +public class MySetListener implements SetListener { |
|
472 | + |
|
473 | + @Override |
|
474 | + public Object onSet(RecordSet rs, String property, Object value){ |
|
475 | + if ( property=="password" && value != null){ |
|
476 | + //对字段内容进行加密 |
|
477 | + value = encrypt(value); |
|
478 | + } |
|
479 | + return value; |
|
480 | + } |
|
481 | +} |
|
482 | + |
|
483 | + |
|
484 | +``` |
|
485 | +step 2: 为实体类配置 onSet 监听 |
|
486 | +```java |
|
487 | +//配置 onSet = MySetListener.class |
|
488 | +@Model(tableName = "tb_account", onSet = MySetListener.class) |
|
489 | +public class Account extends BaseModel<Account> { |
|
490 | + |
|
491 | +} |
|
492 | +``` |
|
493 | + |
|
494 | + |
|
495 | +# SQL 审计 |
|
496 | + SQL 审计是一项非常重要的工作,是企业数据安全体系的重要组成部分,通过 SQL 审计功能为数据库请求进行全程记录,为事后追溯溯源提供了一手的信息,同时可以通过可以对恶意访问及时警告管理员,为防护策略优化提供数据支撑。 |
|
497 | + |
|
498 | +### 开启审计功能 |
|
499 | + > SQL 审计功能,默认是关闭的,若开启审计功能,需添加如下配置。 |
|
500 | +```java |
|
501 | +config.setAuditEnable(true) |
|
502 | +``` |
|
503 | +> 默认情况下,Sql的审计消息(日志)只会输出到控制台,如下所示: |
|
504 | + |
|
505 | +```java |
|
506 | +>>>>>>Sql Audit: {platform='mbm', app='aps', url='null', user='null', userIp='null', hostIp='192.168.3.24', query='SELECT * FROM `tb_account` WHERE `id` = ?', queryParams=[1], queryTime=1679991024523, elapsedTime=1} |
|
507 | +>>>>>>Sql Audit: {platform='mbm', app='aps', url='null', user='null', userIp='null', hostIp='192.168.3.24', query='SELECT * FROM `tb_account` WHERE `id` = ?', queryParams=[1], queryTime=1679991024854, elapsedTime=3} |
|
508 | +>>>>>>Sql Audit: {platform='mbm', app='aps', url='null', user='null', userIp='null', hostIp='192.168.3.24', query='SELECT * FROM `tb_account` WHERE `id` = ?', queryParams=[1], queryTime=1679991025100, elapsedTime=2} |
|
509 | +``` |
|
510 | + |
|
511 | + |
|
512 | +消息包含了如下内容: |
|
513 | + |
|
514 | +- platform:平台,或者是运行的应用 |
|
515 | +- app:应用 |
|
516 | +- url:执行这个 SQL 涉及的 URL 地址 |
|
517 | +- user:执行这个 SQL 涉及的 平台用户 |
|
518 | +- userIp:执行这个 SQL 的平台用户 IP 地址 |
|
519 | +- hostIp:执行这个 SQL 的服务器 IP 地址 |
|
520 | +- query:SQL 内容 |
|
521 | +- queryParams:SQL 参数 |
|
522 | +- queryTime:SQL 执行的时间点(当前时间) |
|
523 | +- elapsedTime:SQL 执行的消耗时间(毫秒) |
|
524 | +- metas:其他扩展元信息 |
|
525 | + |
|
526 | + 提示 : |
|
527 | + |
|
528 | + 通过以上的消息内容可知:每个 SQL 的执行,都包含了:哪个访问用户、哪个 IP 地址访问,访问的是哪个 URL 地址,这个 SQL 的参数是什么,执行的时间是什么,执行 花费了多少时间等等。这样,通过SQL 审计功能,我们能全盘了解到每个 SQL 的执行情况。 |
|
529 | + |
|
530 | +### 自定义 SQL 审计内容 |
|
531 | + |
|
532 | +内置了一个名为 MessageFactory 的接口,我们只需实现该接口,并为 AuditManager 配置新的 MessageFactory 即可,如下所示: |
|
533 | + |
|
534 | +```java |
|
535 | + |
|
536 | +public class MyMessageFactory implements MessageFactory { |
|
537 | + |
|
538 | + @Override |
|
539 | + public AuditMessage create() { |
|
540 | + AuditMessage message = new AuditMessage(); |
|
541 | + |
|
542 | + // 在这里 |
|
543 | + // 设置 message 的基础内容,包括 platform、module、url、user、userIp、hostIp 内容 |
|
544 | + // 剩下的 query、queryParams、queryCount、queryTime、elapsedTime 为 mybatis-flex 设置 |
|
545 | + |
|
546 | + return message; |
|
547 | + } |
|
548 | +} |
|
549 | + |
|
550 | +``` |
|
551 | + |
|
552 | +### 自定义 SQL 收集器 |
|
553 | + |
|
554 | +负责把收集的 SQL 审计日志发送到指定位置 |
|
555 | + |
|
556 | +```java |
|
557 | +public class MyMessageReporter implements MessageReporter { |
|
558 | + public void sendMessages(List<AuditMessage> messages) { |
|
559 | + //在这里把 messages 审计日志发送到指定位置 |
|
560 | + //比如 |
|
561 | + // 1、通过 http 协议发送到指定服务器 |
|
562 | + // 2、通过日志工具发送到日志平台 |
|
563 | + // 3、通过 Kafka 等 MQ 发送到指定平台 |
|
564 | + } |
|
565 | +} |
|
566 | +``` |
|
567 | + |
|
568 | +# SQL 日志 |
|
569 | + |
|
570 | +# 数据脱敏 |
|
571 | + |
|
572 | +> 对某些敏感信息通过脱敏规则进行数据的变形, 实现敏感隐私数据的可靠保护。在涉及客户安全数据或者一些商业性敏感数据的情况下,在不违反系统规则条件下,对真实数据进行改造并提供使用, 如身份证号、手机号、卡号、客户号等个人信息都需要进行数据脱敏。 |
|
573 | + |
|
574 | +@DataMask |
|
575 | + |
|
576 | +提供了 @DataMask() 注解,以及内置的9种脱敏规则,帮助开发者方便的进行数据脱敏。例如: |
|
577 | +```java |
|
578 | +@Model("tb_account") |
|
579 | +public class Account { |
|
580 | + |
|
581 | + @DataMask(Masks.CHINESE_NAME) |
|
582 | + private String userName; |
|
583 | +} |
|
584 | +``` |
|
585 | +以上的示例中,使用了 CHINESE_NAME 的脱敏规则,其主要用于处理 "中文名字" 的场景。当我们查询到 userName 为 张三丰 的时候,其内容自动被处理成 张**。 |
|
586 | + |
|
587 | + |
|
588 | +方便开发者直接使用: |
|
589 | +- 手机号脱敏 |
|
590 | +- 固定电话脱敏 |
|
591 | +- 身份证号脱敏 |
|
592 | +- 车牌号脱敏 |
|
593 | +- 地址脱敏 |
|
594 | +- 邮件脱敏 |
|
595 | +- 密码脱敏 |
|
596 | +- 银行卡号脱敏 |
|
597 | + |
|
598 | +### 自定义脱敏规则 |
|
599 | + |
|
600 | +内置的9种脱敏规则无法满足要求时,我们还可以自定义脱敏规则 |
|
601 | + |
|
602 | +```java |
|
603 | +@Model("tb_account") |
|
604 | +public class Account { |
|
605 | + |
|
606 | + @DataMask("自定义规则名称") |
|
607 | + private String userName; |
|
608 | +} |
|
609 | +``` |
|
610 | + |
|
611 | +### 取消脱敏处理 |
|
612 | + |
|
613 | +在某些场景下,程序希望查询得到的数据是原始数据,而非脱敏数据。比如要去查询用户的手机号,然后给用户发送短信。 |
|
614 | + |
|
615 | + |
|
616 | +# 乐观锁 |
|
617 | + |
|
618 | +用于当有多个用户(或者多场景)去同时修改同一条数据的时候,只允许有一个修改成功。 |
|
619 | + |
|
620 | +### 实现原理 |
|
621 | +使用一个字段,用于记录数据的版本,当修改数据的时候,会去检测当前版本是否是正在修改的版本,同时修改成功后会把 版本号 + 1。 |
|
622 | + |
|
623 | +```sql |
|
624 | + UPDATE account SET nickname = ?, version = version + 1 |
|
625 | + WHERE id = ? AND version = ? |
|
626 | +``` |
|
627 | + |
|
628 | +示例代码: |
|
629 | +```java |
|
630 | +@Model("tb_account") |
|
631 | +public class Account { |
|
632 | + |
|
633 | + @Version(version = true) |
|
634 | + private Long version; |
|
635 | +} |
|
636 | +``` |
|
637 | + |
|
638 | +# DB+模型Row操作 |
|
639 | + |
|
640 | +参考资料:https://mybatis-flex.com/zh/base/db-row.html |
|
641 | + |
|
642 | +> 使用 Row 插入数据 |
|
643 | + |
|
644 | +```java |
|
645 | +Row account = new Row(); |
|
646 | +account.set("id",100); |
|
647 | +account.set(ACCOUNT.USER_NAME,"Michael"); |
|
648 | +DbChain.app("aps").model("Account").create(account); |
|
649 | +``` |
|
650 | + |
|
651 | +> 根据主键查询数据 |
|
652 | + |
|
653 | +```java |
|
654 | +Row row = DbChain.app("aps").model("Account").selectOneById(1); |
|
655 | +``` |
|
656 | + |
|
657 | +> 根据条件查询数据 |
|
658 | + |
|
659 | +```java |
|
660 | +Row row = DbChain.app("aps").model("Account") |
|
661 | + .where(ACCOUNT.AGE.ge(18)) |
|
662 | + .selectOne(); |
|
663 | +``` |
|
664 | + |
|
665 | +> Row 可以直接转换为实体类,且性能要求极高 |
|
666 | + |
|
667 | +```java |
|
668 | +Account account = row.toEntity(Account.class); |
|
669 | +``` |
|
670 | + |
|
671 | +# 事务管理 |
|
672 | + |
|
673 | +### 事务传播属性 |
|
674 | +```java |
|
675 | +//若存在当前事务,则加入当前事务,若不存在当前事务,则创建新的事务 |
|
676 | +REQUIRED(0), |
|
677 | + |
|
678 | +//若存在当前事务,则加入当前事务,若不存在当前事务,则已非事务的方式运行 |
|
679 | +SUPPORTS(1), |
|
680 | + |
|
681 | +//若存在当前事务,则加入当前事务,若不存在当前事务,则抛出异常 |
|
682 | +MANDATORY(2), |
|
683 | + |
|
684 | +//始终以新事务的方式运行,若存在当前事务,则暂停(挂起)当前事务。 |
|
685 | +REQUIRES_NEW(3), |
|
686 | + |
|
687 | +//以非事务的方式运行,若存在当前事务,则暂停(挂起)当前事务。 |
|
688 | +NOT_SUPPORTED(4), |
|
689 | + |
|
690 | +//以非事务的方式运行,若存在当前事务,则抛出异常。 |
|
691 | +NEVER(5), |
|
692 | + |
|
693 | +//暂时不支持 |
|
694 | +NESTED(6), |
|
695 | +``` |
|
696 | + |
|
697 | +### 嵌套事务 |
|
698 | + |
|
699 | +支持无限极嵌套,默认情况下,嵌套事务直接的关系是:REQUIRED(若存在当前事务,则加入当前事务,若不存在当前事务,则创建新的事务)。 |
|
700 | + |
|
701 | + |
|
702 | +# 属性生成器 |
|
703 | + |
|
704 | +# 数据源加密 |
|
705 | + |
|
706 | +数据源加密指的是我们对数据库的链接信息进行加密,支持加密的内容有 |
|
707 | + |
|
708 | +- 数据源 URL |
|
709 | +- 数据源用户名 |
|
710 | +- 数据源用户密码 |
|
711 | + |
|
712 | +通过数据源加密功能,我们可以有效的保证数据库安全,减少数据盗用风险。 |
|
713 | + |
|
714 | +# 多数据源 |
|
715 | + |
|
716 | +# 读写分离 |
|
717 | + |
|
718 | +读写分离的功能,要求当前环境必须是多个数据库(也可理解为多个数据源),其原理是: 让主数据库(master)处理事务性操作,比如:增、删、改(INSERT、DELETE、UPDATE),而从数据库(slave)处理查询(SELECT)操作。 |
RabbitMQ.md
... | ... | @@ -0,0 +1,95 @@ |
1 | +代码 [[sie-snest-rabbit-mq-master.zip|/uploads/RabbitMQ/sie-snest-rabbit-mq-master.zip]] |
|
2 | + |
|
3 | +App [[sie-snest-rabbitmq-v1.0.0.RELEASE.jar|/uploads/RabbitMQ/sie-snest-rabbitmq-v1.0.0.RELEASE.jar]] |
|
4 | + |
|
5 | +# 基础使用 |
|
6 | + |
|
7 | +## 添加配置 |
|
8 | + |
|
9 | +在 `application.properties` 配置文件添加以下配置 |
|
10 | + |
|
11 | +```properties |
|
12 | +rabbitmq.host=127.0.0.1 |
|
13 | +rabbitmq.port=5672 |
|
14 | +rabbitmq.username=admin |
|
15 | +rabbitmq.password=password |
|
16 | +``` |
|
17 | + |
|
18 | +## 添加 rabbitmq 应用 |
|
19 | + |
|
20 | +可以通过应用市场安装 rabbitmq 应用,或下载 rabbitmq app 到 apps 目录 |
|
21 | + |
|
22 | +## 添加应用依赖 |
|
23 | + |
|
24 | +在需要使用 RabbitMQ 的 app 中添加依赖,修改 `app.json` , 在 `dependencies` 添加 `rabbitmq` |
|
25 | + |
|
26 | +```json |
|
27 | +{ |
|
28 | + "name": "rabbitmq-test", |
|
29 | + "displayName": "RabbitMQ测试", |
|
30 | + "dependencies": ["rabbitmq"], |
|
31 | + "events":{ |
|
32 | + "startUp": [ |
|
33 | + "RabbitMqTest::registerConsumer" |
|
34 | + ] |
|
35 | + } |
|
36 | +} |
|
37 | +``` |
|
38 | + |
|
39 | +## 编写测试代码 |
|
40 | + |
|
41 | +```java |
|
42 | +@Model(displayName = "RabbitMQ 测试") |
|
43 | +public class RabbitMqTest extends BaseModel<RabbitMqTest> { |
|
44 | + |
|
45 | + private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMqTest.class); |
|
46 | + private static final String EXCHANGE_NAME = "rabbitmq-test.topic"; |
|
47 | + private static final String QUEUE_NAME = "rabbitmq-test.changeName.queue"; |
|
48 | + private static final String ROUTING_KEY = "rabbitmq-test.changeName"; |
|
49 | + public static final String RABBIT_MQ_MODEL = "RabbitMQ"; |
|
50 | + |
|
51 | + @Property(displayName = "名称") |
|
52 | + private String name; |
|
53 | + |
|
54 | + public void registerConsumer() { |
|
55 | + getMeta().get(RABBIT_MQ_MODEL).call("declareExchange", EXCHANGE_NAME, "topic"); |
|
56 | + getMeta().get(RABBIT_MQ_MODEL).call("declareQueue", QUEUE_NAME); |
|
57 | + getMeta().get(RABBIT_MQ_MODEL).call("queueBind", EXCHANGE_NAME, ROUTING_KEY, QUEUE_NAME); |
|
58 | + getMeta().get(RABBIT_MQ_MODEL).call("registerConsumer", QUEUE_NAME, "RabbitMqTest", "handleChangeNameEvent"); |
|
59 | + } |
|
60 | + |
|
61 | + @MethodService(description = "发送事件") |
|
62 | + public void publishChangeNameEvent() { |
|
63 | + ChangeNameEvent event = new ChangeNameEvent("1", "Mary"); |
|
64 | + getMeta().get(RABBIT_MQ_MODEL).call("send", EXCHANGE_NAME, ROUTING_KEY, JSON.toJSONString(event)); |
|
65 | + } |
|
66 | + |
|
67 | + @MethodService(description = "处理事件") |
|
68 | + public void handleChangeNameEvent(String event) { |
|
69 | + LOGGER.debug("接收到消息:{}", event); |
|
70 | + } |
|
71 | +} |
|
72 | +``` |
|
73 | + |
|
74 | +方法说明 |
|
75 | + |
|
76 | +- registerConsumer:用于作为启动事件,启动的时候声明交换机、声明队列、绑定队列、注册消费者 |
|
77 | +- publishChangeNameEvent:用于发送事件 |
|
78 | +- handleChangeNameEvent:业务中处理事件的逻辑 |
|
79 | + |
|
80 | +## 添加启动事件 |
|
81 | + |
|
82 | +在 `app.json` 中添加启动事件,在应用启动的时候注册消费者。 |
|
83 | + |
|
84 | +```json |
|
85 | +{ |
|
86 | + "name": "rabbitmq-test", |
|
87 | + "displayName": "RabbitMQ测试", |
|
88 | + "dependencies": ["rabbitmq"], |
|
89 | + "events":{ |
|
90 | + "startUp": [ |
|
91 | + "RabbitMqTest::registerConsumer" |
|
92 | + ] |
|
93 | + } |
|
94 | +} |
|
95 | +``` |
|
... | ... | \ No newline at end of file |
SqlProvider\351\200\202\351\205\215\344\270\215\345\220\214\347\232\204\346\225\260\346\215\256\345\272\223.md
... | ... | @@ -0,0 +1,325 @@ |
1 | +# 使用SqlProvider适配不同的数据库和手写SQL最佳实践 |
|
2 | + |
|
3 | + |
|
4 | +## 1. 支持的数据库类型 |
|
5 | + |
|
6 | +* Oracle :OracleProvider |
|
7 | +* MySQL:MySqlProvider |
|
8 | +* Dameng国产达梦数据库:DamengProvider,达梦数据库兼容oracle,和oracle的用法和语法基本一致 |
|
9 | + |
|
10 | + |
|
11 | +## 2. Oracle 与 MySQL 语法差异对比 |
|
12 | + |
|
13 | +### 2.1 分页查询 |
|
14 | + |
|
15 | +- **Oracle**: |
|
16 | + ```sql |
|
17 | + SELECT * FROM ( |
|
18 | + SELECT a.*, ROWNUM rn |
|
19 | + FROM your_table a |
|
20 | + WHERE condition |
|
21 | + ORDER BY some_column |
|
22 | + ) |
|
23 | + WHERE rn BETWEEN start_num AND end_num; |
|
24 | + ``` |
|
25 | + |
|
26 | +- **MySQL**: |
|
27 | + ```sql |
|
28 | + SELECT * FROM your_table |
|
29 | + WHERE condition |
|
30 | + LIMIT start_num, row_count; -- row_count = end_num - start_num + 1 |
|
31 | + ``` |
|
32 | + |
|
33 | +### 2.2 字符串连接 |
|
34 | + |
|
35 | +- **Oracle**: |
|
36 | + ```sql |
|
37 | + SELECT CONCAT(column1, ' ', column2) FROM your_table; |
|
38 | + ``` |
|
39 | + |
|
40 | +- **MySQL**: |
|
41 | + ```sql |
|
42 | + SELECT CONCAT(column1, ' ', column2) FROM your_table; |
|
43 | + ``` |
|
44 | + (此处两者语法相同,但请注意在较早版本的 MySQL 中可能使用 `CONCAT_WS` 或 `CONCATENATE` 函数) |
|
45 | + |
|
46 | +### 2.3 自增列(序列) |
|
47 | + |
|
48 | +- **Oracle**: |
|
49 | + 创建序列: |
|
50 | + ```sql |
|
51 | + CREATE SEQUENCE seq_name; |
|
52 | + ``` |
|
53 | + 使用序列: |
|
54 | + ```sql |
|
55 | + INSERT INTO your_table (id, other_columns) |
|
56 | + VALUES (seq_name.NEXTVAL, 'value'); |
|
57 | + ``` |
|
58 | + |
|
59 | +- **MySQL**: |
|
60 | + 在表创建时声明自增列: |
|
61 | + ```sql |
|
62 | + CREATE TABLE your_table ( |
|
63 | + id INT AUTO_INCREMENT PRIMARY KEY, |
|
64 | + other_columns VARCHAR(255) |
|
65 | + ); |
|
66 | + ``` |
|
67 | + |
|
68 | +### 2.4 数据类型差异 |
|
69 | + |
|
70 | +- **Oracle** 支持 `NUMBER(p,s)` 类型,对应 MySQL 的可能是 `DECIMAL(p,s)` 或者 `INT`、`BIGINT` 等。 |
|
71 | + |
|
72 | +- **MySQL** 提供了额外的数据类型如 `SET` 和 `ENUM`,而 Oracle 没有直接对应的类型。 |
|
73 | + |
|
74 | +- **TEXT长文本类型**: |
|
75 | +- **Oracle** 中用于存储大文本数据的主要类型是 `CLOB` (Character Large Object) 和 `NCLOB` (National Character Large Object)。 |
|
76 | + |
|
77 | +- **MySQL** 有明确的 `TEXT` 数据类型,包括 `TINYTEXT`, `TEXT`, `MEDIUMTEXT`, 和 `LONGTEXT`,分别对应不同大小的文本数据范围,最大可存储至 2^32 字节的数据。 |
|
78 | + |
|
79 | + |
|
80 | + |
|
81 | +### 2.5 时间日期函数 |
|
82 | + |
|
83 | +- **Oracle**: |
|
84 | + 获取当前日期时间: |
|
85 | + ```sql |
|
86 | + SELECT SYSDATE FROM dual; |
|
87 | + ``` |
|
88 | + |
|
89 | +- **MySQL**: |
|
90 | + 获取当前日期时间: |
|
91 | + ```sql |
|
92 | + SELECT NOW(); |
|
93 | + ``` |
|
94 | + |
|
95 | +### 2.6 事务处理 |
|
96 | + |
|
97 | +- **Oracle** 默认情况下不自动提交事务,需要显式执行 `COMMIT` 或 `ROLLBACK`。 |
|
98 | + |
|
99 | +- **MySQL** 默认开启了自动提交模式,可以通过 `SET autocommit=0;` 关闭并手动控制事务。 |
|
100 | + |
|
101 | +### 2.7 字符串内引用的分隔符 |
|
102 | + |
|
103 | + - **Oracle** 中,可以使用双引号 (`"`) 来定义标识符(如表名、列名),单引号 (`'`) 用于定义字符串常量。 |
|
104 | + - **MySQL** 中,可以使用反引号 (`` ` ``)、双引号 (`"`) 或者单引号 (`'`) 来定义标识符,但推荐使用反引号以避免与SQL标准冲突;单引号同样用于定义字符串常量。 |
|
105 | + |
|
106 | +### 2.8 Oracle 和 MySQL 在大小写处理方面的差异: |
|
107 | + |
|
108 | + |
|
109 | +**Oracle:** |
|
110 | +- **SQL关键字:** Oracle 不区分大小写,但通常建议大写 SQL 关键字以提高可读性。 |
|
111 | +- **对象名(如表名、列名、序列名等):** **在Linux/Unix环境下是区分大小写的,默认都是大写,返回的结果列默认都是大写******。 |
|
112 | + |
|
113 | +**MySQL:** |
|
114 | +- **SQL关键字:** MySQL 对于SQL关键字也不区分大小写。 |
|
115 | +- **对象名(如表名、列名、数据库名等):** MySQL 默认在所有环境下都不区分大小写。 |
|
116 | +例如: |
|
117 | +- 在Oracle中,`SELECT * FROM MyTable` 和 `select * from mytable` 指的是不同的一个表(在Linux/Unix环境)。 |
|
118 | +- 在MySQL中,`SELECT * FROM MyTable` 和 `select * from mytable` 同样默认视为相同的表名,除非你用反引号定义为 `SELECT * FROM \`MyTable\`;` 与 `select * from \`mytable\`;` 这时它们会被认为是不同的表名。 |
|
119 | + |
|
120 | +**总结来说,对于大小写处理,Oracle区分大小写, 默认都是大写,而MySQL则在所有系统上都默认不区分大小写。** |
|
121 | + |
|
122 | + |
|
123 | + |
|
124 | +## 3. SqlProvider的使用 |
|
125 | + |
|
126 | +### 建议我们的代码中写的SQL都是标准的SQL,如果遇到数据库的差异的SQL,可以使用SqlProvider提供的方法来屏蔽不同数据库之间的差异。 |
|
127 | + |
|
128 | + |
|
129 | +- **SqlProvider的使用**: |
|
130 | + ```java |
|
131 | + RelationDBAccessor da = rs.getMeta().getRelationDBAccessor(); |
|
132 | + //获取SqlProvider |
|
133 | + SqlProvider sqlProvider = da.getSqlProvider(); |
|
134 | + //对关键字添加分隔符:示例: oracle:"identify", sqlserver:[identify], mysql: `identify` |
|
135 | + String joinTable = sqlProvider.quote((String)property.getAttr(MetaConstant.TABLE_NAME)); |
|
136 | + String table1 = sqlProvider.quote((String)property.getAttr(MetaConstant.RELATE_TABLE)); |
|
137 | + String id1 = sqlProvider.quote((String)property.getAttr(MetaConstant.INVERSE_JOIN_COLUMN)); |
|
138 | + String id2 = sqlProvider.quote((String)property.getAttr(MetaConstant.COLUMN_NAME)); |
|
139 | + String column1 = sqlProvider.quote((String)displayProperty.getColumnName()); |
|
140 | + |
|
141 | + String sql = "SELECT " + joinTable + "." + id1 + ", " + joinTable + "." + id2 + ", " + table1 + "." + column1 + " FROM " + table1 + " JOIN " + joinTable |
|
142 | + + " ON " + joinTable + "." + id2 + "=" + table1 + ".id" + " AND " + joinTable + "." + id1 + " IN %s "; |
|
143 | + |
|
144 | + if (!useDisplayForModel) { |
|
145 | + sql = "SELECT " + joinTable + "." + id1 + ", " + joinTable + "." + id2 + " FROM " + joinTable + " WHERE " + joinTable + "." + id1 + " IN %s "; |
|
146 | + } |
|
147 | + //分页查询 |
|
148 | + sql = sqlProvider.getPaging(sql, 99999, 0); |
|
149 | + List<Object> params = new ArrayList<Object>(ids); |
|
150 | + //执行sql |
|
151 | + da.executeWithoutAuth(sql, Arrays.asList(params)); |
|
152 | + |
|
153 | + // 读取数据,如果是oracle数据库,则所有的列名都自动转成小写(因为oracle默认都是全部大小,不想转小写的话,请调用fetchMap) |
|
154 | + List<Map<String, Object>> results = da.fetchMapAll(); |
|
155 | + for (Map<String, Object> rows : searchResults) { |
|
156 | + //TODO |
|
157 | + } |
|
158 | + ``` |
|
159 | + |
|
160 | + |
|
161 | + |
|
162 | + |
|
163 | +## 4. SqlProvider接口使用介绍 |
|
164 | + |
|
165 | + |
|
166 | +### 4.1 SqlProvider接口提供的方法 |
|
167 | + |
|
168 | + |
|
169 | + ```java |
|
170 | +public abstract class SqlProvider { |
|
171 | + |
|
172 | + /** |
|
173 | + * 获取数据库时间 |
|
174 | + * |
|
175 | + * @return |
|
176 | + */ |
|
177 | + public abstract String getNowUtc(); |
|
178 | + |
|
179 | + |
|
180 | + /** |
|
181 | + * 为数据库对象名(如表名、列名、数据库名等)添加分隔符:示例:</br> |
|
182 | + * oracle:"identify", sqlserver:[identify], mysql: `identify` |
|
183 | + * |
|
184 | + * @param identify |
|
185 | + * @return |
|
186 | + */ |
|
187 | + public abstract String quote(String identify); |
|
188 | + |
|
189 | + |
|
190 | + /** |
|
191 | + * 为值添加分隔符,示例:'value' |
|
192 | + * @param identify |
|
193 | + * @return |
|
194 | + */ |
|
195 | + public String quoteValue(String identify){ |
|
196 | + return StringUtils.join("'" , identify , "'"); |
|
197 | + } |
|
198 | + |
|
199 | + /** |
|
200 | + * 为as后的别名添加分隔符 |
|
201 | + * |
|
202 | + * @param alias |
|
203 | + * @return |
|
204 | + */ |
|
205 | + public String asQuote(String alias) { |
|
206 | + return quote(alias); |
|
207 | + } |
|
208 | + |
|
209 | + /** |
|
210 | + * AS 关键字,oracle列名可以使用AS,但是表名不能添加AS |
|
211 | + * 建议:oracle 列名和表名都不使用as ; mysql:AS |
|
212 | + * |
|
213 | + * @return |
|
214 | + */ |
|
215 | + public abstract String as(); |
|
216 | + |
|
217 | + /** |
|
218 | + * 生成分页sql |
|
219 | + * |
|
220 | + * @param sql |
|
221 | + * @param limit 总条数 |
|
222 | + * @param offset 偏移量 |
|
223 | + * @return |
|
224 | + */ |
|
225 | + public abstract String getPaging(String sql, Integer limit, Integer offset); |
|
226 | + |
|
227 | + |
|
228 | + /** |
|
229 | + * 使用日期字符串函数,插入日期字符串 |
|
230 | + * @param value |
|
231 | + * @return |
|
232 | + */ |
|
233 | + public String toDate(String value) { |
|
234 | + } |
|
235 | + |
|
236 | + /** |
|
237 | + * 使用日期字符串函数,插入日期时间格式的字符串 |
|
238 | + * @param value |
|
239 | + * @return |
|
240 | + */ |
|
241 | + public String toDateTime(String value) { |
|
242 | + } |
|
243 | + |
|
244 | + /** |
|
245 | + * 使用日期字符串函数,插入时间戳字符串 |
|
246 | + * @param value |
|
247 | + * @return |
|
248 | + */ |
|
249 | + public String toTimestamp(String value) { |
|
250 | + return String.format("'%s'", value); |
|
251 | + } |
|
252 | + |
|
253 | + /** |
|
254 | + * 插入boolean值,存储在数据库的字段为字符串 0和1,true:'1';false:'0' |
|
255 | + * @param value |
|
256 | + * @return |
|
257 | + */ |
|
258 | + public String toBoolean(Boolean value) { |
|
259 | + if (value) { |
|
260 | + return String.format("'%s'", 1); |
|
261 | + } else { |
|
262 | + return String.format("'%s'", 0); |
|
263 | + } |
|
264 | + } |
|
265 | + |
|
266 | + |
|
267 | + |
|
268 | + /** |
|
269 | + * 插入text类型:oracle varchar2最大字节数都是4000,超过4000的话就需要使用clob存储。 |
|
270 | + * 如果属性中是text类型,插入的时候必须使用toText转换成对应数据库的类型 |
|
271 | + * 转成text类型 |
|
272 | + * @param value |
|
273 | + * @return |
|
274 | + */ |
|
275 | + public abstract String toText(String value); |
|
276 | + |
|
277 | + |
|
278 | + /** |
|
279 | + *Oracle toText实现 |
|
280 | + * Oracle的sql语句单引号内部的字节长度不能大于4000 |
|
281 | + * 把传入的body字符串,按宽度splitLength,拆分成多条 |
|
282 | + * 并用to_clob拼接: |
|
283 | + * https://stackoverflow.com/questions/66974557/oracle-clob-data-type-giving-an-error-sql-error-ora-01704-string-literal-too |
|
284 | + */ |
|
285 | + @Override |
|
286 | + public String toText(String content){ |
|
287 | + int maxLen = 1024; |
|
288 | + int count = Math.floorDiv(content.length(), maxLen) + (content.length() % maxLen == 0 ? 0 : 1); |
|
289 | + String toClob = IntStream.range(0, count).mapToObj(i -> { |
|
290 | + String subStr = content.substring(i * 1024, Math.min((i + 1) * maxLen, content.length())); |
|
291 | + subStr = StringUtils.replace(subStr, "'", "''"); |
|
292 | + return "to_clob('" + subStr + "')"; |
|
293 | + }).collect(Collectors.joining("||")); |
|
294 | + return toClob; |
|
295 | + } |
|
296 | + |
|
297 | + |
|
298 | + /** |
|
299 | + * 在 Oracle 和 MySQL 中,转义字符主要用于处理字符串中的特殊字符或通配符。在 SQL 语句中,常见的转义字符是反斜杠 \。 |
|
300 | + * @param content |
|
301 | + * @return |
|
302 | + */ |
|
303 | + @Override |
|
304 | + public String escapeText(String content){ |
|
305 | + String replace = StringUtils.replace(content.toString(), "\\", "\\\\"); |
|
306 | + replace = replace.replace("'","\\'"); |
|
307 | + return replace; |
|
308 | + } |
|
309 | + |
|
310 | +} |
|
311 | + ``` |
|
312 | + |
|
313 | + |
|
314 | +## 5 手写SQL最佳实践: |
|
315 | +- **建议我们的代码中写的SQL都是标准的SQL,不要使用不兼容的函数,如果遇到数据库的差异的SQL,可以使用SqlProvider提供的方法来屏蔽数据库之间的差异。** |
|
316 | +- **对一些数据库的关键字,对象名(如表名、列名、序列名等),使用`quote`包裹** 。 |
|
317 | +- **Oracle 列名和表名都不使用关键字AS,直接空着不写就行; MySql可以使用AS**。 |
|
318 | +- **对于TXET长文本类型,必须使用`toText`方法进行转换 **。 |
|
319 | +- **分页使用`getPaging`**。 |
|
320 | +- **插入boolean属性使用`toBoolean`**,存储在数据库的字段为字符串 0和1,`true:'1'; false:'0'`,因为oracle没有boolean数据类型,所以统一成字符串类型。 |
|
321 | +- **插入日期格式的字符串时候,使用`toDate`,`toDateTime`,`toTimestamp`**。 |
|
322 | +- **获取数据库的当前时间使用 `getNowUtc`**。 |
|
323 | + |
|
324 | + |
|
325 | + |
|
... | ... | \ No newline at end of file |
_Sidebar.md
... | ... | @@ -0,0 +1,2 @@ |
1 | +[[_TOC_]] |
|
2 | + |
baseApp.md
... | ... | @@ -0,0 +1,3 @@ |
1 | +111 |
|
2 | + |
|
3 | +### [[interfaceApp]] |
|
... | ... | \ No newline at end of file |
demo.md
... | ... | @@ -0,0 +1,11 @@ |
1 | +[视频详情](https://player.bilibili.com/player.html?aid=797853080&bvid=BV1Ey4y167XN&cid=258756210&page=1) |
|
2 | + |
|
3 | +<video crossorigin="anonymous" preload="auto" src="blob:https://player.bilibili.com/54bb8130-2a89-469d-9d5f-3dee44958d54"></video> |
|
4 | + |
|
5 | + |
|
6 | + |
|
7 | + |
|
8 | + |
|
9 | + |
|
10 | +<video width="100%" height="100%" src="http://192.168.175.198:10003/iidpminio/iidp-plugin/01-iidp-helper-install.mp4" controls="true" /> |
|
11 | + |
grant.md
... | ... | @@ -0,0 +1 @@ |
1 | +grant |
|
... | ... | \ No newline at end of file |
guide/create-view.md
... | ... | @@ -0,0 +1,43 @@ |
1 | +# 生成视图 |
|
2 | + |
|
3 | +# 目标 |
|
4 | + |
|
5 | +1. 创建简单的增删改查页面 |
|
6 | + |
|
7 | +# 生成预览视图 |
|
8 | + |
|
9 | +在开发者中心-在线建模,点击【生成视图】按钮。 |
|
10 | + |
|
11 | +[[/uploads/guide/create-view/Snipaste_2024-02-26_16-12-54.png]] |
|
12 | + |
|
13 | +# 保存视图到工程中 |
|
14 | + |
|
15 | +在开发者中心-视图管理,找到刚刚生成的预览视图 |
|
16 | + |
|
17 | +[[/uploads/guide/create-view/Snipaste_2024-02-26_16-14-29.png]] |
|
18 | + |
|
19 | +点击详情,拷贝视图结构 |
|
20 | + |
|
21 | +[[/uploads/guide/create-view/Snipaste_2024-02-26_16-14-46.png]] |
|
22 | + |
|
23 | +在 view 目录下创建视图文件,并且将预览视图的内容拷贝进去 |
|
24 | + |
|
25 | +[[/uploads/guide/create-view/Snipaste_2024-02-26_16-18-51.png]] |
|
26 | + |
|
27 | +# 声明菜单 |
|
28 | + |
|
29 | +在 menus.json 声明菜单 |
|
30 | + |
|
31 | +[[/uploads/guide/create-view/Snipaste_2024-02-26_16-23-11.png]] |
|
32 | + |
|
33 | +# 重启后端服务 |
|
34 | + |
|
35 | +刷新页面,你可以看到刚刚声明的菜单 |
|
36 | + |
|
37 | +[[/uploads/guide/create-view/Snipaste_2024-02-26_16-25-22.png]] |
|
38 | + |
|
39 | +# 重新生成预览视图 |
|
40 | + |
|
41 | +如果你给模型加了属性,你希望重新生成预览视图。你可以在 `ui_view_seed` 删除掉原来的视图,然后重复上述步骤。 |
|
42 | + |
|
43 | +[[/uploads/guide/create-view/Snipaste_2024-02-26_16-28-46.png]] |
guide/guide/project-struct.md
... | ... | @@ -0,0 +1,28 @@ |
1 | +# 项目结构 |
|
2 | + |
|
3 | +- apps |
|
4 | + - apps.json # 内置应用声明文件 |
|
5 | + - sie-snest-base.jar # 内置应用 |
|
6 | + - sie-snest-file.jar |
|
7 | + - sie-snest-dict.jar |
|
8 | + - sie-snest-demo.jar |
|
9 | +- sie-snest-server |
|
10 | + - Server # 后端服务 |
|
11 | + - resources |
|
12 | + - application.properties |
|
13 | + - application-dev.properties # 配置文件,例如端口、是否输出 SQL 日志等 |
|
14 | + - dbcp.properties # 数据库配置文件 |
|
15 | + - logback-spring.properties # 日志配置文件 |
|
16 | +- sie-senst-apps |
|
17 | + - pom.xml # 为所有 app 引入 sie-snest-sdk 依赖。并且有打包插件。会将 app 的 jar 包拷贝到 apps 目录下。 |
|
18 | + - demo |
|
19 | + - src/main/java/com/sie/apps/demo |
|
20 | + - data # 业务种子数据目录 |
|
21 | + - model # 模型目录 |
|
22 | + - Material.java |
|
23 | + - views |
|
24 | + - menus.json # 菜单声明文档 |
|
25 | + - xxx_view.json # 视图文件 |
|
26 | + - app.json # 声明 app 的文件 |
|
27 | + - |
|
28 | +- pom.xml # 引入 sie-snest-engine 依赖 |
|
... | ... | \ No newline at end of file |
guide/init-project.md
... | ... | @@ -0,0 +1,84 @@ |
1 | +# IIDP 后端服务启动 |
|
2 | + |
|
3 | +# 目标 |
|
4 | + |
|
5 | +1. 成功运行后端服务 |
|
6 | +2. 访问接口文档 |
|
7 | + |
|
8 | +# 开发工具准备 |
|
9 | + |
|
10 | +1. MySQL 8.0+ |
|
11 | +2. IDEA |
|
12 | +3. maven |
|
13 | +4. iidp-demo 项目。gitlab [仓库地址](http://192.168.175.55:9888/caiqijun/iidp-demo) |
|
14 | +5. [[settings.xml|/uploads/guide/init-project/settings.xml]] 文件。maven 的配置文件 |
|
15 | +6. 确保已经连上公司 Wi-Fi |
|
16 | + |
|
17 | +# 配置 maven 仓库 |
|
18 | + |
|
19 | +下载 [[settings.xml|/uploads/guide/init-project/settings.xml]] 文件。拷贝到你的 maven home。一般是用户目录下的 .m2 目录。 |
|
20 | + |
|
21 | +# 创建数据库 |
|
22 | + |
|
23 | +连接 MySQL,新建一个数据库,字符集选择 utf8mb4,排序规则选择 utf8mb4_bin。 |
|
24 | + |
|
25 | +如果你从其他数据库导入数据,请确保导入后的表和字段排序规则都是 utf8mb4_bin。 |
|
26 | +如果两个表的字段排序规则不一致,进行联表查询时会出现报错。 |
|
27 | + |
|
28 | +[[/uploads/guide/init-project/新建数据库.png]] |
|
29 | + |
|
30 | +# 使用 IDEA 打开 iidp-demo 项目 |
|
31 | + |
|
32 | +*直接打开 iidp-demo 这个目录,不要打开父级目录* |
|
33 | + |
|
34 | +[[/uploads/guide/init-project/iidp-demo目录.png]] |
|
35 | + |
|
36 | +## 配置 maven |
|
37 | + |
|
38 | +打开 IDEA Setting,配置 maven 的 settings.xml 文件,选择覆盖。 |
|
39 | + |
|
40 | +[[/uploads/guide/init-project/idea-maven-settings.png]] |
|
41 | + |
|
42 | +由于公司 nexus 仓库没有使用 https,如果你使用 maven 3.8 以上的版本,你需要修改一下 maven 的配置文件。 |
|
43 | + |
|
44 | +[[/uploads/guide/init-project/maven-http.png]] |
|
45 | + |
|
46 | +注释掉以下内容 |
|
47 | + |
|
48 | +```xml |
|
49 | +<!-- |
|
50 | +<mirror> |
|
51 | + <id>maven-default-http-blocker</id> |
|
52 | + <mirrorOf>external:http:*</mirrorOf> |
|
53 | + <name>Pseudo repository to mirror external repositories initially using HTTP.</name> |
|
54 | + <url>http://0.0.0.0/</url> |
|
55 | + <blocked>true</blocked> |
|
56 | +</mirror> |
|
57 | +--> |
|
58 | +``` |
|
59 | + |
|
60 | +## 运行项目 |
|
61 | + |
|
62 | +修改数据库配置 |
|
63 | + |
|
64 | +[[/uploads/guide/init-project/修改数据库连接.png]] |
|
65 | + |
|
66 | +新增一个启动配置 |
|
67 | + |
|
68 | +[[/uploads/guide/init-project/新建启动配置.png]] |
|
69 | + |
|
70 | +点击 Modify options。启动前自动执行 maven 的 clean package 命令 |
|
71 | + |
|
72 | +[[/uploads/guide/init-project/add-maven-task.png]] |
|
73 | + |
|
74 | +[[/uploads/guide/init-project/add-maven-clean-package.png]] |
|
75 | + |
|
76 | +[[项目目录结构说明|guide/project-struct]] |
|
77 | + |
|
78 | +## 访问接口文档 |
|
79 | + |
|
80 | +打开浏览器,访问 [http://localhost:8060/root/api/master](http://localhost:8060/root/api/master) |
|
81 | + |
|
82 | +你会看到一个接口文档列表。 |
|
83 | + |
|
84 | +[[/uploads/guide/init-project/Snipaste_2024-02-26_11-47-37.png]] |
|
... | ... | \ No newline at end of file |
guide/init-web-project.md
... | ... | @@ -0,0 +1,31 @@ |
1 | +# 前端服务启动 |
|
2 | + |
|
3 | +# 目标 |
|
4 | + |
|
5 | +1. 运行前端,访问后端服务 |
|
6 | + |
|
7 | +# 方式一 |
|
8 | + |
|
9 | +使用 vscode 运行前端 demo 项目 |
|
10 | + |
|
11 | +参考前端[环境准备](http://iidp.chinasie.com:9999/iidpdoc/pages/de67e3/#%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83) |
|
12 | + |
|
13 | +# 方式二 |
|
14 | + |
|
15 | +使用线上前端项目,例如 [http://release.snest.com:31815/iidp/](http://release.snest.com:31815/iidp/) |
|
16 | + |
|
17 | +按下 F12,在 console,输入 |
|
18 | + |
|
19 | +``` |
|
20 | +sessionStorage.setItem('tempApi', 'http://127.0.0.1:8060') |
|
21 | +``` |
|
22 | + |
|
23 | +然后刷新页面,输入账号密码登录 |
|
24 | + |
|
25 | +[[/uploads/guide/init-project/第一次项目界面.png]] |
|
26 | + |
|
27 | +# 登陆 |
|
28 | + |
|
29 | +如果你没有使用多租户版本,你可以直接使用 admin / admin 登陆进去。 |
|
30 | + |
|
31 | +如果你安装了多租户,你应该先使用 implementer / Implementer_2023 登陆进去。可参考 [[多租户APP使用|/基础APP/多租户(tenant)APP]] |
|
... | ... | \ No newline at end of file |
guide/model.md
... | ... | @@ -0,0 +1,22 @@ |
1 | +# 模型介绍 |
|
2 | + |
|
3 | +# 创建一个模型 |
|
4 | + |
|
5 | +在 model 目录下新建模型,使用 @Model 注解声明一个模型。**模型类必须继承 BaseModel**。 |
|
6 | + |
|
7 | +[[/uploads/guide/model/模型声明.png]] |
|
8 | + |
|
9 | + |
|
10 | + |
|
11 | +```java |
|
12 | +@Model(name = "material", tableName = "wms_material", displayName = "物料", description = "物料信息", isAutoLog = Bool.True) |
|
13 | +public class Material extends BaseModel<Material> { |
|
14 | + |
|
15 | + @Property(displayName = "名称", displayForModel = true) |
|
16 | + private String name; |
|
17 | +} |
|
18 | +``` |
|
19 | + |
|
20 | +然后重启后端服务,进入开发者中心-模型管理,可以查看到这个模型。 |
|
21 | + |
|
22 | +[[/uploads/guide/model/Snipaste_2024-02-26_15-49-59.png]] |
|
... | ... | \ No newline at end of file |
guide/service/updateByFilter.md
... | ... | @@ -0,0 +1,6 @@ |
1 | +# 批量更新-updateByFilter |
|
2 | + |
|
3 | +```java |
|
4 | +Map<String, Object> values = new HashMap<>(); |
|
5 | +getMeta().get("model").call("updateByFilter", values, filter); |
|
6 | +``` |
|
... | ... | \ No newline at end of file |
hazelcast.md
... | ... | @@ -0,0 +1,6 @@ |
1 | +# 使用入门 |
|
2 | + |
|
3 | + |
|
4 | +# web管理端使用教程 |
|
5 | +[hazelcast](http://192.168.168.25:32680/clusters/hazelcast-cluster) |
|
6 | + |
hazelcast\351\233\206\347\276\244\345\256\211\350\243\205.md
... | ... | @@ -0,0 +1,345 @@ |
1 | +### 在k8s上部署嵌入式模式的hazelcast |
|
2 | + |
|
3 | +将带有嵌入式Hazelcast的应用程序部署到Kubernetes集群中。 |
|
4 | + |
|
5 | +来自每个应用程序副本的Hazelcast实例都将自动发现它们自己,并形成一个一致的Hazelcast集群。得益于 Hazelcast Kubernetes自动发现插件,不需要静态地写死配置。 |
|
6 | + |
|
7 | + |
|
8 | +### 准备 |
|
9 | + |
|
10 | +- Docker (Docker for Desktop is good enough) |
|
11 | +- Kubernetes cluster (Docker for Desktop or Minikube is good enough) |
|
12 | +- Git |
|
13 | +- JDK 1.8+ |
|
14 | +- Apache Maven 3.2+ |
|
15 | + |
|
16 | +### 示例 |
|
17 | + |
|
18 | +我们可以将 Hazelcast 嵌入到任何基于jvm的应用程序中,并使用任何您想要的web框架。 |
|
19 | +作为本教程的一个示例,我们使用 Spring Boot 入门教程中的应用程序。我执行以下命令来下载: |
|
20 | + |
|
21 | + git clone https://github.com/hazelcast-guides/hazelcast-embedded-springboot.git |
|
22 | + |
|
23 | +### 配置 |
|
24 | + |
|
25 | +Hazelcast提供了专用的Hazelcast Kubernetes插件,允许在Kubernetes环境中自动形成Hazelcast集群。我们使用以下Hazelcast配置来启动这个功能。 |
|
26 | + |
|
27 | +```yaml |
|
28 | +hazelcast: |
|
29 | + network: |
|
30 | + join: |
|
31 | + multicast: |
|
32 | + enabled: false # 反使能多播模式 |
|
33 | + kubernetes: |
|
34 | + enabled: true # 使能kubernetes模式 |
|
35 | +``` |
|
36 | + |
|
37 | +要将此文件包含在Spring Boot项目中,将其复制到hazelcast-embedded-springboot/src/resources/中。 |
|
38 | + |
|
39 | +cp hazelcast.yaml hazelcast-embedded-springboot/src/main/resources/ |
|
40 | + |
|
41 | + |
|
42 | +现在,我们可以使用以下命令构建项目。 |
|
43 | + |
|
44 | + mvn package -f hazelcast-embedded-springboot/pom.xml |
|
45 | + |
|
46 | + |
|
47 | +作为输出,我们的应用程序的JAR文件应该在hazelcast-embedded-springboot/target/*. JAR中创建。 |
|
48 | + |
|
49 | +### 将应用程序容器化 |
|
50 | + |
|
51 | +要将应用程序容器化,需要安装Docker,我们可以使用以下Dockerfile |
|
52 | + |
|
53 | +``` |
|
54 | +FROM openjdk:8-jre-alpine |
|
55 | +COPY hazelcast-embedded-springboot/target/*.jar app.jar |
|
56 | +ENTRYPOINT ["java","-jar","app.jar"] |
|
57 | + |
|
58 | +编译成镜像 |
|
59 | +docker build -t hazelcastguides/hazelcast-embedded-kubernetes . |
|
60 | + |
|
61 | +推送到远程 |
|
62 | +docker push hazelcastguides/hazelcast-embedded-kubernetes |
|
63 | +``` |
|
64 | + |
|
65 | +### 配置Configure RBAC |
|
66 | + |
|
67 | +Hazelcast Kubernetes发现插件通过调用Kubernetes API来提供自动成员发现。因此,它需要授予特定的ClusterRole规则。我们可以使用以下命令应用最小的RBAC配置(对于默认名称空间中的默认服务帐户)。 |
|
68 | +```yaml |
|
69 | +kubectl apply -f https://raw.githubusercontent.com/hazelcast/hazelcast/master/kubernetes-rbac.yaml |
|
70 | + |
|
71 | +apiVersion: rbac.authorization.k8s.io/v1 |
|
72 | +kind: ClusterRole |
|
73 | +metadata: |
|
74 | + name: hazelcast-cluster-role |
|
75 | +rules: |
|
76 | + - apiGroups: |
|
77 | + - "" |
|
78 | + # Access to apps API is only required to support automatic cluster state management |
|
79 | + # when persistence (hot-restart) is enabled. |
|
80 | + - apps |
|
81 | + resources: |
|
82 | + - endpoints |
|
83 | + - pods |
|
84 | + - nodes |
|
85 | + - services |
|
86 | + # Access to statefulsets resource is only required to support automatic cluster state management |
|
87 | + # when persistence (hot-restart) is enabled. |
|
88 | + - statefulsets |
|
89 | + verbs: |
|
90 | + - get |
|
91 | + - list |
|
92 | + # Watching resources is only required to support automatic cluster state management |
|
93 | + # when persistence (hot-restart) is enabled. |
|
94 | + - watch |
|
95 | + - apiGroups: |
|
96 | + - "discovery.k8s.io" |
|
97 | + resources: |
|
98 | + - endpointslices |
|
99 | + verbs: |
|
100 | + - get |
|
101 | + - list |
|
102 | + |
|
103 | +--- |
|
104 | + |
|
105 | +apiVersion: rbac.authorization.k8s.io/v1 |
|
106 | +kind: ClusterRoleBinding |
|
107 | +metadata: |
|
108 | + name: hazelcast-cluster-role-binding |
|
109 | +roleRef: |
|
110 | + apiGroup: rbac.authorization.k8s.io |
|
111 | + kind: ClusterRole |
|
112 | + name: hazelcast-cluster-role |
|
113 | +subjects: |
|
114 | + - kind: ServiceAccount |
|
115 | + name: default |
|
116 | + namespace: default |
|
117 | +``` |
|
118 | + |
|
119 | +如果您使用非默认的服务帐户或非默认的命名空间,则需要修改 https://raw.githubusercontent.com/hazelcast/hazelcast/master/kubernetes-rbac.yaml |
|
120 | +如果您的Kubernetes集群不使用RBAC,则可以跳过整个“配置RBAC”步骤 |
|
121 | + |
|
122 | +### 将应用程序部署到Kubernetes |
|
123 | + |
|
124 | +可以直接 apply 以下的部署的yaml文件 |
|
125 | +```yaml |
|
126 | +apiVersion: apps/v1 |
|
127 | +kind: Deployment |
|
128 | +metadata: |
|
129 | + name: hazelcast-test |
|
130 | +spec: |
|
131 | + replicas: 2 |
|
132 | + selector: |
|
133 | + matchLabels: |
|
134 | + app: hazelcast-test |
|
135 | + template: |
|
136 | + metadata: |
|
137 | + labels: |
|
138 | + app: hazelcast-test |
|
139 | + app.hazelcast/name: iidp |
|
140 | + spec: |
|
141 | + containers: |
|
142 | + - name: hazelcast-test |
|
143 | + image: lizhanbin/hazelcast-test |
|
144 | + imagePullPolicy: Always |
|
145 | + resources: |
|
146 | + requests: |
|
147 | + memory: 100Mi |
|
148 | + |
|
149 | +--- |
|
150 | + |
|
151 | +apiVersion: v1 |
|
152 | +kind: Service |
|
153 | +metadata: |
|
154 | + name: my-app-svc |
|
155 | + labels: |
|
156 | + app: my-app-svc |
|
157 | +spec: |
|
158 | + type: ClusterIP |
|
159 | + ports: |
|
160 | + - port: 8080 |
|
161 | + selector: |
|
162 | + app: my-app |
|
163 | + |
|
164 | +``` |
|
165 | + |
|
166 | +然后我们直接查看组网情况 |
|
167 | +``` |
|
168 | +$ kubectl logs pod/my-app-86df8b785f-4x9pj |
|
169 | +... |
|
170 | +Members {size:2, ver:2} [ |
|
171 | + Member [10.24.1.10]:5701 - a7eb36b6-6d86-4d26-8eb6-47986e46d055 this |
|
172 | + Member [10.24.2.6]:5701 - 9994d6c6-d271-4ddd-9aa9-1ac4767c1a73 |
|
173 | +] |
|
174 | +``` |
|
175 | + |
|
176 | +### 测试应用程序 |
|
177 | + |
|
178 | +直接执行命令请求服务 |
|
179 | +```java |
|
180 | + |
|
181 | +$ curl --data "key=key1&value=hazelcast" "localhost:8080/put" |
|
182 | +{"value":"hazelcast"} |
|
183 | +$ curl "localhost:8080/get?key=key1" |
|
184 | +{"value":"hazelcast"} |
|
185 | + |
|
186 | +``` |
|
187 | + |
|
188 | +### 停止集群 |
|
189 | + |
|
190 | +```java |
|
191 | +kubectl delete deployment/my-app service/my-app |
|
192 | +kubectl delete -f https://raw.githubusercontent.com/hazelcast/hazelcast/master/kubernetes-rbac.yaml |
|
193 | + |
|
194 | +``` |
|
195 | + |
|
196 | +### sidecar形式部署 |
|
197 | + |
|
198 | +hazelcast支持以sidecar形式进行部署,从而可以跨语言来进行通信,而不仅仅是在java中使用,我们以golang为例,来实现一个集群,且与java版的hazelcast集群正常通信和组网。 |
|
199 | + |
|
200 | +我们编写 main.go |
|
201 | + |
|
202 | +```go |
|
203 | +package main |
|
204 | + |
|
205 | +import ( |
|
206 | + "context" |
|
207 | + "fmt" |
|
208 | + |
|
209 | + "github.com/gin-gonic/gin" |
|
210 | + "github.com/hazelcast/hazelcast-go-client" |
|
211 | +) |
|
212 | + |
|
213 | +var ( |
|
214 | + hz *hazelcast.Client |
|
215 | + mp *hazelcast.Map |
|
216 | +) |
|
217 | + |
|
218 | +func main() { |
|
219 | + r := gin.New() |
|
220 | + r.GET("/ping", func(c *gin.Context) { |
|
221 | + c.JSON(200, gin.H{ |
|
222 | + "message": "pong", |
|
223 | + "code": 200, |
|
224 | + }) |
|
225 | + }) |
|
226 | + |
|
227 | + r.POST("/set", func(c *gin.Context) { |
|
228 | + // get kv |
|
229 | + // get from hazelcast |
|
230 | + err := mp.Set(c, "foo", "bar") |
|
231 | + if err != nil { |
|
232 | + c.JSON(500, gin.H{ |
|
233 | + "message": "get error", |
|
234 | + "code": 500, |
|
235 | + }) |
|
236 | + return |
|
237 | + } |
|
238 | + c.JSON(200, gin.H{ |
|
239 | + "message": "ok", |
|
240 | + "code": 200, |
|
241 | + }) |
|
242 | + }) |
|
243 | + |
|
244 | + r.GET("/get", func(c *gin.Context) { |
|
245 | + // get kv |
|
246 | + // get from hazelcast |
|
247 | + resp, err := mp.Get(c, "foo") |
|
248 | + if err != nil { |
|
249 | + c.JSON(500, gin.H{ |
|
250 | + "message": "get error", |
|
251 | + "code": 500, |
|
252 | + }) |
|
253 | + return |
|
254 | + } |
|
255 | + c.JSON(200, gin.H{ |
|
256 | + "message": "ok", |
|
257 | + "code": 200, |
|
258 | + }) |
|
259 | + }) |
|
260 | + |
|
261 | + r.GET("/get", func(c *gin.Context) { |
|
262 | + // get kv |
|
263 | + // get from hazelcast |
|
264 | + resp, err := mp.Get(c, "foo") |
|
265 | + if err != nil { |
|
266 | + c.JSON(500, gin.H{ |
|
267 | + "message": "get error", |
|
268 | + "code": 500, |
|
269 | + }) |
|
270 | + return |
|
271 | + } |
|
272 | + c.JSON(200, gin.H{ |
|
273 | + "message": resp, |
|
274 | + "code": 200, |
|
275 | + }) |
|
276 | + }) |
|
277 | + |
|
278 | + r.Run(":8888") |
|
279 | +} |
|
280 | + |
|
281 | +func init() { |
|
282 | + ctx := context.TODO() |
|
283 | + |
|
284 | + cfg := hazelcast.Config{} |
|
285 | + cfg.Cluster.Name = "hazelcast-benchmark" |
|
286 | + // cfg.Cluster.Network.Addresses = []string{"192.168.168.176:5701"} |
|
287 | + |
|
288 | + var err error |
|
289 | + hz, err = hazelcast.StartNewClientWithConfig(ctx, cfg) |
|
290 | + if err != nil { |
|
291 | + panic(fmt.Errorf("starting the client with config: %w", err)) |
|
292 | + } |
|
293 | + mp, err = hz.GetMap(ctx, "my-distributed-map") |
|
294 | + if err != nil { |
|
295 | + panic(fmt.Errorf("trying to get a map: %w", err)) |
|
296 | + } |
|
297 | +} |
|
298 | + |
|
299 | +``` |
|
300 | + |
|
301 | + |
|
302 | + |
|
303 | +同样地,我们编写yaml文件来部署这个golang程序,并携带上sidecar,如下所示。得益于k8s容器机制,如果是在同一个pod下的容器,则天然是共享同一个网络的,那么就可以直接基于localhost来进行通信。 |
|
304 | + |
|
305 | +```golang |
|
306 | + |
|
307 | +apiVersion: apps/v1 |
|
308 | +kind: Deployment |
|
309 | +metadata: |
|
310 | + name: sidecar-test |
|
311 | +spec: |
|
312 | + replicas: 3 |
|
313 | + selector: |
|
314 | + matchLabels: |
|
315 | + app: test |
|
316 | + template: |
|
317 | + metadata: |
|
318 | + labels: |
|
319 | + app: test |
|
320 | + spec: |
|
321 | + containers: |
|
322 | + - name: test |
|
323 | + image: lizhanbin/sidecar-test |
|
324 | + imagePullPolicy: Always |
|
325 | + ports: |
|
326 | + - containerPort: 8888 |
|
327 | + |
|
328 | + - name: hazelcast |
|
329 | + image: hazelcast/hazelcast:5.3.6 |
|
330 | + lifecycle: |
|
331 | + type: Sidecar |
|
332 | + env: |
|
333 | + - name: HZ_CLUSTERNAME |
|
334 | + value: hazelcast-benchmark |
|
335 | + ports: |
|
336 | + - name: hazelcast |
|
337 | + containerPort: 5701 |
|
338 | + - name: mc |
|
339 | + image: hazelcast/management-center:latest-snapshot |
|
340 | + ports: |
|
341 | + - name: mc |
|
342 | + containerPort: 8080 |
|
343 | + |
|
344 | +``` |
|
345 | + |
iidp-helper \344\275\277\347\224\250\346\211\213\345\206\214.md
... | ... | @@ -0,0 +1,220 @@ |
1 | +### 参考 |
|
2 | + |
|
3 | +[发版详情](http://192.168.175.198:10001/iidpwiki/iidp-plugin/change-log.md) |
|
4 | + |
|
5 | + |
|
6 | +### 1、需求分析 |
|
7 | +我们在这里均以 ```sie-snest-log``` 操作日志这个app为例来描述```iidp-helper```插件的需求、设计和实现方案。 |
|
8 | + |
|
9 | +- 我们定义一个model的时候,采取的注解格式是这样的: |
|
10 | +```java |
|
11 | +@Model(name = "operator_log", parent = "operator_log", displayName = "操作日志", isAutoLog = Bool.True, |
|
12 | + type = Model.ModelType.Buss) |
|
13 | +public class OperatorLog extends BaseModel<OperatorLog> { |
|
14 | + |
|
15 | + @Property(displayName = "id") |
|
16 | + private String id; |
|
17 | + |
|
18 | + @Property(displayName = "访问时间", dataType = DataType.DATE_TIME) |
|
19 | + private Date accessStartTime; |
|
20 | + |
|
21 | + @Property(displayName = "app名称") |
|
22 | + private String appName; |
|
23 | + |
|
24 | + @Property(displayName = "模型名称") |
|
25 | + private String logModelName; |
|
26 | + |
|
27 | + @Property(displayName = "服务名称") |
|
28 | + private String serviceName; |
|
29 | + |
|
30 | + @Property(displayName = "入参", dataType = DataType.TEXT, widget = "codeeditor") |
|
31 | + private String inParameterData; |
|
32 | + |
|
33 | + @Property(displayName = "出参", dataType = DataType.TEXT, widget = "codeeditor") |
|
34 | + private String outParameterData; |
|
35 | + |
|
36 | + // 用户id |
|
37 | + @ManyToOne(targetModel = "rbac_user", displayName = "用户") |
|
38 | + @JoinColumn(name = "caller_user_id") |
|
39 | + private Map<String, Object> callerUserId; |
|
40 | + |
|
41 | + // ip |
|
42 | + @Property(displayName = "ip") |
|
43 | + private String callerIp; |
|
44 | + |
|
45 | + // 耗时 |
|
46 | + @Property(displayName = "耗时(ms)") |
|
47 | + private String takeTime; |
|
48 | + |
|
49 | + // 变更 |
|
50 | + @Property(displayName = "数据变更") |
|
51 | + private Boolean modify; |
|
52 | +} |
|
53 | +``` |
|
54 | +比如说在上述模型定义的注解中有个 ```parent```字段,它是一个string,意味着它可以写任何的字符串,在编译阶段是不知道parent是否存在,是否填写正确的, |
|
55 | +这给开发阶段带来一些容易出错的地方,这些错误只能到运行阶段才能发现,而且还不容易发现。 |
|
56 | + |
|
57 | +- 访问模型名称和模型字段等 |
|
58 | + 继续举例通过meta访问模型数据的方式: |
|
59 | +```java |
|
60 | + meta = new Meta(Meta.SUPERUSER, new HashMap<>()); |
|
61 | + meta.get("operator_log").call("create", operatorLogList); |
|
62 | + |
|
63 | + RecordSet rs2 = meta.get("operator_details"); |
|
64 | + for (OperatorLog ol : operatorLogList) { |
|
65 | + rs2.call("create", ol.getOperatorDetailsList()); |
|
66 | + } |
|
67 | +``` |
|
68 | + |
|
69 | +```java |
|
70 | + OperatorLog operatorLog = DbUtils.select(filter, "operator_log", OperatorLog.class); |
|
71 | + Filter f = Filter.equal("traceID", filter.getFilterOp("id").getValue()); |
|
72 | +``` |
|
73 | +同样地发现存在 operator_log、create、traceID 等都是string字符串的形式存在,在编译阶段也不会做任何的校验, |
|
74 | +只能在运行阶段才发现问题,而且还不容易发现,比如 traceID 写错了,只是这个filter失效,不会报错,但查出的结果却是不对的。 |
|
75 | + |
|
76 | +- 元模型get set方法 |
|
77 | +```java |
|
78 | + // getter setter |
|
79 | + public String getID() { |
|
80 | + return this.getStr("id"); |
|
81 | + } |
|
82 | + |
|
83 | + public void setID(String id) { |
|
84 | + this.set("id", id); |
|
85 | + } |
|
86 | + |
|
87 | + public Date getAccessStartTime() { |
|
88 | + return this.getDate("accessStartTime"); |
|
89 | + } |
|
90 | + |
|
91 | + public void setAccessStartTime(Date accessStartTime) { |
|
92 | + this.set("accessStartTime", accessStartTime); |
|
93 | + } |
|
94 | + |
|
95 | + public Date getAccessEndTime() { |
|
96 | + return this.getDate("accessEndTime"); |
|
97 | + } |
|
98 | + |
|
99 | + public void setAccessEndTime(Date accessEndTime) { |
|
100 | + this.set("accessEndTime", accessEndTime); |
|
101 | + } |
|
102 | + |
|
103 | + public String getAppName() { |
|
104 | + return this.getStr("appName"); |
|
105 | + } |
|
106 | + |
|
107 | + public void setAppName(String appName) { |
|
108 | + this.set("appName", appName); |
|
109 | + } |
|
110 | + |
|
111 | + public String getLogModelName() { |
|
112 | + return this.getStr("logModelName"); |
|
113 | + } |
|
114 | + |
|
115 | + public void setLogModelName(String logModelName) { |
|
116 | + this.set("logModelName", logModelName); |
|
117 | + } |
|
118 | + |
|
119 | + public String getServiceName() { |
|
120 | + return this.getStr("serviceName"); |
|
121 | + } |
|
122 | + |
|
123 | + public void setServiceName(String serviceName) { |
|
124 | + this.set("serviceName", serviceName); |
|
125 | + } |
|
126 | + |
|
127 | + public String getInParameterData() { |
|
128 | + return this.getStr("inParameterData"); |
|
129 | + } |
|
130 | + |
|
131 | + public void setInParameterData(String inParameterData) { |
|
132 | + this.set("inParameterData", inParameterData); |
|
133 | + } |
|
134 | + |
|
135 | + public String getOutParameterData() { |
|
136 | + return this.getStr("outParameterData"); |
|
137 | + } |
|
138 | + |
|
139 | + public void setOutParameterData(String outParameterData) { |
|
140 | + this.set("outParameterData", outParameterData); |
|
141 | + } |
|
142 | + |
|
143 | + public int getResultDisplay() { |
|
144 | + return this.getInt("resultDisplay"); |
|
145 | + } |
|
146 | + |
|
147 | + public void setResultDisplay(int resultDisplay) { |
|
148 | + this.set("resultDisplay", resultDisplay); |
|
149 | + } |
|
150 | + |
|
151 | + public String getAbnormalDisplay() { |
|
152 | + return this.getStr("abnormalDisplay"); |
|
153 | + } |
|
154 | + |
|
155 | + public void setAbnormalDisplay(String abnormalDisplay) { |
|
156 | + this.set("abnormalDisplay", abnormalDisplay); |
|
157 | + } |
|
158 | + |
|
159 | + public String getCallerUserName() { |
|
160 | + return this.getStr("callerUserName"); |
|
161 | + } |
|
162 | + |
|
163 | + public void setCallerUserName(String callerUserName) { |
|
164 | + this.set("callerUserName", callerUserName); |
|
165 | + } |
|
166 | + |
|
167 | + public String getCallerUserId() { |
|
168 | + return this.getStr("callerUserId"); |
|
169 | + } |
|
170 | + |
|
171 | + public void setCallerUserId(String callerUserId) { |
|
172 | + this.set("callerUserId", callerUserId); |
|
173 | + } |
|
174 | + |
|
175 | + public String getCallerIp() { |
|
176 | + return this.getStr("callerIp"); |
|
177 | + } |
|
178 | + |
|
179 | + public void setCallerIp(String callerIp) { |
|
180 | + this.set("callerIp", callerIp); |
|
181 | + } |
|
182 | + |
|
183 | + public String getTakeTime() { |
|
184 | + return this.getStr("takeTime"); |
|
185 | + } |
|
186 | + |
|
187 | + public void setTakeTime(String takeTime) { |
|
188 | + this.set("takeTime", takeTime); |
|
189 | + } |
|
190 | + |
|
191 | + public Boolean getModify() { |
|
192 | + return this.getBoolean("modify"); |
|
193 | + } |
|
194 | + |
|
195 | + public void setModify(Boolean modify) { |
|
196 | + this.set("modify", modify); |
|
197 | + } |
|
198 | + |
|
199 | + public List<OperatorDetails> getOperatorDetailsList() { |
|
200 | + return (List<OperatorDetails>) this.get("operatorDetailsList"); |
|
201 | + } |
|
202 | + |
|
203 | + public void setOperatorDetailsList(List<OperatorDetails> operatorDetailsList) { |
|
204 | + this.set("operatorDetailsList", operatorDetailsList); |
|
205 | + } |
|
206 | +``` |
|
207 | +有时候我们需要从模型中get set对应成员变量的值,这些方法写起来也相对繁琐,而且很容易写错,因为这里的get其实会去查数据库, |
|
208 | +而有时候我们需要的是从map直接获取值而已,但如果直接使用get方法并不是从map中获取值,所以这里会有一些容易混淆的地方。 |
|
209 | + |
|
210 | + |
|
211 | +总结:从上述的几种基于iidp平台编写app的过程中,我们发现有很多地方都是基于string字符串字面量的形式来表述,很容易写错, |
|
212 | +而且没办法在编译器进行校验,这给app开发者带来了很多麻烦,降低了开发的效率。如果我们能够提供一个插件,对模型名称、模型属性和模型方法等字符串字面量给与它本身的含义,变得结构化起来,那么在开发阶段会让开发者更容易识别这些含义,同时也很容易检测出错误,提高开发者开发效率,同时也能保持代码的精简。 |
|
213 | + |
|
214 | +### 2、方案设计 |
|
215 | + |
|
216 | +建立模型名称、模型属性和模型方法与它的字符串字面量建立引用关系,同时对字符串进行着色、跳转和检测 |
|
217 | + |
|
218 | +### 3、方案实现 |
|
219 | + |
|
220 | +基于 IDEA插件开发api,建立字符串字面量到实际定义处的引用,同时扫描当前工程的所有模型文件,建立所有模型的索引信息,从而达到模型名称、属性和方法的校验等功能。 |
|
... | ... | \ No newline at end of file |
iidp-helper \350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md
... | ... | @@ -0,0 +1,220 @@ |
1 | +### 参考 |
|
2 | + |
|
3 | +[版本发布](http://192.168.175.198:10001/iidpwiki/iidp-plugin/change-log.md) |
|
4 | + |
|
5 | + |
|
6 | +### 1、需求分析 |
|
7 | +我们在这里均以 ```sie-snest-log``` 操作日志这个app为例来描述```iidp-helper```插件的需求、设计和实现方案。 |
|
8 | + |
|
9 | +- 我们定义一个model的时候,采取的注解格式是这样的: |
|
10 | +```java |
|
11 | +@Model(name = "operator_log", parent = "operator_log", displayName = "操作日志", isAutoLog = Bool.True, |
|
12 | + type = Model.ModelType.Buss) |
|
13 | +public class OperatorLog extends BaseModel<OperatorLog> { |
|
14 | + |
|
15 | + @Property(displayName = "id") |
|
16 | + private String id; |
|
17 | + |
|
18 | + @Property(displayName = "访问时间", dataType = DataType.DATE_TIME) |
|
19 | + private Date accessStartTime; |
|
20 | + |
|
21 | + @Property(displayName = "app名称") |
|
22 | + private String appName; |
|
23 | + |
|
24 | + @Property(displayName = "模型名称") |
|
25 | + private String logModelName; |
|
26 | + |
|
27 | + @Property(displayName = "服务名称") |
|
28 | + private String serviceName; |
|
29 | + |
|
30 | + @Property(displayName = "入参", dataType = DataType.TEXT, widget = "codeeditor") |
|
31 | + private String inParameterData; |
|
32 | + |
|
33 | + @Property(displayName = "出参", dataType = DataType.TEXT, widget = "codeeditor") |
|
34 | + private String outParameterData; |
|
35 | + |
|
36 | + // 用户id |
|
37 | + @ManyToOne(targetModel = "rbac_user", displayName = "用户") |
|
38 | + @JoinColumn(name = "caller_user_id") |
|
39 | + private Map<String, Object> callerUserId; |
|
40 | + |
|
41 | + // ip |
|
42 | + @Property(displayName = "ip") |
|
43 | + private String callerIp; |
|
44 | + |
|
45 | + // 耗时 |
|
46 | + @Property(displayName = "耗时(ms)") |
|
47 | + private String takeTime; |
|
48 | + |
|
49 | + // 变更 |
|
50 | + @Property(displayName = "数据变更") |
|
51 | + private Boolean modify; |
|
52 | +} |
|
53 | +``` |
|
54 | +比如说在上述模型定义的注解中有个 ```parent```字段,它是一个string,意味着它可以写任何的字符串,在编译阶段是不知道parent是否存在,是否填写正确的, |
|
55 | +这给开发阶段带来一些容易出错的地方,这些错误只能到运行阶段才能发现,而且还不容易发现。 |
|
56 | + |
|
57 | +- 访问模型名称和模型字段等 |
|
58 | + 继续举例通过meta访问模型数据的方式: |
|
59 | +```java |
|
60 | + meta = new Meta(Meta.SUPERUSER, new HashMap<>()); |
|
61 | + meta.get("operator_log").call("create", operatorLogList); |
|
62 | + |
|
63 | + RecordSet rs2 = meta.get("operator_details"); |
|
64 | + for (OperatorLog ol : operatorLogList) { |
|
65 | + rs2.call("create", ol.getOperatorDetailsList()); |
|
66 | + } |
|
67 | +``` |
|
68 | + |
|
69 | +```java |
|
70 | + OperatorLog operatorLog = DbUtils.select(filter, "operator_log", OperatorLog.class); |
|
71 | + Filter f = Filter.equal("traceID", filter.getFilterOp("id").getValue()); |
|
72 | +``` |
|
73 | +同样地发现存在 operator_log、create、traceID 等都是string字符串的形式存在,在编译阶段也不会做任何的校验, |
|
74 | +只能在运行阶段才发现问题,而且还不容易发现,比如 traceID 写错了,只是这个filter失效,不会报错,但查出的结果却是不对的。 |
|
75 | + |
|
76 | +- 元模型get set方法 |
|
77 | +```java |
|
78 | + // getter setter |
|
79 | + public String getID() { |
|
80 | + return this.getStr("id"); |
|
81 | + } |
|
82 | + |
|
83 | + public void setID(String id) { |
|
84 | + this.set("id", id); |
|
85 | + } |
|
86 | + |
|
87 | + public Date getAccessStartTime() { |
|
88 | + return this.getDate("accessStartTime"); |
|
89 | + } |
|
90 | + |
|
91 | + public void setAccessStartTime(Date accessStartTime) { |
|
92 | + this.set("accessStartTime", accessStartTime); |
|
93 | + } |
|
94 | + |
|
95 | + public Date getAccessEndTime() { |
|
96 | + return this.getDate("accessEndTime"); |
|
97 | + } |
|
98 | + |
|
99 | + public void setAccessEndTime(Date accessEndTime) { |
|
100 | + this.set("accessEndTime", accessEndTime); |
|
101 | + } |
|
102 | + |
|
103 | + public String getAppName() { |
|
104 | + return this.getStr("appName"); |
|
105 | + } |
|
106 | + |
|
107 | + public void setAppName(String appName) { |
|
108 | + this.set("appName", appName); |
|
109 | + } |
|
110 | + |
|
111 | + public String getLogModelName() { |
|
112 | + return this.getStr("logModelName"); |
|
113 | + } |
|
114 | + |
|
115 | + public void setLogModelName(String logModelName) { |
|
116 | + this.set("logModelName", logModelName); |
|
117 | + } |
|
118 | + |
|
119 | + public String getServiceName() { |
|
120 | + return this.getStr("serviceName"); |
|
121 | + } |
|
122 | + |
|
123 | + public void setServiceName(String serviceName) { |
|
124 | + this.set("serviceName", serviceName); |
|
125 | + } |
|
126 | + |
|
127 | + public String getInParameterData() { |
|
128 | + return this.getStr("inParameterData"); |
|
129 | + } |
|
130 | + |
|
131 | + public void setInParameterData(String inParameterData) { |
|
132 | + this.set("inParameterData", inParameterData); |
|
133 | + } |
|
134 | + |
|
135 | + public String getOutParameterData() { |
|
136 | + return this.getStr("outParameterData"); |
|
137 | + } |
|
138 | + |
|
139 | + public void setOutParameterData(String outParameterData) { |
|
140 | + this.set("outParameterData", outParameterData); |
|
141 | + } |
|
142 | + |
|
143 | + public int getResultDisplay() { |
|
144 | + return this.getInt("resultDisplay"); |
|
145 | + } |
|
146 | + |
|
147 | + public void setResultDisplay(int resultDisplay) { |
|
148 | + this.set("resultDisplay", resultDisplay); |
|
149 | + } |
|
150 | + |
|
151 | + public String getAbnormalDisplay() { |
|
152 | + return this.getStr("abnormalDisplay"); |
|
153 | + } |
|
154 | + |
|
155 | + public void setAbnormalDisplay(String abnormalDisplay) { |
|
156 | + this.set("abnormalDisplay", abnormalDisplay); |
|
157 | + } |
|
158 | + |
|
159 | + public String getCallerUserName() { |
|
160 | + return this.getStr("callerUserName"); |
|
161 | + } |
|
162 | + |
|
163 | + public void setCallerUserName(String callerUserName) { |
|
164 | + this.set("callerUserName", callerUserName); |
|
165 | + } |
|
166 | + |
|
167 | + public String getCallerUserId() { |
|
168 | + return this.getStr("callerUserId"); |
|
169 | + } |
|
170 | + |
|
171 | + public void setCallerUserId(String callerUserId) { |
|
172 | + this.set("callerUserId", callerUserId); |
|
173 | + } |
|
174 | + |
|
175 | + public String getCallerIp() { |
|
176 | + return this.getStr("callerIp"); |
|
177 | + } |
|
178 | + |
|
179 | + public void setCallerIp(String callerIp) { |
|
180 | + this.set("callerIp", callerIp); |
|
181 | + } |
|
182 | + |
|
183 | + public String getTakeTime() { |
|
184 | + return this.getStr("takeTime"); |
|
185 | + } |
|
186 | + |
|
187 | + public void setTakeTime(String takeTime) { |
|
188 | + this.set("takeTime", takeTime); |
|
189 | + } |
|
190 | + |
|
191 | + public Boolean getModify() { |
|
192 | + return this.getBoolean("modify"); |
|
193 | + } |
|
194 | + |
|
195 | + public void setModify(Boolean modify) { |
|
196 | + this.set("modify", modify); |
|
197 | + } |
|
198 | + |
|
199 | + public List<OperatorDetails> getOperatorDetailsList() { |
|
200 | + return (List<OperatorDetails>) this.get("operatorDetailsList"); |
|
201 | + } |
|
202 | + |
|
203 | + public void setOperatorDetailsList(List<OperatorDetails> operatorDetailsList) { |
|
204 | + this.set("operatorDetailsList", operatorDetailsList); |
|
205 | + } |
|
206 | +``` |
|
207 | +有时候我们需要从模型中get set对应成员变量的值,这些方法写起来也相对繁琐,而且很容易写错,因为这里的get其实会去查数据库, |
|
208 | +而有时候我们需要的是从map直接获取值而已,但如果直接使用get方法并不是从map中获取值,所以这里会有一些容易混淆的地方。 |
|
209 | + |
|
210 | + |
|
211 | +总结:从上述的几种基于iidp平台编写app的过程中,我们发现有很多地方都是基于string字符串字面量的形式来表述,很容易写错, |
|
212 | +而且没办法在编译器进行校验,这给app开发者带来了很多麻烦,降低了开发的效率。如果我们能够提供一个插件,对模型名称、模型属性和模型方法等字符串字面量给与它本身的含义,变得结构化起来,那么在开发阶段会让开发者更容易识别这些含义,同时也很容易检测出错误,提高开发者开发效率,同时也能保持代码的精简。 |
|
213 | + |
|
214 | +### 2、方案设计 |
|
215 | + |
|
216 | +建立模型名称、模型属性和模型方法与它的字符串字面量建立引用关系,同时对字符串进行着色、跳转和检测 |
|
217 | + |
|
218 | +### 3、方案实现 |
|
219 | + |
|
220 | +基于 IDEA插件开发api,建立字符串字面量到实际定义处的引用,同时扫描当前工程的所有模型文件,建立所有模型的索引信息,从而达到模型名称、属性和方法的校验等功能。 |
|
... | ... | \ No newline at end of file |
iidp-plugin \350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md
... | ... | @@ -0,0 +1,302 @@ |
1 | +<font color=Blue> 下载插件:</font> |
|
2 | +[[iidp-sonar-0.0.1.zip|/uploads/iidp-plugin/iidp-sonar-0.0.1.zip]] |
|
3 | +### 1、需求分析 |
|
4 | +我们在这里均以 ```sie-snest-log``` 操作日志这个app为例来描述```iidp-plugin```的需求、设计和实现方案。 |
|
5 | + |
|
6 | +- 我们定义一个model的时候,采取的注解格式是这样的: |
|
7 | +```java |
|
8 | +@Model(name = "operator_log", parent = "operator_log", displayName = "操作日志", isAutoLog = Bool.True, |
|
9 | + type = Model.ModelType.Buss) |
|
10 | +public class OperatorLog extends BaseModel<OperatorLog> { |
|
11 | + |
|
12 | + @Property(displayName = "id") |
|
13 | + private String id; |
|
14 | + |
|
15 | + @Property(displayName = "访问时间", dataType = DataType.DATE_TIME) |
|
16 | + private Date accessStartTime; |
|
17 | + |
|
18 | + @Property(displayName = "app名称") |
|
19 | + private String appName; |
|
20 | + |
|
21 | + @Property(displayName = "模型名称") |
|
22 | + private String logModelName; |
|
23 | + |
|
24 | + @Property(displayName = "服务名称") |
|
25 | + private String serviceName; |
|
26 | + |
|
27 | + @Property(displayName = "入参", dataType = DataType.TEXT, widget = "codeeditor") |
|
28 | + private String inParameterData; |
|
29 | + |
|
30 | + @Property(displayName = "出参", dataType = DataType.TEXT, widget = "codeeditor") |
|
31 | + private String outParameterData; |
|
32 | + |
|
33 | + // 用户id |
|
34 | + @ManyToOne(targetModel = "rbac_user", displayName = "用户") |
|
35 | + @JoinColumn(name = "caller_user_id") |
|
36 | + private Map<String, Object> callerUserId; |
|
37 | + |
|
38 | + // ip |
|
39 | + @Property(displayName = "ip") |
|
40 | + private String callerIp; |
|
41 | + |
|
42 | + // 耗时 |
|
43 | + @Property(displayName = "耗时(ms)") |
|
44 | + private String takeTime; |
|
45 | + |
|
46 | + // 变更 |
|
47 | + @Property(displayName = "数据变更") |
|
48 | + private Boolean modify; |
|
49 | +} |
|
50 | +``` |
|
51 | +比如说在上述模型定义的注解中有个 ```parent```字段,它是一个string,意味着它可以写任何的字符串,在编译阶段是不知道parent是否存在,是否填写正确的, |
|
52 | +这给开发阶段带来一些容易出错的地方,这些错误只能到运行阶段才能发现,而且还不容易发现。 |
|
53 | + |
|
54 | +- 我们定义一个views的时候,采取的是json的描述方式: |
|
55 | +```json |
|
56 | +"operator_log_grid": { |
|
57 | + "body": { |
|
58 | + "buttons": [ |
|
59 | + { |
|
60 | + "action": "preview", |
|
61 | + "auth": "read", |
|
62 | + "name": "详情" |
|
63 | + }, |
|
64 | + { |
|
65 | + "action": "previewEr", |
|
66 | + "auth": "read", |
|
67 | + "name": "参数" |
|
68 | + } |
|
69 | + ], |
|
70 | + "columns": [ |
|
71 | + { |
|
72 | + "displayName": "访问时间", |
|
73 | + "name": "accessStartTime" |
|
74 | + }, |
|
75 | + { |
|
76 | + "displayName": "app名称", |
|
77 | + "name": "appName" |
|
78 | + }, |
|
79 | + { |
|
80 | + "displayName": "模型名称", |
|
81 | + "name": "logModelName" |
|
82 | + }, |
|
83 | + { |
|
84 | + "displayName": "服务名称", |
|
85 | + "name": "serviceName" |
|
86 | + }, |
|
87 | + { |
|
88 | + "displayName": "调用者用户id", |
|
89 | + "name": "callerUserId" |
|
90 | + }, |
|
91 | + { |
|
92 | + "displayName": "ip", |
|
93 | + "name": "callerIp" |
|
94 | + }, |
|
95 | + { |
|
96 | + "displayName": "耗时", |
|
97 | + "name": "takeTime" |
|
98 | + }, |
|
99 | + { |
|
100 | + "displayName": "数据变更", |
|
101 | + "name": "modify" |
|
102 | + } |
|
103 | + ], |
|
104 | + "tbar": [ |
|
105 | + { |
|
106 | + "action": "delete", |
|
107 | + "auth": "delete", |
|
108 | + "name": "删除" |
|
109 | + } |
|
110 | + ], |
|
111 | + "type": "grid" |
|
112 | + }, |
|
113 | + "mode": "primary", |
|
114 | + "model": "operator_log", |
|
115 | + "name": "操作日志-表格", |
|
116 | + "type": "grid" |
|
117 | + } |
|
118 | +``` |
|
119 | +结合model的定义,能够发现views中的json定义也是基于string,比如```columns.name```是跟model中定义的```private String appName;```定义的成员变量名称保持一致,如果在开发阶段编写错了, |
|
120 | +也是无法发现的,只能在运行阶段前端获取view的时候出错。 |
|
121 | + |
|
122 | +- 访问模型名称和模型字段等 |
|
123 | +继续举例通过meta访问模型数据的方式: |
|
124 | +```java |
|
125 | + meta = new Meta(Meta.SUPERUSER, new HashMap<>()); |
|
126 | + meta.get("operator_log").call("create", operatorLogList); |
|
127 | + |
|
128 | + RecordSet rs2 = meta.get("operator_details"); |
|
129 | + for (OperatorLog ol : operatorLogList) { |
|
130 | + rs2.call("create", ol.getOperatorDetailsList()); |
|
131 | + } |
|
132 | +``` |
|
133 | + |
|
134 | + ```java |
|
135 | + OperatorLog operatorLog = DbUtils.select(filter, "operator_log", OperatorLog.class); |
|
136 | + Filter f = Filter.equal("traceID", filter.getFilterOp("id").getValue()); |
|
137 | + ``` |
|
138 | +同样地发现存在 operator_log、create、traceID 等都是string字符串的形式存在,在编译阶段也不会做任何的校验, |
|
139 | +只能在运行阶段才发现问题,而且还不容易发现,比如 traceID 写错了,只是这个filter失效,不会报错,但查出的结果却是不对的。 |
|
140 | + |
|
141 | +- 元模型get set方法 |
|
142 | +```java |
|
143 | + // getter setter |
|
144 | + public String getID() { |
|
145 | + return this.getStr("id"); |
|
146 | + } |
|
147 | + |
|
148 | + public void setID(String id) { |
|
149 | + this.set("id", id); |
|
150 | + } |
|
151 | + |
|
152 | + public Date getAccessStartTime() { |
|
153 | + return this.getDate("accessStartTime"); |
|
154 | + } |
|
155 | + |
|
156 | + public void setAccessStartTime(Date accessStartTime) { |
|
157 | + this.set("accessStartTime", accessStartTime); |
|
158 | + } |
|
159 | + |
|
160 | + public Date getAccessEndTime() { |
|
161 | + return this.getDate("accessEndTime"); |
|
162 | + } |
|
163 | + |
|
164 | + public void setAccessEndTime(Date accessEndTime) { |
|
165 | + this.set("accessEndTime", accessEndTime); |
|
166 | + } |
|
167 | + |
|
168 | + public String getAppName() { |
|
169 | + return this.getStr("appName"); |
|
170 | + } |
|
171 | + |
|
172 | + public void setAppName(String appName) { |
|
173 | + this.set("appName", appName); |
|
174 | + } |
|
175 | + |
|
176 | + public String getLogModelName() { |
|
177 | + return this.getStr("logModelName"); |
|
178 | + } |
|
179 | + |
|
180 | + public void setLogModelName(String logModelName) { |
|
181 | + this.set("logModelName", logModelName); |
|
182 | + } |
|
183 | + |
|
184 | + public String getServiceName() { |
|
185 | + return this.getStr("serviceName"); |
|
186 | + } |
|
187 | + |
|
188 | + public void setServiceName(String serviceName) { |
|
189 | + this.set("serviceName", serviceName); |
|
190 | + } |
|
191 | + |
|
192 | + public String getInParameterData() { |
|
193 | + return this.getStr("inParameterData"); |
|
194 | + } |
|
195 | + |
|
196 | + public void setInParameterData(String inParameterData) { |
|
197 | + this.set("inParameterData", inParameterData); |
|
198 | + } |
|
199 | + |
|
200 | + public String getOutParameterData() { |
|
201 | + return this.getStr("outParameterData"); |
|
202 | + } |
|
203 | + |
|
204 | + public void setOutParameterData(String outParameterData) { |
|
205 | + this.set("outParameterData", outParameterData); |
|
206 | + } |
|
207 | + |
|
208 | + public int getResultDisplay() { |
|
209 | + return this.getInt("resultDisplay"); |
|
210 | + } |
|
211 | + |
|
212 | + public void setResultDisplay(int resultDisplay) { |
|
213 | + this.set("resultDisplay", resultDisplay); |
|
214 | + } |
|
215 | + |
|
216 | + public String getAbnormalDisplay() { |
|
217 | + return this.getStr("abnormalDisplay"); |
|
218 | + } |
|
219 | + |
|
220 | + public void setAbnormalDisplay(String abnormalDisplay) { |
|
221 | + this.set("abnormalDisplay", abnormalDisplay); |
|
222 | + } |
|
223 | + |
|
224 | + public String getCallerUserName() { |
|
225 | + return this.getStr("callerUserName"); |
|
226 | + } |
|
227 | + |
|
228 | + public void setCallerUserName(String callerUserName) { |
|
229 | + this.set("callerUserName", callerUserName); |
|
230 | + } |
|
231 | + |
|
232 | + public String getCallerUserId() { |
|
233 | + return this.getStr("callerUserId"); |
|
234 | + } |
|
235 | + |
|
236 | + public void setCallerUserId(String callerUserId) { |
|
237 | + this.set("callerUserId", callerUserId); |
|
238 | + } |
|
239 | + |
|
240 | + public String getCallerIp() { |
|
241 | + return this.getStr("callerIp"); |
|
242 | + } |
|
243 | + |
|
244 | + public void setCallerIp(String callerIp) { |
|
245 | + this.set("callerIp", callerIp); |
|
246 | + } |
|
247 | + |
|
248 | + public String getTakeTime() { |
|
249 | + return this.getStr("takeTime"); |
|
250 | + } |
|
251 | + |
|
252 | + public void setTakeTime(String takeTime) { |
|
253 | + this.set("takeTime", takeTime); |
|
254 | + } |
|
255 | + |
|
256 | + public Boolean getModify() { |
|
257 | + return this.getBoolean("modify"); |
|
258 | + } |
|
259 | + |
|
260 | + public void setModify(Boolean modify) { |
|
261 | + this.set("modify", modify); |
|
262 | + } |
|
263 | + |
|
264 | + public List<OperatorDetails> getOperatorDetailsList() { |
|
265 | + return (List<OperatorDetails>) this.get("operatorDetailsList"); |
|
266 | + } |
|
267 | + |
|
268 | + public void setOperatorDetailsList(List<OperatorDetails> operatorDetailsList) { |
|
269 | + this.set("operatorDetailsList", operatorDetailsList); |
|
270 | + } |
|
271 | +``` |
|
272 | +有时候我们需要从模型中get set对应成员变量的值,这些方法写起来也相对繁琐,而且很容易写错,因为这里的get其实会去查数据库, |
|
273 | +而有时候我们需要的是从map直接获取值而已,但如果直接使用get方法并不是从map中获取值,所以这里会有一些容易混淆的地方。 |
|
274 | + |
|
275 | + |
|
276 | +总结:从上述的几种基于iidp编写app的过程中,我们发现有很多地方都是基于string字符串的形式来表述,很容易写错, |
|
277 | +而且没办法在编译器进行校验,这给app开发者带来了很多麻烦,降低了开发的效率。如果我们能够提供一个插件,生成获取模型filed的字符串方法,生成获取模型名称字符串方法,更通用地说生成任何字符串的方法,那么在开发阶段,只需要调用className.getFiled(),就能够获取所需的字符串,这个获取是确定性的,IDE会进行静态检查,不存在不小心输入错误的可能,提高开发者开发效率,同时也能保持代码的精简。 |
|
278 | + |
|
279 | +### 2、方案设计 |
|
280 | +- 原理 |
|
281 | +[[/uploads/iidp-plugin/jsr-269.png]] |
|
282 | + |
|
283 | +(1):准备过程,初始化插入式注解处理器 |
|
284 | +(2):词法分析,语法分析 |
|
285 | +(3):输入到符号表 |
|
286 | +(4):注解处理 |
|
287 | +(5):分析及字节码生成 |
|
288 | +(6):标注 |
|
289 | +(7):数据流分析 |
|
290 | +(8):解语法糖 |
|
291 | +(9):字节码生成 |
|
292 | + |
|
293 | +- 参考lombok进行代码生成 |
|
294 | + 1. 基于注解的process处理 |
|
295 | + 2. 生成指定的代码 |
|
296 | + |
|
297 | +- 生成符号表,对特定字符串进行校验和提示 |
|
298 | + 1. 对引用的字符串进行校验 |
|
299 | + 2. 对字符串进行智能跳转 |
|
300 | + |
|
301 | +### 3、方案实现 |
|
302 | + |
iidp-plugin.md
... | ... | @@ -0,0 +1,13 @@ |
1 | +### [[iidp-plugin/change-log]] |
|
2 | + |
|
3 | +### [[iidp-plugin/iidp-helper用户手册.md]] |
|
4 | + |
|
5 | +### ~~[[iidp-plugin/iidp-plugin 设计与实现.md]]~~ |
|
6 | + |
|
7 | +### [[iidp-plugin/tutorials]] |
|
8 | + |
|
9 | + |
|
10 | + |
|
11 | +### [[iidp-plugin/iidp-plugin 用户反馈]] |
|
12 | + |
|
13 | + |
iidp-plugin/change-log.md
... | ... | @@ -0,0 +1,157 @@ |
1 | +## <font color=green>v0.2.2-dev</font> |
|
2 | +插件下载: [[iidp-helper-v0.2.2-dev|/uploads/iidp-plugin/iidp-helper-0.2.2-dev.jar]] |
|
3 | +### change log |
|
4 | +- 修复当模型属性类型是 boolen 时,IDEA 提示get开头方法的问题,现已修复为提示正确的 is 开头方法 |
|
5 | + |
|
6 | +### mvn repository |
|
7 | +```xml |
|
8 | + <dependency> |
|
9 | + <groupId>com.sie.meta</groupId> |
|
10 | + <artifactId>sie-iidp-plugin</artifactId> |
|
11 | + <version>0.3.1</version> |
|
12 | + </dependency> |
|
13 | +``` |
|
14 | +<hr> |
|
15 | + |
|
16 | +## <font color=green>v0.2.1-dev</font> |
|
17 | +插件下载: [[iidp-helper-v0.2.1-dev|/uploads/iidp-plugin/iidp-helper-0.2.1-dev.jar]] |
|
18 | +### change log |
|
19 | +- 新增代码生成 getter setter 功能 |
|
20 | +- 新增 getter setter 在IDEA中提示功能 |
|
21 | + |
|
22 | +### mvn repository |
|
23 | +```xml |
|
24 | + <dependency> |
|
25 | + <groupId>com.sie.meta</groupId> |
|
26 | + <artifactId>sie-iidp-plugin</artifactId> |
|
27 | + <version>0.3.0</version> |
|
28 | + </dependency> |
|
29 | +``` |
|
30 | +<hr> |
|
31 | + |
|
32 | + |
|
33 | +## <font color=green>v0.2.0-dev</font> |
|
34 | +插件下载: [[iidp-helper-v0.2.0-dev|/uploads/iidp-plugin/iidp-helper-0.2.0.jar]] |
|
35 | +### change log |
|
36 | +- 新增代码生成功能,基于@StaticVar注解,生成模型信息相关的静态变量,包括模型名称、模型属性和模型方法 |
|
37 | +- 插件新增模型信息的静态变量提示补全功能 |
|
38 | +- 为了兼容基于生成静态变量的提示补全,修改原来的提示触发条件为必须在双引号之中的字符串才提示 |
|
39 | + |
|
40 | +### 新功能介绍视频 |
|
41 | +<video width="100%" height="100%" src="/iidpwiki/uploads/iidp-plugin/iidp-helper-v0.2.0-dev.mkv" controls="true" /> |
|
42 | + |
|
43 | + |
|
44 | +### 功能列表 |
|
45 | +- 支持对当前工程中的模型文件进行索引并缓存,支持模型名称、模型属性和方法的局部变动后的同步更新 |
|
46 | +- 支持对方法参数中标注有 @ModelName @ModelField @ModelMethod 注解的方法参数进行着色、跳转、检测和补全提示 |
|
47 | +- 支持 @Model 注解中的 name 和 parent 补全提示,对 parent 进行着色、跳转和检测 |
|
48 | +- 支持查看扫描后的模型统计信息,包括模型总数,属性总数和方法总数 |
|
49 | +- 支持手动扫描全工程文件、当前打开文件模型信息 |
|
50 | +- 支持 2021.2.4 及以上版本IDEA |
|
51 | + |
|
52 | +### mvn repository |
|
53 | + <dependency> |
|
54 | + <groupId>com.sie.meta</groupId> |
|
55 | + <artifactId>sie-iidp-plugin</artifactId> |
|
56 | + <version>0.2.1</version> |
|
57 | + </dependency> |
|
58 | + |
|
59 | +<hr> |
|
60 | + |
|
61 | +## <font color=red>v0.1.0-rc</font> |
|
62 | +<font color=green>下载:</font> [[iidp-helper-v0.1.0-rc|/uploads/iidp-plugin/iidp-helper-0.1.0.jar]] |
|
63 | +### change-log |
|
64 | +- rc发版 |
|
65 | + |
|
66 | +### 功能列表 |
|
67 | +- 支持对当前工程中的模型文件进行索引并缓存,支持模型名称、模型属性和方法的局部变动后的同步更新 |
|
68 | +- 支持对方法参数中标注有 @ModelName @ModelField @ModelMethod 注解的方法参数进行着色、跳转、检测和补全提示 |
|
69 | +- 支持 @Model 注解中的 name 和 parent 补全提示,对 parent 进行着色、跳转和检测 |
|
70 | +- 支持查看扫描后的模型统计信息,包括模型总数,属性总数和方法总数 |
|
71 | +- 支持手动扫描全工程文件、当前打开文件模型信息 |
|
72 | +- 支持 2021.2.4 及以上版本IDEA |
|
73 | + |
|
74 | +### 安装视频 |
|
75 | +<video width="100%" height="100%" src="/iidpwiki/uploads/iidp-plugin/iidp-helper-setup.mkv" controls="true" /> |
|
76 | + |
|
77 | +### 视频教程 |
|
78 | +<video width="100%" height="100%" src="/iidpwiki/uploads/iidp-plugin/iidp-helper-introduce.mkv" controls="true" /> |
|
79 | + |
|
80 | +<hr> |
|
81 | + |
|
82 | +## v0.0.9 |
|
83 | +<font color=green>下载:</font> [[iidp-helper-v0.0.9-内测|/uploads/iidp-plugin/iidp-helper-0.0.9.jar]] |
|
84 | +### change-log |
|
85 | +- 优化局部更新同步功能 |
|
86 | +- 新增模型索引信息查看功能 |
|
87 | +- 新增parent模型跳转、着色和检测 |
|
88 | + |
|
89 | +<hr> |
|
90 | + |
|
91 | +## v0.0.8 |
|
92 | +<font color=green>下载:</font> [[iidp-helper-v0.0.8-内测|/uploads/iidp-plugin/iidp-helper-0.0.8.jar]] |
|
93 | +### change-log |
|
94 | +- 支持最新的IDEA版本2024.1.* |
|
95 | + |
|
96 | +<hr> |
|
97 | + |
|
98 | +## v0.0.7 |
|
99 | +<font color=green>下载:</font> [[iidp-helper-v0.0.7-内测|/uploads/iidp-plugin/iidp-helper-0.0.7.jar]] |
|
100 | +### change-log |
|
101 | +- 新增只扫描当前模型功能 |
|
102 | +- 新增扫描后的模型统计信息,包括模型总数,属性总数和方法总数 |
|
103 | +- 新增版本展示 |
|
104 | + |
|
105 | +<hr> |
|
106 | + |
|
107 | +## v0.0.6 |
|
108 | +<font color=green>下载:</font> [[iidp-helper-v0.0.6-内测|/uploads/iidp-plugin/iidp-helper-0.0.6.jar]] |
|
109 | +### change-log |
|
110 | +- 支持模型method提示补全、引用、高亮等 |
|
111 | +- 进行一轮重构,极大提高性能,减少了很多无意义的检测和判断 |
|
112 | + |
|
113 | +<hr> |
|
114 | + |
|
115 | +## v0.0.5 |
|
116 | +<font color=green>下载:</font> [[iidp-helper-v0.0.5-内测|/uploads/iidp-plugin/iidp-helper-0.0.5.jar]] |
|
117 | +### change-log |
|
118 | +- 支持 2021.2.4 及以上版本IDEA |
|
119 | + |
|
120 | +<hr> |
|
121 | + |
|
122 | +## v0.0.4 |
|
123 | +<font color=green>下载:</font> [[iidp-helper-v0.0.4-内测|/uploads/iidp-plugin/iidp-helper-0.0.4.jar]] |
|
124 | +### change-log |
|
125 | +- 新增支持ManyToOne、ManyToMany、OneToMany注解标识模型属性 |
|
126 | +- 对于不存在模型信息的错误提示,下调为weak warning |
|
127 | +- 新增模型信息字符串字面量的着色,便以区分普通字符串 |
|
128 | + |
|
129 | +<hr> |
|
130 | + |
|
131 | +## v0.0.3 |
|
132 | +<font color=green>下载:</font> [[iidp-helper-v0.0.3-内测|/uploads/iidp-plugin/iidp-helper-0.0.3.jar]] |
|
133 | +### change-log |
|
134 | +- 新增模型名称、模型属性字符串字面量的智能提示和补全 |
|
135 | +- 新增模型名称、模型属性字符串字面量的智能检查,如果不存在该模型名称、模型属性,则标红 |
|
136 | +- 新增模型名称、模型属性字符串字面量到该模型定义地方的索引和跳转 |
|
137 | +- 新增模型名称、模型属性的编辑变化的同步更新 |
|
138 | + |
|
139 | +### 安装和介绍视频 |
|
140 | +<video width="100%" height="100%" src="/iidpwiki/uploads/iidp-plugin/iidp-helper-tutorial.mp4" controls="true" /> |
|
141 | + |
|
142 | +<hr> |
|
143 | + |
|
144 | +## v0.0.2 |
|
145 | +[[iidp-plugin-v0.0.2|/uploads/iidp-plugin/iidp-plugin-0.0.2.zip]] |
|
146 | +### change-log |
|
147 | +- 新增校验 views/xxx.json 下的json文件功能 |
|
148 | + |
|
149 | +<hr> |
|
150 | + |
|
151 | +## v0.0.1 |
|
152 | +[[iidp-plugin-v0.0.1|/uploads/iidp-plugin/iidp-plugin-0.0.1.zip]] |
|
153 | + |
|
154 | +<!--插件下载: [[my-test.jar|/uploads/iidp-plugin/my-test.jar]]--> |
|
155 | +### change-log |
|
156 | +- 实现了获取模型field字符串名称、模型字符串名称、以及模型服务方法的字符串名称的功能; |
|
157 | +- 实现了IDEA智能提示和跳转。 |
iidp-plugin/iidp-helper\347\224\250\346\210\267\346\211\213\345\206\214.md
... | ... | @@ -0,0 +1,220 @@ |
1 | +### 参考 |
|
2 | + |
|
3 | +[发版详情](http://192.168.175.198:10001/iidpwiki/iidp-plugin/change-log.md) |
|
4 | + |
|
5 | + |
|
6 | +### 1、需求分析 |
|
7 | +我们在这里均以 ```sie-snest-log``` 操作日志这个app为例来描述```iidp-helper```插件的需求、设计和实现方案。 |
|
8 | + |
|
9 | +- 我们定义一个model的时候,采取的注解格式是这样的: |
|
10 | +```java |
|
11 | +@Model(name = "operator_log", parent = "operator_log", displayName = "操作日志", isAutoLog = Bool.True, |
|
12 | + type = Model.ModelType.Buss) |
|
13 | +public class OperatorLog extends BaseModel<OperatorLog> { |
|
14 | + |
|
15 | + @Property(displayName = "id") |
|
16 | + private String id; |
|
17 | + |
|
18 | + @Property(displayName = "访问时间", dataType = DataType.DATE_TIME) |
|
19 | + private Date accessStartTime; |
|
20 | + |
|
21 | + @Property(displayName = "app名称") |
|
22 | + private String appName; |
|
23 | + |
|
24 | + @Property(displayName = "模型名称") |
|
25 | + private String logModelName; |
|
26 | + |
|
27 | + @Property(displayName = "服务名称") |
|
28 | + private String serviceName; |
|
29 | + |
|
30 | + @Property(displayName = "入参", dataType = DataType.TEXT, widget = "codeeditor") |
|
31 | + private String inParameterData; |
|
32 | + |
|
33 | + @Property(displayName = "出参", dataType = DataType.TEXT, widget = "codeeditor") |
|
34 | + private String outParameterData; |
|
35 | + |
|
36 | + // 用户id |
|
37 | + @ManyToOne(targetModel = "rbac_user", displayName = "用户") |
|
38 | + @JoinColumn(name = "caller_user_id") |
|
39 | + private Map<String, Object> callerUserId; |
|
40 | + |
|
41 | + // ip |
|
42 | + @Property(displayName = "ip") |
|
43 | + private String callerIp; |
|
44 | + |
|
45 | + // 耗时 |
|
46 | + @Property(displayName = "耗时(ms)") |
|
47 | + private String takeTime; |
|
48 | + |
|
49 | + // 变更 |
|
50 | + @Property(displayName = "数据变更") |
|
51 | + private Boolean modify; |
|
52 | +} |
|
53 | +``` |
|
54 | +比如说在上述模型定义的注解中有个 ```parent```字段,它是一个string,意味着它可以写任何的字符串,在编译阶段是不知道parent是否存在,是否填写正确的, |
|
55 | +这给开发阶段带来一些容易出错的地方,这些错误只能到运行阶段才能发现,而且还不容易发现。 |
|
56 | + |
|
57 | +- 访问模型名称和模型字段等 |
|
58 | + 继续举例通过meta访问模型数据的方式: |
|
59 | +```java |
|
60 | + meta = new Meta(Meta.SUPERUSER, new HashMap<>()); |
|
61 | + meta.get("operator_log").call("create", operatorLogList); |
|
62 | + |
|
63 | + RecordSet rs2 = meta.get("operator_details"); |
|
64 | + for (OperatorLog ol : operatorLogList) { |
|
65 | + rs2.call("create", ol.getOperatorDetailsList()); |
|
66 | + } |
|
67 | +``` |
|
68 | + |
|
69 | +```java |
|
70 | + OperatorLog operatorLog = DbUtils.select(filter, "operator_log", OperatorLog.class); |
|
71 | + Filter f = Filter.equal("traceID", filter.getFilterOp("id").getValue()); |
|
72 | +``` |
|
73 | +同样地发现存在 operator_log、create、traceID 等都是string字符串的形式存在,在编译阶段也不会做任何的校验, |
|
74 | +只能在运行阶段才发现问题,而且还不容易发现,比如 traceID 写错了,只是这个filter失效,不会报错,但查出的结果却是不对的。 |
|
75 | + |
|
76 | +- 元模型get set方法 |
|
77 | +```java |
|
78 | + // getter setter |
|
79 | + public String getID() { |
|
80 | + return this.getStr("id"); |
|
81 | + } |
|
82 | + |
|
83 | + public void setID(String id) { |
|
84 | + this.set("id", id); |
|
85 | + } |
|
86 | + |
|
87 | + public Date getAccessStartTime() { |
|
88 | + return this.getDate("accessStartTime"); |
|
89 | + } |
|
90 | + |
|
91 | + public void setAccessStartTime(Date accessStartTime) { |
|
92 | + this.set("accessStartTime", accessStartTime); |
|
93 | + } |
|
94 | + |
|
95 | + public Date getAccessEndTime() { |
|
96 | + return this.getDate("accessEndTime"); |
|
97 | + } |
|
98 | + |
|
99 | + public void setAccessEndTime(Date accessEndTime) { |
|
100 | + this.set("accessEndTime", accessEndTime); |
|
101 | + } |
|
102 | + |
|
103 | + public String getAppName() { |
|
104 | + return this.getStr("appName"); |
|
105 | + } |
|
106 | + |
|
107 | + public void setAppName(String appName) { |
|
108 | + this.set("appName", appName); |
|
109 | + } |
|
110 | + |
|
111 | + public String getLogModelName() { |
|
112 | + return this.getStr("logModelName"); |
|
113 | + } |
|
114 | + |
|
115 | + public void setLogModelName(String logModelName) { |
|
116 | + this.set("logModelName", logModelName); |
|
117 | + } |
|
118 | + |
|
119 | + public String getServiceName() { |
|
120 | + return this.getStr("serviceName"); |
|
121 | + } |
|
122 | + |
|
123 | + public void setServiceName(String serviceName) { |
|
124 | + this.set("serviceName", serviceName); |
|
125 | + } |
|
126 | + |
|
127 | + public String getInParameterData() { |
|
128 | + return this.getStr("inParameterData"); |
|
129 | + } |
|
130 | + |
|
131 | + public void setInParameterData(String inParameterData) { |
|
132 | + this.set("inParameterData", inParameterData); |
|
133 | + } |
|
134 | + |
|
135 | + public String getOutParameterData() { |
|
136 | + return this.getStr("outParameterData"); |
|
137 | + } |
|
138 | + |
|
139 | + public void setOutParameterData(String outParameterData) { |
|
140 | + this.set("outParameterData", outParameterData); |
|
141 | + } |
|
142 | + |
|
143 | + public int getResultDisplay() { |
|
144 | + return this.getInt("resultDisplay"); |
|
145 | + } |
|
146 | + |
|
147 | + public void setResultDisplay(int resultDisplay) { |
|
148 | + this.set("resultDisplay", resultDisplay); |
|
149 | + } |
|
150 | + |
|
151 | + public String getAbnormalDisplay() { |
|
152 | + return this.getStr("abnormalDisplay"); |
|
153 | + } |
|
154 | + |
|
155 | + public void setAbnormalDisplay(String abnormalDisplay) { |
|
156 | + this.set("abnormalDisplay", abnormalDisplay); |
|
157 | + } |
|
158 | + |
|
159 | + public String getCallerUserName() { |
|
160 | + return this.getStr("callerUserName"); |
|
161 | + } |
|
162 | + |
|
163 | + public void setCallerUserName(String callerUserName) { |
|
164 | + this.set("callerUserName", callerUserName); |
|
165 | + } |
|
166 | + |
|
167 | + public String getCallerUserId() { |
|
168 | + return this.getStr("callerUserId"); |
|
169 | + } |
|
170 | + |
|
171 | + public void setCallerUserId(String callerUserId) { |
|
172 | + this.set("callerUserId", callerUserId); |
|
173 | + } |
|
174 | + |
|
175 | + public String getCallerIp() { |
|
176 | + return this.getStr("callerIp"); |
|
177 | + } |
|
178 | + |
|
179 | + public void setCallerIp(String callerIp) { |
|
180 | + this.set("callerIp", callerIp); |
|
181 | + } |
|
182 | + |
|
183 | + public String getTakeTime() { |
|
184 | + return this.getStr("takeTime"); |
|
185 | + } |
|
186 | + |
|
187 | + public void setTakeTime(String takeTime) { |
|
188 | + this.set("takeTime", takeTime); |
|
189 | + } |
|
190 | + |
|
191 | + public Boolean getModify() { |
|
192 | + return this.getBoolean("modify"); |
|
193 | + } |
|
194 | + |
|
195 | + public void setModify(Boolean modify) { |
|
196 | + this.set("modify", modify); |
|
197 | + } |
|
198 | + |
|
199 | + public List<OperatorDetails> getOperatorDetailsList() { |
|
200 | + return (List<OperatorDetails>) this.get("operatorDetailsList"); |
|
201 | + } |
|
202 | + |
|
203 | + public void setOperatorDetailsList(List<OperatorDetails> operatorDetailsList) { |
|
204 | + this.set("operatorDetailsList", operatorDetailsList); |
|
205 | + } |
|
206 | +``` |
|
207 | +有时候我们需要从模型中get set对应成员变量的值,这些方法写起来也相对繁琐,而且很容易写错,因为这里的get其实会去查数据库, |
|
208 | +而有时候我们需要的是从map直接获取值而已,但如果直接使用get方法并不是从map中获取值,所以这里会有一些容易混淆的地方。 |
|
209 | + |
|
210 | + |
|
211 | +总结:从上述的几种基于iidp平台编写app的过程中,我们发现有很多地方都是基于string字符串字面量的形式来表述,很容易写错, |
|
212 | +而且没办法在编译器进行校验,这给app开发者带来了很多麻烦,降低了开发的效率。如果我们能够提供一个插件,对模型名称、模型属性和模型方法等字符串字面量给与它本身的含义,变得结构化起来,那么在开发阶段会让开发者更容易识别这些含义,同时也很容易检测出错误,提高开发者开发效率,同时也能保持代码的精简。 |
|
213 | + |
|
214 | +### 2、方案设计 |
|
215 | + |
|
216 | +建立模型名称、模型属性和模型方法与它的字符串字面量建立引用关系,同时对字符串进行着色、跳转和检测 |
|
217 | + |
|
218 | +### 3、方案实现 |
|
219 | + |
|
220 | +基于 IDEA插件开发api,建立字符串字面量到实际定义处的引用,同时扫描当前工程的所有模型文件,建立所有模型的索引信息,从而达到模型名称、属性和方法的校验等功能。 |
|
... | ... | \ No newline at end of file |
iidp-plugin/iidp-plugin \347\224\250\346\210\267\345\217\215\351\246\210.md
... | ... | @@ -0,0 +1,23 @@ |
1 | +### Bug |
|
2 | +1、跨APP使用常态常量时,mvn install 会报程序包xxx不存在的错误 - 程小虎(2024-05-06) |
|
3 | + |
|
4 | + |
|
5 | + |
|
6 | +### 需求 |
|
7 | +- 1、元模型的主键ID未生成字符串常量 - 程小虎(2024-04-30) |
|
8 | + |
|
9 | +- 2、建议默认生成的静态常量加上所有字段的数据库字段名(user_id这种),方便某些数据库相关的操作传参 - 程小虎(2024-04-30) |
|
10 | + |
|
11 | +- 3、parent = "example_student,example_personal" 类似于这种,继承多个模型时,无法智能提示,点击不能转跳到对应模型 - 程小虎(2024-05-06) |
|
12 | + |
|
13 | +- 4、@Getter @Setter 注解是否可以采用@Model注解,安装了插件希望默认支持 |
|
14 | + |
|
15 | +- 5、平台封装的jar包中的模型调用不能使用 静态常量调用,如 redis_utils - 程小虎(2024-05-07) |
|
16 | + |
|
17 | +- 6、“IIDP助手”菜单栏加入一栏“版本信息”,描述当前插件的版本信息,便于项目过程中开发人员知道自己当前使用的插件版本- 周斌(2024-05-07) |
|
18 | + |
|
19 | +- 7、元模型的被继承的属性,也需要自动生成get、set方法- 周斌(2024-05-09) |
|
20 | +- 8、@One2Many注解 声明属性能否校验是否定义集合 |
|
21 | +- 9、点击对应方法时,建议能够展示该方法被哪些地方调用(显示被其他模型使用call方法调用)- 程小虎(2024-05-19) |
|
22 | +- 10、校验模型里面的方法,如果重写了 search、count、validate 等方法时,方法签名需要跟 BussModelDataAccess 里面的一致。蔡奇君(2024-06-06) |
|
23 | + |
iidp-plugin/iidp-plugin \347\224\250\346\210\267\351\234\200\346\261\202.md
... | ... | @@ -0,0 +1,2 @@ |
1 | +针对代码分层结构的优化 |
|
2 | +[点击查看内容](https://www.yuque.com/cuiguiyang-x3oor/yob7un/ze7o8f7ybwcyxxvs?singleDoc# 《针对代码分层结构的优化》) |
|
... | ... | \ No newline at end of file |
iidp-plugin/iidp-plugin \350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md
... | ... | @@ -0,0 +1,353 @@ |
1 | +<!--<font color=Blue> 插件下载</font>--> |
|
2 | +<!--[[iidp-sonar-0.0.1.zip|/uploads/iidp-plugin/iidp-sonar-0.0.1.zip]]--> |
|
3 | +### 1、需求分析 |
|
4 | +我们在这里均以 ```sie-snest-log``` 操作日志这个app为例来描述```iidp-plugin```的需求、设计和实现方案。 |
|
5 | + |
|
6 | +- 我们定义一个model的时候,采取的注解格式是这样的: |
|
7 | +```java |
|
8 | +@Model(name = "operator_log", parent = "operator_log", displayName = "操作日志", isAutoLog = Bool.True, |
|
9 | + type = Model.ModelType.Buss) |
|
10 | +public class OperatorLog extends BaseModel<OperatorLog> { |
|
11 | + |
|
12 | + @Property(displayName = "id") |
|
13 | + private String id; |
|
14 | + |
|
15 | + @Property(displayName = "访问时间", dataType = DataType.DATE_TIME) |
|
16 | + private Date accessStartTime; |
|
17 | + |
|
18 | + @Property(displayName = "app名称") |
|
19 | + private String appName; |
|
20 | + |
|
21 | + @Property(displayName = "模型名称") |
|
22 | + private String logModelName; |
|
23 | + |
|
24 | + @Property(displayName = "服务名称") |
|
25 | + private String serviceName; |
|
26 | + |
|
27 | + @Property(displayName = "入参", dataType = DataType.TEXT, widget = "codeeditor") |
|
28 | + private String inParameterData; |
|
29 | + |
|
30 | + @Property(displayName = "出参", dataType = DataType.TEXT, widget = "codeeditor") |
|
31 | + private String outParameterData; |
|
32 | + |
|
33 | + // 用户id |
|
34 | + @ManyToOne(targetModel = "rbac_user", displayName = "用户") |
|
35 | + @JoinColumn(name = "caller_user_id") |
|
36 | + private Map<String, Object> callerUserId; |
|
37 | + |
|
38 | + // ip |
|
39 | + @Property(displayName = "ip") |
|
40 | + private String callerIp; |
|
41 | + |
|
42 | + // 耗时 |
|
43 | + @Property(displayName = "耗时(ms)") |
|
44 | + private String takeTime; |
|
45 | + |
|
46 | + // 变更 |
|
47 | + @Property(displayName = "数据变更") |
|
48 | + private Boolean modify; |
|
49 | +} |
|
50 | +``` |
|
51 | +比如说在上述模型定义的注解中有个 ```parent```字段,它是一个string,意味着它可以写任何的字符串,在编译阶段是不知道parent是否存在,是否填写正确的, |
|
52 | +这给开发阶段带来一些容易出错的地方,这些错误只能到运行阶段才能发现,而且还不容易发现。 |
|
53 | + |
|
54 | +- 我们定义一个views的时候,采取的是json的描述方式: |
|
55 | +```json |
|
56 | +"operator_log_grid": { |
|
57 | + "body": { |
|
58 | + "buttons": [ |
|
59 | + { |
|
60 | + "action": "preview", |
|
61 | + "auth": "read", |
|
62 | + "name": "详情" |
|
63 | + }, |
|
64 | + { |
|
65 | + "action": "previewEr", |
|
66 | + "auth": "read", |
|
67 | + "name": "参数" |
|
68 | + } |
|
69 | + ], |
|
70 | + "columns": [ |
|
71 | + { |
|
72 | + "displayName": "访问时间", |
|
73 | + "name": "accessStartTime" |
|
74 | + }, |
|
75 | + { |
|
76 | + "displayName": "app名称", |
|
77 | + "name": "appName" |
|
78 | + }, |
|
79 | + { |
|
80 | + "displayName": "模型名称", |
|
81 | + "name": "logModelName" |
|
82 | + }, |
|
83 | + { |
|
84 | + "displayName": "服务名称", |
|
85 | + "name": "serviceName" |
|
86 | + }, |
|
87 | + { |
|
88 | + "displayName": "调用者用户id", |
|
89 | + "name": "callerUserId" |
|
90 | + }, |
|
91 | + { |
|
92 | + "displayName": "ip", |
|
93 | + "name": "callerIp" |
|
94 | + }, |
|
95 | + { |
|
96 | + "displayName": "耗时", |
|
97 | + "name": "takeTime" |
|
98 | + }, |
|
99 | + { |
|
100 | + "displayName": "数据变更", |
|
101 | + "name": "modify" |
|
102 | + } |
|
103 | + ], |
|
104 | + "tbar": [ |
|
105 | + { |
|
106 | + "action": "delete", |
|
107 | + "auth": "delete", |
|
108 | + "name": "删除" |
|
109 | + } |
|
110 | + ], |
|
111 | + "type": "grid" |
|
112 | + }, |
|
113 | + "mode": "primary", |
|
114 | + "model": "operator_log", |
|
115 | + "name": "操作日志-表格", |
|
116 | + "type": "grid" |
|
117 | + } |
|
118 | +``` |
|
119 | +结合model的定义,能够发现views中的json定义也是基于string,比如```columns.name```是跟model中定义的```private String appName;```定义的成员变量名称保持一致,如果在开发阶段编写错了, |
|
120 | +也是无法发现的,只能在运行阶段前端获取view的时候出错。 |
|
121 | + |
|
122 | +- 访问模型名称和模型字段等 |
|
123 | +继续举例通过meta访问模型数据的方式: |
|
124 | +```java |
|
125 | + meta = new Meta(Meta.SUPERUSER, new HashMap<>()); |
|
126 | + meta.get("operator_log").call("create", operatorLogList); |
|
127 | + |
|
128 | + RecordSet rs2 = meta.get("operator_details"); |
|
129 | + for (OperatorLog ol : operatorLogList) { |
|
130 | + rs2.call("create", ol.getOperatorDetailsList()); |
|
131 | + } |
|
132 | +``` |
|
133 | + |
|
134 | + ```java |
|
135 | + OperatorLog operatorLog = DbUtils.select(filter, "operator_log", OperatorLog.class); |
|
136 | + Filter f = Filter.equal("traceID", filter.getFilterOp("id").getValue()); |
|
137 | + ``` |
|
138 | +同样地发现存在 operator_log、create、traceID 等都是string字符串的形式存在,在编译阶段也不会做任何的校验, |
|
139 | +只能在运行阶段才发现问题,而且还不容易发现,比如 traceID 写错了,只是这个filter失效,不会报错,但查出的结果却是不对的。 |
|
140 | + |
|
141 | +- 元模型get set方法 |
|
142 | +```java |
|
143 | + // getter setter |
|
144 | + public String getID() { |
|
145 | + return this.getStr("id"); |
|
146 | + } |
|
147 | + |
|
148 | + public void setID(String id) { |
|
149 | + this.set("id", id); |
|
150 | + } |
|
151 | + |
|
152 | + public Date getAccessStartTime() { |
|
153 | + return this.getDate("accessStartTime"); |
|
154 | + } |
|
155 | + |
|
156 | + public void setAccessStartTime(Date accessStartTime) { |
|
157 | + this.set("accessStartTime", accessStartTime); |
|
158 | + } |
|
159 | + |
|
160 | + public Date getAccessEndTime() { |
|
161 | + return this.getDate("accessEndTime"); |
|
162 | + } |
|
163 | + |
|
164 | + public void setAccessEndTime(Date accessEndTime) { |
|
165 | + this.set("accessEndTime", accessEndTime); |
|
166 | + } |
|
167 | + |
|
168 | + public String getAppName() { |
|
169 | + return this.getStr("appName"); |
|
170 | + } |
|
171 | + |
|
172 | + public void setAppName(String appName) { |
|
173 | + this.set("appName", appName); |
|
174 | + } |
|
175 | + |
|
176 | + public String getLogModelName() { |
|
177 | + return this.getStr("logModelName"); |
|
178 | + } |
|
179 | + |
|
180 | + public void setLogModelName(String logModelName) { |
|
181 | + this.set("logModelName", logModelName); |
|
182 | + } |
|
183 | + |
|
184 | + public String getServiceName() { |
|
185 | + return this.getStr("serviceName"); |
|
186 | + } |
|
187 | + |
|
188 | + public void setServiceName(String serviceName) { |
|
189 | + this.set("serviceName", serviceName); |
|
190 | + } |
|
191 | + |
|
192 | + public String getInParameterData() { |
|
193 | + return this.getStr("inParameterData"); |
|
194 | + } |
|
195 | + |
|
196 | + public void setInParameterData(String inParameterData) { |
|
197 | + this.set("inParameterData", inParameterData); |
|
198 | + } |
|
199 | + |
|
200 | + public String getOutParameterData() { |
|
201 | + return this.getStr("outParameterData"); |
|
202 | + } |
|
203 | + |
|
204 | + public void setOutParameterData(String outParameterData) { |
|
205 | + this.set("outParameterData", outParameterData); |
|
206 | + } |
|
207 | + |
|
208 | + public int getResultDisplay() { |
|
209 | + return this.getInt("resultDisplay"); |
|
210 | + } |
|
211 | + |
|
212 | + public void setResultDisplay(int resultDisplay) { |
|
213 | + this.set("resultDisplay", resultDisplay); |
|
214 | + } |
|
215 | + |
|
216 | + public String getAbnormalDisplay() { |
|
217 | + return this.getStr("abnormalDisplay"); |
|
218 | + } |
|
219 | + |
|
220 | + public void setAbnormalDisplay(String abnormalDisplay) { |
|
221 | + this.set("abnormalDisplay", abnormalDisplay); |
|
222 | + } |
|
223 | + |
|
224 | + public String getCallerUserName() { |
|
225 | + return this.getStr("callerUserName"); |
|
226 | + } |
|
227 | + |
|
228 | + public void setCallerUserName(String callerUserName) { |
|
229 | + this.set("callerUserName", callerUserName); |
|
230 | + } |
|
231 | + |
|
232 | + public String getCallerUserId() { |
|
233 | + return this.getStr("callerUserId"); |
|
234 | + } |
|
235 | + |
|
236 | + public void setCallerUserId(String callerUserId) { |
|
237 | + this.set("callerUserId", callerUserId); |
|
238 | + } |
|
239 | + |
|
240 | + public String getCallerIp() { |
|
241 | + return this.getStr("callerIp"); |
|
242 | + } |
|
243 | + |
|
244 | + public void setCallerIp(String callerIp) { |
|
245 | + this.set("callerIp", callerIp); |
|
246 | + } |
|
247 | + |
|
248 | + public String getTakeTime() { |
|
249 | + return this.getStr("takeTime"); |
|
250 | + } |
|
251 | + |
|
252 | + public void setTakeTime(String takeTime) { |
|
253 | + this.set("takeTime", takeTime); |
|
254 | + } |
|
255 | + |
|
256 | + public Boolean getModify() { |
|
257 | + return this.getBoolean("modify"); |
|
258 | + } |
|
259 | + |
|
260 | + public void setModify(Boolean modify) { |
|
261 | + this.set("modify", modify); |
|
262 | + } |
|
263 | + |
|
264 | + public List<OperatorDetails> getOperatorDetailsList() { |
|
265 | + return (List<OperatorDetails>) this.get("operatorDetailsList"); |
|
266 | + } |
|
267 | + |
|
268 | + public void setOperatorDetailsList(List<OperatorDetails> operatorDetailsList) { |
|
269 | + this.set("operatorDetailsList", operatorDetailsList); |
|
270 | + } |
|
271 | +``` |
|
272 | +有时候我们需要从模型中get set对应成员变量的值,这些方法写起来也相对繁琐,而且很容易写错,因为这里的get其实会去查数据库, |
|
273 | +而有时候我们需要的是从map直接获取值而已,但如果直接使用get方法并不是从map中获取值,所以这里会有一些容易混淆的地方。 |
|
274 | + |
|
275 | + |
|
276 | +总结:从上述的几种基于iidp编写app的过程中,我们发现有很多地方都是基于string字符串的形式来表述,很容易写错, |
|
277 | +而且没办法在编译器进行校验,这给app开发者带来了很多麻烦,降低了开发的效率。如果我们能够提供一个插件,生成获取模型filed的字符串方法,生成获取模型名称字符串方法,更通用地说生成任何字符串的方法,那么在开发阶段,只需要调用className.getFiled(),就能够获取所需的字符串,这个获取是确定性的,IDE会进行静态检查,不存在不小心输入错误的可能,提高开发者开发效率,同时也能保持代码的精简。 |
|
278 | + |
|
279 | +### 2、方案设计 |
|
280 | +- 原理 |
|
281 | +从Javac的代码来看,编译过程大致可以分为3个过程: |
|
282 | + - 解析与填充符号表过程 |
|
283 | + - 插入式注解处理器的注解处理过程 |
|
284 | + - 分析与字节码生成过程 |
|
285 | + |
|
286 | +[[/uploads/iidp-plugin/jsr-269.png]] |
|
287 | + |
|
288 | +Javac编译动作的入口是com.sun.tools.javac.main.JavaCompiler类,上述3个过程的代码逻辑集中在这个类的compile()和compile2()方法中,下面给出整个编译过程中最关键的几个步骤: |
|
289 | + |
|
290 | +[[/uploads/iidp-plugin/javac.png]] |
|
291 | + |
|
292 | +(1):准备过程,初始化插入式注解处理器 |
|
293 | +(2):词法分析,语法分析 |
|
294 | +(3):输入到符号表 |
|
295 | +(4):注解处理 |
|
296 | +(5):分析及字节码生成 |
|
297 | +(6):标注 |
|
298 | +(7):数据流分析 |
|
299 | +(8):解语法糖 |
|
300 | +(9):字节码生成 |
|
301 | + |
|
302 | + - 1.1 解析 |
|
303 | + |
|
304 | +解析步骤由上述代码清单中的parseFiles()方法(过程(2))完成,解析步骤包括了经典程序编译原理中的词法分析和语法分析两个过程 |
|
305 | + |
|
306 | +词法分析是将源代码的字符流转变为标记(Token)集合,单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素,关键字、变量名、字面量、运算符都可以成为标记,如int a= b + 2这句代码包含了6个标记,分别是int、a、=、b、+、2,虽然关键字int由3个字符构成,但是它只是一个Token,不可再拆分。在Javac的源码中,词法分析过程由com.sun.tools.javac.parser.Scanner类来实现 |
|
307 | + |
|
308 | +语法分析是根据Token序列构造抽象语法树的过程,抽象语法树(Abstract Syntax Tree,AST)是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释等都可以是一个语法结构。在Javac的源码中,语法分析过程由com.sun.tools.javac.parser.Parser类实现,这个阶段产出的抽象语法树由com.sun.tools.javac.tree.JCTree类表示,经过这个步骤之后,编译器就基本不会再对源码文件进行操作了,后续的操作都建立在抽象语法树之上. |
|
309 | + |
|
310 | + |
|
311 | +- 1.2 填充符号表 |
|
312 | + |
|
313 | +完成了语法分析和词法分析之后,下一步就是填充符号表的过程,也就是enterTrees()方法(过程(3))所做的事情。符号表(Symbol Table)是由一组符号地址和符号信息构成的表格,可以把它想象成哈希表中K-V值对的形式(实际上符号表不一定是哈希表实现,可以是有序符号表、树状符号表、栈结构符号表等)。符号表中所登记的信息在编译的不同阶段都要用到。在语义分析中,符号表所登记的内容将用于语义检查(如检查一个名字的使用和原先的说明是否一致)和产生中间代码。在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的依据 |
|
314 | + |
|
315 | +在Javac源代码中,填充符号表的过程由com.sun.tools.javac.comp.Enter类实现,此过程的出口是一个待处理列表(To Do List),包含了每一个编译单元的抽象语法树的顶级节点,以及package-info.java(如果存在的话)的顶级节点 |
|
316 | + |
|
317 | +- 2 JSR-269 |
|
318 | + |
|
319 | +在Javac源码中,插入式注解处理器的初始化过程是在initPorcessAnnotations()方法中完成的,而它的执行过程则是在processAnnotations()方法中完成的,这个方法判断是否还有新的注解处理器需要执行,如果有的话,通过com.sun.tools.javac.processing.JavacProcessingEnvironment类的doProcessing()方法生成一个新的JavaCompiler对象对编译的后续步骤进行处理 |
|
320 | + |
|
321 | +在JDK 1.6中实现了JSR-269规范JSR-269:Pluggable Annotations Processing API(插入式注解处理API)。提供了一组插入式注解处理器的标准API在编译期间对注解进行处理。我们可以把它看做是一组编译器的插件,在这些插件里面,可以读取、修改、添加抽象语法树中的任意元素。如果这些插件在处理注解期间对语法树进行了修改,编译器将回到解析及填充符号表的过程重新处理,直到所有插入式注解处理器都没有再对语法树进行修改为止,每一次循环称为一个Round,也就是第一张图中的回环过程。 有了编译器注解处理的标准API后,我们的代码才有可能干涉编译器的行为,由于语法树中的任意元素,甚至包括代码注释都可以在插件之中访问到,所以通过插入式注解处理器实现的插件在功能上有很大的发挥空间。只要有足够的创意,程序员可以使用插入式注解处理器来实现许多原本只能在编码中完成的事情 |
|
322 | + |
|
323 | +- 3 编译相关的数据结构与API |
|
324 | + |
|
325 | + - 3.1 JCTree |
|
326 | + |
|
327 | + - 3.2 TreeMaker |
|
328 | + |
|
329 | + - 3.2.1 TreeMaker.Modifiers |
|
330 | + |
|
331 | + |
|
332 | +以iidp-plugin源码为例: |
|
333 | + |
|
334 | +[[/uploads/iidp-plugin/iidp-plugin_1.png]] |
|
335 | +[[/uploads/iidp-plugin/iidp-plugin_2.png]] |
|
336 | + |
|
337 | + |
|
338 | +iidp-plugin IDEA 端进行解析 |
|
339 | + |
|
340 | +[[/uploads/iidp-plugin/iidp-plugin-3.png]] |
|
341 | + |
|
342 | +[[/uploads/iidp-plugin/iidp-plugin-4.png]] |
|
343 | + |
|
344 | +- 参考lombok进行代码生成 |
|
345 | + 1. 基于注解的process处理 |
|
346 | + 2. 生成指定的代码 |
|
347 | + |
|
348 | +- 生成符号表,对特定字符串进行校验和提示 |
|
349 | + 1. 对引用的字符串进行校验 |
|
350 | + 2. 对字符串进行智能跳转 |
|
351 | + |
|
352 | +### 3、方案实现 |
|
353 | + |
iidp-plugin/iidp-plugin \351\227\256\351\242\230\345\217\215\351\246\210.md
... | ... | @@ -0,0 +1,3 @@ |
1 | +### Bug xxxx |
|
2 | + |
|
3 | +### Bug yyy |
|
... | ... | \ No newline at end of file |
iidp-plugin/tutorials.md
... | ... | @@ -0,0 +1,99 @@ |
1 | +### 1、引入iidp-plugin包 |
|
2 | +```xml |
|
3 | +<dependency> |
|
4 | + <groupId>com.sie.meta</groupId> |
|
5 | + <artifactId>sie-iidp-plugin</artifactId> |
|
6 | + <version>0.0.2</version> |
|
7 | +</dependency> |
|
8 | +``` |
|
9 | + |
|
10 | +### 2、使用@StringField注解 |
|
11 | +```java |
|
12 | +import com.sie.meta.plugin.StringField; |
|
13 | +import com.sie.snest.engine.model.Bool; |
|
14 | +import com.sie.snest.sdk.annotation.meta.MethodService; |
|
15 | +import com.sie.snest.sdk.annotation.meta.Model; |
|
16 | + |
|
17 | +@StringField |
|
18 | +@Model(isAutoLog = Bool.True, name = "my_test") |
|
19 | +public class MyAnnoTest { |
|
20 | + |
|
21 | + private String a; |
|
22 | + private String c = "cccc"; |
|
23 | + |
|
24 | + public static void main(String[] args) { |
|
25 | + MyAnnoTest test = new MyAnnoTest(); |
|
26 | + MyAnnoTest.modelName(); |
|
27 | + MyAnnoTest.testMethod_META(); |
|
28 | + System.out.println(MyAnnoTest.a()); |
|
29 | + System.out.println(MyAnnoTest.c()); |
|
30 | + } |
|
31 | + |
|
32 | + @MethodService(description = "testMethod") |
|
33 | + void testMethod() { |
|
34 | + } |
|
35 | +} |
|
36 | + |
|
37 | +``` |
|
38 | +编译并查看生成的class文件 |
|
39 | +```java |
|
40 | +// |
|
41 | +// Source code recreated from a .class file by IntelliJ IDEA |
|
42 | +// (powered by FernFlower decompiler) |
|
43 | +// |
|
44 | + |
|
45 | +public class MyAnnoTest { |
|
46 | + private String a; |
|
47 | + private String c = "cccc"; |
|
48 | + |
|
49 | + public static String c() { |
|
50 | + return "c"; |
|
51 | + } |
|
52 | + |
|
53 | + public static String a() { |
|
54 | + return "a"; |
|
55 | + } |
|
56 | + public static String modelName() { |
|
57 | + return "my_test"; |
|
58 | + } |
|
59 | + public static String testMethod_META() { |
|
60 | + return "testMethod"; |
|
61 | + } |
|
62 | + public MyAnnoTest() { |
|
63 | + } |
|
64 | + |
|
65 | + public static void main(String[] args) { |
|
66 | + new MyAnnoTest(); |
|
67 | + System.out.println(a()); |
|
68 | + System.out.println(c()); |
|
69 | + } |
|
70 | +} |
|
71 | +``` |
|
72 | +可以发现,新生成了3个静态方法 ```public static String modelName() ``` 返回模型名称, ``` public static String a() ``` 和 ``` public static String c() ``` 它们分别返回该filed的字符串名称。 |
|
73 | + |
|
74 | +### 3、使用IDEA插件 |
|
75 | +由于APT(Annotation Processing Tool)只是在编译阶段生成代码,并没有侵入IDEA的行为,所以在IDEA中进行开发并不会对生成的代码进行智能提示,为此需要配套编写iidp-plugin辅助在IDEA中进行智能提示(详细参考 [[iidp-plugin 设计与实现.md]])。可在这里直接下载插件 [[iidp-plugin-v0.0.1|/uploads/iidp-plugin/iidp-plugin-0.0.1.zip]] 并按照下图所示进行安装: |
|
76 | + |
|
77 | +[[/uploads/iidp-plugin/tutorials/install_iidp_plugin.png]] |
|
78 | + |
|
79 | +安装完成以后,开始编写代码,可以实现IDEA代码智能提示增强的代码,以及单步调试打印结果,如下图: |
|
80 | + |
|
81 | +[[/uploads/iidp-plugin/tutorials/intelli_sense.png]] |
|
82 | + |
|
83 | +[[/uploads/iidp-plugin/tutorials/debug_result.png]] |
|
84 | + |
|
85 | +[[/uploads/iidp-plugin/tutorials/modelName.png]] |
|
86 | +[[/uploads/iidp-plugin/tutorials/method_service.png]] |
|
87 | + |
|
88 | +#### 校验json文件 |
|
89 | + |
|
90 | +[[/uploads/iidp-plugin/tutorials/verify_json.png]] |
|
91 | + |
|
92 | +[[/uploads/iidp-plugin/tutorials/verify_json_failed.png]] |
|
93 | + |
|
94 | +### 4、注意事项 |
|
95 | +- 另外,如果通过IDEA调试报错,可以配置VM options,-Djps.track.ap.dependencies=false |
|
96 | + |
|
97 | +[[/uploads/iidp-plugin/tutorials/vm_options.png]] |
|
98 | + |
|
99 | +- 目前只支持IDEA智能提示,不支持如eclipse vscode 等。 |
iidp-plugin/\346\217\222\344\273\266\345\274\200\345\217\221\346\200\235\350\200\203.md
... | ... | @@ -0,0 +1,115 @@ |
1 | +## 2024.01.02-2024.01.05思考 |
|
2 | + |
|
3 | +1.打开IIDP项目时,会因为打开的目录不对,导致找不到apps目录,加载不了app报错(建议插件提示路径) |
|
4 | +2.打开IIDP项目时,要设置file encodings,建议通过插件进行定制化配置 |
|
5 | +3.新建app工程,以往的方法是从别的app复制过来再做修改, |
|
6 | + |
|
7 | +* 例如修改pom文件的artifactId,修改父pom的子module |
|
8 | + |
|
9 | +* 删除复制工程自带的model和视图json文件 |
|
10 | + |
|
11 | +* 修改包名,不然和被复制的工程同一个包 |
|
12 | + |
|
13 | +* 建议通过向导生成标准app项目 |
|
14 | + |
|
15 | +4.生成getter方法的模版会因为复制,导致某些行默认加了/t,导致getter模版不能用,建议插件集成这个功能,或者是直接不用写getter,setter,因为属性如果做修改,setter/getter又要生成一次,期间不会报错,很容易忘记 |
|
16 | + |
|
17 | +5.模型中各个属性注解的用法解释缺少提示,建议插件提醒 |
|
18 | + |
|
19 | +6.跨模型调用的call方法不能强提示要传入的参数,思考下插件能否辅助提示 |
|
20 | + |
|
21 | +7.重写search方法必须要重写count方法,可以插件辅助提示 |
|
22 | + |
|
23 | +## 2024.01.08-2024.01.12思考 |
|
24 | + |
|
25 | +8.views文件夹的menu.json文件依赖复制粘贴,考虑用插件向导辅助生成,其中 |
|
26 | + |
|
27 | +(1)menus.json可固定名称和文件类型,路径固定放在当前module的com.sie.apps.xxx.views文件夹下 |
|
28 | + |
|
29 | +(2)一级层级XXX_root_menu特征:XXX_root_menu命名,非强制,但通用,建议取com/sie/apps/xxx/app.json中的name属性,驼峰可按规律以"_"分割,后缀+_root_menu |
|
30 | + |
|
31 | +(3)一级层级XXX_root_menu中name属性:默认保持和XXX_root_menu保持一致 |
|
32 | + |
|
33 | +(4)一级层级XXX_root_menu中display_name属性:com/sie/apps/xxx/app.json中的displayName属性,中文 |
|
34 | + |
|
35 | +(5)一级层级XXX_root_menu中active属性默认可以设置为true |
|
36 | + |
|
37 | +(6)一级层级XXX_root_menu中sequence属性默认可以设置为1 |
|
38 | + |
|
39 | +(7)二级层级XXX_menu特征:XXX_menu命名,非强制,但通用 |
|
40 | + |
|
41 | +(8)二级层级XXX_menu中name属性:默认和XXX_menu保持一致,建议取模型@Model的name属性,后缀+_menu |
|
42 | + |
|
43 | +(9)二级层级XXX_menu中display_name属性:建议取模型@Model中的displayName属性,中文 |
|
44 | + |
|
45 | +(10)二级层级XXX_menu中view属性:规定取自XXX_view.json(也就是元模型对应视图文件)中的XXX_grid属性,XXX_search属性,XXX_form属性 |
|
46 | + |
|
47 | +(11)二级层级XXX_menu中sequece属性,依照模型的解析顺序,默认生成1->n的整数 |
|
48 | + |
|
49 | +(12)二级层级XXX_menu中active属性默认可以设置为true |
|
50 | + |
|
51 | +(13)二级层级XXX_menu中parent_ids属性,默认指定一级层级菜单XXX_root_menu |
|
52 | + |
|
53 | +9.思考可否在@Model注解的属性中加一个属性,来控制模型在前端展示的顺序,因为不是强制性的属性,如果强制性则编码过程中很可能会忘记,也可以做成注释,解析后依照顺序生成menus.json中的二级菜单数据 |
|
54 | + |
|
55 | +10.views文件夹的XXX_view.json文件,其中 |
|
56 | + |
|
57 | + (1)XXX_views不可固定名称,可固定文件类型,路径固定放在当前module的com.sie.apps.xxx.views文件夹下 |
|
58 | + |
|
59 | + (2)XXX_views的前缀来自于模型@Model中的name属性 |
|
60 | + |
|
61 | + (3)XXX_views文件中的二级层级有XXX_grid,XXX_form,XXX_search,名称前缀可解析模型@Model中的name属性,后缀固定 |
|
62 | + |
|
63 | + (4)XXX_views文件中的三级层级有body,mode,model,name,type这五个元素可固定生成 |
|
64 | + |
|
65 | + (5)XXX_views文件中的三级层级中mode属性默认生成为primary |
|
66 | + |
|
67 | + (6)XXX_views文件中的三级层级中model属性默认生成为模型@Model 的name属性 |
|
68 | + |
|
69 | + (7)XXX_views文件中的三级层级中name根据二级层级中的XXX_grid,XXX_form,XXX_search属性,生成对应的XXX-表格,XXX-表单,XXX-搜索框,前缀取自@Model 的displayName属性 |
|
70 | + |
|
71 | + (8)XXX_views文件中的三级层级中type属性,根据二级层级中的XXX_grid,XXX_form,XXX_search属性,生成对应的grid,form,search固定值 |
|
72 | + |
|
73 | +11.XXX_views文件中的三级层级中body属性中 |
|
74 | + |
|
75 | + (1)XXX_grid下: |
|
76 | + |
|
77 | + 1)type属性跟随grid的后缀,固定为grid |
|
78 | + |
|
79 | + 2)tbar属性默认生成,包含新增和删除两个按钮 |
|
80 | + |
|
81 | + 3)buttons属性默认生成,包含详情和编辑按钮 |
|
82 | + |
|
83 | + 4)columns属性中 |
|
84 | + |
|
85 | + ○ name通过解析模型中java属性的变量名来默认生成 |
|
86 | + |
|
87 | + ○ displayName通过解析模型中@Property 中的displayName来默认生成 |
|
88 | + |
|
89 | + (2)XXX_form下: |
|
90 | + |
|
91 | + 1)type属性跟随grid的后缀,固定为form |
|
92 | + |
|
93 | + 2)tabs默认生成为[](todo了解一下有值场景) |
|
94 | + |
|
95 | + 3)columns属性中 |
|
96 | + |
|
97 | + ○ name通过解析模型中java属性的变量名来默认生成 |
|
98 | + |
|
99 | + ○ displayName通过解析模型中 |
|
100 | + |
|
101 | + ○ @Property 中的displayName来默认生成 |
|
102 | + ○ ER注解中的displayName来默认生成 |
|
103 | + |
|
104 | + (3)XXX_search下: |
|
105 | + |
|
106 | + 1)type属性跟随grid的后缀,固定为form |
|
107 | + |
|
108 | + 2)columns属性中 |
|
109 | + |
|
110 | + ○ name通过解析模型中java属性的变量名来默认生成 |
|
111 | + |
|
112 | + ○ displayName通过解析模型中 |
|
113 | + |
|
114 | + ○ @Property 中的displayName来默认生成 |
|
115 | + ○ ER注解中的displayName来默认生成 |
iidp-plugin/\351\222\210\345\257\271\344\273\243\347\240\201\345\210\206\345\261\202\347\273\223\346\236\204\347\232\204\344\274\230\345\214\226.md
iidp-plugin/\351\222\210\345\257\271\344\273\243\347\240\201\345\210\206\345\261\202\347\273\223\346\236\204\347\232\204\344\274\230\345\214\226\346\200\235\350\200\203.md
... | ... | @@ -0,0 +1 @@ |
1 | +[请点击查看内容](https://www.yuque.com/cuiguiyang-x3oor/yob7un/ze7o8f7ybwcyxxvs?singleDoc#%20%E3%80%8A%E9%92%88%E5%AF%B9%E4%BB%A3%E7%A0%81%E5%88%86%E5%B1%82%E7%BB%93%E6%9E%84%E7%9A%84%E4%BC%98%E5%8C%96%E3%80%8B) |
|
... | ... | \ No newline at end of file |
iidp.md
... | ... | @@ -0,0 +1,2540 @@ |
1 | +## IIDP开发规范 |
|
2 | + |
|
3 | + |
|
4 | + |
|
5 | +目录 |
|
6 | + |
|
7 | +[IIDP开发规范 1](#_Toc17399) |
|
8 | + |
|
9 | +[前言](#_Toc10206) 3 |
|
10 | + |
|
11 | +[一、 编程规范](#_Toc14773) 4 |
|
12 | + |
|
13 | +[(1) 命名规范 4](#_Toc16252) |
|
14 | + |
|
15 | +[(2) 常量定义 7](#_Toc24694) |
|
16 | + |
|
17 | +[(3) 模型规范 8](#_Toc31245) |
|
18 | + |
|
19 | +[(4) 在线IDE使用规范 12](#_Toc15718) |
|
20 | + |
|
21 | +[(5) SDK使用规范 12](#_Toc29165) |
|
22 | + |
|
23 | +[(6) 日志规范 13](#_Toc3025) |
|
24 | + |
|
25 | +[二、 测试规范](#_Toc15921) 20 |
|
26 | + |
|
27 | +[(1) 单元测试 20](#_Toc29598) |
|
28 | + |
|
29 | +[(2) 功能测试 22](#_Toc18332) |
|
30 | + |
|
31 | +[(3) 集成测试 30](#_Toc8448) |
|
32 | + |
|
33 | +[(4) 性能测试 30](#_Toc15920) |
|
34 | + |
|
35 | +[(5) 自动化测试 31](#_Toc11833) |
|
36 | + |
|
37 | +[(6) 回归测试 31](#_Toc31811) |
|
38 | + |
|
39 | +[三、 授权管理规范](#_Toc6369) 35 |
|
40 | + |
|
41 | +[(1) 平台授权 35](#_Toc19268) |
|
42 | + |
|
43 | +[(2) 多租户 35](#_Toc1561) |
|
44 | + |
|
45 | +[四、 版本管理规范](#_Toc24462) 35 |
|
46 | + |
|
47 | +[(1) GIT使用规范 35](#_Toc30087) |
|
48 | + |
|
49 | +[(2) CICD规范 37](#_Toc19455) |
|
50 | + |
|
51 | +[(3) 发版规范 38](#_Toc16666) |
|
52 | + |
|
53 | +[(4) APP版本规范 38](#_Toc29493) |
|
54 | + |
|
55 | +[五、 工程结构规范](#_Toc30677) 38 |
|
56 | + |
|
57 | +[(1) APP设计规范 38](#_Toc27533) |
|
58 | + |
|
59 | +[(2) pom引入规范 40](#_Toc21575) |
|
60 | + |
|
61 | +[(3) 工程结构规范 41](#_Toc13415) |
|
62 | + |
|
63 | +[(4) 公共错误码规范 43](#_Toc21324) |
|
64 | + |
|
65 | +[六、 部署规范](#_Toc20026) 43 |
|
66 | + |
|
67 | +[(1) 前端部署 43](#_Toc22094) |
|
68 | + |
|
69 | +[(2) 后端部署 44](#_Toc5231) |
|
70 | + |
|
71 | +前言 |
|
72 | + |
|
73 | +《IIDP开发规范》是赛意工业软件及物联子公司技术平台研发团队,基于IIDP低代码平台设计、开发和测试过程中,汇集团队内部和外部交付团队集体的智慧结晶和开发经验总结,经历了从零开始自研IIDP低代码平台,多次大规模一线实战的检验及不断的完善,系统化地整理成册,回馈给开发者的一份开发规范。现代软件行业的高速发展对开发者的综合素质要求越来越高,因为不仅是编程知识点,其它维度的知识点也会影响到软件的最终交付质量。企业数字化转型,需要部署大量企业级应用,随着业务的发展,需求无法得到及时响应,大大增加了数字化转型的成本,这也是我们开发IIDP的初衷,极大地给开发者带来了很多开发方面的便利和快捷,同时为了提高开发者编码的质量和可靠性,我们也约定了很多规范,比如:对象命名的混乱带来代码的不好维护;模型设计的不规范带来从一开始就导致设计上的混乱给;数据库的表结构和索引设计缺陷可能带来软件上的架构缺陷或性能风险;工程结构混乱导致后续维护艰难;没有鉴权的漏洞代码易被黑客攻击等等。所以本手册以IIDP开发者为中心视角,划分为编程规范、测试规范、授权管理规范、版本管理规范、工程结构规范、部署规范等几个维度,再根据内容特征,细分成若干二级子目录。根据约束力强弱及故障敏感性,规约依次分为【强制】、【推荐】、【参考】三大类。对于规约条目的延伸信息中,"说明"对内容做了适当扩展和解释;"正例"提倡什么样的编码和实现方式;"反例"说明需要提防的雷区,以及真实的错误案例。 |
|
74 | +现代软件架构都需要协同开发完成,高效协作即降低协同成本,提升沟通效率,所谓无规矩不成方圆,无规范不能协作。众所周知,制订交通法规表面上是要限制行车权,实际上是保障公众的人身安全。试想如果没有限速,没有红绿灯,谁还敢上路行驶。对软件来说,适当的规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率。代码的字里行间流淌的是软件生命中的血液,质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升质量意识。 |
|
75 | + |
|
76 | +希望IIDP开发者通过阅读本手册,能够充分利用IIDP平台进行开发,避免常见的错误和陷阱,写出高质量的代码,提高开发效率。让我们一起码出高效、码出质量的软件系统。 |
|
77 | + |
|
78 | + |
|
79 | +| 版本号 | 指定团队 | 更新日期 | 备注 | |
|
80 | +| --- | --- | --- | --- | |
|
81 | +| 0.0.1 | IIDP开发团队 | 2023.9.10 | 正在编写中 | |
|
82 | + |
|
83 | +1. |
|
84 | +#### 编程规范 |
|
85 | + |
|
86 | +1. |
|
87 | +##### 命名规范 |
|
88 | + |
|
89 | +1. 【强制】app命名规范。应用名称必须唯一,必须在app.json中定义,使用业务含义的应 用名称作为前缀。(长度,允许包含哪些字符,开头。保留关键字,平台预留) |
|
90 | + |
|
91 | +说明:在IIDP平台中,app是名称唯一标识,确保应用名称的唯一性,便于识别和管理。 |
|
92 | + |
|
93 | +正例:iot\_net / iot\_base / smi\_base / smi\_redis / snest\_tenant / snest\_log / snest\_base 等大的项目名称作为前缀,具体的业务场景作为后缀名称,具有业务含义,也能唯一区分。 |
|
94 | + |
|
95 | +反例:net / base / log 等模糊或不具有业务含义的名称很容易重名,且不知道是具体哪个项目的名称。 |
|
96 | + |
|
97 | +1. 【强制】模型命名规范。模型名称必须唯一,并在注解中定义。推荐使用业务含义的模型名称作为前缀。 |
|
98 | + |
|
99 | +说明:在IIDP平台中,模型名称是名称唯一标识,对应着数据库中的表名称,使用下划线拼接,控制字符串长度,确保应用名称的唯一性,便于识别和管理。 |
|
100 | + |
|
101 | +正例:tenant\_action\_rule / tenant\_rbac\_organization 等大的项目名称作为前缀,具体的业务场景作为后缀名称。 |
|
102 | + |
|
103 | +反例:net\_model / base\_model / logModel 等,不需要加model后缀,不需要采用驼峰拼接,且不知道是具体哪个项目的名称。 |
|
104 | + |
|
105 | +1. 【强制】定义模型时,格式要求为 public class XXXClass extends BaseModel\<XXXClass\> |
|
106 | + |
|
107 | +说明:用户自定义的类继承自BaseModel\<T\>,其中T为当前实体类的类型参数,实体类应该继承自BaseModel,以获得基本的模型功能和属性。 |
|
108 | + |
|
109 | +正例: |
|
110 | + |
|
111 | +public class TenantOrg extends BaseModel\<TenantOrg\> { |
|
112 | + |
|
113 | + _// __类的定义__..._ |
|
114 | + |
|
115 | +} |
|
116 | + |
|
117 | +1. 【强制】Property命名规范。Property命名采用驼峰的形式。 |
|
118 | + |
|
119 | +说明:为了保持代码的一致性和可读性,Property的命名应该使用驼峰命名法。驼峰命名法是一种命名约定,其中单词之间没有空格,每个单词的首字母大写,其他字母小写。 |
|
120 | + |
|
121 | +正例:如companyName。这样可以提高代码的可读性和一致性。 |
|
122 | + |
|
123 | +@Validate.NotBlank |
|
124 | + |
|
125 | +@Validate.Pattern(regexp = "^.{2,200}$") |
|
126 | + |
|
127 | +@Property(displayName = "公司名称") |
|
128 | + |
|
129 | +private String companyName; |
|
130 | + |
|
131 | +1. 【强制】Property注解的dataType数据类型,必须选择以下类型之一: |
|
132 | + |
|
133 | +BigDecimal: 用于存储大数值 |
|
134 | + |
|
135 | +Boolean: 用于存储true或false值 |
|
136 | + |
|
137 | +Date: 日期类型,默认格式为yyyy-MM-dd |
|
138 | + |
|
139 | +DateTime: 时间日期类型,默认格式为yyyy-MM-dd HH:mm:ss |
|
140 | + |
|
141 | +Double: 双精度浮点型。精度可由位数和小数位数对来定义,默认精度为2 |
|
142 | + |
|
143 | +Selection: 选择类型,包括常量枚举、下拉框单选、下拉框多选、下拉框联动 |
|
144 | + |
|
145 | +File: 文件类型,文件类型可由contentType指定 |
|
146 | + |
|
147 | +Integer: 整型 |
|
148 | + |
|
149 | +Long: 长整型 |
|
150 | + |
|
151 | +List: 存储List对象,以JSON格式存储数据,在读取时反序列化为List对象 |
|
152 | + |
|
153 | +Map: 存储Map对象,以JSON格式存储数据,在读取时反序列化为Map对象 |
|
154 | + |
|
155 | +Object: 存储对象,以JSON格式存储数据,在读取时反序列化为对象 |
|
156 | + |
|
157 | +String: 字符串类型 |
|
158 | + |
|
159 | +Text: 长文本类型 |
|
160 | + |
|
161 | +根据上述新增规范,可以根据需要选择合适的类型来注解Property的值 |
|
162 | + |
|
163 | +1. 【强制】validatealidate 提供的校验方式为在类的属性上加入相应的注解来达到校验的目 的。validator提供的用于校验的注解如下: |
|
164 | + |
|
165 | + |
|
166 | +| @NotBlank | 不能为null | |
|
167 | +| --- | --- | |
|
168 | +| @Pattern(regex=) | 被注释的元素必须符合指定的正则表达式 | |
|
169 | +| @Email | 检查是否是一个有效的email地址 | |
|
170 | +| @Max | 该字段的值只能小于或等于该值 | |
|
171 | +| @Min | 该字段的值只能大于或等于该值 | |
|
172 | +| @Size(min=,max=) | 检查所属的字段的长度是否在min和max之间,只能用于字符串 | |
|
173 | +| @Null | 能为null | |
|
174 | +| @Unique | 唯一校验。默认只校验当前字段。可以设置 properties,指定多个字段联合唯一 | |
|
175 | + |
|
176 | +1. 【强制】getter setter 命名规范。get set 方法的列名称定义的是下划线拼接(比如下例中 的role\_name),但是实际是roleName驼峰命名的格式。 |
|
177 | + |
|
178 | +说明:为了保持代码的一致性和可读性,get和set方法应该使用驼峰命名法。驼峰命名法是一种命名约定,其中单词之间没有空格,每个单词的首字母大写,其他字母小写。 |
|
179 | + |
|
180 | +正例: |
|
181 | + |
|
182 | +@Property(columnName = "role\_name", displayName = "角色名称") |
|
183 | + |
|
184 | +private String roleName; |
|
185 | + |
|
186 | +public String getRoleName(){ |
|
187 | + |
|
188 | + return (String) get("roleName"); |
|
189 | + |
|
190 | +} |
|
191 | + |
|
192 | +public void setRoleName(String roleName){ |
|
193 | + |
|
194 | + set("roleName", roleName); |
|
195 | + |
|
196 | +} |
|
197 | + |
|
198 | +根据上述新增规范,getter和setter方法的命名应采用驼峰命名法,如getRoleName()和setRoleName(String roleName)。这样可以提高代码的可读性和一致性。 |
|
199 | + |
|
200 | +为了方便开发者快速生成getter setter 代码,下面给出配置IDEA getter setter 代码模板。 |
|
201 | + |
|
202 | +getter模板: |
|
203 | + |
|
204 | +#if($field.modifierStatic) |
|
205 | + |
|
206 | +static ## |
|
207 | + |
|
208 | +#end |
|
209 | + |
|
210 | +$field.type _##_ |
|
211 | + |
|
212 | +#if($field.recordComponent) |
|
213 | + |
|
214 | + ${field.name}## |
|
215 | + |
|
216 | +#else |
|
217 | + |
|
218 | +#set($name = $StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field, $project)))) |
|
219 | + |
|
220 | +#if ($field.boolean && $field.primitive) |
|
221 | + |
|
222 | + is#_#_ |
|
223 | + |
|
224 | +#else |
|
225 | + |
|
226 | + get#_#_ |
|
227 | + |
|
228 | +#end |
|
229 | + |
|
230 | +${name}_##_ |
|
231 | + |
|
232 | +#end |
|
233 | + |
|
234 | +() { |
|
235 | + |
|
236 | +return ($field.type)this.get("$field.name"); |
|
237 | + |
|
238 | +} |
|
239 | + |
|
240 | +Setter 模板: |
|
241 | + |
|
242 | +_#set($paramName = $helper.getParamName($field, $project))_ |
|
243 | + |
|
244 | +_#if($field.modifierStatic)_ |
|
245 | + |
|
246 | +static _##_ |
|
247 | + |
|
248 | +_#end_ |
|
249 | + |
|
250 | +$classname set$StringUtil.capitalizeWithJavaBeanConvention($StringUtil.sanitizeJavaIdentifier($helper.getPropertyName($field, $project)))($field.type $paramName) { |
|
251 | + |
|
252 | +_#if ($field.name == $paramName)_ |
|
253 | + |
|
254 | + _#if (!$field.modifierStatic)_ |
|
255 | + |
|
256 | + this._##_ |
|
257 | + |
|
258 | + _#else_ |
|
259 | + |
|
260 | + $classname._##_ |
|
261 | + |
|
262 | + _#end_ |
|
263 | + |
|
264 | +_#end_ |
|
265 | + |
|
266 | +set("$field.name", $paramName); |
|
267 | + |
|
268 | +return this; |
|
269 | + |
|
270 | +} |
|
271 | + |
|
272 | +1. 【强制】MethodService 命名规范。注意与默认的方法重名,内置的方法目前有create delete select update search 等,所以要明确要重写父类的方法,还是定义新方法。 |
|
273 | + |
|
274 | +说明:重写父类的方法时,一定要明确是继承还是扩展,如果想指定调用父类的方法,需要指明callSuper. |
|
275 | + |
|
276 | +1. 【强制】模型注解命名规范。哪些注解是必填的,不填写则警告。 |
|
277 | + |
|
278 | +说明:模型注解。 |
|
279 | + |
|
280 | +1. |
|
281 | +##### 常量定义 |
|
282 | + |
|
283 | +1. 【强制】不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。 |
|
284 | + |
|
285 | +正例:通过枚举值定义模型类型 |
|
286 | + |
|
287 | +public static enum ModelType { |
|
288 | + |
|
289 | + Default, |
|
290 | + |
|
291 | + Define, |
|
292 | + |
|
293 | + Buss, |
|
294 | + |
|
295 | + Memory, |
|
296 | + |
|
297 | + Data, |
|
298 | + |
|
299 | + Cache, |
|
300 | + |
|
301 | + Config, |
|
302 | + |
|
303 | + Tree, |
|
304 | + |
|
305 | + Template, |
|
306 | + |
|
307 | + View; |
|
308 | + |
|
309 | + private ModelType() { |
|
310 | + |
|
311 | + } |
|
312 | + |
|
313 | + } |
|
314 | + |
|
315 | +反例:模型名称用字符串,调用方法也是字符串。导致其他地方使用这个模型名称时,需要重新再写一遍字符串,如果修改了一处,另外一处不会自动同步修改。 |
|
316 | + |
|
317 | +this.getMeta().get("rbac\_tenant").callSuper(Tenant.class, "create", valuesList); |
|
318 | + |
|
319 | +1. 【强制】枚举值是确定不变的值列表;平台建立了dict app,支持动态配置字典类型与字 典值,java sdk用注解的方式可以便捷进行关联使用。 |
|
320 | + |
|
321 | +1. |
|
322 | +##### 模型规范 |
|
323 | + |
|
324 | +1. 【参考】基于元模型驱动的平台核心思想:一切皆模型、模型可扩展可继承。 |
|
325 | + |
|
326 | +说明:关于平台的几点说明: |
|
327 | + |
|
328 | +1. 每个元模型都有唯一身份证; |
|
329 | +2. 平台的模型不仅提供属性、服务等能力,模型之间还可以方便进行继承、扩展; |
|
330 | +3. 模型还分了业务模型(缓存与db的存储与访问能力)、数据模型(内存存储和访问能力)、树状模型(行级存储与访问能力)等; |
|
331 | +4. 业务模型根据是否抽象来确定是否需要建立表; |
|
332 | + |
|
333 | +1. 【强制】通过@Model注解声明模型 |
|
334 | + |
|
335 | +1. 【强制】通过extends BaseModel标识模型具备元模型能力 |
|
336 | + |
|
337 | +1. 【推荐】视图模型解决的场景 |
|
338 | + |
|
339 | +1. 页面一样,处理逻辑不一样 |
|
340 | + |
|
341 | +很多时候一个模型可能对应的不只是一个菜单,可能根据不同的列显示要求对应多个菜单。如果仅仅是列显示的变化,而crud的逻辑是一样的,这样可以通过权限加以控制;视图模型用在同一个模型,crud逻辑不一样的场景。 |
|
342 | + |
|
343 | +1. 多模型数据聚合、按字段分组合并 |
|
344 | + |
|
345 | +可以根据关联模型的过滤条件进行检索,查询多个模型的数据、并支持按字段进行分组后将字段多行数据合并。 |
|
346 | + |
|
347 | +1. 【强制】模型继承规范。不推荐使用多继承。 |
|
348 | + |
|
349 | +说明:通过@Model指定name和parent,当name和parent不一致时为模型继承关系,parent是父模型名称,name是子模型的名称。新模型将继承原模型的所有字段、方法和服务。示例如下: |
|
350 | + |
|
351 | +@Model(name = "a\_model",parent = "b\_model")public class AModel extends TestUser{} |
|
352 | + |
|
353 | +@Model 中parent是个数组,可以通过设置多个parent,表示多继承,该模型会自动继承多个父模型。 |
|
354 | + |
|
355 | +继承服务移除:子模型继承了父模型,但是不想具备父模型中个别的服务,这时需要用到服务移除@Service(remove=["serviceName1","serviceName2"] |
|
356 | + |
|
357 | +1. 【强制】扩展规范。 |
|
358 | + |
|
359 | +说明:平台最核心的能力就是扩展能力。平台可以通过模型扩展增强或改变原有模型内部定义的服务、属性,甚至模型自身的元数据。 |
|
360 | + |
|
361 | +正例:模型内部crud扩展。常见场景说明:想在平台提供默认的crud能力的基础上,做一些业务操作。通过在业务模型中定义与默认方法签名相同的方法,来重写默认逻辑。 通过@Model指定name和parent,当name和parent一致时为模型扩展关系。扩展已定义的模型,为已有模型增加能力,或改变已有模型的现有能力,示例如下: |
|
362 | + |
|
363 | +_// __新增_ |
|
364 | + |
|
365 | +public void create(@Spec(doc = "k v") List\<Map\<String, Object\>\> valuesList) { |
|
366 | + |
|
367 | +} |
|
368 | + |
|
369 | +_// __修改_ |
|
370 | + |
|
371 | +public int update(@BaseService.Spec(doc = "k v") Map\<String, Object\> values) { |
|
372 | + |
|
373 | + ... |
|
374 | + |
|
375 | +} |
|
376 | + |
|
377 | +_// __删除_ |
|
378 | + |
|
379 | +public boolean delete() { |
|
380 | + |
|
381 | + ... |
|
382 | + |
|
383 | +} |
|
384 | + |
|
385 | +_// __查询_ |
|
386 | + |
|
387 | +public List\<Map\<String, Object\>\> search(@Spec(doc = "过滤器") Filter filter, |
|
388 | + |
|
389 | + @Spec(doc = "多个属性") List\<String\> properties, |
|
390 | + |
|
391 | + @Spec(doc = "初始位置") Integer limit, |
|
392 | + |
|
393 | + @Spec(doc = "记录数") Integer offset, |
|
394 | + |
|
395 | + @Spec(doc = "排序") String order) { |
|
396 | + |
|
397 | + ... |
|
398 | + |
|
399 | +} |
|
400 | + |
|
401 | +_// __统计数量_ |
|
402 | + |
|
403 | +public long count(@Spec(doc = "过滤") Filter filter) { |
|
404 | + |
|
405 | + ... |
|
406 | + |
|
407 | +} |
|
408 | + |
|
409 | +1. 【强制】唯一索引 |
|
410 | + |
|
411 | +1. 【强制】约束。(循环依赖) |
|
412 | + |
|
413 | +1. 【强制】ER关系 |
|
414 | + |
|
415 | +说明:尽量不要在循环中获取ER关系字段,平台对当前线程中的N:1按id进行了缓存,在循环中多次查询可能会命中缓存,但是存在1+N次查询的风险。建议分2次查询后进行组装返回最终结果。 |
|
416 | + |
|
417 | +正例: |
|
418 | + |
|
419 | +1. OneToMany 一对多 |
|
420 | + |
|
421 | +@OneToMany |
|
422 | + |
|
423 | +private List\<TestUser\> userList; |
|
424 | + |
|
425 | +1. ManyToOne 多对一 |
|
426 | + |
|
427 | +@ManyToOne(displayName="组织机构")@JoinColumn(name="org\_id", referencedColumnName = "id") |
|
428 | + |
|
429 | +private TestOrg org; |
|
430 | + |
|
431 | +1. ManyToMany 多对多 |
|
432 | + |
|
433 | +@ManyToMany |
|
434 | + |
|
435 | +@JoinTable(name = "role\_user", joinColumns = @JoinColumn(name = "role\_id",nullable = false), |
|
436 | + |
|
437 | +inverseJoinColumns = @JoinColumn(name = "user\_id", nullable = false)) |
|
438 | + |
|
439 | +private List\<TestUser\> userList; |
|
440 | + |
|
441 | +1. 【强制】数据库连接 |
|
442 | + |
|
443 | +1. 【强制】ORM使用 |
|
444 | + |
|
445 | +1. 【强制】Filter过滤条件 |
|
446 | + |
|
447 | +1. filter表达式 |
|
448 | + |
|
449 | +filter表达式通常用来筛选数据记录。它们使用波兰表示法语法,以便于将它们解析后生成对应的SQL WHERE数据库筛选语句。 filter通常为一个数组,数组元素为过滤条件,每个条件是一个三元表达式,例如:["&",["state","=","confirm"],["user\_id","in",[1,2,3]]] filter多个条件的逻辑运算使用了"波兰表示法",波兰表示法的特定是操作符置于操作数前,运算顺序为:从左至右读入表达式,遇到一个操作符后跟随两个操作数时,则计算之,然后将结果作为操作数替换这个操作符和两个操作数;重复此步骤,直至所有操作符处理完毕。 |
|
450 | + |
|
451 | +1. filter写法 |
|
452 | + |
|
453 | +filter表达式是一个条件列表,每个条件是一个形如["field\_name", "operator", value]的数组。 |
|
454 | + |
|
455 | +正例: |
|
456 | + |
|
457 | +[ |
|
458 | + |
|
459 | + "|", |
|
460 | + |
|
461 | + [ |
|
462 | + |
|
463 | + "message\_follower\_ids", |
|
464 | + |
|
465 | + "in", |
|
466 | + |
|
467 | + [ |
|
468 | + |
|
469 | + "1", |
|
470 | + |
|
471 | + "2", |
|
472 | + |
|
473 | + "3" |
|
474 | + |
|
475 | + ] |
|
476 | + |
|
477 | + ], |
|
478 | + |
|
479 | + "|", |
|
480 | + |
|
481 | + [ |
|
482 | + |
|
483 | + "user\_id", |
|
484 | + |
|
485 | + "=", |
|
486 | + |
|
487 | + "1000" |
|
488 | + |
|
489 | + ], |
|
490 | + |
|
491 | + [ |
|
492 | + |
|
493 | + "user\_id", |
|
494 | + |
|
495 | + "=", |
|
496 | + |
|
497 | + false |
|
498 | + |
|
499 | + ] |
|
500 | + |
|
501 | +] |
|
502 | + |
|
503 | +filed\_name 是需要筛选的字段,它可以使用点(.)来访问关系模块的字段。 value 是一个表达式的值。它可以使用字符值,比如:字符串,数字,布尔值,或则列表、某个字段。 operator 可以为: 常用的操作符:\<,\>,\<=,\>=,=,!=。 "like"匹配一个"%value%"的字符串。"ilike"与此类似但不区分大小写。"not like"和"not ilike"也可以使用 "child\_of","parent\_of"在层级关系中,筛选子集 "in"和"not in"筛选是否在一个列表里面,所以,给的值应该是个list。当在"to-many"的关系字段中,"in"的作用和contains的作用一样。 |
|
504 | + |
|
505 | +1. Filter的操作符 |
|
506 | + |
|
507 | +操作符主要有如下类型。逻辑运算符,主要用于多个条件处理,逻辑运算符链接。逻辑运算符作为前缀放置于条件前面。: "|"(or) "&" (and) "!"(no)" 默认逻辑运算符为"&" |
|
508 | + |
|
509 | +| **操作符** | **说明** | |
|
510 | +| --- | --- | |
|
511 | +| =,!=,\>,\>=,\<,\<= | 比较运算,等于,不等于,大于,大于等于,小于,小于等于 | |
|
512 | +| --- | --- | |
|
513 | +| like | 模糊匹配,可以使用通配符,,百分号"%"匹配零或者多个字符 | |
|
514 | +| ilike | 类似like,不区分大小写 | |
|
515 | +| not like | 模糊不匹配的 | |
|
516 | +| in | 包含,判断值是否在元素的列表里面 | |
|
517 | +| not in | 不包含,判断值是否不在元素的列表里面 | |
|
518 | +| child\_of | 判断是否value的子记录 | |
|
519 | +| parent\_of | 用于有 父子关系的模型,13版本开始使用。 在旧版本使用 parent\_left, parent\_right | |
|
520 | + |
|
521 | +1. Filter使用的算法是波兰表达式 |
|
522 | + |
|
523 | +计算的核心思想:运算波兰表达式时,无需记住运算的层次,只需要直接寻找第一个运算的操作符。以二元运算为例,从左至右读入表达式, 遇到一个操作符后跟随两个操作数时,则计算之,然后将结果作为操作数替换这个操作符和两个操作数;重复此步骤,直至所有操作符处理完毕。 简单来说,波兰表示法是一种操作符置于操作数前,并且不需要括号仍然能无歧义地解析表达的方法。 |
|
524 | + |
|
525 | +1. Filter的使用,大写的AND,小写的and不一样。 |
|
526 | + |
|
527 | +预加载 |
|
528 | + |
|
529 | +级联 |
|
530 | + |
|
531 | +分库分表 |
|
532 | + |
|
533 | +依赖项,数据依赖,配置,种子数据,app内聚,装完就全部有了。任意一个环境装app都可用。 |
|
534 | + |
|
535 | +机制问题。扩展对方的种子数据(看板,数据字典) |
|
536 | + |
|
537 | +1. |
|
538 | +##### 在线IDE使用规范 |
|
539 | +2. |
|
540 | +##### SDK使用规范 |
|
541 | + |
|
542 | +1. 熟悉SDK文档和功能: |
|
543 | + |
|
544 | +在开始使用SDK之前,仔细阅读官方文档,了解SDK提供的功能和用法。 |
|
545 | + |
|
546 | +理解SDK的设计理念和工作原理,以便正确地使用和集成SDK。 |
|
547 | + |
|
548 | +1. 遵循SDK的最佳实践: |
|
549 | + |
|
550 | +官方文档已提供一些最佳实践和推荐的使用方式,尽量遵循这些指南。 |
|
551 | + |
|
552 | +遵循SDK的设计模式和约定,以便与其他开发者更好地协作。 |
|
553 | + |
|
554 | +1. 使用适当的错误处理机制: |
|
555 | + |
|
556 | +当使用SDK的方法或者函数时,要注意捕获和处理可能发生的异常。 |
|
557 | + |
|
558 | +根据SDK提供的错误码或者异常类型,进行适当的错误处理,例如记录日志、返回错误信息等。 |
|
559 | + |
|
560 | +1. 避免滥用SDK: |
|
561 | + |
|
562 | +只使用SDK提供的必要功能,避免滥用SDK的高级功能,以免增加复杂性和性能开销。 |
|
563 | + |
|
564 | +不要过度依赖SDK,尽量保持代码的灵活性和可扩展性。 |
|
565 | + |
|
566 | +1. 使用SDK提供的扩展点: |
|
567 | + |
|
568 | +如果SDK提供了扩展点或者插件机制,可以使用这些机制来自定义功能或者扩展SDK的行为。 |
|
569 | + |
|
570 | +1. 更新和升级SDK: |
|
571 | + |
|
572 | +定期检查SDK的新版本和更新,了解新功能、修复的问题和性能改进。 |
|
573 | + |
|
574 | +在合适的时机,考虑升级到新版本的SDK,以获得更好的功能和性能。 |
|
575 | + |
|
576 | +1. 与SDK开发者保持互动: |
|
577 | + |
|
578 | +如果SDK有相关的群聊或者社区,可以积极参与其中,与其他开发者交流经验和解决问题。提交反馈和建议,帮助SDK的改进和发展。(外网可访问的在线文档,反馈和建议的途径) |
|
579 | + |
|
580 | +1. IIDP SDK使用规范 |
|
581 | + |
|
582 | +api调用 |
|
583 | + filter |
|
584 | + crud |
|
585 | +与前端页面交互 |
|
586 | + |
|
587 | +总之,SDK使用规范的核心是熟悉SDK的功能和用法,并遵循官方文档中的建议和最佳实践。合理地使用SDK,并与其他团队成员保持一致的使用方式,可以提高代码的可读性、可维护性和可扩展性。 |
|
588 | + |
|
589 | +1. |
|
590 | +##### 日志规范 |
|
591 | + |
|
592 | +1. 【强制】IIDP引擎日志规范。 |
|
593 | + |
|
594 | +说明:IIDP引擎对通用的日志接口进行封装,规范日志打印格式,自动追加引擎相关日志信息,比如自动最佳model service等信息,自动可知道访问的是哪个model哪个service,基于这种结构化的日志,便于后续的日志分析。 |
|
595 | + |
|
596 | +1. 【强制】在日志输出中包含有用的上下文信息。 |
|
597 | + |
|
598 | +说明:日志应该包含有助于定位问题的上下文信息,例如时间戳、线程ID、请求ID、关键参数值等。 |
|
599 | + |
|
600 | +正例: |
|
601 | + |
|
602 | + import org.slf4j.Logger; |
|
603 | + |
|
604 | + import org.slf4j.LoggerFactory; |
|
605 | + |
|
606 | + public class ExampleClass { |
|
607 | + |
|
608 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
609 | + |
|
610 | + public void doSomething(int id) { |
|
611 | + |
|
612 | + logger.info("Received request with ID: {}", id); |
|
613 | + |
|
614 | + _// __执行业务逻辑_ |
|
615 | + |
|
616 | + } |
|
617 | + |
|
618 | + } |
|
619 | + |
|
620 | +反例: |
|
621 | + |
|
622 | + import org.slf4j.Logger; |
|
623 | + |
|
624 | + import org.slf4j.LoggerFactory; |
|
625 | + |
|
626 | + public class ExampleClass { |
|
627 | + |
|
628 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
629 | + |
|
630 | + public void doSomething(int id) { |
|
631 | + |
|
632 | + logger.info("Received request"); _// __缺少关键信息_ |
|
633 | + |
|
634 | + _// __执行业务逻辑_ |
|
635 | + |
|
636 | + } |
|
637 | + |
|
638 | + } |
|
639 | + |
|
640 | +1. 【强制】使用适当的日志级别。 |
|
641 | + |
|
642 | +说明:根据日志的重要性和信息量选择适当的日志级别,例如使用`DEBUG`级别进行调试和开发阶段,使用`INFO`级别进行常规信息输出,使用`WARN`级别进行警告信息输出,使用`ERROR`级别进行错误信息输出。 (动态提升日志的级别,指定模型的日志级别) |
|
643 | + |
|
644 | +正例: |
|
645 | + |
|
646 | +import org.slf4j.Logger; |
|
647 | + |
|
648 | + import org.slf4j.LoggerFactory; |
|
649 | + |
|
650 | + public class ExampleClass { |
|
651 | + |
|
652 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
653 | + |
|
654 | + public void doSomething() { |
|
655 | + |
|
656 | + logger.debug("Debug message"); _// __调试信息_ |
|
657 | + |
|
658 | + logger.info("Info message"); _// __常规信息_ |
|
659 | + |
|
660 | + logger.warn("Warning message"); _// __警告信息_ |
|
661 | + |
|
662 | + logger.error("Error message"); _// __错误信息_ |
|
663 | + |
|
664 | + } |
|
665 | + |
|
666 | + } |
|
667 | + |
|
668 | +反例: |
|
669 | + |
|
670 | + import org.slf4j.Logger; |
|
671 | + |
|
672 | + import org.slf4j.LoggerFactory; |
|
673 | + |
|
674 | + public class ExampleClass { |
|
675 | + |
|
676 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
677 | + |
|
678 | + public void doSomething() { |
|
679 | + |
|
680 | + logger.info("Debug message"); _// __错误的日志级别_ |
|
681 | + |
|
682 | + logger.info("Error message"); _// __错误的日志级别_ |
|
683 | + |
|
684 | + } |
|
685 | + |
|
686 | + } |
|
687 | + |
|
688 | +1. 【推荐】在日志输出中包含异常信息。 |
|
689 | + |
|
690 | +说明:在捕获和处理异常时,应该将异常信息记录在日志中,以便更好地了解问题的原因。 |
|
691 | + |
|
692 | +正例: |
|
693 | + |
|
694 | +import org.slf4j.Logger; |
|
695 | + |
|
696 | + import org.slf4j.LoggerFactory; |
|
697 | + |
|
698 | + public class ExampleClass { |
|
699 | + |
|
700 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
701 | + |
|
702 | + public void doSomething() { |
|
703 | + |
|
704 | + try { |
|
705 | + |
|
706 | + _// __执行业务逻辑_ |
|
707 | + |
|
708 | + } catch (Exception e) { |
|
709 | + |
|
710 | + logger.error("An error occurred", e); _// __记录异常信息_ |
|
711 | + |
|
712 | + } |
|
713 | + |
|
714 | + } |
|
715 | + |
|
716 | + } |
|
717 | + |
|
718 | +反例: |
|
719 | + |
|
720 | + import org.slf4j.Logger; |
|
721 | + |
|
722 | + import org.slf4j.LoggerFactory; |
|
723 | + |
|
724 | + public class ExampleClass { |
|
725 | + |
|
726 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
727 | + |
|
728 | + public void doSomething() { |
|
729 | + |
|
730 | + try { |
|
731 | + |
|
732 | + _// __执行业务逻辑_ |
|
733 | + |
|
734 | + } catch (Exception e) { |
|
735 | + |
|
736 | + logger.error("An error occurred"); _// __缺少异常信息_ |
|
737 | + |
|
738 | + } |
|
739 | + |
|
740 | + } |
|
741 | + |
|
742 | + } |
|
743 | + |
|
744 | +1. 【推荐】避免在循环中频繁输出日志。 |
|
745 | + |
|
746 | +说明:在循环中频繁输出日志会导致日志文件过大,降低系统性能。应该根据实际需要选择适当的日志输出频率。 |
|
747 | + |
|
748 | +正向例子: |
|
749 | + |
|
750 | +import org.slf4j.Logger; |
|
751 | + |
|
752 | + import org.slf4j.LoggerFactory; |
|
753 | + |
|
754 | + public class ExampleClass { |
|
755 | + |
|
756 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
757 | + |
|
758 | + public void processItems(List\<Item\> items) { |
|
759 | + |
|
760 | + for (Item item : items) { |
|
761 | + |
|
762 | + _// __处理每个项目_ |
|
763 | + |
|
764 | + logger.debug("Processing item: {}", item.getId()); |
|
765 | + |
|
766 | + } |
|
767 | + |
|
768 | + } |
|
769 | + |
|
770 | + } |
|
771 | + |
|
772 | +反例: |
|
773 | + |
|
774 | +import org.slf4j.Logger; |
|
775 | + |
|
776 | + import org.slf4j.LoggerFactory; |
|
777 | + |
|
778 | + public class ExampleClass { |
|
779 | + |
|
780 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
781 | + |
|
782 | + public void processItems(List\<Item\> items) { |
|
783 | + |
|
784 | + for (Item item : items) { |
|
785 | + |
|
786 | + _// __处理每个项目_ |
|
787 | + |
|
788 | + logger.debug("Processing item: {}", item.getId()); |
|
789 | + |
|
790 | + logger.debug("Item processed"); _// __频繁输出日志_ |
|
791 | + |
|
792 | + } |
|
793 | + |
|
794 | + } |
|
795 | + |
|
796 | + } |
|
797 | + |
|
798 | +1. 【推荐】使用合适的日志框架和配置。 |
|
799 | + |
|
800 | +说明:选择适合项目需求的日志框架,并进行合适的配置。常见的日志框架包括Log4j、Logback和Slf4j等。 |
|
801 | + |
|
802 | +示例:使用Slf4j和Logback作为日志框架,并配置合适的日志级别和输出格式。(平台封装,对外提供平台规定的接口) |
|
803 | + |
|
804 | + _\<!-- pom.xml --\>_ |
|
805 | + |
|
806 | + \<dependencies\> |
|
807 | + |
|
808 | + \<dependency\> |
|
809 | + |
|
810 | + \<groupId\>org.slf4j\</groupId\> |
|
811 | + |
|
812 | + \<artifactId\>slf4j-api\</artifactId\> |
|
813 | + |
|
814 | + \<version\>1.7.32\</version\> |
|
815 | + |
|
816 | + \</dependency\> |
|
817 | + |
|
818 | + \<dependency\> |
|
819 | + |
|
820 | + \<groupId\>ch.qos.logback\</groupId\> |
|
821 | + |
|
822 | + \<artifactId\>logback-classic\</artifactId\> |
|
823 | + |
|
824 | + \<version\>1.2.6\</version\> |
|
825 | + |
|
826 | + \</dependency\> |
|
827 | + |
|
828 | + \</dependencies\> |
|
829 | + |
|
830 | + _\<!-- logback.xml --\>_ |
|
831 | + |
|
832 | + \<configuration\> |
|
833 | + |
|
834 | + \<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"\> |
|
835 | + |
|
836 | + \<encoder\> |
|
837 | + |
|
838 | + \<pattern\>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n\</pattern\> |
|
839 | + |
|
840 | + \</encoder\> |
|
841 | + |
|
842 | + \</appender\> |
|
843 | + |
|
844 | + \<root level="INFO"\> |
|
845 | + |
|
846 | + \<appender-ref ref="CONSOLE"/\> |
|
847 | + |
|
848 | + \</root\> |
|
849 | + |
|
850 | + \</configuration\> |
|
851 | + |
|
852 | +6. 【推荐】使用有意义的日志消息。 |
|
853 | + |
|
854 | +说明:日志消息应该清晰、简洁且有意义,以便于开发人员和运维人员理解日志的含义。 |
|
855 | + |
|
856 | +正例: |
|
857 | + |
|
858 | +import org.slf4j.Logger; |
|
859 | + |
|
860 | + import org.slf4j.LoggerFactory; |
|
861 | + |
|
862 | + public class ExampleClass { |
|
863 | + |
|
864 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
865 | + |
|
866 | + public void processOrder(Order order) { |
|
867 | + |
|
868 | + logger.info("Processing order: {}", order.getId()); |
|
869 | + |
|
870 | + _// __处理订单逻辑_ |
|
871 | + |
|
872 | + } |
|
873 | + |
|
874 | + } |
|
875 | + |
|
876 | +反例: |
|
877 | + |
|
878 | + import org.slf4j.Logger; |
|
879 | + |
|
880 | + import org.slf4j.LoggerFactory; |
|
881 | + |
|
882 | + public class ExampleClass { |
|
883 | + |
|
884 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
885 | + |
|
886 | + public void processOrder(Order order) { |
|
887 | + |
|
888 | + logger.info("Method called"); _// __无意义的日志消息_ |
|
889 | + |
|
890 | + _// __处理订单逻辑_ |
|
891 | + |
|
892 | + } |
|
893 | + |
|
894 | + } |
|
895 | + |
|
896 | +7. 【推荐】避免在生产环境中输出过多的调试信息。 |
|
897 | + |
|
898 | +说明:在生产环境中,应该避免输出过多的调试信息,以减少日志文件大小和系统性能开销。可以使用条件判断或配置项来控制是否输出调试信息。 |
|
899 | + |
|
900 | +正例: |
|
901 | + |
|
902 | + import org.slf4j.Logger; |
|
903 | + |
|
904 | + import org.slf4j.LoggerFactory; |
|
905 | + |
|
906 | + public class ExampleClass { |
|
907 | + |
|
908 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
909 | + |
|
910 | + private static final boolean DEBUG\_ENABLED = false; _// __控制调试信息输出的开关_ |
|
911 | + |
|
912 | + public void doSomething() { |
|
913 | + |
|
914 | + if (DEBUG\_ENABLED) { |
|
915 | + |
|
916 | + logger.debug("Debug message"); _// __只在调试模式下输出_ |
|
917 | + |
|
918 | + } |
|
919 | + |
|
920 | + _// __执行业务逻辑_ |
|
921 | + |
|
922 | + } |
|
923 | + |
|
924 | + } |
|
925 | + |
|
926 | +反例: |
|
927 | + |
|
928 | +import org.slf4j.Logger; |
|
929 | + |
|
930 | + import org.slf4j.LoggerFactory; |
|
931 | + |
|
932 | + public class ExampleClass { |
|
933 | + |
|
934 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
935 | + |
|
936 | + public void doSomething() { |
|
937 | + |
|
938 | + logger.debug("Debug message"); _// __无条件输出调试信息_ |
|
939 | + |
|
940 | + _// __执行业务逻辑_ |
|
941 | + |
|
942 | + } |
|
943 | + |
|
944 | + } |
|
945 | + |
|
946 | +8. 【推荐】使用适当的日志输出格式。 |
|
947 | + |
|
948 | +说明:选择合适的日志输出格式,使日志易于阅读和解析。常见的格式包括普通文本格式、JSON格式和XML格式等。 |
|
949 | + |
|
950 | +正例: |
|
951 | + |
|
952 | +import org.slf4j.Logger; |
|
953 | + |
|
954 | + import org.slf4j.LoggerFactory; |
|
955 | + |
|
956 | + public class ExampleClass { |
|
957 | + |
|
958 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
959 | + |
|
960 | + public void doSomething() { |
|
961 | + |
|
962 | + String message = "Log message with dynamic data"; |
|
963 | + |
|
964 | + logger.info("{}", message); _// __使用占位符输出日志消息_ |
|
965 | + |
|
966 | + _// __执行业务逻辑_ |
|
967 | + |
|
968 | + } |
|
969 | + |
|
970 | + } |
|
971 | + |
|
972 | +反例: |
|
973 | + |
|
974 | + import org.slf4j.Logger; |
|
975 | + |
|
976 | + import org.slf4j.LoggerFactory; |
|
977 | + |
|
978 | + public class ExampleClass { |
|
979 | + |
|
980 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
981 | + |
|
982 | + public void doSomething() { |
|
983 | + |
|
984 | + String message = "Log message with dynamic data"; |
|
985 | + |
|
986 | + logger.info(message); _// __直接输出日志消息_ |
|
987 | + |
|
988 | + _// __执行业务逻辑_ |
|
989 | + |
|
990 | + } |
|
991 | + |
|
992 | + } |
|
993 | + |
|
994 | +9. 【推荐】定期审查和清理日志文件。 |
|
995 | + |
|
996 | +说明:定期审查和清理日志文件可以避免日志文件过大,占用过多的磁盘空间。可以根据实际需求和存储能力,设置合适的日志保留期限和清理策略。 |
|
997 | + |
|
998 | +示例:使用日志框架提供的日志轮转和清理功能,定期清理过期的日志文件。 |
|
999 | + |
|
1000 | +_\<!-- logback.xml --\>_ |
|
1001 | + |
|
1002 | + \<configuration\> |
|
1003 | + |
|
1004 | + \<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"\> |
|
1005 | + |
|
1006 | + \<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"\> |
|
1007 | + |
|
1008 | + \<fileNamePattern\>logs/example.log.%d{yyyy-MM-dd}\</fileNamePattern\> |
|
1009 | + |
|
1010 | + \<maxHistory\>30\</maxHistory\> _\<!-- __保留最近__ 30 __天的日志文件 __ --\>_ |
|
1011 | + |
|
1012 | + \</rollingPolicy\> |
|
1013 | + |
|
1014 | + \<encoder\> |
|
1015 | + |
|
1016 | + \<pattern\>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n\</pattern\> |
|
1017 | + |
|
1018 | + \</encoder\> |
|
1019 | + |
|
1020 | + \</appender\> |
|
1021 | + |
|
1022 | + \<root level="INFO"\> |
|
1023 | + |
|
1024 | + \<appender-ref ref="FILE"/\> |
|
1025 | + |
|
1026 | + \</root\> |
|
1027 | + |
|
1028 | + \</configuration\> |
|
1029 | + |
|
1030 | +10. 【推荐】在关键路径和重要功能中增加详细的日志输出。 |
|
1031 | + |
|
1032 | +说明:在关键路径和重要功能中增加详细的日志输出可以帮助跟踪和排查问题。可以根据实际情况,在关键的方法、条件判断和异常处理等位置添加详细的日志输出。 |
|
1033 | + |
|
1034 | +正例: |
|
1035 | + |
|
1036 | + import org.slf4j.Logger; |
|
1037 | + |
|
1038 | + import org.slf4j.LoggerFactory; |
|
1039 | + |
|
1040 | + public class ExampleClass { |
|
1041 | + |
|
1042 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
1043 | + |
|
1044 | + public void processOrder(Order order) { |
|
1045 | + |
|
1046 | + logger.info("Processing order: {}", order.getId()); |
|
1047 | + |
|
1048 | + _// __执行订单处理逻辑_ |
|
1049 | + |
|
1050 | + logger.info("Order processed successfully: {}", order.getId()); |
|
1051 | + |
|
1052 | + } |
|
1053 | + |
|
1054 | + } |
|
1055 | + |
|
1056 | +反例: |
|
1057 | + |
|
1058 | +import org.slf4j.Logger; |
|
1059 | + |
|
1060 | + import org.slf4j.LoggerFactory; |
|
1061 | + |
|
1062 | + public class ExampleClass { |
|
1063 | + |
|
1064 | + private static final Logger logger = LoggerFactory.getLogger(ExampleClass.class); |
|
1065 | + |
|
1066 | + public void processOrder(Order order) { |
|
1067 | + |
|
1068 | + logger.info("Processing order"); _// __缺少关键信息_ |
|
1069 | + |
|
1070 | + _// __执行订单处理逻辑_ |
|
1071 | + |
|
1072 | + logger.info("Order processed"); _// __缺少关键信息_ |
|
1073 | + |
|
1074 | + } |
|
1075 | + |
|
1076 | + } |
|
1077 | + |
|
1078 | +这些规范和示例可以帮助您在日志打印功能中提高质量和可读性。请根据您的项目需求和团队约定,选择适合的规范并进行实施。 |
|
1079 | + |
|
1080 | +1. |
|
1081 | +#### 测试规范 |
|
1082 | + |
|
1083 | +1. |
|
1084 | +##### 单元测试 |
|
1085 | + |
|
1086 | +1. 【强制】编写独立、可重复执行的单元测试。 |
|
1087 | + |
|
1088 | +说明:单元测试应该是独立的、不依赖外部资源的测试,可以在任何环境下重复执行。确保每个单元测试之间相互独立,不会相互影响,提高测试的可靠性和可维护性。 |
|
1089 | + |
|
1090 | +1. 【强制】使用适当的测试框架和断言库。 |
|
1091 | + |
|
1092 | +说明:选择适合项目的测试框架(如JUnit、TestNG等)和断言库(如AssertJ、Hamcrest等),以便编写清晰、简洁的测试代码,并提供丰富的断言方法来验证测试结果的正确性。 |
|
1093 | + |
|
1094 | +1. 【强制】 测试覆盖率达到预定目标。 |
|
1095 | + |
|
1096 | +说明:测试覆盖率是衡量测试代码覆盖业务代码的程度的指标。根据项目的需求和复杂度,设定合理的测试覆盖率目标,并确保单元测试覆盖率达到或超过这个目标。 |
|
1097 | + |
|
1098 | +1. 【强制】 每个单元测试应该只测试一个功能点或场景。 |
|
1099 | + |
|
1100 | +说明:每个单元测试应该聚焦于测试一个特定的功能点或场景,避免将多个功能点或场景混合在一个测试中。这样可以提高测试的可读性和可维护性,并能更准确地定位问题。 |
|
1101 | + |
|
1102 | +1. 【强制】 使用合适的测试数据进行测试。 |
|
1103 | + |
|
1104 | +说明:在编写单元测试时,应该使用合适的测试数据来覆盖不同的情况和边界条件,以验证代码在各种情况下的正确性。包括正常情况、边界情况、异常情况等。 |
|
1105 | + |
|
1106 | +1. 【强制】验证预期的行为和结果。 |
|
1107 | + |
|
1108 | +说明:每个单元测试应该明确验证预期的行为和结果,通过断言来判断测试是否通过。确保测试代码能够准确地验证被测试代码的行为和结果。 |
|
1109 | + |
|
1110 | +1. 【强制】 针对可能出现的异常情况进行测试。 |
|
1111 | + |
|
1112 | +说明:在编写单元测试时,应该针对可能出现的异常情况进行测试,以验证代码在异常情况下的处理能力。包括输入参数为空、越界访问、异常抛出等。 |
|
1113 | + |
|
1114 | +1. 【强制】 定期运行单元测试,并及时修复失败的测试。 |
|
1115 | + |
|
1116 | +说明:单元测试应该定期运行,以确保代码的质量和稳定性。当单元测试失败时,应该及时修复失败的测试,并确保测试通过。这样可以保证代码的可靠性和可维护性。 |
|
1117 | + |
|
1118 | +1. 【建议】 使用测试替身(Mock、Stub等)来隔离外部依赖。 |
|
1119 | + |
|
1120 | +说明:在单元测试中,应该使用测试替身(如Mock、Stub等)来隔离外部依赖,以便更好地控制测试环境和验证被测试代码的行为。通过模拟外部依赖的行为,可以更方便地编写和执行单元测试。 |
|
1121 | + |
|
1122 | +1. 【建议】 使用参数化测试来减少重复代码。 |
|
1123 | + |
|
1124 | +说明:参数化测试是一种通过传入不同的参数来执行相同测试逻辑的方式,可以减少重复的测试代码。通过使用参数化测试,可以更高效地编写和维护单元测试。 |
|
1125 | + |
|
1126 | +下面给出详细的正向例子和反向例子来说明单元测试的规范。 |
|
1127 | + |
|
1128 | +正例:(使用JUnit和AssertJ): |
|
1129 | + |
|
1130 | +import org.junit.jupiter.api.Test; |
|
1131 | + |
|
1132 | +import static org.assertj.core.api.Assertions.\*; |
|
1133 | + |
|
1134 | +public class CalculatorTest { |
|
1135 | + |
|
1136 | + @Test |
|
1137 | + |
|
1138 | + public void testAdd() { |
|
1139 | + |
|
1140 | + Calculator calculator = new Calculator(); |
|
1141 | + |
|
1142 | + int result = calculator.add(2, 3); |
|
1143 | + |
|
1144 | + assertThat(result).isEqualTo(5); |
|
1145 | + |
|
1146 | + } |
|
1147 | + |
|
1148 | + @Test |
|
1149 | + |
|
1150 | + public void testSubtract() { |
|
1151 | + |
|
1152 | + Calculator calculator = new Calculator(); |
|
1153 | + |
|
1154 | + int result = calculator.subtract(5, 3); |
|
1155 | + |
|
1156 | + assertThat(result).isEqualTo(2); |
|
1157 | + |
|
1158 | + } |
|
1159 | + |
|
1160 | +} |
|
1161 | + |
|
1162 | +反例:(未使用断言库): |
|
1163 | + |
|
1164 | +import org.junit.jupiter.api.Test; |
|
1165 | + |
|
1166 | +import static org.junit.jupiter.api.Assertions.\*; |
|
1167 | + |
|
1168 | +public class CalculatorTest { |
|
1169 | + |
|
1170 | + @Test |
|
1171 | + |
|
1172 | + public void testAdd() { |
|
1173 | + |
|
1174 | + Calculator calculator = new Calculator(); |
|
1175 | + |
|
1176 | + int result = calculator.add(2, 3); |
|
1177 | + |
|
1178 | + assertEquals(5, result); |
|
1179 | + |
|
1180 | + } |
|
1181 | + |
|
1182 | + @Test |
|
1183 | + |
|
1184 | + public void testSubtract() { |
|
1185 | + |
|
1186 | + Calculator calculator = new Calculator(); |
|
1187 | + |
|
1188 | + int result = calculator.subtract(5, 3); |
|
1189 | + |
|
1190 | + assertEquals(2, result); |
|
1191 | + |
|
1192 | + } |
|
1193 | + |
|
1194 | +} |
|
1195 | + |
|
1196 | +在正向例子中,使用了JUnit作为测试框架和AssertJ作为断言库。每个单元测试聚焦于测试一个特定的功能点(add和subtract方法),使用合适的测试数据进行测试,并通过断言来验证预期的结果。测试代码清晰、简洁,并且易于阅读和维护。 |
|
1197 | + |
|
1198 | +而在反向例子中,虽然也使用了JUnit作为测试框架,但未使用断言库,而是使用了JUnit提供的断言方法。这样的测试代码可读性较差,断言的错误信息也不够清晰,不利于定位问题和维护测试代码。 |
|
1199 | + |
|
1200 | +1. |
|
1201 | +##### 功能测试 |
|
1202 | + |
|
1203 | +1. 【强制】验证系统的用户界面(UI)功能。 |
|
1204 | + |
|
1205 | +说明:功能测试应该验证系统的用户界面功能是否符合预期,包括页面布局、交互操作、表单验证等。通过功能测试可以确保用户界面的可用性和易用性。 |
|
1206 | + |
|
1207 | +正例: |
|
1208 | + |
|
1209 | + import org.junit.jupiter.api.Test; |
|
1210 | + |
|
1211 | + import static org.junit.jupiter.api.Assertions.\*; |
|
1212 | + |
|
1213 | + public class LoginPageTest { |
|
1214 | + |
|
1215 | + @Test |
|
1216 | + |
|
1217 | + public void testLoginPageLayout() { |
|
1218 | + |
|
1219 | + LoginPage page = new LoginPage(); |
|
1220 | + |
|
1221 | + assertEquals("Login", page.getTitle()); |
|
1222 | + |
|
1223 | + assertTrue(page.isUsernameFieldDisplayed()); |
|
1224 | + |
|
1225 | + assertTrue(page.isPasswordFieldDisplayed()); |
|
1226 | + |
|
1227 | + assertTrue(page.isLoginButtonDisplayed()); |
|
1228 | + |
|
1229 | + } |
|
1230 | + |
|
1231 | + @Test |
|
1232 | + |
|
1233 | + public void testLoginWithValidCredentials() { |
|
1234 | + |
|
1235 | + LoginPage page = new LoginPage(); |
|
1236 | + |
|
1237 | + page.setUsername("admin"); |
|
1238 | + |
|
1239 | + page.setPassword("password"); |
|
1240 | + |
|
1241 | + page.clickLoginButton(); |
|
1242 | + |
|
1243 | + assertTrue(page.isUserLoggedIn()); |
|
1244 | + |
|
1245 | + assertEquals("Welcome, admin!", page.getWelcomeMessage()); |
|
1246 | + |
|
1247 | + } |
|
1248 | + |
|
1249 | + } |
|
1250 | + |
|
1251 | +反例: |
|
1252 | + |
|
1253 | +import org.junit.jupiter.api.Test; |
|
1254 | + |
|
1255 | + import static org.junit.jupiter.api.Assertions.\*; |
|
1256 | + |
|
1257 | + public class LoginPageTest { |
|
1258 | + |
|
1259 | + @Test |
|
1260 | + |
|
1261 | + public void testLoginPageLayout() { |
|
1262 | + |
|
1263 | + LoginPage page = new LoginPage(); |
|
1264 | + |
|
1265 | + assertEquals("Login", page.getTitle()); |
|
1266 | + |
|
1267 | + assertTrue(page.isUsernameFieldDisplayed()); |
|
1268 | + |
|
1269 | + assertTrue(page.isPasswordFieldDisplayed()); |
|
1270 | + |
|
1271 | + assertFalse(page.isLoginButtonDisplayed()); |
|
1272 | + |
|
1273 | + } |
|
1274 | + |
|
1275 | + @Test |
|
1276 | + |
|
1277 | + public void testLoginWithValidCredentials() { |
|
1278 | + |
|
1279 | + LoginPage page = new LoginPage(); |
|
1280 | + |
|
1281 | + page.setUsername("admin"); |
|
1282 | + |
|
1283 | + page.setPassword("password"); |
|
1284 | + |
|
1285 | + page.clickLoginButton(); |
|
1286 | + |
|
1287 | + assertFalse(page.isUserLoggedIn()); |
|
1288 | + |
|
1289 | + assertEquals("Invalid username or password", page.getErrorMessage()); |
|
1290 | + |
|
1291 | + } |
|
1292 | + |
|
1293 | + } |
|
1294 | + |
|
1295 | +1. 【强制】验证系统的数据输入和输出。 |
|
1296 | + |
|
1297 | +说明:功能测试应该验证系统的数据输入和输出是否正确。包括验证输入数据的合法性、验证输出数据的准确性等。 |
|
1298 | + |
|
1299 | +正例: |
|
1300 | + |
|
1301 | +import org.junit.jupiter.api.Test; |
|
1302 | + |
|
1303 | + import static org.junit.jupiter.api.Assertions.\*; |
|
1304 | + |
|
1305 | + public class CalculatorTest { |
|
1306 | + |
|
1307 | + @Test |
|
1308 | + |
|
1309 | + public void testAddition() { |
|
1310 | + |
|
1311 | + Calculator calculator = new Calculator(); |
|
1312 | + |
|
1313 | + int result = calculator.add(2, 3); |
|
1314 | + |
|
1315 | + assertEquals(5, result); |
|
1316 | + |
|
1317 | + } |
|
1318 | + |
|
1319 | + @Test |
|
1320 | + |
|
1321 | + public void testDivision() { |
|
1322 | + |
|
1323 | + Calculator calculator = new Calculator(); |
|
1324 | + |
|
1325 | + double result = calculator.divide(10, 2); |
|
1326 | + |
|
1327 | + assertEquals(5.0, result); |
|
1328 | + |
|
1329 | + } |
|
1330 | + |
|
1331 | + } |
|
1332 | + |
|
1333 | +反例: |
|
1334 | + |
|
1335 | +import org.junit.jupiter.api.Test; |
|
1336 | + |
|
1337 | + import static org.junit.jupiter.api.Assertions.\*; |
|
1338 | + |
|
1339 | + public class CalculatorTest { |
|
1340 | + |
|
1341 | + @Test |
|
1342 | + |
|
1343 | + public void testAddition() { |
|
1344 | + |
|
1345 | + Calculator calculator = new Calculator(); |
|
1346 | + |
|
1347 | + int result = calculator.add(2, 3); |
|
1348 | + |
|
1349 | + assertEquals(6, result); |
|
1350 | + |
|
1351 | + } |
|
1352 | + |
|
1353 | + @Test |
|
1354 | + |
|
1355 | + public void testDivision() { |
|
1356 | + |
|
1357 | + Calculator calculator = new Calculator(); |
|
1358 | + |
|
1359 | + double result = calculator.divide(10, 0); |
|
1360 | + |
|
1361 | + assertEquals(Double.POSITIVE\_INFINITY, result); |
|
1362 | + |
|
1363 | + } |
|
1364 | + |
|
1365 | + } |
|
1366 | + |
|
1367 | +1. 【强制】针对系统的不同功能模块编写独立的测试用例。 |
|
1368 | + |
|
1369 | +说明:功能测试应该针对系统的不同功能模块编写独立的测试用例,以确保每个功能模块的正确性和稳定性。每个测试用例应该只验证一个功能模块,避免功能之间的耦合。 |
|
1370 | + |
|
1371 | +正例: |
|
1372 | + |
|
1373 | +import org.junit.jupiter.api.Test; |
|
1374 | + |
|
1375 | + import static org.junit.jupiter.api.Assertions.\*; |
|
1376 | + |
|
1377 | + public class ShoppingCartTest { |
|
1378 | + |
|
1379 | + @Test |
|
1380 | + |
|
1381 | + public void testAddItemToCart() { |
|
1382 | + |
|
1383 | + ShoppingCart cart = new ShoppingCart(); |
|
1384 | + |
|
1385 | + cart.addItem("item1", 10); |
|
1386 | + |
|
1387 | + assertEquals(1, cart.getItemCount()); |
|
1388 | + |
|
1389 | + assertEquals(10, cart.getTotalPrice()); |
|
1390 | + |
|
1391 | + } |
|
1392 | + |
|
1393 | + @Test |
|
1394 | + |
|
1395 | + public void testRemoveItemFromCart() { |
|
1396 | + |
|
1397 | + ShoppingCart cart = new ShoppingCart(); |
|
1398 | + |
|
1399 | + cart.addItem("item1", 10); |
|
1400 | + |
|
1401 | + cart.removeItem("item1"); |
|
1402 | + |
|
1403 | + assertEquals(0, cart.getItemCount()); |
|
1404 | + |
|
1405 | + assertEquals(0, cart.getTotalPrice()); |
|
1406 | + |
|
1407 | + } |
|
1408 | + |
|
1409 | + } |
|
1410 | + |
|
1411 | + public class OrderProcessingTest { |
|
1412 | + |
|
1413 | + @Test |
|
1414 | + |
|
1415 | + public void testPlaceOrder() { |
|
1416 | + |
|
1417 | + OrderProcessing orderProcessing = new OrderProcessing(); |
|
1418 | + |
|
1419 | + Order order = new Order(); |
|
1420 | + |
|
1421 | + order.addItem("item1", 10); |
|
1422 | + |
|
1423 | + order.addItem("item2", 20); |
|
1424 | + |
|
1425 | + orderProcessing.placeOrder(order); |
|
1426 | + |
|
1427 | + assertTrue(order.isOrderPlaced()); |
|
1428 | + |
|
1429 | + assertEquals(30, order.getTotalPrice()); |
|
1430 | + |
|
1431 | + } |
|
1432 | + |
|
1433 | + @Test |
|
1434 | + |
|
1435 | + public void testCancelOrder() { |
|
1436 | + |
|
1437 | + OrderProcessing orderProcessing = new OrderProcessing(); |
|
1438 | + |
|
1439 | + Order order = new Order(); |
|
1440 | + |
|
1441 | + order.addItem("item1", 10); |
|
1442 | + |
|
1443 | + order.addItem("item2", 20); |
|
1444 | + |
|
1445 | + orderProcessing.cancelOrder(order); |
|
1446 | + |
|
1447 | + assertTrue(order.isOrderCancelled()); |
|
1448 | + |
|
1449 | + assertEquals(0, order.getTotalPrice()); |
|
1450 | + |
|
1451 | + } |
|
1452 | + |
|
1453 | + } |
|
1454 | + |
|
1455 | +反例: |
|
1456 | + |
|
1457 | +import org.junit.jupiter.api.Test; |
|
1458 | + |
|
1459 | + import static org.junit.jupiter.api.Assertions.\*; |
|
1460 | + |
|
1461 | + public class ShoppingCartTest { |
|
1462 | + |
|
1463 | + @Test |
|
1464 | + |
|
1465 | + public void testAddItemToCart() { |
|
1466 | + |
|
1467 | + ShoppingCart cart = new ShoppingCart(); |
|
1468 | + |
|
1469 | + cart.addItem("item1", 10); |
|
1470 | + |
|
1471 | + assertEquals(1, cart.getItemCount()); |
|
1472 | + |
|
1473 | + assertEquals(10, cart.getTotalPrice()); |
|
1474 | + |
|
1475 | + } |
|
1476 | + |
|
1477 | + @Test |
|
1478 | + |
|
1479 | + public void testRemoveItemFromCart() { |
|
1480 | + |
|
1481 | + ShoppingCart cart = new ShoppingCart(); |
|
1482 | + |
|
1483 | + cart.addItem("item1", 10); |
|
1484 | + |
|
1485 | + cart.removeItem("item1"); |
|
1486 | + |
|
1487 | + assertEquals(1, cart.getItemCount()); _// __错误的断言_ |
|
1488 | + |
|
1489 | + assertEquals(0, cart.getTotalPrice()); |
|
1490 | + |
|
1491 | + } |
|
1492 | + |
|
1493 | + } |
|
1494 | + |
|
1495 | + public class OrderProcessingTest { |
|
1496 | + |
|
1497 | + @Test |
|
1498 | + |
|
1499 | + public void testPlaceOrder() { |
|
1500 | + |
|
1501 | + OrderProcessing orderProcessing = new OrderProcessing(); |
|
1502 | + |
|
1503 | + Order order = new Order(); |
|
1504 | + |
|
1505 | + order.addItem("item1", 10); |
|
1506 | + |
|
1507 | + order.addItem("item2", 20); |
|
1508 | + |
|
1509 | + orderProcessing.placeOrder(order); |
|
1510 | + |
|
1511 | + assertFalse(order.isOrderPlaced()); _// __错误的断言_ |
|
1512 | + |
|
1513 | + assertEquals(30, order.getTotalPrice()); |
|
1514 | + |
|
1515 | + } |
|
1516 | + |
|
1517 | + @Test |
|
1518 | + |
|
1519 | + public void testCancelOrder() { |
|
1520 | + |
|
1521 | + OrderProcessing orderProcessing = new OrderProcessing(); |
|
1522 | + |
|
1523 | + Order order = new Order(); |
|
1524 | + |
|
1525 | + order.addItem("item1", 10); |
|
1526 | + |
|
1527 | + order.addItem("item2", 20); |
|
1528 | + |
|
1529 | + orderProcessing.cancelOrder(order); |
|
1530 | + |
|
1531 | + assertFalse(order.isOrderCancelled()); _// __错误的断言_ |
|
1532 | + |
|
1533 | + assertEquals(0, order.getTotalPrice()); |
|
1534 | + |
|
1535 | + } |
|
1536 | + |
|
1537 | + } |
|
1538 | + |
|
1539 | +1. 【强制】验证系统的权限和访问控制。 |
|
1540 | + |
|
1541 | +说明:功能测试应该验证系统的权限和访问控制是否正确。包括验证不同用户角色的访问权限、验证权限控制的正确性等。 |
|
1542 | + |
|
1543 | +正例: |
|
1544 | + |
|
1545 | +import org.junit.jupiter.api.Test; |
|
1546 | + |
|
1547 | + import static org.junit.jupiter.api.Assertions.\*; |
|
1548 | + |
|
1549 | + public class AccessControlTest { |
|
1550 | + |
|
1551 | + @Test |
|
1552 | + |
|
1553 | + public void testAdminAccess() { |
|
1554 | + |
|
1555 | + AccessControl accessControl = new AccessControl(); |
|
1556 | + |
|
1557 | + User admin = new User("admin", "admin"); |
|
1558 | + |
|
1559 | + assertTrue(accessControl.hasAccess(admin, "adminPage")); |
|
1560 | + |
|
1561 | + assertTrue(accessControl.hasAccess(admin, "userManagement")); |
|
1562 | + |
|
1563 | + } |
|
1564 | + |
|
1565 | + @Test |
|
1566 | + |
|
1567 | + public void testUserAccess() { |
|
1568 | + |
|
1569 | + AccessControl accessControl = new AccessControl(); |
|
1570 | + |
|
1571 | + User user = new User("user", "password"); |
|
1572 | + |
|
1573 | + assertFalse(accessControl.hasAccess(user, "adminPage")); |
|
1574 | + |
|
1575 | + assertTrue(accessControl.hasAccess(user, "userProfile")); |
|
1576 | + |
|
1577 | + } |
|
1578 | + |
|
1579 | + } |
|
1580 | + |
|
1581 | +反例: |
|
1582 | + |
|
1583 | +import org.junit.jupiter.api.Test; |
|
1584 | + |
|
1585 | + import static org.junit.jupiter.api.Assertions.\*; |
|
1586 | + |
|
1587 | + public class AccessControlTest { |
|
1588 | + |
|
1589 | + @Test |
|
1590 | + |
|
1591 | + public void testAdminAccess() { |
|
1592 | + |
|
1593 | + AccessControl accessControl = new AccessControl(); |
|
1594 | + |
|
1595 | + User admin = new User("admin", "admin"); |
|
1596 | + |
|
1597 | + assertFalse(accessControl.hasAccess(admin, "adminPage")); _// __错误的断言_ |
|
1598 | + |
|
1599 | + assertTrue(accessControl.hasAccess(admin, "userManagement")); |
|
1600 | + |
|
1601 | + } |
|
1602 | + |
|
1603 | + @Test |
|
1604 | + |
|
1605 | + public void testUserAccess() { |
|
1606 | + |
|
1607 | + AccessControl accessControl = new AccessControl(); |
|
1608 | + |
|
1609 | + User user = new User("user", "password"); |
|
1610 | + |
|
1611 | + assertTrue(accessControl.hasAccess(user, "adminPage")); _// __错误的断言_ |
|
1612 | + |
|
1613 | + assertFalse(accessControl.hasAccess(user, "userProfile")); |
|
1614 | + |
|
1615 | + } |
|
1616 | + |
|
1617 | +} |
|
1618 | + |
|
1619 | +1. 【强制】验证系统的异常处理和错误处理。 |
|
1620 | + |
|
1621 | +说明:功能测试应该验证系统在面对异常情况和错误时的处理是否正确。包括验证系统是否能够正确地捕获和处理异常、是否能够给出合适的错误提示等。1,纯业务的(元模型定义好了,可自动化测试);1,幂等性测试;2,依赖项的测试(app要包含功能的依赖,数据的依赖,配置依赖) |
|
1622 | + |
|
1623 | +正例: |
|
1624 | + |
|
1625 | +import org.junit.jupiter.api.Test; |
|
1626 | + |
|
1627 | + import static org.junit.jupiter.api.Assertions.\*; |
|
1628 | + |
|
1629 | + public class ExceptionHandlingTest { |
|
1630 | + |
|
1631 | + @Test |
|
1632 | + |
|
1633 | + public void testDivideByZeroException() { |
|
1634 | + |
|
1635 | + Calculator calculator = new Calculator(); |
|
1636 | + |
|
1637 | + assertThrows(ArithmeticException.class, () -\> { |
|
1638 | + |
|
1639 | + calculator.divide(10, 0); |
|
1640 | + |
|
1641 | + }); |
|
1642 | + |
|
1643 | + } |
|
1644 | + |
|
1645 | + @Test |
|
1646 | + |
|
1647 | + public void testInvalidInputException() { |
|
1648 | + |
|
1649 | + Validator validator = new Validator(); |
|
1650 | + |
|
1651 | + assertThrows(InvalidInputException.class, () -\> { |
|
1652 | + |
|
1653 | + validator.validateInput(null); |
|
1654 | + |
|
1655 | + }); |
|
1656 | + |
|
1657 | + } |
|
1658 | + |
|
1659 | + } |
|
1660 | + |
|
1661 | +反例: |
|
1662 | + |
|
1663 | +import org.junit.jupiter.api.Test; |
|
1664 | + |
|
1665 | + import static org.junit.jupiter.api.Assertions.\*; |
|
1666 | + |
|
1667 | + public class ExceptionHandlingTest { |
|
1668 | + |
|
1669 | + @Test |
|
1670 | + |
|
1671 | + public void testDivideByZeroException() { |
|
1672 | + |
|
1673 | + Calculator calculator = new Calculator(); |
|
1674 | + |
|
1675 | + assertDoesNotThrow(() -\> { |
|
1676 | + |
|
1677 | + calculator.divide(10, 0); |
|
1678 | + |
|
1679 | + }); _// __错误的断言_ |
|
1680 | + |
|
1681 | + } |
|
1682 | + |
|
1683 | + @Test |
|
1684 | + |
|
1685 | + public void testInvalidInputException() { |
|
1686 | + |
|
1687 | + Validator validator = new Validator(); |
|
1688 | + |
|
1689 | + assertDoesNotThrow(() -\> { |
|
1690 | + |
|
1691 | + validator.validateInput(null); |
|
1692 | + |
|
1693 | + }); _// __错误的断言_ |
|
1694 | + |
|
1695 | + } |
|
1696 | + |
|
1697 | + } |
|
1698 | + |
|
1699 | +1. 【推荐】验证系统的性能和可扩展性。 |
|
1700 | + |
|
1701 | +说明:功能测试可以用于验证系统的性能和可扩展性。可以通过模拟并发用户、大数据量测试等方式来评估系统的性能和可扩展性。 |
|
1702 | + |
|
1703 | +正例: |
|
1704 | + |
|
1705 | +import org.junit.jupiter.api.Test; |
|
1706 | + |
|
1707 | + import static org.junit.jupiter.api.Assertions.\*; |
|
1708 | + |
|
1709 | + public class PerformanceTest { |
|
1710 | + |
|
1711 | + @Test |
|
1712 | + |
|
1713 | + public void testConcurrentUsers() { |
|
1714 | + |
|
1715 | + SystemUnderTest system = new SystemUnderTest(); |
|
1716 | + |
|
1717 | + int concurrentUsers = 100; |
|
1718 | + |
|
1719 | + PerformanceResult result = system.runConcurrentUsersTest(concurrentUsers); |
|
1720 | + |
|
1721 | + assertTrue(result.getAverageResponseTime() \< 500); |
|
1722 | + |
|
1723 | + assertTrue(result.getThroughput() \> 100); |
|
1724 | + |
|
1725 | + } |
|
1726 | + |
|
1727 | + @Test |
|
1728 | + |
|
1729 | + public void testLargeDataProcessing() { |
|
1730 | + |
|
1731 | + SystemUnderTest system = new SystemUnderTest(); |
|
1732 | + |
|
1733 | + DataGenerator dataGenerator = new DataGenerator(); |
|
1734 | + |
|
1735 | + int dataSize = 1000000; |
|
1736 | + |
|
1737 | + DataProcessingResult result = system.processLargeData(dataGenerator.generateData(dataSize)); |
|
1738 | + |
|
1739 | + assertEquals(dataSize, result.getProcessedDataCount()); |
|
1740 | + |
|
1741 | + assertTrue(result.getProcessingTime() \< 1000); |
|
1742 | + |
|
1743 | + } |
|
1744 | + |
|
1745 | + } |
|
1746 | + |
|
1747 | +1. 【推荐】验证系统的兼容性和互操作性。 |
|
1748 | + |
|
1749 | +说明:功能测试可以用于验证系统在不同平台、不同浏览器、不同设备上的兼容性和互操作性。可以通过跨浏览器测试、跨平台测试、API集成测试等方式来验证系统的兼容性和互操作性。 |
|
1750 | + |
|
1751 | +正例: |
|
1752 | + |
|
1753 | +import org.junit.jupiter.api.Test; |
|
1754 | + |
|
1755 | + import static org.junit.jupiter.api.Assertions.\*; |
|
1756 | + |
|
1757 | + public class CompatibilityTest { |
|
1758 | + |
|
1759 | + @Test |
|
1760 | + |
|
1761 | + public void testCrossBrowserCompatibility() { |
|
1762 | + |
|
1763 | + SystemUnderTest system = new SystemUnderTest(); |
|
1764 | + |
|
1765 | + Browser chrome = new Browser("Chrome"); |
|
1766 | + |
|
1767 | + Browser firefox = new Browser("Firefox"); |
|
1768 | + |
|
1769 | + CompatibilityResult result1 = system.testCompatibility(chrome); |
|
1770 | + |
|
1771 | + CompatibilityResult result2 = system.testCompatibility(firefox); |
|
1772 | + |
|
1773 | + assertTrue(result1.isCompatible()); |
|
1774 | + |
|
1775 | + assertTrue(result2.isCompatible()); |
|
1776 | + |
|
1777 | + } |
|
1778 | + |
|
1779 | + @Test |
|
1780 | + |
|
1781 | + public void testAPIIntegration() { |
|
1782 | + |
|
1783 | + SystemUnderTest system = new SystemUnderTest(); |
|
1784 | + |
|
1785 | + API api = new API("https://api.example.com"); |
|
1786 | + |
|
1787 | + IntegrationResult result = system.testAPIIntegration(api); |
|
1788 | + |
|
1789 | + assertTrue(result.isIntegrationSuccessful()); |
|
1790 | + |
|
1791 | + assertEquals(200, result.getResponseCode()); |
|
1792 | + |
|
1793 | + } |
|
1794 | + |
|
1795 | + } |
|
1796 | + |
|
1797 | +1. 【推荐】定期运行测试并修复失败的测试。 |
|
1798 | + |
|
1799 | +说明:功能测试应该定期运行,并及时修复失败的测试用例。定期运行测试可以帮助发现系统的潜在问题,并及时采取措施进行修复。可以使用持续集成/持续交付(CI/CD)工具来定期运行功能测试,并在测试失败时发送通知给开发团队。开发团队应该及时分析失败的测试用例,并修复相关的问题。 |
|
1800 | + |
|
1801 | +这些是更多的功能测试规范和示例。这些规范可以帮助您在功能测试中提高测试质量和效率。请根据您的实际项目需求和情况,选择适合您团队的规范,并进行相应的调整和实现。 |
|
1802 | + |
|
1803 | +1. |
|
1804 | +##### 集成测试 |
|
1805 | +2. |
|
1806 | +##### 性能测试 |
|
1807 | + |
|
1808 | +【推荐】性能测试是评估系统在不同负载条件下的性能表现的过程。为了确保性能测试的准确性和可重复性,需要遵循一套规范和最佳实践。 |
|
1809 | + |
|
1810 | +以下是性能测试规范的一些要点: |
|
1811 | + |
|
1812 | +1. 目标定义:明确性能测试的目标和需求。确定要测试的系统组件、功能或场景,以及期望的性能指标,如响应时间、吞吐量、并发用户数等。 |
|
1813 | +2. 测试环境:建立逼近真实生产环境的测试环境。包括硬件、网络、操作系统、数据库等方面的配置和设置。确保测试环境与生产环境的相似性,以便更准确地模拟实际情况。 |
|
1814 | +3. 测试数据:使用真实、多样化的测试数据进行性能测试。测试数据应该涵盖典型场景和边界情况,并具有一定的数据量和变化。 |
|
1815 | +4. 测试脚本和工具:编写清晰、可重复执行的性能测试脚本。选择适当的性能测试工具,如JMeter、LoadRunner等,用于执行和监控性能测试。 |
|
1816 | + |
|
1817 | +1. 负载模拟:根据实际使用情况和预期负载,设计和模拟不同负载条件下的测试场景。包括正常负载、峰值负载、压力测试等。确保测试覆盖了系统的不同使用情况和负载情况。 |
|
1818 | +2. 测试监控和度量:监控系统在测试过程中的各项性能指标,如响应时间、CPU利用率、内存使用等。使用合适的监控工具和指标,以便及时发现性能瓶颈和问题。 |
|
1819 | +3. 结果分析和报告:对性能测试结果进行分析和总结。识别性能瓶颈、性能优化的潜力和建议等。生成详尽的测试报告,包括测试配置、执行过程、结果数据和结论。 |
|
1820 | +4. 迭代和持续测试:性能测试应该是一个迭代的过程,随着系统的演化和变化,不断进行性能测试和优化。同时,建立持续性能测试的机制,确保系统在每次发布和部署后的性能稳定性。 |
|
1821 | + |
|
1822 | +通过遵循性能测试规范,可以提高性能测试的准确性、可重复性和可靠性。这有助于发现和解决系统的性能问题,提升系统的性能和可扩展性,提供更好的用户体验。 |
|
1823 | + |
|
1824 | +1. |
|
1825 | +##### 自动化测试 |
|
1826 | + |
|
1827 | +元模型自动化测试 |
|
1828 | + |
|
1829 | +自动化测试工具 |
|
1830 | + |
|
1831 | +1. |
|
1832 | +##### 回归测试 |
|
1833 | + |
|
1834 | +1. 【强制】所有代码必须进行单元测试,确保功能的正确性和稳定性。 |
|
1835 | + |
|
1836 | +说明:单元测试是保证代码质量的重要手段,通过编写针对各个模块和函数的测试用例,可以验证代码的正确性和稳定性。所有代码的提交前必须通过相应的单元测试。 |
|
1837 | + |
|
1838 | +正例: |
|
1839 | + |
|
1840 | + _// __类:计算器_ |
|
1841 | + |
|
1842 | + public class Calculator { |
|
1843 | + |
|
1844 | + public int add(int a, int b) { |
|
1845 | + |
|
1846 | + return a + b; |
|
1847 | + |
|
1848 | + } |
|
1849 | + |
|
1850 | + } |
|
1851 | + |
|
1852 | + _// __单元_测试_用例_ |
|
1853 | + |
|
1854 | + public class CalculatorTest { |
|
1855 | + |
|
1856 | + @Test |
|
1857 | + |
|
1858 | + public void testAdd() { |
|
1859 | + |
|
1860 | + Calculator calculator = new Calculator(); |
|
1861 | + |
|
1862 | + assertEquals(5, calculator.add(2, 3)); _// __正常情况_ |
|
1863 | + |
|
1864 | + assertEquals(-1, calculator.add(-1, 0)); _// __负数情况_ |
|
1865 | + |
|
1866 | + assertEquals(0, calculator.add(0, 0)); _// __边界情况,两个零相加_ |
|
1867 | + |
|
1868 | + } |
|
1869 | + |
|
1870 | + } |
|
1871 | + |
|
1872 | +反例: |
|
1873 | + |
|
1874 | +_// __类:_计算器 |
|
1875 | + |
|
1876 | + public class Calculator { |
|
1877 | + |
|
1878 | + public int add(int a, int b) { |
|
1879 | + |
|
1880 | + return a - b; |
|
1881 | + |
|
1882 | + } |
|
1883 | + |
|
1884 | + } |
|
1885 | + |
|
1886 | + _// __单元测_试_用例_ |
|
1887 | + |
|
1888 | + public class CalculatorTest { |
|
1889 | + |
|
1890 | + @Test |
|
1891 | + |
|
1892 | + public void testAdd() { |
|
1893 | + |
|
1894 | + Calculator calculator = new Calculator(); |
|
1895 | + |
|
1896 | + assertEquals(5, calculator.add(2, 3)); _// __错误的实现,应该返回 __ 2 - 3 = -1_ |
|
1897 | + |
|
1898 | + assertEquals(-1, calculator.add(-1, 0)); |
|
1899 | + |
|
1900 | + assertEquals(0, calculator.add(0, 0)); |
|
1901 | + |
|
1902 | + } |
|
1903 | + |
|
1904 | + } |
|
1905 | + |
|
1906 | +1. 【强制】测试用例必须覆盖各种边界情况和异常情况。 |
|
1907 | + |
|
1908 | +说明:测试用例应该覆盖各种可能的输入和输出情况,包括边界值、异常情况、特殊字符等。通过充分的测试用例覆盖,可以发现潜在的问题和错误,提高代码的健壮性。 |
|
1909 | + |
|
1910 | +正例: |
|
1911 | + |
|
1912 | +_// __类:数组操作工具类_ |
|
1913 | + |
|
1914 | + public class ArrayUtils { |
|
1915 | + |
|
1916 | + public static int getMaxValue(int[] array) { |
|
1917 | + |
|
1918 | + if (array.length == 0) { |
|
1919 | + |
|
1920 | + throw new IllegalArgumentException("数组为空"); |
|
1921 | + |
|
1922 | + } |
|
1923 | + |
|
1924 | + int max = array[0]; |
|
1925 | + |
|
1926 | + for (int i = 1; i \< array.length; i++) { |
|
1927 | + |
|
1928 | + if (array[i] \> max) { |
|
1929 | + |
|
1930 | + max = array[i]; |
|
1931 | + |
|
1932 | + } |
|
1933 | + |
|
1934 | + } |
|
1935 | + |
|
1936 | + return max; |
|
1937 | + |
|
1938 | + } |
|
1939 | + |
|
1940 | + } |
|
1941 | + |
|
1942 | + _// __单元测试用例_ |
|
1943 | + |
|
1944 | + public class ArrayUtilsTest { |
|
1945 | + |
|
1946 | + @Test |
|
1947 | + |
|
1948 | + public void testGetMaxValue() { |
|
1949 | + |
|
1950 | + int[] array1 = {1, 2, 3}; |
|
1951 | + |
|
1952 | + assertEquals(3, ArrayUtils.getMaxValue(array1)); _// __正常情况_ |
|
1953 | + |
|
1954 | + int[] array2 = {-1, -2, -3}; |
|
1955 | + |
|
1956 | + assertEquals(-1, ArrayUtils.getMaxValue(array2)); _// __负数情况_ |
|
1957 | + |
|
1958 | + int[] array3 = {1}; |
|
1959 | + |
|
1960 | + assertEquals(1, ArrayUtils.getMaxValue(array3)); _// __边界情况,只有一个元素_ |
|
1961 | + |
|
1962 | + int[] array4 = {}; |
|
1963 | + |
|
1964 | + try { |
|
1965 | + |
|
1966 | + ArrayUtils.getMaxValue(array4); _// __边界情况,空数组_ |
|
1967 | + |
|
1968 | + fail("未抛出异常"); _// __未抛出异常,测试失败_ |
|
1969 | + |
|
1970 | + } catch (IllegalArgumentException e) { |
|
1971 | + |
|
1972 | + _// __预期异常,测试通过_ |
|
1973 | + |
|
1974 | + } |
|
1975 | + |
|
1976 | + } |
|
1977 | + |
|
1978 | + } |
|
1979 | + |
|
1980 | +反例: |
|
1981 | + |
|
1982 | +_// __类:数组操作工具类_ |
|
1983 | + |
|
1984 | + public class ArrayUtils { |
|
1985 | + |
|
1986 | + public static int getMaxValue(int[] array) { |
|
1987 | + |
|
1988 | + int max = array[0]; |
|
1989 | + |
|
1990 | + for (int i = 1; i \< array.length; i++) { |
|
1991 | + |
|
1992 | + if (array[i] \> max) { |
|
1993 | + |
|
1994 | + max = array[i]; |
|
1995 | + |
|
1996 | + } |
|
1997 | + |
|
1998 | + } |
|
1999 | + |
|
2000 | + return max; |
|
2001 | + |
|
2002 | + } |
|
2003 | + |
|
2004 | + } |
|
2005 | + |
|
2006 | + _// __单元测试用例_ |
|
2007 | + |
|
2008 | + public class ArrayUtilsTest { |
|
2009 | + |
|
2010 | + @Test |
|
2011 | + |
|
2012 | + public void testGetMaxValue() { |
|
2013 | + |
|
2014 | + int[] array1 = {1, 2, 3}; |
|
2015 | + |
|
2016 | + assertEquals(3, ArrayUtils.getMaxValue(array1)); _// __正常情况_ |
|
2017 | + |
|
2018 | + int[] array2 = {-1, -2, -3}; |
|
2019 | + |
|
2020 | + assertEquals(-1, ArrayUtils.getMaxValue(array2)); _// __负数情况_ |
|
2021 | + |
|
2022 | + int[] array3 = {1}; |
|
2023 | + |
|
2024 | + assertEquals(1, ArrayUtils.getMaxValue(array3)); _// __边界情况,只有一个元素_ |
|
2025 | + |
|
2026 | + int[] array4 = {}; |
|
2027 | + |
|
2028 | + try { |
|
2029 | + |
|
2030 | + ArrayUtils.getMaxValue(array4); _// __边界情况,空数组_ |
|
2031 | + |
|
2032 | + fail("未抛出异常"); _// __未抛出异常,测试失败_ |
|
2033 | + |
|
2034 | + } catch (ArrayIndexOutOfBoundsException e) { |
|
2035 | + |
|
2036 | + _// __错误的实现,未处理空数组情况,抛出了数组越界异常_ |
|
2037 | + |
|
2038 | + } |
|
2039 | + |
|
2040 | + } |
|
2041 | + |
|
2042 | + } |
|
2043 | + |
|
2044 | +1. 【强制】提倡使用自动化测试工具进行测试。 |
|
2045 | + |
|
2046 | +说明:自动化测试工具可以提高测试效率和准确性,减少人工测试的工作量。常用的自动化测试工具包括JUnit、TestNG、Selenium等,根据具体需求选择合适的工具进行自动化测试。 |
|
2047 | + |
|
2048 | +1. 【推荐】进行性能测试,确保系统在负载下的稳定性和性能表现。 |
|
2049 | + |
|
2050 | +说明:性能测试是评估系统在不同负载条件下的性能和稳定性的重要手段。通过模拟真实的负载情况,可以发现系统在高并发、大数据量等场景下的性能问题,并采取相应的优化措施。 |
|
2051 | + |
|
2052 | +1. 【推荐】进行回归测试,确保代码修改不会对现有功能产生负面影响。 |
|
2053 | + |
|
2054 | +说明:回归测试是在代码发生变更后重新执行之前通过的测试用例,以确保修改代码不会破坏现有功能。回归测试可以防止由于代码修改引入的新错误,并保证系统的稳定性。 |
|
2055 | + |
|
2056 | +1. 【推荐】对代码进行覆盖率评估,确保测试用例对代码的覆盖率达到预期。 |
|
2057 | + |
|
2058 | +说明:代码覆盖率评估是衡量测试用例对代码覆盖程度的指标。通过使用代码覆盖率工具,可以统计测试用例对代码的覆盖情况,帮助发现测试用例不足或冗余的情况,提高测试的有效性。 |
|
2059 | + |
|
2060 | +1. 【推荐】编写清晰易懂的测试用例和文档,方便其他人理解和执行测试。 |
|
2061 | + |
|
2062 | +说明:良好的测试用例和文档可以提高测试的可维护性和可理解性。测试用例应该具备清晰的命名、详细的描述、预期结果等信息,文档应该包含测试环境的配置、测试步骤、预期结果等内容。 |
|
2063 | + |
|
2064 | +1. 【推荐】进行安全性测试,确保系统的安全性和防御能力。 |
|
2065 | + |
|
2066 | +说明:安全性测试是评估系统在面对各种安全攻击和威胁时的表现和防御能力的测试活动。通过模拟常见的安全攻击场景,可以发现系统的安全漏洞和弱点,并采取相应的安全措施。 |
|
2067 | + |
|
2068 | +1. 【推荐】进行用户体验测试,确保系统的易用性和用户满意度。 |
|
2069 | + |
|
2070 | +说明:用户体验测试是评估系统在用户角度下的易用性和用户满意度的测试活动。通过模拟真实用户的使用场景和操作行为,可以发现系统的界面设计、交互流程等方面的问题,并提供改进意见。 |
|
2071 | + |
|
2072 | +1. |
|
2073 | +#### 授权管理规范 |
|
2074 | + |
|
2075 | +1. |
|
2076 | +##### 平台授权 |
|
2077 | + |
|
2078 | +1. 【强制】dev开发环境不需要授权,但Dev无法使用应用市场。 |
|
2079 | + |
|
2080 | +1. 【强制】在启动iidp平台之前,必须进行授权操作。 |
|
2081 | + |
|
2082 | +说明:为了确保iidp平台的安全性和合规性,必须在启动之前进行授权操作。授权可以包括但不限于身份验证、访问权限控制等措施,以确保只有经过授权的用户可以访问和使用iidp平台。 |
|
2083 | + |
|
2084 | +根据上述新增规范,在启动iidp平台之前,必须进行授权操作,以确保平台的安全性和合规性。具体的授权方式可以根据实际情况进行选择和实施。 |
|
2085 | + |
|
2086 | +1. 【强制】在启动iidp平台之前,必须进行授权操作。 |
|
2087 | + |
|
2088 | +说明:为了确保iidp平台的安全性和合规性,必须在启动之前进行授权操作。授权可以包括但不限于身份验证、访问权限控制等措施,以确保只有经过授权的用户可以访问和使用iidp平台。 |
|
2089 | + |
|
2090 | +1. |
|
2091 | +##### 多租户 |
|
2092 | + |
|
2093 | +1. 【强制】多租户授权 |
|
2094 | + |
|
2095 | +说明:每一个租户的创建都必须进行授权,包括租户能够使用的应用和数量,防止大规模使用多租户功能。 |
|
2096 | + |
|
2097 | +1. 【强制】行列权限控制规范 |
|
2098 | + |
|
2099 | +说明:平台基于元模型进行行、列权限控制,在模型处理逻辑一致的情况下,可以通过权限控制实现千人千面效果。 |
|
2100 | + |
|
2101 | +1. |
|
2102 | +#### 版本管理规范 |
|
2103 | + |
|
2104 | +1. |
|
2105 | +##### GIT使用规范 |
|
2106 | + |
|
2107 | +1. 【建议】不建议使用rebase。 |
|
2108 | + |
|
2109 | +说明:虽然rebase能够使得提交的commit线很整洁,但这并不是实际的提交记录的真正反应,而且由于rebase会重新生成commit id,可能会导致很多冲突的情况。 |
|
2110 | + |
|
2111 | +1. 【强制】使用版本管理工具Git进行代码版本管理。 |
|
2112 | + |
|
2113 | +说明:Git是一种分布式版本控制系统,可以有效地管理代码的版本和变更历史。通过使用Git,可以轻松地进行代码的协作开发、版本回退、分支管理等操作,提高团队协作效率和代码质量。 |
|
2114 | + |
|
2115 | +1. 【强制】使用合适的分支策略进行代码开发和管理。 |
|
2116 | + |
|
2117 | +说明:合适的分支策略可以有效地组织代码的开发和管理流程,常见的分支策略包括主分支(master/main)、开发分支(develop)、特性分支(feature)、发布分支(release)、修复分支(hotfix)等。根据具体项目和团队的需求,选择合适的分支策略进行代码管理。 |
|
2118 | + |
|
2119 | +1. 【强制】提交代码前进行代码审查。 |
|
2120 | + |
|
2121 | +说明:代码审查是保证代码质量和一致性的重要环节。通过代码审查,可以发现潜在的问题和错误,提高代码的可读性和可维护性。在提交代码之前,应邀请其他团队成员进行代码审查,并根据审查结果进行相应的修改和优化。 |
|
2122 | + |
|
2123 | +1. 【强制】使用有意义的提交消息。 |
|
2124 | + |
|
2125 | +说明:提交消息是对代码变更的简要描述,应该清晰、有意义且符合规范。提交消息应该包含变更的目的、内容和影响等信息,方便其他团队成员理解和追踪代码变更历史。 |
|
2126 | + |
|
2127 | +正例: |
|
2128 | + |
|
2129 | +``` |
|
2130 | + |
|
2131 | +feat: 添加用户注册功能 |
|
2132 | + |
|
2133 | +添加了用户注册功能,包括表单验证、数据存储和页面跳转等功能。 |
|
2134 | + |
|
2135 | +``` |
|
2136 | + |
|
2137 | +反例: |
|
2138 | + |
|
2139 | +``` |
|
2140 | + |
|
2141 | +fix: 修复bug |
|
2142 | + |
|
2143 | +修复了一个bug。 |
|
2144 | + |
|
2145 | +``` |
|
2146 | + |
|
2147 | +1. 【强制】遵循代码合并流程,确保代码的一致性和稳定性。 |
|
2148 | + |
|
2149 | +说明:代码合并是将不同分支上的代码合并到一起的过程,应该遵循合适的合并流程,包括解决冲突、测试合并后的代码等步骤,确保合并后的代码一致性和稳定性。 |
|
2150 | + |
|
2151 | +1. 【强制】使用标签管理发布版本。 |
|
2152 | + |
|
2153 | +说明:标签是对特定版本的代码进行命名和标记,方便追踪和发布。在每次发布稳定版本时,应该创建相应的标签,并注明版本号和发布日期等信息。 |
|
2154 | + |
|
2155 | +1. 【建议】使用Git Hooks进行代码质量检查。 |
|
2156 | + |
|
2157 | +说明:Git Hooks是Git提供的钩子机制,可以在特定的Git操作触发相应的脚本。通过使用Git Hooks,可以在代码提交、合并等操作前进行代码质量检查,例如代码格式化、静态代码分析等,提高代码的质量和一致性。 |
|
2158 | + |
|
2159 | +1. 【建议】使用Git Flow等工具辅助分支管理。 |
|
2160 | + |
|
2161 | +说明:Git Flow是一种流行的Git分支管理工具,可以简化分支管理流程,提供命令行工具和图形界面工具来支持分支的创建、合并、发布等操作。使用Git Flow等工具可以提高分支管理的效率和可靠性。 |
|
2162 | + |
|
2163 | +1. 【建议】定期进行代码仓库的维护和清理。 |
|
2164 | + |
|
2165 | +说明:定期进行代码仓库的维护和清理可以减少仓库的冗余和混乱,提高代码仓库的性能和可用性。包括删除不再需要的分支、清理过期的标签、整理和优化仓库结构等操作。 |
|
2166 | + |
|
2167 | +1. 【建议】使用Git相关的工具和服务进行代码托管和协作开发。 |
|
2168 | + |
|
2169 | +说明:Git相关的工具和服务提供了丰富的功能和便捷的操作,可以方便地进行代码托管、协作开发、问题追踪等工作。常见的Git工具和服务包括GitHub、GitLab、Bitbucket等,根据团队的需求选择合适的工具和服务进行使用。 |
|
2170 | + |
|
2171 | +以上是使用Git的一些常见规范,包括分支管理、代码审查、提交消息、合并流程等方面根据具体项目和团队的需求,可以进行相应的调整和补充。 |
|
2172 | + |
|
2173 | +1. |
|
2174 | +##### CICD规范 |
|
2175 | + |
|
2176 | +1. 【强制】 使用CI/CD工具进行自动化的代码构建、测试和部署。 |
|
2177 | + |
|
2178 | +说明:CI/CD(持续集成/持续部署)是一种软件开发实践,通过自动化的流程来构建、测试和部署代码,以提高开发效率和软件质量。选择合适的CI/CD工具(如Jenkins、GitLab CI、Travis CI等)来配置和管理CI/CD流程,确保代码的自动化构建、测试和部署。 |
|
2179 | + |
|
2180 | +1. 【强制】 将CI/CD配置文件纳入版本控制。 |
|
2181 | + |
|
2182 | +说明:CI/CD配置文件是定义CI/CD流程的文件,应该将其纳入版本控制,与代码一起进行管理。这样可以确保CI/CD配置与代码版本的一致性,方便团队成员查看和修改CI/CD配置。 |
|
2183 | + |
|
2184 | +1. 【强制】 在CI/CD流程中包含代码编译、单元测试和静态代码分析等步骤。 |
|
2185 | + |
|
2186 | +说明:CI/CD流程应该包含代码的编译、单元测试和静态代码分析等步骤,以确保代码的质量和稳定性。在编译阶段,将源代码编译成可执行的程序或库;在单元测试阶段,运行针对代码的单元测试,验证代码的正确性;在静态代码分析阶段,使用工具对代码进行静态分析,检查潜在的问题和代码质量。 |
|
2187 | + |
|
2188 | +1. 【强制】 使用环境变量管理敏感信息。 |
|
2189 | + |
|
2190 | +说明:敏感信息(如数据库密码、API密钥等)应该通过环境变量进行管理,而不应直接写入代码或配置文件中。在CI/CD流程中,使用环境变量来获取敏感信息,并确保在不同环境中使用不同的敏感信息。 |
|
2191 | + |
|
2192 | +1. 【强制】 在CI/CD流程中进行集成测试和部署到测试环境。 |
|
2193 | + |
|
2194 | +说明:在CI/CD流程的后续阶段,应该进行集成测试和部署到测试环境。集成测试是对不同模块或服务的集成进行测试,验证系统的整体功能和兼容性;部署到测试环境是将代码部署到与生产环境相似的测试环境中,以进行更全面的测试和验证。 |
|
2195 | + |
|
2196 | +1. 【强制】 使用持续集成服务器进行自动化构建和测试。 |
|
2197 | + |
|
2198 | +说明:持续集成服务器是用于执行CI/CD流程的服务器,可以自动触发代码构建、测试和部署。通过配置持续集成服务器,可以实现代码的自动化构建和测试,提高开发效率和代码质量。 |
|
2199 | + |
|
2200 | +1. 【强制】 使用容器化技术进行部署。 |
|
2201 | + |
|
2202 | +说明:容器化技术(如Docker)可以将应用程序及其依赖项打包成独立的容器,提供了一致的运行环境,方便部署和扩展。在CI/CD流程中,可以使用容器化技术将应用程序打包成镜像,并在部署阶段使用这些镜像进行快速部署和回滚。 |
|
2203 | + |
|
2204 | +1. 【建议】 使用自动化测试工具进行端到端测试。 |
|
2205 | + |
|
2206 | +说明:自动化测试工具可以模拟用户的行为,对整个应用程序进行端到端的测试,以验证系统的功能和性能。在CI/CD流程中,可以使用自动化测试工具进行端到端测试,提高测试的覆盖范围和准确性。 |
|
2207 | + |
|
2208 | +1. 【建议】 使用日志和监控工具进行应用程序的监控和故障排查。 |
|
2209 | + |
|
2210 | +说明:日志和监控工具可以帮助监控应用程序的运行状态和性能指标,并及时发现和排查故障。在CI/CD流程中,可以配置日志和监控工具,以便在部署后及时获取应用程序的运行情况,并进行故障排查和优化。 |
|
2211 | + |
|
2212 | +1. 【建议】 定期审查和优化CI/CD流程。 |
|
2213 | + |
|
2214 | +说明:CI/CD流程是一个持续演进的过程,应该定期审查和优化流程,以适应项目的需求和变化。通过审查和优化CI/CD流程,可以提高开发效率和代码质量,减少部署和发布的风险。 |
|
2215 | + |
|
2216 | +1. |
|
2217 | +##### 发版规范 |
|
2218 | +2. |
|
2219 | +##### APP版本规范 |
|
2220 | + |
|
2221 | +1. App的名称,版本,描述,产品线(分类,业务领域,不好约定,业务行为) |
|
2222 | +2. 行业,领域,分类的方式体现 |
|
2223 | + |
|
2224 | +1. |
|
2225 | +#### 工程结构规范 |
|
2226 | + |
|
2227 | +1. |
|
2228 | +##### APP设计规范 |
|
2229 | + |
|
2230 | +1. 【推荐】app的拆分推荐考虑基于商业角度、复用性、职责、性能、部署场景等要素综合考虑后进行拆分,业务的变化是复杂的,要基于具体的业务场景来进行设计,这点非常非常重要,关系到后续的具体业务开发的方向。 |
|
2231 | + |
|
2232 | +说明:具体app的拆分规范主要包括以下几个方面: |
|
2233 | + |
|
2234 | +1. 对app进行分层 |
|
2235 | + |
|
2236 | +app层级越低则通用性越强,层级越高则有更多的通用app选择,可以拿来即用或通过扩展使用。随着app层级清晰且越来越多,可以逐步形成app货架: |
|
2237 | + |
|
2238 | +1. L1:平台通用app |
|
2239 | + |
|
2240 | +包括运维、权限、中间件集成、api集成、数据流处理等相关的app |
|
2241 | + |
|
2242 | +1. L2:业务通用app |
|
2243 | + |
|
2244 | +包括通知、告警、文件、字典、审批流、打印、主数据等业务通用app |
|
2245 | + |
|
2246 | +1. L3:产品通用app |
|
2247 | + |
|
2248 | +包括Iot、smi、qms等产品通用app |
|
2249 | + |
|
2250 | +1. L4:行业通用app |
|
2251 | + |
|
2252 | +包括电子套件、pcb、光伏等行业app |
|
2253 | + |
|
2254 | +1. L5:定制app |
|
2255 | + |
|
2256 | +包括各类企业定制的app |
|
2257 | + |
|
2258 | +1. 按商业模式划分app |
|
2259 | + |
|
2260 | +哪些应用、模块、功能是打算独立销售的,可以将这些功能封装到独立app中。 |
|
2261 | + |
|
2262 | +1. 按耦合程度划分app |
|
2263 | + |
|
2264 | +可参考DDD,识别领域模型、聚合根等,将高内聚的模型封装在一个app。 |
|
2265 | + |
|
2266 | +1. 识别并分离不变与变化app |
|
2267 | + |
|
2268 | +将不变的或很少变化的模型封装在一个app,将变化频繁的模型、或扩展模型封装在另外的app。 |
|
2269 | + |
|
2270 | +1. 识别高并发场景 |
|
2271 | + |
|
2272 | +将对性能要求非常苛刻的模型封装到独立app中。 |
|
2273 | + |
|
2274 | +1. 【推荐】app调用关系规范 |
|
2275 | + |
|
2276 | +说明:appA方法或服务调用到另外一个appB的方法或服务,这是调用关系: |
|
2277 | + |
|
2278 | +1. 调用关系相关方app具备安装顺序无关性; |
|
2279 | +2. 调用关系会影响到运行时的方法或服务调用,如指定appB的方法或服务找不到. |
|
2280 | + |
|
2281 | +1. 【推荐】app依赖关系规范 |
|
2282 | + |
|
2283 | +说明:appA与另外appB的属性之间具备ER关系,或模型之间具有继承、扩展关系,这是依赖关系,我们应梳理好依赖关系后再进行建模,以免导致循环依赖: |
|
2284 | + |
|
2285 | +1. A继承/扩展了B的模型,必须要先装B,再安装A; |
|
2286 | +2. A与B的模型之间可能存在1:N、N:1、N:N三种ER关系,N:1与N:N支持单边关系, |
|
2287 | + |
|
2288 | +1. 【推荐】单边关系与双边关系使用规范 |
|
2289 | + |
|
2290 | +说明:可以独立安装,1:N不支持单边关系需要N方先安装,因此在使用1:N时需要尽量避免循环依赖。平台支持N:1、N:N的单边关系,特别适用于跨App扩展ER关系的 场景,在保证不改动原App的情况下,扩展ER关系: |
|
2291 | + |
|
2292 | +1. 双边关系 |
|
2293 | + |
|
2294 | +模型双方建立ER关系,可以双向获取对端的数据 |
|
2295 | + |
|
2296 | +1. 单边关系 |
|
2297 | + |
|
2298 | +模型A建立关联模型B的ER关系,B不需要配置与A的关系,适用于通过A获取B,但是不需要通过B获取A。如:业务模型关联码表,码表不需要关联业务模型。 |
|
2299 | + |
|
2300 | +1. 【推荐】跨模型方法调用规范 |
|
2301 | + |
|
2302 | +说明:平台为了保证扩展能力,所有的模型、属性、服务等都是可以动态扩展的,因此 本质上每个元模型都是Map,而不是具体的Class: |
|
2303 | + |
|
2304 | +1. 不建议在平台中用new模型的方式创建对象 |
|
2305 | +2. 调用方法也应该采用统一的call方法 |
|
2306 | +3. get set方法建议采用平台提供的模板(基于idea模版) |
|
2307 | + |
|
2308 | +1. |
|
2309 | +##### pom引入规范 |
|
2310 | + |
|
2311 | +1. 【强制】禁止引入hutool包。 |
|
2312 | +2. 第三方的包,谨慎引入,防止app打包体积过大 |
|
2313 | +3. pom编写规范 |
|
2314 | + |
|
2315 | +\<?xml\> |
|
2316 | + |
|
2317 | + ...... |
|
2318 | + |
|
2319 | + \<dependencies\> |
|
2320 | + |
|
2321 | + \<!_-- springboot --\> _ |
|
2322 | + |
|
2323 | + \<dependency\> |
|
2324 | + |
|
2325 | + \<groupId\>org.springframework.boot\</groupId\> |
|
2326 | + |
|
2327 | + \<artifactId\>spring-boot-starter-web\</artifactId\> |
|
2328 | + |
|
2329 | + \<version\>2.1.8.RELEASE\</version\> |
|
2330 | + |
|
2331 | + \</dependency\> |
|
2332 | + |
|
2333 | + \<!_-- __引擎 __ --\> _ |
|
2334 | + |
|
2335 | + \<dependency\> |
|
2336 | + |
|
2337 | + \<groupId\>com.sie.meta\</groupId\> |
|
2338 | + |
|
2339 | + \<artifactId\>sie-iidp-engine\</artifactId\> |
|
2340 | + |
|
2341 | + \<version\>1.0-SNAPSHOT\</version\> |
|
2342 | + |
|
2343 | + \<scope\>compile\</scope\> |
|
2344 | + |
|
2345 | + \</dependency\> |
|
2346 | + |
|
2347 | + \<!_-- __脚本引擎(可选) __ --\>_ |
|
2348 | + |
|
2349 | + \<dependency\> |
|
2350 | + |
|
2351 | + \<groupId\>com.sie.meta\</groupId\> |
|
2352 | + |
|
2353 | + \<artifactId\>sie-iidp-script-engine\</artifactId\> |
|
2354 | + |
|
2355 | + \<version\>1.0-SNAPSHOT\</version\> |
|
2356 | + |
|
2357 | + \</dependency\> |
|
2358 | + |
|
2359 | + \</dependencies\> |
|
2360 | + |
|
2361 | + \<build\> |
|
2362 | + |
|
2363 | + \<plugins\> |
|
2364 | + |
|
2365 | + \<plugin\> |
|
2366 | + |
|
2367 | + \<groupId\>org.springframework.boot\</groupId\> |
|
2368 | + |
|
2369 | + \<artifactId\>spring-boot-maven-plugin\</artifactId\> |
|
2370 | + |
|
2371 | + \<version\>2.7.2\</version\> |
|
2372 | + |
|
2373 | + \<executions\> |
|
2374 | + |
|
2375 | + \<execution\> |
|
2376 | + |
|
2377 | + \<goals\> |
|
2378 | + |
|
2379 | + \<goal\>repackage\</goal\> |
|
2380 | + |
|
2381 | + \</goals\> |
|
2382 | + |
|
2383 | + \</execution\> |
|
2384 | + |
|
2385 | + \</executions\> |
|
2386 | + |
|
2387 | + \</plugin\> |
|
2388 | + |
|
2389 | + \<plugin\> |
|
2390 | + |
|
2391 | + \<groupId\>org.apache.maven.plugins\</groupId\> |
|
2392 | + |
|
2393 | + \<artifactId\>maven-compiler-plugin\</artifactId\> |
|
2394 | + |
|
2395 | + \<version\>3.3\</version\> |
|
2396 | + |
|
2397 | + \<configuration\> |
|
2398 | + |
|
2399 | + \<source\>1.8\</source\> |
|
2400 | + |
|
2401 | + \<target\>1.8\</target\> |
|
2402 | + |
|
2403 | + \<compilerArgs\> |
|
2404 | + |
|
2405 | + \<arg\>-parameters\</arg\> |
|
2406 | + |
|
2407 | + \</compilerArgs\> |
|
2408 | + |
|
2409 | + \</configuration\> |
|
2410 | + |
|
2411 | + \</plugin\> |
|
2412 | + |
|
2413 | + \</plugins\> |
|
2414 | + |
|
2415 | + \</build\> |
|
2416 | + |
|
2417 | +\</xml\> |
|
2418 | + |
|
2419 | +1. |
|
2420 | +##### 工程结构规范 |
|
2421 | + |
|
2422 | +代码目录结构,怎么安排,命名,约定大于配置 |
|
2423 | + |
|
2424 | +Model、app.json、views、service 必须在同一级目录 |
|
2425 | + |
|
2426 | +Views里面的文件内容字段命名和model里面的字段有关系。 |
|
2427 | + |
|
2428 | +字符串关联,编译插件来识别并提示。 |
|
2429 | + |
|
2430 | +运行阶段,提示字符串相关问题,可以考虑通过静态分析来进行处理。 |
|
2431 | + |
|
2432 | +由于引擎是一个jar包,在开发环境中,通过一个引擎jar包来加载正在开发的apps,对于调试非常不友好,建议通过新建一个server项目依赖引擎jar包,以及必要的配置(如端口配置)来启动引擎,加载正在开发的apps进行调试。 |
|
2433 | + |
|
2434 | + |
|
2435 | + |
|
2436 | +server启动时,会默认加载apps目录下在apps.json中配置的所有app jar包;iidp-demo-apps是我们需要开发的app工程,一般来说我们现在都分多个工程进行开发,可以在iidp-demo-apps下面建多个module。 |
|
2437 | + |
|
2438 | +{ |
|
2439 | + |
|
2440 | + "loaders": { |
|
2441 | + |
|
2442 | + "API": "com.sie.iidp.engine.loaders.ApiLoader", |
|
2443 | + |
|
2444 | + "SDK": "com.sie.iidp.loaders.SdkLoader" |
|
2445 | + |
|
2446 | + }, |
|
2447 | + |
|
2448 | + "apps": { |
|
2449 | + |
|
2450 | + "master": { |
|
2451 | + |
|
2452 | + "SDK": { |
|
2453 | + |
|
2454 | + "base": "sie-iidp-base-1.0-SNAPSHOT.jar", |
|
2455 | + |
|
2456 | + "app名称: "jar包名称" |
|
2457 | + |
|
2458 | + } |
|
2459 | + |
|
2460 | + } |
|
2461 | + |
|
2462 | + } |
|
2463 | + |
|
2464 | +} |
|
2465 | + |
|
2466 | +apps.json文件是引擎必须的文件,默认在${user.dir}/apps/apps.json,需要把app打包生成的jar放置在此,并按照上面的格式配置。sie-iidp-base-1.0-SNAPSHOT.jar由引擎提供,开发阶段可从平台处获得。 |
|
2467 | + |
|
2468 | +1. |
|
2469 | +##### 公共错误码规范 |
|
2470 | + |
|
2471 | +【推荐】在开发和测试过程中,定义和使用一套统一的公共错误码规范可以提高代码的可读性、可维护性和可测试性。公共错误码规范定义了一系列标准错误码及其对应的错误信息,使得开发人员和测试人员能够快速理解和处理错误情况。 |
|
2472 | + |
|
2473 | +具体要求和建议如下: |
|
2474 | + |
|
2475 | +1. 错误码命名规范:定义一套统一的错误码命名规范,例如使用大写字母和下划线分隔的形式,例如:INVALID\_PARAMETER。 |
|
2476 | +2. 错误码分类:根据错误类型或模块进行分类,例如将参数相关的错误码放在一个范围内,将数据库相关的错误码放在另一个范围内。这样可以更好地组织和管理错误码。 |
|
2477 | +3. 错误码取值范围:为每个错误码定义一个取值范围,以便后续的扩展和维护。例如,可以为参数相关的错误码分配范围为 1000-1999,数据库相关的错误码分配范围为 2000-2999。 |
|
2478 | +4. 错误码文档化:为每个错误码提供清晰的错误信息和解释,以便开发人员和测试人员能够快速理解错误的含义和处理方式。这些信息可以在代码注释、文档或错误码定义文件中进行记录。 |
|
2479 | +5. 错误码使用范围:明确错误码的使用范围,例如哪些模块或功能应该使用哪些错误码。这样可以避免错误码的混乱和重复使用。 |
|
2480 | +6. 错误码的处理和返回:在代码中正确处理错误码,并根据错误码返回适当的错误信息给用户或调用方。错误信息应该清晰、准确地描述错误的原因,帮助用户或调用方理解和解决问题。 |
|
2481 | + |
|
2482 | +通过制定和遵守公共错误码规范,可以提高代码的可维护性和可测试性,减少错误处理的复杂性,并提供更好的用户体验。同时,还能够促进团队之间的协作和沟通,减少因错误处理不一致而引起的问题。 |
|
2483 | + |
|
2484 | +1. |
|
2485 | +#### 部署规范 |
|
2486 | + |
|
2487 | +1. |
|
2488 | +##### 前端部署 |
|
2489 | + |
|
2490 | +1. 【强制】获取真实IP时,需要在Nginx配置中添加以下配置项: |
|
2491 | + |
|
2492 | +proxy\_set\_header X-Real-IP $remote\_addr; |
|
2493 | + |
|
2494 | +proxy\_set\_header X-Forwarded-For $proxy\_add\_x\_forwarded\_for; |
|
2495 | + |
|
2496 | +说明:在使用Nginx作为反向代理时,为了获取客户端的真实IP地址,需要在Nginx的配置文件中添加上述配置项。这样,Nginx会将客户端的真实IP地址添加到请求头的X-Forwarded-For字段中,以便后端服务器获取到真实IP地址。 |
|
2497 | +正例:在Nginx配置文件中添加如下配置项: |
|
2498 | + |
|
2499 | +location / { |
|
2500 | + |
|
2501 | +proxy\_pass http://backend; |
|
2502 | + |
|
2503 | +proxy\_set\_header X-Real-IP $remote\_addr; |
|
2504 | + |
|
2505 | + proxy\_set\_header X-Forwarded-For $proxy\_add\_x\_forwarded\_for; |
|
2506 | + |
|
2507 | +} |
|
2508 | + |
|
2509 | +反例:未添加上述配置项,导致后端服务器无法获取到真实IP地址。 |
|
2510 | + |
|
2511 | +根据上述新增规范,应在Nginx的配置文件中添加配置项proxy\_set\_header X-Real-IP remote\_addr 和 proxy\_set\_header X-Forwarded-For $proxy\_add\_x\_forwarded\_for,以确保能够获取到客户端的真实IP地址。 |
|
2512 | + |
|
2513 | +1. |
|
2514 | +##### 后端部署 |
|
2515 | + |
|
2516 | +1. 【强制】部署模式,确定是单机版部署还是分布式模式部署。 |
|
2517 | + |
|
2518 | +说明:由配置文件中的engine.run.mode=SINGLE 配置项确定。 |
|
2519 | + |
|
2520 | +1. 【推荐】用linux,不要用windows |
|
2521 | + |
|
2522 | +说明:在开发和部署过程中,选择适合的服务器操作系统对于项目的稳定性和性能至关重要。Linux 作为一种开源操作系统,具有广泛的支持和强大的性能优势,尤其在服务器领域表现出色。相比之下,Windows 服务器在某些方面可能不如 Linux 服务器稳定和高效。 |
|
2523 | + |
|
2524 | +正例:使用 Linux 服务器可以获得更好的性能和稳定性。它提供了强大的命令行工具和灵活的配置选项,适用于各种服务器应用和开发环境。 |
|
2525 | + |
|
2526 | +反例:使用 Windows 服务器可能会面临一些限制和性能瓶颈。Windows 操作系统相对较重,可能需要更多的系统资源,并且在某些情况下可能不够稳定。 |
|
2527 | + |
|
2528 | +请注意,选择适合的服务器操作系统应根据具体项目需求和技术栈来决定。在某些特定情况下,使用 Windows 服务器可能是必要的,比如需要与 Microsoft 技术栈紧密集成的项目。然而,总体而言,推荐使用 Linux 服务器可以获得更好的性能和稳定性。 |
|
2529 | + |
|
2530 | +1. 【强制】开发环境和部署环境要保持一致 |
|
2531 | + |
|
2532 | +说明:为了确保代码在不同环境中的一致性和可靠性,开发环境和部署环境应该尽可能保持一致。这包括操作系统、软件版本、配置文件等方面的一致性。 |
|
2533 | + |
|
2534 | +正例:在开发过程中,使用与目标部署环境相同的操作系统和软件版本进行开发和测试。确保开发团队和部署团队使用相同的工具链和依赖项,以减少环境差异可能带来的问题。 |
|
2535 | + |
|
2536 | +反例:在开发过程中使用不同于目标部署环境的操作系统或软件版本,导致在部署时出现兼容性问题或意外的行为差异。 |
|
2537 | + |
|
2538 | +通过保持开发环境和部署环境的一致性,可以最大程度地减少因环境差异而引起的问题。这样可以更好地预测和管理应用程序的行为,并提高部署过程的可靠性和效率。同时,也方便开发团队和运维团队之间的协作和沟通,减少因环境差异而导致的沟通和排查问题的成本。 |
|
2539 | + |
|
2540 | +需要注意的是,有时候在开发环境和部署环境之间可能存在一些差异,比如数据库的配置、外部服务的依赖等。在这种情况下,应该及时通知和协调相关团队,确保环境差异被妥善处理,并进行必要的测试和验证,以确保代码在部署环境中的正常运行。 |
|
... | ... | \ No newline at end of file |
interfaceApp.md
... | ... | @@ -0,0 +1 @@ |
1 | +### [[111]] |
|
... | ... | \ No newline at end of file |
junit5.md
... | ... | @@ -0,0 +1,3 @@ |
1 | +junit5 |
|
2 | + |
|
3 | +[[/uploads/junit5/zhenghua.png]] |
|
... | ... | \ No newline at end of file |
k8s-distributed-upgrade-guide.md
... | ... | @@ -0,0 +1,46 @@ |
1 | +# 分布式部署 k8s 升级步骤 |
|
2 | + |
|
3 | +## 部署视频 |
|
4 | + |
|
5 | +<video width="100%" height="100%" src="http://192.168.175.198:10003/iidpminio/k8s-depoly-iidp.mp4" controls="true" /> |
|
6 | + |
|
7 | +## 后端升级 |
|
8 | + |
|
9 | +### 升级内置 APP 版本 |
|
10 | + |
|
11 | +例如需要升级多租户 APP 的版本,需要在 `base.json` 中修改 APP 的版本 |
|
12 | + |
|
13 | +[[/uploads/k8s-distributed-upgrade-guide/update_base_json_app.png]] |
|
14 | + |
|
15 | +### 修改 Sidecar 配置中的引擎版本 |
|
16 | + |
|
17 | +分布式部署在安装的时候,sidecar 会通过这个配置创建新的服务。 |
|
18 | + |
|
19 | +[[/uploads/k8s-distributed-upgrade-guide/update_sidecar_enginer_version.png]] |
|
20 | + |
|
21 | +### 修改现有工作负载的引擎版本 |
|
22 | + |
|
23 | +进入工作负载-更多操作-编辑设置 |
|
24 | + |
|
25 | +[[/uploads/k8s-distributed-upgrade-guide/update_pod_setting.png]] |
|
26 | + |
|
27 | +修改引擎镜像,点击保存后会自动重新创建 |
|
28 | + |
|
29 | +**注意:不要同时更新多个工作负载。先更新 iidp-app 节点,等启动完成后,再更新其他服务节点** |
|
30 | + |
|
31 | +[[/uploads/k8s-distributed-upgrade-guide/update_pod_version.png]] |
|
32 | + |
|
33 | +## JVM 参数设置 |
|
34 | + |
|
35 | +默认没有设置 JVM 内存使用,如果是分布式部署,可能会因为 JVM 默认内存参数太小导致内存溢出。 |
|
36 | + |
|
37 | +可以通过环境变量给工作负载设置 JVM 参数。对于分布式部署,需要修改 Sidecar 的配置文件。 |
|
38 | + |
|
39 | +**注意:最大内存不要超过单节点的内存大小。单节点的配置可以在节点-集群节点查看** |
|
40 | + |
|
41 | +[[/uploads/k8s-distributed-upgrade-guide/update_sw_opts.png]] |
|
42 | + |
|
43 | +对于已经创建好的工作负载,可以编辑容器设置。保存后会自动重新创建容器。 |
|
44 | + |
|
45 | +[[/uploads/k8s-distributed-upgrade-guide/update_pod_sw_opts.png]] |
|
46 | + |
my-pic.md
... | ... | @@ -0,0 +1 @@ |
1 | +pic |
|
... | ... | \ No newline at end of file |
operatorLog.md
... | ... | @@ -0,0 +1 @@ |
1 | +1 |
|
... | ... | \ No newline at end of file |
seed-data.md
... | ... | @@ -0,0 +1,25 @@ |
1 | +# 种子数据重置 |
|
2 | +### 1. 菜单-已安装应用-重置种子数据 |
|
3 | +超级管理员implementer实施人员才有权限操作,使用场景: |
|
4 | +* 开发阶段; |
|
5 | +* 项目第一次上线; |
|
6 | +* 需要重置整个应用才能使用的。 |
|
7 | + |
|
8 | +<p style="color: red;">种子数据重置会把整个应用的视图,字典、菜单,业务数据都重置为JSON里面的种子数据, |
|
9 | +即使业务数据数据库已经修改了,也会被重置,这种操作比较危险,慎用。</p> |
|
10 | + |
|
11 | + |
|
12 | +### 2. 菜单-已安装应用-更新数据 |
|
13 | +使用场景: |
|
14 | +* 应用有新增的种子数据(包含菜单,字典、视图,业务数据)有新增,会新增到数据库。 |
|
15 | +* 更新应用所有的视图文件,不会更新菜单,字典和业务数据。 |
|
16 | + |
|
17 | + |
|
18 | +<p style="color: red;">这个操作不会更新数据库的菜单和业务数据(如果数据库有修改,不会更新)。</p> |
|
19 | + |
|
20 | + |
|
21 | + |
|
22 | +### 3. 菜单-开发者中心-种子数据管理 |
|
23 | +使用场景: |
|
24 | +* 测试环境、生产环境 |
|
25 | +* 重置单个种子数据为JSON里面的数据,包含视图,菜单,字典,业务数据。 |
|
... | ... | \ No newline at end of file |
sonar-lint.md
... | ... | @@ -0,0 +1 @@ |
1 | +111 |
|
... | ... | \ No newline at end of file |
sonar-lint\344\275\277\347\224\250.md
... | ... | @@ -0,0 +1,39 @@ |
1 | +### sonar简介 |
|
2 | + |
|
3 | +sonar(https://www.sonarqube.org/)是一款静态代码质量分析工具,支持Java、Python、PHP、JavaScript、CSS等25种以上的语言,而且能够集成在IDE、Jenkins、Git等服务中,方便随时查看代码质量分析报告。 |
|
4 | + |
|
5 | +### IDEA 插件 |
|
6 | + |
|
7 | +IDEA 应用市场提供了sonar插件,可直接搜索并安装 |
|
8 | + |
|
9 | +[[/uploads/sonar-lint/sonar-1.png]] |
|
10 | + |
|
11 | +安装完毕以后,我们就可以对已有的工程进行静态分析 |
|
12 | + |
|
13 | +[[/uploads/sonar-lint/sonar-2.png]] |
|
14 | + |
|
15 | +并在IDEA查看分析结果 |
|
16 | + |
|
17 | + |
|
18 | +[[/uploads/sonar-lint/sonar-3.png]] |
|
19 | + |
|
20 | +也可以对指定的文件进行静态分析 |
|
21 | + |
|
22 | +[[/uploads/sonar-lint/sonar-4.png]] |
|
23 | + |
|
24 | +还可以在远程服务进行静态分析,一般在CICD阶段进行,在176上已经部署了sonar server,那么就可以执行一下命令进行静态分析: |
|
25 | + |
|
26 | +```shell |
|
27 | +mvn sonar:sonar -Dsonar.projectKey=sie-snest-sonar -Dsonar.host.url=http://192.168.168.176:9000 -Dsonar.login=02671b5cb5fbb31d3affe8988f73dd758c98d32c -s settings.xml |
|
28 | + |
|
29 | +``` |
|
30 | +然后我们登录到服务端web页面(http://192.168.168.176:9000/dashboard?id=sie-snest-sonar),查看静态分析结果: |
|
31 | + |
|
32 | +[[/uploads/sonar-lint/sonar-5.png]] |
|
33 | + |
|
34 | +不管是本地IDEA和远程sonar服务都可以查看代码经过静态分析以后的一些问题,并有准对性的修改。 |
|
35 | + |
|
36 | +对于开发者来说可以在完成某一阶段功能编写后进行静态分析,并解决; |
|
37 | +对于代码review者来说可以先执行静态分析。 |
|
38 | + |
|
39 | +总的来说就是最终保证代码的质量。 |
|
... | ... | \ No newline at end of file |
tenant-user-manual.md
... | ... | @@ -0,0 +1,2 @@ |
1 | +# 多租户操作手册V1.1 |
|
2 | +[[多租户操作手册V1.2.docx|/uploads/tenant-user-manual/多租户操作手册V1.2.docx]] |
|
... | ... | \ No newline at end of file |
upgrad.md
... | ... | @@ -0,0 +1,114 @@ |
1 | +# 升级指引 |
|
2 | +## 一、IDEA开发升级指引 |
|
3 | + |
|
4 | +### 1. 修改POM |
|
5 | + |
|
6 | +1. SDK版本升级 |
|
7 | +以iidp-backend-demo工程举例 |
|
8 | + |
|
9 | +[[/uploads/upgrad/4.png]] |
|
10 | + |
|
11 | +2. sie-iidp-demo-apps 目录下的pom文件找到SDK讲 |
|
12 | +sdk版本升级到 v2.3.0.RELEASE (这个版本随着平台升级而升级) |
|
13 | + |
|
14 | +[[/uploads/upgrad/5.png]] |
|
15 | + |
|
16 | +### 2. 修改APP.JSON |
|
17 | + |
|
18 | +APP.JSON文件属于平台内置应用 |
|
19 | + |
|
20 | +JAR文件下载路径 [http://docs-iidp.sieiot.com/index.php/s/2SpBjOaTPN7l2co ](http://docs-iidp.sieiot.com/index.php/s/2SpBjOaTPN7l2co ) |
|
21 | + |
|
22 | +1. 将apps.josn配置升级到平台提供 |
|
23 | +```json |
|
24 | +{ |
|
25 | + "loaders": { |
|
26 | + "API": "com.sie.snest.engine.loaders.ApiLoader", |
|
27 | + "SDK": "com.sie.snest.sdk.loaders.SdkLoader" |
|
28 | + }, |
|
29 | + "apps": { |
|
30 | + "SDK": [ |
|
31 | + "sie-snest-base-v2.3.0.RELEASE.jar", |
|
32 | + "sie-snest-file-v2.3.0.RELEASE.jar", |
|
33 | + "sie-snest-dict-v2.3.0.RELEASE.jar", |
|
34 | + "sie-snest-log-v2.2.0-RELEASE.jar", |
|
35 | + "sie-snest-config-v2.0.0.RELEASE.jar", |
|
36 | + "sie-snest-cache-v2.3.0.RELEASE.jar" |
|
37 | + ] |
|
38 | + } |
|
39 | +} |
|
40 | +``` |
|
41 | + |
|
42 | +## 二、KubeSphere服务器升级指引 |
|
43 | +KubeSphere 容器升级指引 |
|
44 | + |
|
45 | +1.检查容器实例版本 |
|
46 | +master容器示例: |
|
47 | +[[/uploads/upgrad/master.png]] |
|
48 | +首先检查引擎版本 [harbor.sieiot.com/iidp/sie-snest:v2.3.1.RELEASE](http://192.168.175.198:10001/iidpwiki/%E7%89%88%E6%9C%AC%E5%8F%91%E5%B8%83/%E5%89%8D%E5%90%8E%E7%AB%AF%E7%89%88%E6%9C%AC%E6%9B%B4%E6%96%B0%E4%BF%A1%E6%81%AF) 升级版本 |
|
49 | + |
|
50 | + |
|
51 | + |
|
52 | +> 引擎harbor.sieiot.com/iidp/sie-snest:v2.3.1.RELEASE |
|
53 | + |
|
54 | +> 边车harbor.sieiot.com/iidp/distributed-engine:v2.0.7.RELEASE |
|
55 | + |
|
56 | + |
|
57 | + |
|
58 | +2.检查配置信息 |
|
59 | +Master yml配置 |
|
60 | +```json |
|
61 | + readinessProbe: |
|
62 | + httpGet: |
|
63 | + path: /checkhealth |
|
64 | + port: 8060 |
|
65 | + scheme: HTTP |
|
66 | +``` |
|
67 | +Sidecar yml配置 |
|
68 | + |
|
69 | +[[/uploads/upgrad/sidarcar.png]] |
|
70 | + |
|
71 | + |
|
72 | +第一步确认端口为$SERVER_PORT |
|
73 | + |
|
74 | +snest-sidecar YML配置 |
|
75 | +```json |
|
76 | + readinessProbe: |
|
77 | + httpGet: |
|
78 | + path: /checkhealth |
|
79 | + port: $SERVER_PORT |
|
80 | + scheme: HTTP |
|
81 | +``` |
|
82 | +第二步确认版本一致 |
|
83 | +```json |
|
84 | + app.snest.image = harbor.sieiot.com/iidp/sie-snest:v2.3.1.RELEASE |
|
85 | + app.engine.image = harbor.sieiot.com/iidp/distributed-engine:v2.0.7.RELEASE |
|
86 | +``` |
|
87 | +[[/uploads/upgrad/sidarca2.png]] |
|
88 | + |
|
89 | +第三步检查base内置应用配置文件 |
|
90 | + |
|
91 | +[[/uploads/upgrad/snestapp.png]] |
|
92 | +base.json配置文件 |
|
93 | + |
|
94 | +```json |
|
95 | +{ |
|
96 | + "loaders": { |
|
97 | + "API": "", |
|
98 | + "SDK": "" |
|
99 | + }, |
|
100 | + "apiToken": "", |
|
101 | + "apps": { |
|
102 | + "SDK": [ |
|
103 | + "sie-snest-tenant-v2.3.5-RELEASE.jar", |
|
104 | + "sie-snest-mail-v2.3.0.RELEASE.jar",//工作流邮件,可选 |
|
105 | + "sie-snest-workflow-v1.0.0-RELEASE.jar",//工作流App,可选 |
|
106 | + "sie-snest-datasource-v2.3.0.RELEASE.jar",//数据源分库分表,可选 |
|
107 | + "sie-snest-interface-v1.2.0-RELEASE.jar",//接口App,可选 |
|
108 | + "sie-snest-i18n-v1.0.0-RELEASE.jar"//国际化APP,可选 |
|
109 | + ] |
|
110 | + } |
|
111 | +} |
|
112 | +``` |
|
113 | + |
|
114 | +## 三、 Docker升级指引 |
|
... | ... | \ No newline at end of file |
web-backend-information.md
... | ... | @@ -0,0 +1,91 @@ |
1 | +# 后端学习资料 |
|
2 | + |
|
3 | +## 视频 |
|
4 | +### 1. ER关系与业务流程 |
|
5 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/ER关系与业务流程.mp4" controls="true" /> |
|
6 | + |
|
7 | +### 2. 图书管理APP使用说明 |
|
8 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/图书管理APP使用说明.mp4" controls="true" /> |
|
9 | + |
|
10 | +#### 2.1 图书管理 |
|
11 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/图书管理功能实现.mp4" controls="true" /> |
|
12 | + |
|
13 | +#### 2.2 图书分类 |
|
14 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/图书分类.mp4" controls="true" /> |
|
15 | + |
|
16 | +#### 2.3 读者管理 |
|
17 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/读者管理开发流程.mp4" controls="true" /> |
|
18 | + |
|
19 | +#### 2.4 读者分类 |
|
20 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/读者分类.mp4" controls="true" /> |
|
21 | + |
|
22 | +#### 2.5 借书管理 |
|
23 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/借书管理讲解.mp4" controls="true" /> |
|
24 | + |
|
25 | +#### 2.6 还书管理 |
|
26 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/还书管理详解.mp4" controls="true" /> |
|
27 | + |
|
28 | +### 3 功能实现讲解 |
|
29 | + |
|
30 | +##### 3.1 目录讲解 |
|
31 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/目录讲解.mp4" controls="true" /> |
|
32 | + |
|
33 | +##### 3.2 项目启动 |
|
34 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/项目启动讲解.mp4" controls="true" /> |
|
35 | + |
|
36 | +##### 3.3 建模型前ER关系 |
|
37 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/建模型之ER关系讲解.mp4" controls="true" /> |
|
38 | + |
|
39 | +##### 3.4 建模型 |
|
40 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/创建模型讲解.mp4" controls="true" /> |
|
41 | + |
|
42 | +##### 3.5 建视图 |
|
43 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/创建视图讲解.mp4" controls="true" /> |
|
44 | + |
|
45 | +##### 3.6 菜单配置 |
|
46 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/菜单配置讲解.mp4" controls="true" /> |
|
47 | + |
|
48 | +##### 3.7 授权 |
|
49 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/授权讲解.mp4" controls="true" /> |
|
50 | + |
|
51 | +##### 3.8 增删改查 |
|
52 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/增删改查讲解.mp4" controls="true" /> |
|
53 | + |
|
54 | +##### 3.9 导入导出 |
|
55 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/导入导出详解.mp4" controls="true" /> |
|
56 | + |
|
57 | +##### 3.10 启用禁用 |
|
58 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/启用禁用讲解.mp4" controls="true" /> |
|
59 | + |
|
60 | +##### 3.11 跳转页面 |
|
61 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/跳转页面讲解.mp4" controls="true" /> |
|
62 | + |
|
63 | +##### 3.12 编号自动生成 |
|
64 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/编号自动生成讲解.mp4" controls="true" /> |
|
65 | + |
|
66 | +##### 3.13 APP内调用 |
|
67 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/APP内调用讲解.mp4" controls="true" /> |
|
68 | + |
|
69 | +##### 3.14 跨APP调用 |
|
70 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/跨APP调用讲解.mp4" controls="true" /> |
|
71 | + |
|
72 | +##### 3.15 前端扩展 |
|
73 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/前端扩展讲解.mp4" controls="true" /> |
|
74 | + |
|
75 | +### 4. 单位管理APP使用说明测试 |
|
76 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/练习-单位管理2.mp4" controls="true" /> |
|
77 | + |
|
78 | +### 5. 物料管理APP使用说明 |
|
79 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/练习-物料管理.mp4" controls="true" /> |
|
80 | + |
|
81 | +### 6. 供应商管理APP使用说明 |
|
82 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/练习-供应商管理.mp4" controls="true" /> |
|
83 | + |
|
84 | +### 7. 产品管理APP使用说明 |
|
85 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/练习-产品管理.mp4" controls="true" /> |
|
86 | + |
|
87 | +### 8. 订单管理APP使用说明 |
|
88 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/练习-订单管理.mp4" controls="true" /> |
|
89 | + |
|
90 | +### 9. 接收采集APP使用说明 |
|
91 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-backend-information/接收采集.mp4" controls="true" /> |
|
... | ... | \ No newline at end of file |
web-front-information.md
... | ... | @@ -0,0 +1,54 @@ |
1 | +# 前端学习资料 |
|
2 | + |
|
3 | +## 视频 |
|
4 | +### 1.平台及开发模式介绍 |
|
5 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-front-information/meeting_01.mp4" controls="true" /> |
|
6 | + |
|
7 | + |
|
8 | +### 2.开发文档介绍 |
|
9 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-front-information/meeting_02.mp4" controls="true" /> |
|
10 | + |
|
11 | +### 3.标准模板介绍 |
|
12 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-front-information/meeting_03.mp4" controls="true" /> |
|
13 | + |
|
14 | +### 4.其他配置介绍 |
|
15 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-front-information/meeting_04.mp4" controls="true" /> |
|
16 | + |
|
17 | +### 5.Demo工程介绍 |
|
18 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-front-information/meeting_05.mp4" controls="true" /> |
|
19 | + |
|
20 | +### 6.扩展如何开发 |
|
21 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-front-information/meeting_06.mp4" controls="true" /> |
|
22 | + |
|
23 | +### 7.扩展如何复用 |
|
24 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-front-information/meeting_07.mp4" controls="true" /> |
|
25 | + |
|
26 | +### 8.自定义组件实现 |
|
27 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-front-information/meeting_08.mp4" controls="true" /> |
|
28 | + |
|
29 | +### 9.自定义指令实现 |
|
30 | +<video width="100%" height="100%" src="/iidpwiki/uploads/web-front-information/meeting_09.mp4" controls="true" /> |
|
31 | + |
|
32 | +### 10.后端视图放前端工程 |
|
33 | +<video width="100%" height="100%" src="/iidpwiki/uploads/Home/20240319_iidp前端培训.mp4" controls="true" /> |
|
34 | + |
|
35 | + |
|
36 | + |
|
37 | +## 资料 |
|
38 | +<a href='/iidpwiki/uploads/web-front-information/前后端视图区别及配置.pptx'>前后端视图区别及配置.pptx</a> |
|
39 | + |
|
40 | +<a href='/iidpwiki/uploads/web-front-information/前端标准页面渲染过程和页面类型初步介绍.pptx'>前端标准页面渲染过程和页面类型初步介绍.pptx</a> |
|
41 | + |
|
42 | +<a href='/iidpwiki/uploads/web-front-information/上下表格模板的配置与应用.pptx'>上下表格模板的配置与应用.pptx</a> |
|
43 | + |
|
44 | +<a href='/iidpwiki/uploads/web-front-information/表单视图模板的配置与应用.pptx'>表单视图模板的配置与应用.pptx</a> |
|
45 | + |
|
46 | +<a href='/iidpwiki/uploads/web-front-information/表格模板分享.pptx'>表格模板分享.pptx</a> |
|
47 | + |
|
48 | +<a href='/iidpwiki/uploads/web-front-information/前端扩展与组件开发.pptx'>前端扩展与组件开发.pptx</a> |
|
49 | + |
|
50 | +<a href='/iidpwiki/uploads/web-front-information/前端视图复用扩展.pptx'>前端视图复用扩展.pptx</a> |
|
51 | + |
|
52 | + |
|
53 | + |
|
54 | + |
web-front-standard.md
... | ... | @@ -0,0 +1,311 @@ |
1 | +<!--[[_TOC_]]--> |
|
2 | +# IIDP前端开发规范 |
|
3 | + |
|
4 | +[[/uploads/IIDP-png/logo.png]] |
|
5 | + |
|
6 | +# 前言 |
|
7 | + |
|
8 | +希望IIDP开发者通过阅读本手册,能够充分利用IIDP平台进行开发,避免常见的错误和陷阱,写出高质量的代码,提高开发效率。让我们一起码出高效、码出质量的软件系统。 |
|
9 | + |
|
10 | +# 开发规范 |
|
11 | + |
|
12 | +## 命名规范 |
|
13 | + |
|
14 | +### 项目文件命名 |
|
15 | + |
|
16 | +1、项目文件名 |
|
17 | + |
|
18 | +【推荐】命名方法:kebab-case 字母小写短横线连接 |
|
19 | + |
|
20 | +正例:whclould-smart-port / my-project-name |
|
21 | + |
|
22 | +反例:My-Project |
|
23 | + |
|
24 | +2、目录名 |
|
25 | + |
|
26 | +【推荐】有复数结构时,要采用复数命名法 |
|
27 | + |
|
28 | +正例:assets、components、directives、mixins、utils、views |
|
29 | + |
|
30 | +### 组件命名 |
|
31 | + |
|
32 | +1.【强制】 组件命名规范,组件名称必须唯一 |
|
33 | + |
|
34 | +说明:在IIDP平台中,确保组件名称的唯一性,便于识别和管理。 tech-作为名称前缀,具体的组件名作为后缀名称。 |
|
35 | + |
|
36 | +正例:tech-button / tech-dialog / tech-upload / tech-custom-multi-input 使用时type:button |
|
37 | + |
|
38 | +反例:custom-multi-input 没有tech-作为名称前缀,会导致使用时type:custom-multi-input不显示这个组件 |
|
39 | + |
|
40 | + |
|
41 | +2、单文件组件名 |
|
42 | + |
|
43 | +【推荐】单文件组件名应该始终是单词大写开头 (PascalCase),大驼峰式命名法。 |
|
44 | + |
|
45 | +正例:CustomComponent.vue |
|
46 | + |
|
47 | +反例:my-Component.vue |
|
48 | + |
|
49 | +3、基础组件名 |
|
50 | + |
|
51 | +【推荐】基础组件在一个页面内可使用多次,在不同页面内也可复用,是高可复用组件。应该全部以一个特定的前缀开头 —— Base |
|
52 | + |
|
53 | +正例:BaseButton.vue/ BaseInput.vue |
|
54 | + |
|
55 | +反例:myButton.vue |
|
56 | + |
|
57 | +4、业务组件 |
|
58 | + |
|
59 | +【推荐】掺杂了复杂业务的组件(拥有自身 data、prop 的相关处理)即业务组件应该以 Custom 前缀命名。 |
|
60 | + |
|
61 | +正例:CustomCard.vue |
|
62 | + |
|
63 | +反例:myCard.vue |
|
64 | + |
|
65 | +5、组件名中单词顺序 |
|
66 | + |
|
67 | +【推荐】组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾。组件名尽量以完整单词命名,单词尽量语义化,方便阅读。 |
|
68 | + |
|
69 | +正例:SearchContent.vue |
|
70 | + |
|
71 | +反例:ContSech.vue |
|
72 | + |
|
73 | +### 代码参数命名 |
|
74 | +1、【推荐】在声明 prop 的时候,其命名应该始终使用 camelCase(小驼峰式命名),而在模板和 JSX 中应该始终使用 kebab-case(短横线连接式)。我们单纯的遵循每个语言的约定,在 JavaScript 中更自然的是 camelCase。而在 HTML 中则是 kebab-case |
|
75 | + |
|
76 | +正例:camelCase |
|
77 | + |
|
78 | +反例:camel-Case |
|
79 | + |
|
80 | +2、router属性 |
|
81 | + |
|
82 | +【推荐】Vue Router Path 命名采用 kebab-case (短横线连接式)格式。或者PascalCase(大驼峰命名),推荐使用kebab-case 短横线。 用 Snake(下划线连接式)(如:/user_info)或 camelCase(小驼峰命名)(如:/userInfo)的单词会被当成一个单词,搜索引擎无法区分语义 |
|
83 | + |
|
84 | +3、模板中组件 |
|
85 | + |
|
86 | +对于绝大多数项目来说,在单文件组件和字符串模板中组件名应该总是 PascalCase(大驼峰命名) 的,但是在 DOM 模板中总是 kebab-case (短横线连接式)的。 |
|
87 | + |
|
88 | +4、全局变量,全局方法名称使用应用名称+功能名称 |
|
89 | + |
|
90 | + 正例: |
|
91 | +```js |
|
92 | + const IOT_THEME_OPTIONS = ['blue', 'red', 'green'] |
|
93 | + |
|
94 | + function iotGetSearchParams(params){ |
|
95 | + console.log(params) |
|
96 | + } |
|
97 | +``` |
|
98 | + |
|
99 | +反例: const MY_TE = ['blue', 'red', 'green'] |
|
100 | + |
|
101 | +5、变量 |
|
102 | + |
|
103 | +命名规范:类型 + 对象描述或属性的方式 |
|
104 | + |
|
105 | +正例:camelCase |
|
106 | + |
|
107 | +6、常量 |
|
108 | + |
|
109 | +命名规范:使用大写字母和下划线来组合命名,下划线用以分割单词,力求语义表达完整清楚 |
|
110 | + |
|
111 | +正例:const MAX_COUNT = 10 //全部大写下划线分割 |
|
112 | + |
|
113 | +反例: const MC = 10 |
|
114 | + |
|
115 | +7、方法 |
|
116 | + |
|
117 | +命名规范:统一使用动词或者动词 + 名词形式 |
|
118 | + |
|
119 | +正例:jumpPage、loginIn、openDialog、getListApi、refresh |
|
120 | + |
|
121 | +反例:pageGet |
|
122 | + |
|
123 | +8、事件方法 |
|
124 | + |
|
125 | +命名规范:handle + 名称(可选)+ 动词 |
|
126 | + |
|
127 | +正例: |
|
128 | +```js |
|
129 | +<el-pagination |
|
130 | + @size-change="handleSizeChange" |
|
131 | + @current-change="handleCurrentChange" |
|
132 | + :current-page.sync="currentPage" |
|
133 | + :page-size="100" |
|
134 | + layout="total, prev, pager, next" |
|
135 | + :total="1000"> |
|
136 | +</el-pagination> |
|
137 | +``` |
|
138 | + |
|
139 | +反例: |
|
140 | +```js |
|
141 | +<el-pagination |
|
142 | + @size-change="change" |
|
143 | + @current-change="currentChange" |
|
144 | + :current-page.sync="currentPage1" |
|
145 | + :page-size="100" |
|
146 | + layout="total, prev, pager, next" |
|
147 | + :total="1000"> |
|
148 | +</el-pagination> |
|
149 | +``` |
|
150 | +## 代码规范 |
|
151 | +【强制】为 v-for 设置键值,在组件上必须用 key 搭配 v-for,以便维护内部组件及其子树的状态。 |
|
152 | + |
|
153 | +【强制】v-if 和 v-for 互斥,永远不要把 v-if 和 v-for 同时用在同一个元素上。 |
|
154 | + |
|
155 | +【强制】多个 attribute 的元素应该分多行撰写,每个 attribute 一行。视情况而定 |
|
156 | + |
|
157 | +【强制】模板中简单的表达式,组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。 |
|
158 | + |
|
159 | +复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该出现的是什么,而非如何计算那个值。而且计算属性和方法使得代码可以重用。 |
|
160 | + |
|
161 | +【强制】指令缩写 |
|
162 | + |
|
163 | +用 : 表示 v-bind: |
|
164 | + |
|
165 | +用 @ 表示 v-on: |
|
166 | + |
|
167 | +用 # 表示 v-slot |
|
168 | + |
|
169 | +【推荐】不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以提升可读性。 |
|
170 | + |
|
171 | +【推荐】条件判断能使用三目运算符和逻辑运算符解决的,就不要使用条件判断,但不要写太长的三目运算符。如果超过 3 层请抽成函数,并写清楚注释。 |
|
172 | + |
|
173 | +【推荐】慎用 console.log,因 console.log 大量使用会有性能问题,调试完最终提交前清除掉 |
|
174 | + |
|
175 | +## 注释规范 |
|
176 | +注释的原则:提高代码的可读性,从而提高代码的可维护性 |
|
177 | +如无必要,勿增注释 ( As short as possible ) |
|
178 | +如有必要,尽量详尽 ( As long as necessary ) |
|
179 | + |
|
180 | +1、HTML 文件注释 |
|
181 | + |
|
182 | +【推荐】单行注释:一般用于简单的描述,如某些状态描述、属性描述等。注释内容前后各一个空格字符,注释位于要注释代码的上面,单独占一行。 |
|
183 | + |
|
184 | +2、模块注释 |
|
185 | + |
|
186 | +【推荐】一般用于描述模块的名称以及模块开始与结束的位置。 注释内容前后各一个空格字符 |
|
187 | + |
|
188 | +## 扩展规范 |
|
189 | + |
|
190 | +1、主题扩展 |
|
191 | + |
|
192 | +【强制】对页面顶部,侧边栏,菜单等公共模块的扩展 |
|
193 | + |
|
194 | +需独立新增app内扩展,可以单独自行安装卸载,以免影响全局 |
|
195 | + |
|
196 | +2.【推荐】扩展名称使用应用名称+菜单名+功能名称 |
|
197 | + |
|
198 | +正例:iot_iiot_program_menu_search_extend |
|
199 | + |
|
200 | +反例:unit_test_extend_view 不明确生效的位置和功能 |
|
201 | + |
|
202 | +3、样式扩展 |
|
203 | + |
|
204 | +【强制】对框架使用的Element的样式修改时,使用局部作用域,以免影响全局样式 |
|
205 | + |
|
206 | +4、关于扩展选择的节点id |
|
207 | + |
|
208 | +【强制】在选择扩展的节点id时,对于id中包含undefined的节点不能直接扩展,请咨询平台人员 |
|
209 | + |
|
210 | +5、关于扩展注释 |
|
211 | + |
|
212 | +【推荐】在对某节点扩展时,补充注释信息,说明页面节点,功能描述等信息,方便阅读、定位 |
|
213 | + |
|
214 | +# 版本管理规范 |
|
215 | + |
|
216 | +## 关于版本更新 |
|
217 | +为了确保线上使用的依赖版本与开发时的版本一致,有以下几项注意事项: |
|
218 | + |
|
219 | +1、执行npm run update:tech、npm run update:beta、npm update等命令升级后依赖版本中会带有 ^ 等符号,如果需要使用固定版本,需要手动修删除 ^ 符号,使用固定的具体的版本号,并执行npm run init:tech重新安装指定版本 |
|
220 | +正例: |
|
221 | + |
|
222 | +```json |
|
223 | +// 固定(指定)的版本号 |
|
224 | +{ |
|
225 | + "dependencies": { |
|
226 | + "@tech/t-base": "1.0.11", |
|
227 | + "@tech/t-build": "1.0.1", |
|
228 | + "@tech/t-core": "1.0.9", |
|
229 | + "@tech/t-el-ui": "1.0.9", |
|
230 | + } |
|
231 | +} |
|
232 | +``` |
|
233 | +反例: |
|
234 | + |
|
235 | +```json |
|
236 | +// 不固定的版本号,(不推荐) |
|
237 | +{ |
|
238 | + "dependencies": { |
|
239 | + "@tech/t-base": "^1.0.11", |
|
240 | + "@tech/t-build": "^1.0.1", |
|
241 | + "@tech/t-core": "^1.0.9", |
|
242 | + "@tech/t-el-ui": "^1.0.9", |
|
243 | + } |
|
244 | +} |
|
245 | +``` |
|
246 | + |
|
247 | +2、为了保证打包稳定,请直接使用当前开发环境所安装的依赖,不需要执行 npm run update:tech 或者 npm run update:beta |
|
248 | + |
|
249 | +# 工程结构规范 |
|
250 | + |
|
251 | +## 工程结构说明 |
|
252 | +```js |
|
253 | +|— apps |
|
254 | + |— base // 业务App 根据实际情况命名 |
|
255 | + |— common // 公共总扩展,一般情况不用操作,后面会展开讲解 |
|
256 | + |— common - 公共总扩展 |
|
257 | + |— assetImport.js - 内部导入连接资源扩展入口 |
|
258 | + |— asset.json - 外部url连接资源扩展入口 |
|
259 | + |— common.js - 应用公共配置扩展入口 |
|
260 | + |— comps.js - 定制组件扩展入口 |
|
261 | + |— extendView.js - 合并视图扩展入口 |
|
262 | + |— hook.js - 功能钩子扩展入口 |
|
263 | + |— schema.js - 初始视图结构扩展入口 |
|
264 | + |— index.js - 公共扩展总入口 |
|
265 | + |— views // 纯js格式编辑视图,通过视图的扩展能力合并的主视图中 |
|
266 | + |— rbacUser // 业务定制的扩展视图,名称根据实际情况定义 |
|
267 | + |— tview__base__rbac_user.js // 扩展文件,后面会展开讲解 命名规则:[自定义业务名].js |
|
268 | + |— ... |
|
269 | + |— index.js // 扩展视图的入口 |
|
270 | + |— config // 当前App的额外配置,如全局变量 |
|
271 | + |— app.json |
|
272 | + |— resource // 语言包,皮肤 等资源 |
|
273 | + |— static-resource // 静态资源 |
|
274 | + |— index.js // 扩展引入入口 |
|
275 | + |— component // 公共业务组件 |
|
276 | +|— config |
|
277 | + |— nginx |
|
278 | + |— apps.json |
|
279 | +|— build // 各种环境的打包入口 |
|
280 | +``` |
|
281 | +工程运行起来后,以下文件夹是工程临时生成文件不用处理: |
|
282 | +resource、static-resouorce、views、dist、distApp、distTmp |
|
283 | + |
|
284 | +## 资源引入规范 |
|
285 | +1、资源引入 |
|
286 | + |
|
287 | +【强制】前端依赖的第三方库,或组件库的资源地址引入需要做以下配置: |
|
288 | + |
|
289 | +外部资源需要在./apps/[自己的扩展应用]/common/assets.json配置引入 |
|
290 | + |
|
291 | +内部资源需要在./apps/[自己的扩展应用]/common/assetsImport.js配置引入 |
|
292 | + |
|
293 | +2、资源扩展 |
|
294 | + |
|
295 | +asset.json资源引入扩展,会根据定义里面的 title 作为唯一标识覆盖合并 |
|
296 | + |
|
297 | +assetImport.js资源引入扩展,会根据定义里面的 key 值作为唯一标识覆盖合并, 例如上面的 assetImport.js 引入组件库的key值为 my-ui。 |
|
298 | + |
|
299 | +# 部署规范 |
|
300 | + |
|
301 | +1、底座的更新只能通过更新服务器中前端工程。 |
|
302 | + |
|
303 | +2、app 的更新可通过应用市场更新,可也以通过服务器部署更新 |
|
304 | + |
|
305 | +3、打包外部依赖 app 和本地开发的 app 到./dist/umdComps中。适用于通过应用市场上传安装 |
|
306 | + |
|
307 | +【强制】主服务器的 nginx 需要增加允许跨域 |
|
308 | + |
|
309 | +1、所有的涉及静态资源转发的都需要配置允许跨域 |
|
310 | + |
|
311 | +2、涉及代理转发的不需要设置跨域 |
workflow-video.md
... | ... | @@ -0,0 +1,141 @@ |
1 | +# 审批流视频和操作手册 |
|
2 | + |
|
3 | +## 1. 审批流使用手册和文档 |
|
4 | +### [审批流手册](/iidpdoc/pages/2392fd) |
|
5 | + |
|
6 | + |
|
7 | +## 2. 审批流学习视频 |
|
8 | +### 2.1. 搭建环境 |
|
9 | +<video width="100%" height="100%" src="/iidpwiki/uploads/uploads/workflow/wf.1.搭建环境.mp4" controls="true" /> |
|
10 | + |
|
11 | + |
|
12 | +### 2.2.流程介绍 |
|
13 | +<video width="100%" height="100%" src="/iidpwiki/uploads/uploads/workflow/wf.流程介绍.mp4" controls="true" /> |
|
14 | + |
|
15 | +### 2.3.流程入门 |
|
16 | +<video width="100%" height="100%" src="/iidpwiki/uploads/uploads/workflow/wf.流程入门.mp4" controls="true" /> |
|
17 | + |
|
18 | +### 2.4.报销审批服务-条件网关 |
|
19 | +<video width="100%" height="100%" src="/iidpwiki/uploads/uploads/workflow/wf.2.报销审批服务-条件网关.mp4" controls="true" /> |
|
20 | + |
|
21 | +### 2.5.报销审批服务-并行网关 |
|
22 | +<video width="100%" height="100%" src="/iidpwiki/uploads/uploads/workflow/wf.3.报销审批服务-并行网关.mp4" controls="true" /> |
|
23 | + |
|
24 | +### 2.6.子流程与流程调用 |
|
25 | +<video width="100%" height="100%" src="/iidpwiki/uploads/uploads/workflow/wf.子流程与流程调用.mp4" controls="true" /> |
|
26 | + |
|
27 | + |
|
28 | +## 3.WebGME工作流的导入和导出 |
|
29 | + |
|
30 | +### 3.1 操作步骤: |
|
31 | +1. 第一步:先导出**生产环境**的工作流项目备份。 |
|
32 | +2. 第二步:导出**开发环境**的工作流工作流到本地。 |
|
33 | +3. 第三步:将**开发环境**的工作流项目导入到生产环境。 |
|
34 | + |
|
35 | + |
|
36 | +### 3.2 详细操作步骤 |
|
37 | +- 第一步:先导出**生产环境**的工作流项目备份。 |
|
38 | + |
|
39 | +**生产环境**先打开工程,然后选择项目的名字:edo_smbm,鼠标点击右键,选择 Export Model->点击 with asserts,稍等一会后台会导出。 然后点击导出模型linchang+edo_smbm_db203f.webgmexm,会自动下载文件:linchang+edo_smbm_db203f.webgmexm,将文件复制到本地备份。 |
|
40 | + |
|
41 | +[[//uploads/Home/wegme-export.png]] |
|
42 | + |
|
43 | +[[/uploads/Home/wegme-export-download.png]] |
|
44 | + |
|
45 | + |
|
46 | +- 第二步:导出**开发环境**的工作流工作流到本地。操作步骤参考第一步。 |
|
47 | + |
|
48 | +- 第三步:将**开发环境**的工作流项目导入到生产环境。 |
|
49 | + |
|
50 | +* 如果生产环境已经存在edo_smbm项目,需要先删除edo_smbm项目,鼠标点击右键 Delete. 点击Root根节点,右键,点击Import models,选择File,选择刚刚导出的linchang+edo_smbm_db203f.webgmexm文件,点击OK,稍等一会项目就导入成功了。 |
|
51 | + |
|
52 | +[[/uploads/Home/wegme-import-root-models.png]] |
|
53 | + |
|
54 | +[[/uploads/Home/wegme-import-models.png]] |
|
55 | + |
|
56 | +[[/uploads/Home/wegme-import-download.png]] |
|
57 | + |
|
58 | + |
|
59 | + |
|
60 | + |
|
61 | + |
|
62 | + |
|
63 | + |
|
64 | + |
|
65 | + |
|
66 | + |
|
67 | +## 4.业务代码如何发起流程 |
|
68 | +请求json格式 |
|
69 | + |
|
70 | +`{ |
|
71 | + "id": "guid", |
|
72 | + "jsonrpc": "2.0", |
|
73 | + "method": "service", |
|
74 | + "params": { |
|
75 | + "args": { |
|
76 | + "businessKey": "03aitfqviby80", |
|
77 | + "model": "eam_fault_maintenance_order", |
|
78 | + "serviceName": "wf_eam_fault_maintenance_order" |
|
79 | + }, |
|
80 | + "app": "workflow", |
|
81 | + "service": "startProcess", |
|
82 | + "context": { |
|
83 | + "uid": "", |
|
84 | + "lang": "zh_CN" |
|
85 | + }, |
|
86 | + "model": "wf_process_instance", |
|
87 | + "tag": "master" |
|
88 | + } |
|
89 | +}` |
|
90 | + |
|
91 | +JAVA代码 |
|
92 | +模型:wf_process_instance |
|
93 | +方法:startProcess |
|
94 | + |
|
95 | + /** |
|
96 | + * |
|
97 | + * @param rs |
|
98 | + * @param model 模型名称 |
|
99 | + * @param serviceName 服务名称 |
|
100 | + * @param businessKey 业务主键 |
|
101 | + * @return |
|
102 | + */ |
|
103 | + @MethodService(description = "启动流程", auth = "startProcess") |
|
104 | + public Map<String, Object> startProcess(RecordSet rs, String model, String serviceName, String businessKey) { |
|
105 | + |
|
106 | +示例代码 |
|
107 | + |
|
108 | +` @MethodService(description = "启动流程", auth = "tsetStartProcess") |
|
109 | + public Map<String, Object> tsetStartProcess(RecordSet rs, String model, String serviceName, String businessKey) { |
|
110 | + RecordSet processInstance = rs.getMeta().get("wf_process_instance"); |
|
111 | + Map<String, Object> result= (Map<String, Object>)processInstance.call("startProcess", model,serviceName,businessKey); |
|
112 | + System.out.println(result); |
|
113 | + return result; |
|
114 | + }` |
|
115 | + |
|
116 | +Postman: |
|
117 | +`{ |
|
118 | + "id": "guid", |
|
119 | + "jsonrpc": "2.0", |
|
120 | + "method": "service", |
|
121 | + "params": { |
|
122 | + "args": { |
|
123 | + "businessKey": "03aitfqviby80", |
|
124 | + "model": "eam_fault_maintenance_order", |
|
125 | + "serviceName": "wf_eam_fault_maintenance_order" |
|
126 | + }, |
|
127 | + "app": "workflow", |
|
128 | + "service": "tsetStartProcess", |
|
129 | + "context": { |
|
130 | + "uid": "", |
|
131 | + "lang": "zh_CN" |
|
132 | + }, |
|
133 | + "model": "wf_process_instance", |
|
134 | + "tag": "master" |
|
135 | + } |
|
136 | +}` |
|
137 | + |
|
138 | +返回值: |
|
139 | + "rootInstanceId": "03aqrud96v75s" 流程实例Id |
|
140 | + "status": "RUNNING" 流程状态 |
|
141 | + |
xinsenupdate.md
... | ... | @@ -0,0 +1,27 @@ |
1 | +# 升级指引 |
|
2 | + |
|
3 | +# 镜像 |
|
4 | + |
|
5 | +harbor.sieiot.com/iidp/sie-snest:v2.3.4_02.xingsen |
|
6 | +harbor.sieiot.com/iidp/snest-nginx:v2.3.1.xingsen.3 |
|
7 | + |
|
8 | + |
|
9 | + |
|
10 | +sie-snest-tenant-v2.3.5-RELEASE.jar |
|
11 | + |
|
12 | + |
|
13 | +引擎版本(pom)与apps 清单的apps.json的版本是要一起更新 |
|
14 | + |
|
15 | + |
|
16 | +# apps |
|
17 | + |
|
18 | +sie-snest-base-v2.3.4_02.xingsen.jar |
|
19 | + |
|
20 | +sie-snest-tenant-v2.3.5-xingsen.jar |
|
21 | + |
|
22 | + |
|
23 | +# pom |
|
24 | + |
|
25 | + |
|
26 | + |
|
27 | + |
\344\272\247\345\223\201\347\272\277\345\233\276\346\240\207\351\203\250\347\275\262\346\226\207\346\241\243.md
... | ... | @@ -0,0 +1,11 @@ |
1 | +# 创建minio |
|
2 | + |
|
3 | +# 图标路径icon |
|
4 | + |
|
5 | +# NGINX 配置 |
|
6 | +`nginx` |
|
7 | +location ^~/fileSystem/ { |
|
8 | + proxy_http_version 1.1; |
|
9 | + client_max_body_size 12048m; |
|
10 | + proxy_pass http://192.168.168.25:9000/; |
|
11 | + } |
\345\210\206\345\270\203\345\274\217APP-apiadapt.md
... | ... | @@ -0,0 +1,53 @@ |
1 | +# 发版记录 |
|
2 | + |
|
3 | +## v1.3.1 |
|
4 | + |
|
5 | +**发布日期** |
|
6 | + |
|
7 | +2024/04/20 |
|
8 | + |
|
9 | +**下载地址** |
|
10 | + |
|
11 | +1. [sie-snest-api-adapt-v1.3.1.jar](http://idp.chinasie.com/download-app/distributed-app/sie-snest-api-adapt-v1.3.1.jar) |
|
12 | + |
|
13 | +**更新内容** |
|
14 | + |
|
15 | +1. 移除云边管理、运维管理功能,仅保留 ApiProxy 功能。提供给 iot 使用。 |
|
16 | +2. 增加可重复安装标识。 |
|
17 | + |
|
18 | +## v1.2.0-RELEASE |
|
19 | + |
|
20 | +**建议使用 v1.1.0 或 v1.3.1+** |
|
21 | + |
|
22 | +下载地址 |
|
23 | +1. [sie-snest-api-adapt-v1.2.0-RELEASE.jar](http://idp.chinasie.com/download-app/distributed-app/sie-snest-api-adapt-v1.2.0-RELEASE.jar) |
|
24 | + |
|
25 | +更新内容 |
|
26 | +1. 优化云边管理页面 |
|
27 | +2. 增加边端应用管理功能,可能对边端进行应用安装、卸载、更新、重置种子数据等操作。 |
|
28 | + |
|
29 | +## v1.1.0-RELEASE |
|
30 | + |
|
31 | +下载地址 |
|
32 | +1. jar [sie-snest-api-adapt-v1.1.0-RELEASE.jar](http://idp.chinasie.com/download-app/distributed-app/sie-snest-api-adapt-v1.1.0-RELEASE.jar) |
|
33 | + |
|
34 | +版本依赖 |
|
35 | + |
|
36 | +1. 引擎 v2.2.0-RELEASE |
|
37 | +2. 多租户APP v2.1.1-RELEASE |
|
38 | + |
|
39 | +更新内容 |
|
40 | + |
|
41 | +1. 运维中心,增加分布式日志、链路跟踪、Redis监控、资源监控、数据库监控 |
|
42 | +2. 增加云边管理功能 |
|
43 | + |
|
44 | +## v1.0.0-RELEASE |
|
45 | + |
|
46 | +版本依赖 |
|
47 | + |
|
48 | +1. 引擎 v2.2.0-RELEASE |
|
49 | +2. 多租户APP v2.1.1-RELEASE |
|
50 | + |
|
51 | +更新内容 |
|
52 | + |
|
53 | +1. 修复主机列表和服务列表页面 |
|
... | ... | \ No newline at end of file |
\345\210\206\345\270\203\345\274\217\351\203\250\347\275\262\346\226\271\346\241\210.md
\345\237\272\347\241\200APP.md
... | ... | @@ -0,0 +1,19 @@ |
1 | +### [[基础APP/多租户(tenant)APP]] |
|
2 | + |
|
3 | +### [[基础APP/RabbitMQ APP]] |
|
4 | + |
|
5 | +### [[基础APP/文件(file)APP]] |
|
6 | + |
|
7 | +### [[基础APP/工作流(workflow)APP]] |
|
8 | + |
|
9 | +### [[配置中心]] |
|
10 | + |
|
11 | +### [[分布式APP-apiadapt]] |
|
12 | + |
|
13 | +### [[基础APP/云边协同(iidp-cloud-edge)]] |
|
14 | + |
|
15 | +### [[基础APP/运维监控(iidp-monitor)]] |
|
16 | + |
|
17 | +### [[多语言APP]] |
|
18 | + |
|
19 | +### [[接口APP]] |
\345\237\272\347\241\200APP/RabbitMQ APP.md
... | ... | @@ -0,0 +1,95 @@ |
1 | +代码 [[sie-snest-rabbit-mq-master.zip|/uploads/RabbitMQ/sie-snest-rabbit-mq-master.zip]] |
|
2 | + |
|
3 | +App [[sie-snest-rabbitmq-v1.0.0.RELEASE.jar|/uploads/RabbitMQ/sie-snest-rabbitmq-v1.0.0.RELEASE.jar]] |
|
4 | + |
|
5 | +# 基础使用 |
|
6 | + |
|
7 | +## 添加配置 |
|
8 | + |
|
9 | +在 `application.properties` 配置文件添加以下配置 |
|
10 | + |
|
11 | +```properties |
|
12 | +rabbitmq.host=127.0.0.1 |
|
13 | +rabbitmq.port=5672 |
|
14 | +rabbitmq.username=admin |
|
15 | +rabbitmq.password=password |
|
16 | +``` |
|
17 | + |
|
18 | +## 添加 rabbitmq 应用 |
|
19 | + |
|
20 | +可以通过应用市场安装 rabbitmq 应用,或下载 rabbitmq app 到 apps 目录 |
|
21 | + |
|
22 | +## 添加应用依赖 |
|
23 | + |
|
24 | +在需要使用 RabbitMQ 的 app 中添加依赖,修改 `app.json` , 在 `dependencies` 添加 `rabbitmq` |
|
25 | + |
|
26 | +```json |
|
27 | +{ |
|
28 | + "name": "rabbitmq-test", |
|
29 | + "displayName": "RabbitMQ测试", |
|
30 | + "dependencies": ["rabbitmq"], |
|
31 | + "events":{ |
|
32 | + "startUp": [ |
|
33 | + "RabbitMqTest::registerConsumer" |
|
34 | + ] |
|
35 | + } |
|
36 | +} |
|
37 | +``` |
|
38 | + |
|
39 | +## 编写测试代码 |
|
40 | + |
|
41 | +```java |
|
42 | +@Model(displayName = "RabbitMQ 测试") |
|
43 | +public class RabbitMqTest extends BaseModel<RabbitMqTest> { |
|
44 | + |
|
45 | + private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMqTest.class); |
|
46 | + private static final String EXCHANGE_NAME = "rabbitmq-test.topic"; |
|
47 | + private static final String QUEUE_NAME = "rabbitmq-test.changeName.queue"; |
|
48 | + private static final String ROUTING_KEY = "rabbitmq-test.changeName"; |
|
49 | + public static final String RABBIT_MQ_MODEL = "RabbitMQ"; |
|
50 | + |
|
51 | + @Property(displayName = "名称") |
|
52 | + private String name; |
|
53 | + |
|
54 | + public void registerConsumer() { |
|
55 | + getMeta().get(RABBIT_MQ_MODEL).call("declareExchange", EXCHANGE_NAME, "topic"); |
|
56 | + getMeta().get(RABBIT_MQ_MODEL).call("declareQueue", QUEUE_NAME); |
|
57 | + getMeta().get(RABBIT_MQ_MODEL).call("queueBind", EXCHANGE_NAME, ROUTING_KEY, QUEUE_NAME); |
|
58 | + getMeta().get(RABBIT_MQ_MODEL).call("registerConsumer", QUEUE_NAME, "RabbitMqTest", "handleChangeNameEvent"); |
|
59 | + } |
|
60 | + |
|
61 | + @MethodService(description = "发送事件") |
|
62 | + public void publishChangeNameEvent() { |
|
63 | + ChangeNameEvent event = new ChangeNameEvent("1", "Mary"); |
|
64 | + getMeta().get(RABBIT_MQ_MODEL).call("send", EXCHANGE_NAME, ROUTING_KEY, JSON.toJSONString(event)); |
|
65 | + } |
|
66 | + |
|
67 | + @MethodService(description = "处理事件") |
|
68 | + public void handleChangeNameEvent(String event) { |
|
69 | + LOGGER.debug("接收到消息:{}", event); |
|
70 | + } |
|
71 | +} |
|
72 | +``` |
|
73 | + |
|
74 | +方法说明 |
|
75 | + |
|
76 | +- registerConsumer:用于作为启动事件,启动的时候声明交换机、声明队列、绑定队列、注册消费者 |
|
77 | +- publishChangeNameEvent:用于发送事件 |
|
78 | +- handleChangeNameEvent:业务中处理事件的逻辑 |
|
79 | + |
|
80 | +## 添加启动事件 |
|
81 | + |
|
82 | +在 `app.json` 中添加启动事件,在应用启动的时候注册消费者。 |
|
83 | + |
|
84 | +```json |
|
85 | +{ |
|
86 | + "name": "rabbitmq-test", |
|
87 | + "displayName": "RabbitMQ测试", |
|
88 | + "dependencies": ["rabbitmq"], |
|
89 | + "events":{ |
|
90 | + "startUp": [ |
|
91 | + "RabbitMqTest::registerConsumer" |
|
92 | + ] |
|
93 | + } |
|
94 | +} |
|
95 | +``` |
|
... | ... | \ No newline at end of file |
\345\237\272\347\241\200APP/\344\272\221\350\276\271\345\215\217\345\220\214\357\274\210iidp-cloud-edge\357\274\211.md
... | ... | @@ -0,0 +1,19 @@ |
1 | +# 云边协同(iidp-cloud-edge)APP |
|
2 | + |
|
3 | +## v1.3.1 |
|
4 | + |
|
5 | +**发版日期** |
|
6 | + |
|
7 | +2024/04/19 |
|
8 | + |
|
9 | +**下载地址** |
|
10 | + |
|
11 | +后端 jar: [iidp-cloud-edge-v1.3.1.jar](http://idp.chinasie.com/download-app/distributed-app/iidp-cloud-edge-v1.3.1.jar) |
|
12 | + |
|
13 | +md5: `e954a7c96fbdd54f3096a98ffad1c8ab` |
|
14 | + |
|
15 | +sha256: `81b0d6342c203f038c1fb80baf48e8e0eba6879543a2b6207198908aff5f96b7` |
|
16 | + |
|
17 | +**更新内容** |
|
18 | + |
|
19 | +1. 增加云端应用市场。支持对边端进行批量安装应用、更新应用 |
|
... | ... | \ No newline at end of file |
\345\237\272\347\241\200APP/\345\237\272\347\241\200APP/\345\244\232\347\247\237\346\210\267(tenant)APP/\345\244\232\347\247\237\346\210\267-\344\275\234\347\224\250\345\237\237.md
... | ... | @@ -0,0 +1,128 @@ |
1 | +## 概述 |
|
2 | +多租户中,作用域配置是权限控制中的特殊数据权限(行权限)。 作用域配置旨在确保在多租户环境中,不同用户只能访问和操作其所拥有或被授权访问的特定数据行,而无法越权访问其他租户的数据。我们如果需要实现该业务场景功能,我们可以着重考虑使用作用域配置,这是全局数据权限控制。 |
|
3 | + |
|
4 | +作用域配置的使用场景很广泛,涵盖了许多关键方面: |
|
5 | +数据隔离和隐私保护:作用域控制确保不同租户之间的数据相互隔离,防止租户之间获取、修改或删除彼此数据的能力。这保证了保护敏感数据、以及保护客户数据,避免数据泄露; |
|
6 | + |
|
7 | +简化数据管理:作用域控制使得数据管理更加集中和容易,因为每个租户的数据都可以独立管理,而不会干扰其他租户的数据; |
|
8 | + |
|
9 | +定制化访问控制:作用域控制,可以通过配置增加作用作用域,允许管理员或系统角色配置关联作用域来达到控制访问级别。这使得可以根据需要实施精确的访问权限,以便每个用户只能看到其需要的数据行。 |
|
10 | + |
|
11 | +数据权限中模型行权限控制,控制的是单一模型的行数据过滤; |
|
12 | +需要一个功能能做全局配置所有模型行数据过滤,因此提出需求,作用域; |
|
13 | + |
|
14 | +## 作用域使用场景 |
|
15 | + |
|
16 | +1、多租户里面的,以公司作为租户,进行全量数据模型的数据隔离; |
|
17 | + |
|
18 | +2、工业常用场景中的,物料管理功能,一个公司,有多个工厂,每个工厂的物料数据是对工厂内员工进行数据隔离的; |
|
19 | + |
|
20 | +3、一个集团,分为广州分公司,佛山分公司,拥有集团领导能看两家分公司数据,广州分公司领导只能看自己管辖范围内广州分公司数据,佛山分公司领导也只能只能看自己管辖范围内佛山分公司数据,因此,则需要通过配置; |
|
21 | + |
|
22 | +4、对元模型(APP、菜单、模型),进行数据隔离,实现对应用功能的隔离; |
|
23 | + |
|
24 | +## 作用域使用前提 |
|
25 | + |
|
26 | +勾选影响的多个模型必须存在一个字段(名称和类型必须相同);(业务场景中如果考虑需要做数据权限隔离,则需要考虑统一在多个模型表中添加一个公共属性) |
|
27 | + |
|
28 | +举例 |
|
29 | + |
|
30 | +勾选影响的模型(用户模型(表)、组织模型(表)、角色模型(表))里面都有一个公共字段租户id(名称tenantId/类型String) |
|
31 | + |
|
32 | + |
|
33 | +## 作用域页面介绍 |
|
34 | + |
|
35 | +[[/uploads/Home/actionScope-recommend.png]] |
|
36 | + |
|
37 | +如上图所示,作用域配置功能一共包含四个模块 |
|
38 | + |
|
39 | +1、作用域树模块;(作用域树状结构展示) |
|
40 | + |
|
41 | +2、作用域详情模块;(作用域基本信息维护) |
|
42 | + |
|
43 | +3、规则影响模型勾选模块;(勾选了多少个模型,代表右侧,作用域规则影响多个模型) |
|
44 | + |
|
45 | +4、作用域规则配置模块;(核心模块,作用域规则维护) |
|
46 | + |
|
47 | +租户作用域节点不支持编辑、删除、配置作用域规则;(**由于租户作用域是用做默认租户隔离,租户APP已经添加了默认作用域规则,编辑、删除,配置作用域规则都会影响到租户内用户使用**) |
|
48 | + |
|
49 | +## 引擎内置变量 |
|
50 | + |
|
51 | +内置标量 内置标量值 |
|
52 | + |
|
53 | + 当前用户(内置变量) ${meta.userId} |
|
54 | + |
|
55 | + 当前用户名称(内置变量) ${meta.userName} |
|
56 | + |
|
57 | + 当前作用域ID(内置变量) ${meta.curSg} |
|
58 | + |
|
59 | + 当前所属部门ID(内置变量) ${meta.user.orgId} |
|
60 | + |
|
61 | + 当前所属部门编码(内置变量) ${meta.user.orgCode} |
|
62 | + |
|
63 | + 当前登陆人所属租户ID(内置变量) ${meta.user.tenantId} |
|
64 | + |
|
65 | + 当前登陆人用户类型(内置变量) ${meta.user.userType} |
|
66 | + |
|
67 | + |
|
68 | +## 场景配置解析 |
|
69 | + |
|
70 | +[[/uploads/Home/actionScope-20231218133704.png]] |
|
71 | + |
|
72 | +### 场景1:如何配置一个角色实现张三李四王五数据权限控制,只能看到所在组织以及下级组织的的数据(自定义) |
|
73 | + |
|
74 | +数据权限-行权限:组织code like %当前登录人组织code(内置变量)% |
|
75 | + |
|
76 | +作用域: |
|
77 | +组织作用域 |
|
78 | +产品线模型 组织id = 组织模型/id |
|
79 | + |
|
80 | +[[/uploads/Home/actionScope-20231218133705.png]] |
|
81 | + |
|
82 | +[[/uploads/Home/actionScope-20231218133706.png]] |
|
83 | + |
|
84 | +**张三用户** |
|
85 | + |
|
86 | +[[/uploads/Home/actionScope-20231218133707.png]] |
|
87 | + |
|
88 | +李四用户 |
|
89 | +[[/uploads/Home/actionScope-20231218133708.png]] |
|
90 | + |
|
91 | +### 场景2:如何配置张三只具备公司1的权限 |
|
92 | + |
|
93 | +张三用户角色 |
|
94 | + |
|
95 | + |
|
96 | +产品线模型 组织id = 公司1组织id |
|
97 | + |
|
98 | + |
|
99 | +如果公司1下有多个组织则,使用或配置规则 |
|
100 | + |
|
101 | +产品线模型 |
|
102 | +组织id = 公司1组织id |
|
103 | +或 |
|
104 | +组织id = 部门1组织id |
|
105 | + |
|
106 | + |
|
107 | +[[/uploads/Home/actionScope-20231218133709.png]] |
|
108 | + |
|
109 | +### 场景3:如何配置张三同时具有公司2以及下级部门的权限、具有部门1的权限 |
|
110 | + |
|
111 | +张三角色 |
|
112 | + |
|
113 | +产品线模型 |
|
114 | + |
|
115 | +组织id = 公司2组织id |
|
116 | + |
|
117 | +或 |
|
118 | + |
|
119 | +组织id = 部门22组织id |
|
120 | + |
|
121 | +或 |
|
122 | + |
|
123 | +组织id = 部门1组织id |
|
124 | + |
|
125 | + |
|
126 | +[[/uploads/Home/actionScope-20231218133710.png]] |
|
127 | + |
|
128 | + |
\345\237\272\347\241\200APP/\345\244\232\347\247\237\346\210\267(tenant)APP.md
... | ... | @@ -0,0 +1,9 @@ |
1 | +## 多租户操作手册 |
|
2 | +[[多租户操作手册V1.2.docx|/uploads/tenant-user-manual/多租户操作手册V1.2.docx]] |
|
3 | + |
|
4 | +## IIDP引擎多租户&流程引擎升级指引 |
|
5 | +[IIDP引擎多租户&流程引擎升级指引](https://doc.weixin.qq.com/doc/w3_AbUAigYwAIsajpZU0OrRt0werjJHZ?scode=AAIAKAcJAAcV11eNAjAbUAigYwAIs&version=4.1.13.6002&platform=win) |
|
6 | + |
|
7 | +## 多租户-作用域 |
|
8 | + |
|
9 | +### [[基础APP/多租户(tenant)APP/多租户-作用域]] |
|
... | ... | \ No newline at end of file |
\345\237\272\347\241\200APP/\345\267\245\344\275\234\346\265\201(workflow)APP.md
... | ... | @@ -0,0 +1,336 @@ |
1 | +# 流程中心 |
|
2 | + |
|
3 | +### 流程说明 |
|
4 | + |
|
5 | + |
|
6 | + |
|
7 | +## 流程中心 |
|
8 | + |
|
9 | +### <a id="_Toc57301862"></a>功能综述 |
|
10 | + |
|
11 | +查看整体信息,快捷操作,支持跳转我的待办、我的申请、我处理的、草稿箱。 |
|
12 | + |
|
13 | +### <a id="_Toc57301863"></a>系统操作 |
|
14 | + |
|
15 | +#### 卡片栏 |
|
16 | + |
|
17 | +路径:流程中心\->流程中心 |
|
18 | + |
|
19 | +1. 点击我的待办、我的申请、我处理的,可以查看当前登录人的待办、申请、处理信息。 |
|
20 | + |
|
21 | + |
|
22 | + |
|
23 | +1. 选择tab页,点击【更多】按钮,跳转至对应的列表页;点击卡片或点击【查看】按钮,跳转至详情页,同样可以进行处理。 |
|
24 | + |
|
25 | + |
|
26 | + |
|
27 | +#### 常用流程 |
|
28 | + |
|
29 | +路径:流程中心\->流程中心 |
|
30 | + |
|
31 | +1. 添加常用流程:点击【管理】\->点击【添加常用流程】\->勾选流程点击确定。 |
|
32 | + |
|
33 | + |
|
34 | + |
|
35 | + |
|
36 | + |
|
37 | +1. 删除常用流程:点击【管理】\->点击【X】删除常用流程。 |
|
38 | + |
|
39 | + |
|
40 | + |
|
41 | +1. 点击【更多】,跳转至发起流程页面 |
|
42 | + |
|
43 | +#### 草稿箱 |
|
44 | + |
|
45 | +路径:流程中心\->流程中心 |
|
46 | + |
|
47 | +1. 点击【更多】跳转至草稿箱列表,点击卡片跳转至草稿详情页 |
|
48 | + |
|
49 | +## 发起流程 |
|
50 | + |
|
51 | +### 功能综述 |
|
52 | + |
|
53 | +申请人发起流程 |
|
54 | + |
|
55 | +### 系统操作 |
|
56 | + |
|
57 | +#### 保存流程至草稿箱 |
|
58 | + |
|
59 | +路径:流程中心\->发起流程 |
|
60 | + |
|
61 | +1. 点击支持保存的流程图标,填写必要条件,点击【保存】按钮,流程保存至草稿箱 |
|
62 | + |
|
63 | + |
|
64 | + |
|
65 | +#### 提交流程 |
|
66 | + |
|
67 | +路径:流程中心\->发起流程 |
|
68 | + |
|
69 | +1. 点击流程图标,填写必要条件,点击【提交】按钮,流程提交至下一节点 |
|
70 | + |
|
71 | + |
|
72 | + |
|
73 | +## 我的待办 |
|
74 | + |
|
75 | +### 功能综述 |
|
76 | + |
|
77 | +审批人处理流程 |
|
78 | + |
|
79 | +### 系统操作 |
|
80 | + |
|
81 | +#### 同意 |
|
82 | + |
|
83 | +路径:流程中心\->我的待办 |
|
84 | + |
|
85 | +进入我的待办页,点击【审批】按钮,点击【同意】,流程进入下一节点 |
|
86 | + |
|
87 | +#### 驳回 |
|
88 | + |
|
89 | +路径:流程中心\->我的待办 |
|
90 | + |
|
91 | +进入我的待办页,点击【审批】按钮,点击【驳回】,流程进入上一节点 |
|
92 | + |
|
93 | + |
|
94 | + |
|
95 | +#### 转办 |
|
96 | + |
|
97 | +路径:流程中心\->我的待办 |
|
98 | + |
|
99 | +进入我的待办页,点击【审批】按钮,点击【转办】,流程转至他人处理 |
|
100 | + |
|
101 | + |
|
102 | + |
|
103 | +#### 加签 |
|
104 | + |
|
105 | +路径:流程中心\->我的待办 |
|
106 | + |
|
107 | +进入我的待办页,点击【审批】按钮,点击【加签】,流程在添加人处理后处理 |
|
108 | + |
|
109 | + |
|
110 | + |
|
111 | +#### 驳回后再次申请 |
|
112 | + |
|
113 | +路径:流程中心\->我的待办 |
|
114 | + |
|
115 | +进入我的待办页,点击【审批】按钮,点击【加签】,流程在添加人处理后处理 |
|
116 | + |
|
117 | + |
|
118 | + |
|
119 | + |
|
120 | + |
|
121 | +## 我的申请 |
|
122 | + |
|
123 | +### 功能综述 |
|
124 | + |
|
125 | +申请人查看流程与撤回流程 |
|
126 | + |
|
127 | +### 系统操作 |
|
128 | + |
|
129 | +#### 查看审批情况 |
|
130 | + |
|
131 | +路径:流程中心\->我的申请 |
|
132 | + |
|
133 | +入我的申请页,点击【查看】按钮,查看审批情况 |
|
134 | + |
|
135 | + |
|
136 | + |
|
137 | +#### 驳回流程重新提交 |
|
138 | + |
|
139 | +路径:流程中心\->我的申请 |
|
140 | + |
|
141 | +入我的申请页,点击【查看】按钮,点击【同意】按钮 |
|
142 | + |
|
143 | +#### 撤回 |
|
144 | + |
|
145 | +路径:流程中心\->我的申请 |
|
146 | + |
|
147 | +入我的申请页,点击有撤回功能的流程的【查看】按钮,点击【撤回】按钮,流程撤回至起始节点 |
|
148 | + |
|
149 | + |
|
150 | + |
|
151 | + |
|
152 | + |
|
153 | +## 我处理的 |
|
154 | + |
|
155 | +### 功能综述 |
|
156 | + |
|
157 | +记录登录人处理流程的信息,仅供查阅 |
|
158 | + |
|
159 | +### 系统操作 |
|
160 | + |
|
161 | +#### 查看处理记录 |
|
162 | + |
|
163 | +路径:流程中心\->我处理的 |
|
164 | + |
|
165 | +进入我处理的页,点击【查看】按钮,查看处理详情 |
|
166 | + |
|
167 | + |
|
168 | + |
|
169 | +## 草稿箱 |
|
170 | + |
|
171 | +### 功能综述 |
|
172 | + |
|
173 | +暂存流程至草稿箱,可以在草稿箱提交流程 |
|
174 | + |
|
175 | +### 系统操作 |
|
176 | + |
|
177 | +#### 查看处理记录 |
|
178 | + |
|
179 | +路径:流程中心\->草稿箱 |
|
180 | + |
|
181 | +进入草稿箱页面,点击【编辑】按钮,点击【保存】按钮,保存修改信息,点击【提交】按钮,提交流程 |
|
182 | + |
|
183 | + |
|
184 | + |
|
185 | + |
|
186 | + |
|
187 | +## 流程定义 |
|
188 | + |
|
189 | +### 功能综述 |
|
190 | + |
|
191 | +同步配置的流程、定义流程所属分类、定义流程排序、跳转流程配置界面 |
|
192 | + |
|
193 | +### 系统操作 |
|
194 | + |
|
195 | +#### 刷新流程 |
|
196 | + |
|
197 | +路径:流程中心\->流程定义 |
|
198 | + |
|
199 | +进入流程定义页面,点击【刷新流程】按钮,同步配置的流程 |
|
200 | + |
|
201 | + |
|
202 | + |
|
203 | +#### 跳转流程配置 |
|
204 | + |
|
205 | +路径:流程中心\->流程定义 |
|
206 | + |
|
207 | +进入流程定义页面,点击【在线绘制流程】或【流程定义】按钮,跳转至流程配置界面 |
|
208 | + |
|
209 | + |
|
210 | + |
|
211 | +#### 编辑流程 |
|
212 | + |
|
213 | +路径:流程中心\->流程定义 |
|
214 | + |
|
215 | +进入流程定义页面,点击【编辑】按钮,修改分类、排序、视图,点击【保存】按钮 |
|
216 | + |
|
217 | + |
|
218 | + |
|
219 | +## 流程分类 |
|
220 | + |
|
221 | +### 功能综述 |
|
222 | + |
|
223 | +管理流程分类 |
|
224 | + |
|
225 | +### 系统操作 |
|
226 | + |
|
227 | +#### 新增流程分类 |
|
228 | + |
|
229 | +路径:流程中心\->流程分类 |
|
230 | + |
|
231 | +进入流程分类页面,点击【新增】按钮,点击【保存】按钮 |
|
232 | + |
|
233 | + |
|
234 | + |
|
235 | + |
|
236 | + |
|
237 | +#### 删除流程分类 |
|
238 | + |
|
239 | +路径:流程中心\->流程分类 |
|
240 | + |
|
241 | +进入流程分类页面,选中需要删除的列,点击【删除】按钮,二次弹窗点击确认 |
|
242 | + |
|
243 | + |
|
244 | + |
|
245 | +#### 修改流程分类 |
|
246 | + |
|
247 | +路径:流程中心\->流程分类 |
|
248 | + |
|
249 | +进入流程分类页面,点击【编辑】按钮,修改分类内容 |
|
250 | + |
|
251 | + |
|
252 | + |
|
253 | +#### 查看流程分类 |
|
254 | + |
|
255 | +路径:流程中心\->流程分类 |
|
256 | + |
|
257 | +进入流程分类页面,点击【详情】按钮,查看分类内容 |
|
258 | + |
|
259 | + |
|
260 | + |
|
261 | +## 流程配置 |
|
262 | + |
|
263 | +### 功能综述 |
|
264 | + |
|
265 | +配置审批流程 |
|
266 | + |
|
267 | +### 系统操作 |
|
268 | + |
|
269 | +#### 跳转流程配置\(同2\.8\.2\.2\) |
|
270 | + |
|
271 | +#### app\_1下新增包 |
|
272 | + |
|
273 | +双击app\_1目录,拖拽创建包 |
|
274 | + |
|
275 | + |
|
276 | + |
|
277 | +#### 包下新增模块 |
|
278 | + |
|
279 | +双击包目录或包,拖拽创建模块 |
|
280 | + |
|
281 | + |
|
282 | + |
|
283 | +#### 模块下新增服务与属性 |
|
284 | + |
|
285 | +双击模块目录或模块,拖拽创建服务与属性,服务指定流程指向,属性指定流程所包含的字段 |
|
286 | + |
|
287 | + |
|
288 | + |
|
289 | +#### 配置属性 |
|
290 | + |
|
291 | +选择属性,配置字段名称、显示名称、属性类型、长度、最值、是否必填、默认值、若提示 |
|
292 | + |
|
293 | + |
|
294 | + |
|
295 | +#### 服务命名 |
|
296 | + |
|
297 | +选择服务,编写服务名称、服务展示名称 |
|
298 | + |
|
299 | + |
|
300 | + |
|
301 | +#### 新增节点 |
|
302 | + |
|
303 | +双击进入服务,拉取新增开始节点、结束节点、提交节点、审批节点、判断节点、并行节点、子流程 |
|
304 | + |
|
305 | +#### 添加动作,配置权限 |
|
306 | + |
|
307 | +双击使用者,添加动作;单击使用者,配置字段读写权限、人员操作权限 |
|
308 | + |
|
309 | + |
|
310 | + |
|
311 | + |
|
312 | + |
|
313 | +#### 连接节点 |
|
314 | + |
|
315 | +连接节点,指定节点撤回、驳回、前进节点,限定节点同意、不会、撤回功能 |
|
316 | + |
|
317 | + |
|
318 | + |
|
319 | +### 多版本审批流程 |
|
320 | + |
|
321 | +#### 引入支持meta.ServiceVersion的语言包 |
|
322 | +联系系统开发人员拿到语言包后导入webgme服务 |
|
323 | +选中meta-Library-update Library |
|
324 | + |
|
325 | + |
|
326 | + |
|
327 | +选择导入类型为File,选择语言包进行导入。 |
|
328 | + |
|
329 | +导入成功后,在左侧栏会有meta.ServiceVersion类型节点可进行拖拽。 |
|
330 | + |
|
331 | +#### 绘制多版本流程 |
|
332 | +提交流程按照”meta.服务”节点的”版本”属性执行(如该sie_multi_version流程按照版本1.2执行)。 |
|
333 | + |
|
334 | + |
|
335 | +点击版本为1.2的meta.ServiceVersion进入到下一层级绘制流程。 |
|
336 | + |
\345\237\272\347\241\200APP/\346\226\207\344\273\266(file)APP.md
... | ... | @@ -0,0 +1,275 @@ |
1 | +--- |
|
2 | +title: 文件 |
|
3 | + |
|
4 | +--- |
|
5 | + |
|
6 | +# 文件(files) |
|
7 | + |
|
8 | +## 版本 |
|
9 | +sie-snest-file-v2.0.0.RELEASE.jar |
|
10 | + |
|
11 | +## 能力介绍 |
|
12 | +文件上传功能、excel导入导出功能等 |
|
13 | + |
|
14 | +## 使用教程 |
|
15 | + |
|
16 | +### 1、添加配置 |
|
17 | + |
|
18 | +在 `application.properties` 配置文件添加以下配置 |
|
19 | + |
|
20 | +```properties |
|
21 | +#minio |
|
22 | +minio.endpoint=http://192.168.175.54:9000 |
|
23 | +minio.accessKey=snest |
|
24 | +minio.secretKey=12345678 |
|
25 | +minio.bucketName=apps |
|
26 | +``` |
|
27 | + |
|
28 | +### 2、添加file APP |
|
29 | + |
|
30 | + |
|
31 | +在项目路径的apps目录下添加sie-snest-file-v2.0.0.RELEASE.jar项目包 |
|
32 | + |
|
33 | +## 基础接口介绍 |
|
34 | + |
|
35 | +引擎提供了文件上传/file/upload接口。 |
|
36 | + |
|
37 | +文件上传接口 |
|
38 | + |
|
39 | +| 请求方式 | POST | |
|
40 | +| --------- | ------------ | |
|
41 | +| 请求地址 | /file/upload | |
|
42 | +| 请求参数1 | file | |
|
43 | +| 请求参数2 | userId | |
|
44 | + |
|
45 | +文件预览接口 |
|
46 | + |
|
47 | +| 请求方式 | GET | |
|
48 | +| -------- | ------------- | |
|
49 | +| 请求地址 | /file/preview | |
|
50 | +| 请求参数 | id | |
|
51 | + |
|
52 | +文件下载接口 |
|
53 | + |
|
54 | +| 请求方式 | POST/GET | |
|
55 | +| --------- | -------------- | |
|
56 | +| 请求地址 | /file/download | |
|
57 | +| 请求参数1 | id | |
|
58 | +| 请求头 | Authorization | |
|
59 | + |
|
60 | +文件上传自定义 |
|
61 | + |
|
62 | +| 请求方式 | POST | |
|
63 | +| --------- | -------------------- | |
|
64 | +| 请求地址 | /file/uploadForModel | |
|
65 | +| 请求参数1 | model_id | |
|
66 | +| 请求参数2 | user_id | |
|
67 | + |
|
68 | + |
|
69 | + |
|
70 | +### 导入导出(excel) |
|
71 | + |
|
72 | +#### 1.基础用法模型导入导出 |
|
73 | + |
|
74 | +rbac_role继承base_excel模型,拥有了export,Import两个服务。所以只需要配置视图就可以直接访问导入导出功能。如果默认服务不支持你的需求,当然我们也支持自定义导入导出服务,自己编写服务,详情请看高阶用法。 |
|
75 | + |
|
76 | +```json |
|
77 | +@SDK.Model(name = "rbac_role", type = Buss, displayName = "角色", parent = "base_excel") |
|
78 | +public class Role extends Model { |
|
79 | + |
|
80 | +} |
|
81 | +``` |
|
82 | + |
|
83 | +##### 1.1 tbar视图配置导出(export) |
|
84 | + |
|
85 | +```json |
|
86 | + { |
|
87 | + "name": "导出", |
|
88 | + "action": "export", |
|
89 | + "properties": ["name","code","is_admin","remark"], |
|
90 | + "model": "rbac_role", |
|
91 | + "service": "export" |
|
92 | + } |
|
93 | +``` |
|
94 | + |
|
95 | +##### 1.2 tbar视图配置导入(Import) |
|
96 | + |
|
97 | +```json |
|
98 | +{ |
|
99 | + "name":"导入", |
|
100 | + "action": "import", |
|
101 | + "model": "rbac_role", |
|
102 | + "service": "Import", |
|
103 | + "fileLimit": { |
|
104 | + "ext": ".xls,.xlsx", |
|
105 | + "maxSize": "2048" |
|
106 | + } |
|
107 | +} |
|
108 | +``` |
|
109 | + |
|
110 | +#### 2.高阶用法服务自定义 |
|
111 | + |
|
112 | +自定义export,Import服务。需要自己定制化导入导出可以重写这两个服务,或者可以自己命名服务名。 |
|
113 | + |
|
114 | +```java |
|
115 | + @MethodService(description = "Excel导出") |
|
116 | + public void export(RecordSet rs, Filter filter, List<String> properties, Integer limit, Integer offset, String order) { |
|
117 | + Meta meta = rs.getMeta(); |
|
118 | + |
|
119 | + Map<String, List<Map<String, Object>>> datas = new LinkedHashMap<>(); |
|
120 | + |
|
121 | + RecordSet rs1 = meta.get(meta.getModelName()); |
|
122 | + if (Objects.isNull(rs1)) { |
|
123 | + throw new ExceException(String.format("文件导出异常,RecordSet %s 空", meta.getModelName())); |
|
124 | + } |
|
125 | + |
|
126 | + List<Map<String, Object>> values = rs1.search(filter, properties, limit, offset, order); |
|
127 | + |
|
128 | + ModelMeta modelMeta = rs1.getModel(); |
|
129 | + if (Objects.isNull(modelMeta)) { |
|
130 | + throw new ExceException(String.format("文件导出异常,modelMeta %s 空", meta.getModelName())); |
|
131 | + } |
|
132 | + |
|
133 | + List<Map<String, Object>> newValues = new ArrayList<>(); |
|
134 | + for (Map<String, Object> value : values) { |
|
135 | + Map<String, Object> newV = new LinkedHashMap<>(); |
|
136 | + for (String p : properties) { |
|
137 | + PropertyMeta propertyMeta = modelMeta.getProperty(p); |
|
138 | + if (!Objects.isNull(propertyMeta)) { |
|
139 | + Object v = value.getOrDefault(p, ""); |
|
140 | + newV.put(propertyMeta.getDisplayName(), v); |
|
141 | + } |
|
142 | + } |
|
143 | + newValues.add(newV); |
|
144 | + } |
|
145 | + |
|
146 | + |
|
147 | + String modelName = StringUtils.isNotBlank(modelMeta.getDisplayName()) ? modelMeta.getDisplayName() : modelMeta.getName(); |
|
148 | + datas.put(modelName, newValues); |
|
149 | + |
|
150 | + |
|
151 | + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); |
|
152 | + String fileName = modelName + "_" + dateFormat.format(new Date()); |
|
153 | + String suffix = ".xlsx"; |
|
154 | + |
|
155 | + rs.getMeta().get("base_excel").call("fileExport", datas, fileName + suffix); |
|
156 | + |
|
157 | + } |
|
158 | + |
|
159 | + |
|
160 | + @MethodService(description = "Excel导入") |
|
161 | + public boolean Import(RecordSet rs, String fileId) { |
|
162 | + Meta meta = rs.getMeta(); |
|
163 | + |
|
164 | + RecordSet rs1 = meta.get(meta.getModelName()); |
|
165 | + if (Objects.isNull(rs1)) { |
|
166 | + throw new ExceException(String.format("文件导入异常,RecordSet %s 空", meta.getModelName())); |
|
167 | + } |
|
168 | + |
|
169 | + ModelMeta modelMeta = rs1.getModel(); |
|
170 | + if (Objects.isNull(modelMeta)) { |
|
171 | + throw new ExceException(String.format("文件导入异常,modelMeta %s 空", meta.getModelName())); |
|
172 | + } |
|
173 | + |
|
174 | + String modelName = StringUtils.isNotBlank(modelMeta.getDisplayName()) ? modelMeta.getDisplayName() : modelMeta.getName(); |
|
175 | + |
|
176 | + Map<String, List<Map<String, Object>>> fileMap = (Map<String, List<Map<String, Object>>>) rs.getMeta().get( |
|
177 | + "base_excel").call("fileImport", fileId); |
|
178 | + |
|
179 | + List<Map<String, Object>> sheet1 = fileMap.get(modelName); |
|
180 | + for (Map<String, Object> s : sheet1) { |
|
181 | + |
|
182 | + Map<String, Object> mV = new LinkedHashMap<>(); |
|
183 | + for (Map.Entry<String, Object> v : s.entrySet()) { |
|
184 | + PropertyMeta propertyMeta = modelMeta.getProperties().stream().filter(p -> p.getDisplayName().equals(v.getKey())).findFirst().orElse(null); |
|
185 | + if (!Objects.isNull(propertyMeta)) { |
|
186 | + mV.put(propertyMeta.getName(), v.getValue()); |
|
187 | + } |
|
188 | + } |
|
189 | + |
|
190 | + rs.create(mV); |
|
191 | + } |
|
192 | + return true; |
|
193 | + } |
|
194 | +``` |
|
195 | + |
|
196 | +自定义excelExport视图文件 |
|
197 | + |
|
198 | +```json |
|
199 | + { |
|
200 | + "name": "导出", |
|
201 | + "action": "export", |
|
202 | + "properties": ["name","code","is_admin","remark"], |
|
203 | + "model": "rbac_role", |
|
204 | + "service": "export" |
|
205 | + } |
|
206 | +``` |
|
207 | + |
|
208 | +自定义excelImport视图文件 |
|
209 | + |
|
210 | +```json |
|
211 | +{ |
|
212 | + "name":"导入", |
|
213 | + "action": "import", |
|
214 | + "model": "rbac_role", |
|
215 | + "service": "Import", |
|
216 | + "fileLimit": { |
|
217 | + "ext": ".xls,.xlsx", |
|
218 | + "maxSize": "2048" |
|
219 | + } |
|
220 | +} |
|
221 | +``` |
|
222 | + |
|
223 | +#### 3. 名词解释 |
|
224 | + |
|
225 | + | 属性 | 定义 | |
|
226 | + | ---------- | ---------------------------------------------------------- | |
|
227 | + | action | 事件用于前端支持按钮类型,import导入按钮,export导出按钮。 | |
|
228 | + | service | 服务名,就是MethodService标注的方法。 | |
|
229 | + | name | 中文名称 | |
|
230 | + | fileLimit | 文件上传限制,文件大小,格式类型。 | |
|
231 | + | model | 模型名 | |
|
232 | + | properties | 指定需要导出的属性名 | |
|
233 | + |
|
234 | +## 文件(file) |
|
235 | + |
|
236 | +#### 文件后端上传 |
|
237 | +```java |
|
238 | + FileInputStream fileInputStream; |
|
239 | + try { |
|
240 | + fileInputStream = new FileInputStream(new File("D:\\gitlab\\sie-snest-dev\\sie-snest-apps\\snest-apps\\mom\\target\\mom-1.0-SNAPSHOT.jar")); |
|
241 | + Map<String, Object> upload = (Map<String, Object>) recordSet.getMeta().get("meta_attachment").call("putObject", fileInputStream,"mom-1.0-SNAPSHOT.jar","apps"); |
|
242 | + System.out.println(upload); |
|
243 | + } catch (FileNotFoundException e) { |
|
244 | + // TODO Auto-generated catch block |
|
245 | + e.printStackTrace(); |
|
246 | + } |
|
247 | +``` |
|
248 | + |
|
249 | + |
|
250 | +#### 文件后端上传备注 |
|
251 | +若上传的文件流来自于URL,那么需要读取并转成ByteArrayInputStream |
|
252 | +```java |
|
253 | +URLConnection connection = new URL(url).openConnection(); |
|
254 | +InputStream inputStream = connection.getInputStream(); |
|
255 | +byte[] bytes = streamToByteArray(inputStream); |
|
256 | +ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); |
|
257 | +Map<String, Object> upload = (Map<String, Object>) myMeta.get("meta_attachment").call("putObject", byteArrayInputStream, originalFilename, "apps"); |
|
258 | +``` |
|
259 | +```java |
|
260 | +public static byte[] streamToByteArray(InputStream in) throws IOException { |
|
261 | + ByteArrayOutputStream output = new ByteArrayOutputStream(); |
|
262 | + byte[] buffer = new byte[4096]; |
|
263 | + int n; |
|
264 | + while (-1 != (n = in.read(buffer))) { |
|
265 | + output.write(buffer, 0, n); |
|
266 | + } |
|
267 | + return output.toByteArray(); |
|
268 | +} |
|
269 | +``` |
|
270 | + |
|
271 | +#### 获取文件流 |
|
272 | +```java |
|
273 | +String fileId = "meta_attacment 的 id"; |
|
274 | +InputStream is = rs.get("base_file").call("getInputStream", fileId); |
|
275 | +``` |
\345\237\272\347\241\200APP/\350\277\220\347\273\264\347\233\221\346\216\247\357\274\210iidp-monitor\357\274\211.md
... | ... | @@ -0,0 +1,19 @@ |
1 | +# 运维监控(iidp-monitor) APP |
|
2 | + |
|
3 | +## v1.3.1 |
|
4 | + |
|
5 | +**发版日期** |
|
6 | + |
|
7 | +2024/04/19 |
|
8 | + |
|
9 | +**下载地址** |
|
10 | + |
|
11 | +后端 jar: [iidp-monitor-v1.3.1.jar](http://idp.chinasie.com/download-app/distributed-app/iidp-monitor-v1.3.1.jar) |
|
12 | + |
|
13 | +md5: `f089867aaafe07fc6d5ea8647a852105` |
|
14 | + |
|
15 | +sha256: `13d91846956ef4c1c241c52da7a9430f1348bf1417d5af10457cc28f129a24c5` |
|
16 | + |
|
17 | +**更新内容** |
|
18 | + |
|
19 | +1. 提供运维监控功能 |
|
... | ... | \ No newline at end of file |
\345\244\232\347\247\237\346\210\2672.0\345\215\207\347\272\247\351\203\250\347\275\262\346\226\207\346\241\243.md
... | ... | @@ -0,0 +1,197 @@ |
1 | +# Kubernetes相关:配置RBAC |
|
2 | + RBAC全称叫:Role-based access control,即权限相关的配置。 |
|
3 | + |
|
4 | +Hazelcast官网已经为我们准备了一个在线yaml,ServiceAccount=defult,namespace同样为default,如果需要自定义,那么下载rbac.yaml文件自行改之: |
|
5 | +``` |
|
6 | +kubectl apply -f https://raw.githubusercontent.com/hazelcast/hazelcast-kubernetes/master/rbac.yaml |
|
7 | +``` |
|
8 | +```yaml |
|
9 | +apiVersion: rbac.authorization.k8s.io/v1 |
|
10 | +kind: ClusterRole |
|
11 | +metadata: |
|
12 | + name: hazelcast-cluster-role |
|
13 | +rules: |
|
14 | + - apiGroups: |
|
15 | + - "" |
|
16 | + # Access to apps API is only required to support automatic cluster state management |
|
17 | + # when persistence (hot-restart) is enabled. |
|
18 | + - apps |
|
19 | + resources: |
|
20 | + - endpoints |
|
21 | + - pods |
|
22 | + - nodes |
|
23 | + - services |
|
24 | + # Access to statefulsets resource is only required to support automatic cluster state management |
|
25 | + # when persistence (hot-restart) is enabled. |
|
26 | + - statefulsets |
|
27 | + verbs: |
|
28 | + - get |
|
29 | + - list |
|
30 | + # Watching resources is only required to support automatic cluster state management |
|
31 | + # when persistence (hot-restart) is enabled. |
|
32 | + - watch |
|
33 | + - apiGroups: |
|
34 | + - "discovery.k8s.io" |
|
35 | + resources: |
|
36 | + - endpointslices |
|
37 | + verbs: |
|
38 | + - get |
|
39 | + - list |
|
40 | + |
|
41 | +--- |
|
42 | + |
|
43 | +apiVersion: rbac.authorization.k8s.io/v1 |
|
44 | +kind: ClusterRoleBinding |
|
45 | +metadata: |
|
46 | + name: hazelcast-cluster-role-binding |
|
47 | +roleRef: |
|
48 | + apiGroup: rbac.authorization.k8s.io |
|
49 | + kind: ClusterRole |
|
50 | + name: hazelcast-cluster-role |
|
51 | +subjects: |
|
52 | + - kind: ServiceAccount |
|
53 | + name: default |
|
54 | + namespace: mijiuye # 命名空间需要按照实际情况调整 |
|
55 | +``` |
|
56 | + |
|
57 | + |
|
58 | + |
|
59 | +# app.yml配置文件 |
|
60 | + |
|
61 | +```yaml |
|
62 | +kind: Deployment |
|
63 | +apiVersion: apps/v1 |
|
64 | +metadata: |
|
65 | + name: snest-v1 |
|
66 | + namespace: $PROJECT_NAMESPACE |
|
67 | + labels: |
|
68 | + app: snest |
|
69 | + app.kubernetes.io/name: snest |
|
70 | + app.kubernetes.io/version: v1 |
|
71 | + version: v1 |
|
72 | + annotations: |
|
73 | + deployment.kubernetes.io/revision: '3' |
|
74 | + kubesphere.io/creator: admin |
|
75 | + servicemesh.kubesphere.io/enabled: 'true' |
|
76 | +spec: |
|
77 | + replicas: 1 |
|
78 | + selector: |
|
79 | + matchLabels: |
|
80 | + app: snest |
|
81 | + app.kubernetes.io/name: snest |
|
82 | + app.kubernetes.io/version: v1 |
|
83 | + version: v1 |
|
84 | + template: |
|
85 | + metadata: |
|
86 | + creationTimestamp: null |
|
87 | + labels: |
|
88 | + app: snest |
|
89 | + app.kubernetes.io/name: snest |
|
90 | + app.kubernetes.io/version: v1 |
|
91 | + version: v1 |
|
92 | + annotations: |
|
93 | + logging.kubesphere.io/logsidecar-config: '{}' |
|
94 | + sidecar.istio.io/inject: 'true' |
|
95 | + spec: |
|
96 | + volumes: |
|
97 | + - name: host-time |
|
98 | + hostPath: |
|
99 | + path: /etc/localtime |
|
100 | + type: '' |
|
101 | + - name: volume-18unfh |
|
102 | + configMap: |
|
103 | + name: snest |
|
104 | + defaultMode: 420 |
|
105 | + - name: volume-v4qaik |
|
106 | + persistentVolumeClaim: |
|
107 | + claimName: apps-frontend |
|
108 | + - name: volume-ajqw8u |
|
109 | + persistentVolumeClaim: |
|
110 | + claimName: apps |
|
111 | + containers: |
|
112 | + - name: sie-snest-server |
|
113 | + image: $REGISTRY/$DOCKERHUB_NAMESPACE/$APP_NAME:$TAG_NAME_ENV |
|
114 | + ports: |
|
115 | + - name: http-8060 |
|
116 | + containerPort: 8060 |
|
117 | + protocol: TCP |
|
118 | + resources: {} |
|
119 | + volumeMounts: |
|
120 | + - name: host-time |
|
121 | + readOnly: true |
|
122 | + mountPath: /etc/localtime |
|
123 | + - name: volume-18unfh |
|
124 | + readOnly: true |
|
125 | + mountPath: /config |
|
126 | + - name: volume-v4qaik |
|
127 | + mountPath: /apps-frontend |
|
128 | + - name: volume-ajqw8u |
|
129 | + mountPath: /apps |
|
130 | + lifecycle: |
|
131 | + postStart: |
|
132 | + exec: |
|
133 | + command: |
|
134 | + - /bin/sh |
|
135 | + - '-c' |
|
136 | + - >- |
|
137 | + cp -rf /apps_temp/* /apps |
|
138 | + terminationMessagePath: /dev/termination-log |
|
139 | + terminationMessagePolicy: File |
|
140 | + imagePullPolicy: IfNotPresent |
|
141 | + restartPolicy: Always |
|
142 | + terminationGracePeriodSeconds: 30 |
|
143 | + dnsPolicy: ClusterFirst |
|
144 | + serviceAccountName: default |
|
145 | + serviceAccount: default |
|
146 | + securityContext: {} |
|
147 | + imagePullSecrets: |
|
148 | + - name: harbor |
|
149 | + schedulerName: default-scheduler |
|
150 | + strategy: |
|
151 | + type: RollingUpdate |
|
152 | + rollingUpdate: |
|
153 | + maxUnavailable: 25% |
|
154 | + maxSurge: 25% |
|
155 | + revisionHistoryLimit: 10 |
|
156 | + progressDeadlineSeconds: 600 |
|
157 | +``` |
|
158 | + |
|
159 | + |
|
160 | +# IIOT 配置文件 |
|
161 | + |
|
162 | +### 部署方式 |
|
163 | +- 单机:stand-alone; |
|
164 | +- k8s分布式:distributed; |
|
165 | +- docker分布式:docker-distributed deployment.mode=distributed |
|
166 | + |
|
167 | + |
|
168 | + |
|
169 | +# Auth 配置文件 |
|
170 | +```properties |
|
171 | +appAuth.Server=http://192.168.168.176:8080 |
|
172 | +``` |
|
173 | + |
|
174 | + |
|
175 | +# hazelcast 配置文件 |
|
176 | + |
|
177 | +```properties |
|
178 | +hazelcast.cluster-name = hazelcast-cluster |
|
179 | +hazelcast.network.join.auto-detection.enabled = true |
|
180 | +hazelcast.network.join.multicast.enabled = false |
|
181 | +hazelcast.network.join.kubernetes.enabled = true |
|
182 | +hazelcast.network.join.kubernetes.namespace = default # 实际部署时,以实际的命名空间为准。比如 iidp命名空间,则需要改为iidp |
|
183 | +hazelcast.map.hazelcast-map.backup-count = 1 |
|
184 | +``` |
|
185 | +# Engine运行模式配置文件 |
|
186 | +#profile |
|
187 | +```properties |
|
188 | +engine.run.mode=DISTRIBUTED |
|
189 | +engine.store.meta.mode=CLOUD |
|
190 | +``` |
|
191 | +# SG 策略配置文件 |
|
192 | + |
|
193 | +```properties |
|
194 | +url.whiteList=base.rbac_login_model.*,base.rbac_user.*,*.rbac_user.*,*.ui_menu.*,*.meta_app.*,*.meta_product_line.*,*.meta_app_category.*,*.meta_app_dependency.*,*.meta_app_store.*,*.meta_attachment.*,*.ui_view_seed.*,*.meta_app_store_dependency.*,iiot_thing.*,iiot_importexport.*,*.iiot_thing_entity.*,*iiot_thing_overview_model.*,*iiot_thing_model.*,*.iiot_thing_property.* |
|
195 | +getModel.whiteList=*.rbac_login_log,*.meta_tree_data,*.rbac_user,*.ui_menu,*.meta_app,*.meta_product_line,*.meta_app_category,*.meta_app_dependency,*.meta_app_store,*.meta_attachment,*.ui_view_seed,*.meta_app_store_dependency,iiot_importexport.*,iiot_thing.*,,*iiot_thing_model.*,*.iiot_thing_property.* |
|
196 | +sg.whiteList=iiot_alarm.*,iiot_thing.* |
|
197 | +``` |
\345\244\232\350\257\255\350\250\200APP.md
... | ... | @@ -0,0 +1,538 @@ |
1 | +# **国际化多语言APP** |
|
2 | + |
|
3 | +# 基本信息 |
|
4 | +## 文档信息 |
|
5 | +| 文档名称 | 多租户APP_权限通配符 | | | |
|
6 | +| --- | --- | --- | --- | |
|
7 | +| 文档编号 | V2.0 | 文档版本日期 | 2024-01-30 | |
|
8 | +| APP名称 | 多租户APP | | | |
|
9 | +| 权限通配符 | | | | |
|
10 | +| 起草人 | 古宏 黄小杰 范明哲 | 起草日期 | 2022-01-30 | |
|
11 | +| 复审人 | | 复审日期 | | |
|
12 | + |
|
13 | +## 版本历史 |
|
14 | +| 版本 | 日期 | 作者 | 更改参考 | 说明 | |
|
15 | +| --- | --- | --- | --- | --- | |
|
16 | +| V1.0 | 2024-01-30 | 古宏 黄小杰 范明哲 | | 初稿 | |
|
17 | +| | | | | | |
|
18 | + |
|
19 | +## 迭代分支 |
|
20 | +| 名称 | 内容 | |
|
21 | +| --- | --- | |
|
22 | +| 分支 | feat_xxx | |
|
23 | +| 前端原始分支 | | |
|
24 | +| 后端原始分支 | | |
|
25 | + |
|
26 | +## 审批信息 |
|
27 | +| 签字/日期 | | |
|
28 | +| --- | --- | |
|
29 | +| 审核 | | |
|
30 | +| 审批 | | |
|
31 | +| | | |
|
32 | + |
|
33 | +# 目标和范围 |
|
34 | +## 目标 |
|
35 | + |
|
36 | +1. 安装国际化APP后,页面和提示语支持设置多语言 |
|
37 | + |
|
38 | +2. 用户可以页面、提示语内容设置各个语种的译文。 |
|
39 | + |
|
40 | +3. 国际化APP预置简体中文、英语两种语言。 |
|
41 | + |
|
42 | +4. 用户可以设置当前账号的默认语言,平台界面和内容会根据用户选择的语言进行相应的译文映射。 |
|
43 | + |
|
44 | + |
|
45 | +## 范围 |
|
46 | +### 研发范围 |
|
47 | + |
|
48 | +1. UI多语言(按钮、表头、搜索字段等) |
|
49 | +2. 提示语多语言 |
|
50 | +3. 菜单多语言 |
|
51 | +4. 导入导出多语言包 |
|
52 | +5. 日志多语言 |
|
53 | + |
|
54 | +### 影响范围 |
|
55 | +_描述本次优化迭代研发内容范围_ |
|
56 | +#### 前端影响范围 |
|
57 | + |
|
58 | +- xxx组件xxx属性修改 |
|
59 | +- xxx功能 |
|
60 | +#### 后端影响范围 |
|
61 | + |
|
62 | +- 作用域 |
|
63 | +- 用户登录 |
|
64 | +- 依赖库 |
|
65 | +# 参考引用与术语 |
|
66 | + |
|
67 | +## 参考引用 |
|
68 | + |
|
69 | +_列出引用或是参考的文档或是文献。_ |
|
70 | + |
|
71 | +## 相关术语 |
|
72 | + |
|
73 | +_文件中可能会引起混淆的专门术语的定义和缩写词的原文。_ |
|
74 | + |
|
75 | +_例如:_ |
|
76 | + |
|
77 | +| 术语 | 解释 | |
|
78 | +| --- | --- | |
|
79 | +| 多租户 | | |
|
80 | +| 功能权限 | | |
|
81 | +| 数据权限 | | |
|
82 | +| API权限 | | |
|
83 | +| 作用域 | | |
|
84 | +| 设置器 | | |
|
85 | +| 区块组件 | | |
|
86 | +| 大纲 | | |
|
87 | +| 模型 | | |
|
88 | +| 节点树 | | |
|
89 | + |
|
90 | + |
|
91 | +# 总体设计介绍 |
|
92 | +## 过程产物 |
|
93 | +| _过程产物类型_ | _说明_ | |
|
94 | +| --- | --- | |
|
95 | +| 产品需求原型设计(可选) | 根据功能点描述清楚页面结构的设计,可以是产品的设计原型链接。 | |
|
96 | +| 交互设计(可选) | |
|
97 | +- 页面跳转方式:需包含起始点,终结点,例如触发时机/触发节点/触发元素 |
|
98 | + | |
|
99 | +| UI设计稿(可选) | |
|
100 | +- 规定人机界面的内容、界面风格、交互方式等 |
|
101 | +- 样式至少包含状态:常态,选中,不可用,异常,悬停 |
|
102 | + | |
|
103 | +| _后端APP包_ | | |
|
104 | +| 前端APP包 | | |
|
105 | + |
|
106 | +## 性能指标(可选) |
|
107 | +### 操作 |
|
108 | +| 操作 | 响应时间 | |
|
109 | +| --- | --- | |
|
110 | +| 打开一个网站 | xx秒 | |
|
111 | +| 数据库查询一条记录 (有索引) | xx毫秒 | |
|
112 | +| 从内存读取1M数据 | xx毫秒 | |
|
113 | +| Java程序本地方法调用 | xx毫秒 | |
|
114 | +| First paint | | |
|
115 | +| First Contentful Paint | | |
|
116 | +| Time to Interactive | | |
|
117 | +| Largest Contentful Paint | | |
|
118 | +| First Meaning Paint | | |
|
119 | + |
|
120 | +### 吞吐量 |
|
121 | +指单位时间内系统处理的请求数量,体现系统的整体处理能力。对于网站,可用“请求数/秒”、“页面数/秒”或“访问人数/天”、“处理业务数/小时”等来衡量。重要指标有TPS(每秒处理的事物数)、QPS(每秒查询的请求数)、HPS (每秒HTTP请求数)等 |
|
122 | +### 并发量 |
|
123 | +指系统能够同时处理的请求的数目,这个数字反映了系统的负载性能。对于网站而言,并发数指网站用户同时提交 |
|
124 | +请求的用户数目 |
|
125 | + |
|
126 | + |
|
127 | +# 后端详细设计 |
|
128 | + |
|
129 | +_总体原则:承接概要设计,进一步细化。_ |
|
130 | + |
|
131 | +语言包实现方案:内存模型 |
|
132 | +扩展引擎multi_language内存模型,增加autoLog属性; |
|
133 | +定义后端视图view.json,实现在线维护多语言包。 |
|
134 | +导入/导出语言包:直接使用现有导入导出APP。 |
|
135 | +注意项:需验证内存模型是否支持分布式场景!!! |
|
136 | + |
|
137 | +业务/实例数据多语言方案 |
|
138 | +导出数据:使用snest-base-file.jar的APP能力实现导入导出语言包Excel文件。 |
|
139 | +扩展重写原来导入导出方法,导出需要在表头增加时区信息,文件名需要翻译当前语言;根据请求的时区转换时间字段的时间。 |
|
140 | +导入数据:扩展重写原来导入方法,解析表头是否指定时区,没有则取当前请求时区,转换时间字段的时间。 |
|
141 | +根据前端方案,调整调用service的数据结构,符合多语言的CRUD |
|
142 | + |
|
143 | +## APP开发 |
|
144 | +### APP信息 |
|
145 | +| 标题 | 内容 | |
|
146 | +| --- | --- | |
|
147 | +| 名称 | 国际化多语言APP | |
|
148 | +| 版本 | v1.0.0 | |
|
149 | +| 描述 | 描述 | |
|
150 | +| 关键词 | 关键词 | |
|
151 | +| 证书 | 证书 | |
|
152 | +| 作者 | 作者 | |
|
153 | + |
|
154 | + |
|
155 | +### 依赖项 |
|
156 | +在`package.json`中体现 |
|
157 | +##### 开发环境依赖 |
|
158 | +| 依赖 | 版本 | |
|
159 | +| --- | --- | |
|
160 | +| Java | >=1.8 | |
|
161 | +| SpringBoot | >=2.0.0 | |
|
162 | + |
|
163 | + |
|
164 | +##### 平台依赖 |
|
165 | +_用于描述平台安装APP时,依赖其他APP_ |
|
166 | + |
|
167 | +| 平台依赖 | 版本 | |
|
168 | +| --- | --- | |
|
169 | +| 底座 | >1.0.0 | |
|
170 | +| 多租户APP | *(所有版本) | |
|
171 | + |
|
172 | + |
|
173 | +##### 第三方依赖 |
|
174 | +| 依赖 | 版本 | |
|
175 | +| --- | --- | |
|
176 | +| maven | maven 包依赖,包含工具类、开源框架等 | |
|
177 | + |
|
178 | + |
|
179 | +### 业务模型结构(必选)--架构师(业务建模) |
|
180 | + |
|
181 | +_承接概要设计的元模型对象结构清单。_ |
|
182 | +_有枚举值的,需要在字段描述中穷举出来。_ |
|
183 | +#### sie_multil_language_app multil_language MODEL |
|
184 | + |
|
185 | +```java |
|
186 | +/** |
|
187 | + * 多语言模型 |
|
188 | + * |
|
189 | + * @author sie |
|
190 | + */ |
|
191 | +@Model(isLogicDelete = Bool.True) |
|
192 | +public class MultiLanguage extends BaseModel<MultiLanguage> { |
|
193 | + |
|
194 | + @Property(columnName = "appName", displayName = "app名称",displayForModel = true) |
|
195 | + private String appName; |
|
196 | + |
|
197 | + @Property(columnName = "type", displayName = "类型") |
|
198 | + private String type; |
|
199 | + |
|
200 | + |
|
201 | + @Property(columnName = "key", displayName = "格式") |
|
202 | + private String key; |
|
203 | + |
|
204 | + |
|
205 | + @Property(columnName = "value", displayName = "译文") |
|
206 | + private String value; |
|
207 | + |
|
208 | + @Property(columnName = "lang", displayName = "语言") |
|
209 | + private String lang; |
|
210 | + |
|
211 | + |
|
212 | +} |
|
213 | +``` |
|
214 | + |
|
215 | +```java |
|
216 | +/** |
|
217 | + * 支持语言模型 |
|
218 | + * |
|
219 | + * @author sie |
|
220 | + */ |
|
221 | +@Model(isLogicDelete = Bool.True) |
|
222 | +public class SupportLanguage extends BaseModel<SupportLanguage> { |
|
223 | + |
|
224 | + |
|
225 | + @Property(columnName = "lang", displayName = "语言") |
|
226 | + private String lang; |
|
227 | + |
|
228 | + @Property(columnName = "lang_code", displayName = "语言编码") |
|
229 | + private String lang_code; |
|
230 | +} |
|
231 | +``` |
|
232 | + |
|
233 | + |
|
234 | +### 类图(可选)--架构师 |
|
235 | + |
|
236 | +_通过类图展示程序结构。_ |
|
237 | + |
|
238 | + |
|
239 | + |
|
240 | +### 时序图(可选)--架构师 |
|
241 | + |
|
242 | +_通过时序图展示程序逻辑关系和调用链路。_ |
|
243 | + |
|
244 | +_时序图需要承接领域示意图与系统边界图那部分的复杂场景。_ |
|
245 | + |
|
246 | +_存在跨系统、跨领域间的通讯一定要有时序图_ |
|
247 | + |
|
248 | + |
|
249 | + |
|
250 | +### 接口设计(必选)--开发 |
|
251 | + |
|
252 | +_承接概要设计的接口清单,包括web接口、远程接口、本地接口(跨模块接口),详细说明接口IPO(输入、处理过程、输出)。_ |
|
253 | + |
|
254 | +_如果是HTTP接口,可以通过使用swagger定义导出,然后在“接口描述”上增加处理过程逻辑描述,或者直接在swagger定义时描述接口处理过程逻辑。_ |
|
255 | + |
|
256 | +_例如:_ |
|
257 | + |
|
258 | +#### APP-sie_multi_language MODEL-multi_language create接口 |
|
259 | + |
|
260 | +create【多语言翻译数据管理】多语言翻译数据新增接口 |
|
261 | + |
|
262 | +补充描述:此接口支持批量新增翻译管理数据 |
|
263 | + |
|
264 | +接口请求路径示例: |
|
265 | + |
|
266 | +``` |
|
267 | +http://10.10.6.172:8060/root/rpc/service/?model=multi_language&service=create |
|
268 | +``` |
|
269 | + |
|
270 | +请求参数示例: |
|
271 | + |
|
272 | +``` |
|
273 | +{ |
|
274 | + "id": "guid", |
|
275 | + "jsonrpc": "2.0", |
|
276 | + "method": "service", |
|
277 | + "params": { |
|
278 | + "args": { |
|
279 | + "useDisplayForModel": true, |
|
280 | + "valuesList": [ |
|
281 | + { |
|
282 | + "app_name": "base", |
|
283 | + "sys_source_name": "登录名", |
|
284 | + "type": "meta", |
|
285 | + "lang": "zh-CN", |
|
286 | + "key":"base.rbac_user.displayName", |
|
287 | + "value": "登录名" |
|
288 | + }, |
|
289 | + { |
|
290 | + "app_name": "base", |
|
291 | + "sys_source_name": "系统异常!", |
|
292 | + "type": "meta", |
|
293 | + "lang": "en-US", |
|
294 | + "key": "base.rbac_user.displayName", |
|
295 | + "value": "System Error!" |
|
296 | + } |
|
297 | + ] |
|
298 | + }, |
|
299 | + "context": { |
|
300 | + "uid": "", |
|
301 | + "lang": "zh_CN" |
|
302 | + }, |
|
303 | + "model": "multi_language", |
|
304 | + "tag": "master", |
|
305 | + "service": "create", |
|
306 | + "app": "sie-multi-language" |
|
307 | + } |
|
308 | +} |
|
309 | +``` |
|
310 | + |
|
311 | +返回参数示例: |
|
312 | + |
|
313 | +``` |
|
314 | +{ |
|
315 | + "id": "guid", |
|
316 | + "jsonrpc": "2.0", |
|
317 | + "result": { |
|
318 | + "data": [ |
|
319 | + ], |
|
320 | + "context": { |
|
321 | + "uid": "", |
|
322 | + "lang": "zh_CN", |
|
323 | + "tenantId": "03kkylgjxf51b", |
|
324 | + "token": "cae26ebfc0a54a39b2e0da35633d19b6cd11cb19efe6457ce5c8305f32fe206a59763a247662f8ce17da11ef56f4c86292fe7f1c35a3fc07acb24be88f99e0465a240d1185d3e86c2a578c4c8840a02553932676274d68774f8917fe4727ce9ce6683e2858afb96c345cc7dee0e28fcdf46511bb1878408c5f5995d7341aa54f9fa09d230277c4151d7468de927f77a111757cc7a5bce6ab54173fba5fd7029f2993807d54341a01453a469d0774ffe26d57252385d62c84076bb0136dbedeb1d34b738a8030e37b84f75912c1bec62f7992660ea7d2073f2fb30ad7ae2644206db542fc0b06198bd551a6a2ec0676d2", |
|
325 | + "roles": [ |
|
326 | + "rbac_role_implementer_admin" |
|
327 | + ], |
|
328 | + "sgs": [], |
|
329 | + "curSg": null, |
|
330 | + "globalFilter": {}, |
|
331 | + "clientId": null |
|
332 | + } |
|
333 | + } |
|
334 | +} |
|
335 | +``` |
|
336 | + |
|
337 | +**接口时序图(可选)** |
|
338 | + |
|
339 | +_如果接口逻辑复杂,需要详细描述接口的处理过程。_ |
|
340 | + |
|
341 | + |
|
342 | + |
|
343 | +### |
|
344 | + |
|
345 | +### 定时任务(可选)--开发 |
|
346 | + |
|
347 | +_承接概要设计的定时任务清单(如果有),详细说明定时任务触发条件、实现逻辑。_ |
|
348 | + |
|
349 | +_例如:_ |
|
350 | + |
|
351 | +#### Xx定时任务 |
|
352 | +| 任务名称 | Xxx | |
|
353 | +| --- | --- | |
|
354 | +| 描述 | | |
|
355 | +| 调度策略 | 每30分钟一次 | |
|
356 | +| 实现逻辑 | | |
|
357 | + |
|
358 | + |
|
359 | +### 种子数据(可选)--开发 |
|
360 | + |
|
361 | +数据初始化 |
|
362 | + |
|
363 | +配置 |
|
364 | + |
|
365 | +数据字典 |
|
366 | + |
|
367 | +| 字典编码 | 字典名称 | 描述 | 字典项 | |
|
368 | +| --- | --- | --- | --- | |
|
369 | +| LANGUAGES | 语种 | 多语言语种 | 中文简体 (0)中文繁体 (1)英语 (2) | |
|
370 | +| | | | | |
|
371 | +| | | | | |
|
372 | +| | | | | |
|
373 | + |
|
374 | +| 字典编码 | 字典名称 | 描述 | 字典项 | |
|
375 | +| --- | --- | --- | --- | |
|
376 | +| | | | | |
|
377 | +| | | | | |
|
378 | +| | | | | |
|
379 | +| | | | | |
|
380 | + |
|
381 | + |
|
382 | +### Redis Key(可选)--开发 |
|
383 | +| **名称** | **信息** | |
|
384 | +| --- | --- | |
|
385 | +| key名称 | SSO:TOKEN:{登录账号} | |
|
386 | +| value类型 | string | |
|
387 | +| value示例,需要对value的字段进行说明:字段的含义:token来源:登录时候生成作用:校验登录 | eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI1ZDA0NTJmMi1iMzU0LTQ0YjctYjZkYS04OTNiYTRkYjQ5OWUiLCJpc3MiOiJzaWUtaW90LWp3dCIsInN1YiI6IntcInBob25lTnVtYmVyXCI6XCIxMzYyNjIzMjEwOFwiLFwibG9naW5Gcm9tXCI6XCJQQ1wiLFwidXNlcm5hbWVcIjpcIjEzNjI2MjMyMTA4XCJ9IiwiaWF0IjoxNzA2MjMwOTMwfQ.Moftm51UDor0inoqD18tWI_LaQlz030LAqS5oRpmEXA | |
|
388 | + |
|
389 | + |
|
390 | +### 异常处理(可选)--开发 |
|
391 | + |
|
392 | +_列出开可能出现的业务异常、系统异常,并且统一编码说明。_ |
|
393 | + |
|
394 | +| 异常类型 | 异常编码 | 异常提示 | 说明 | |
|
395 | +| --- | --- | --- | --- | |
|
396 | +| | | | | |
|
397 | +| | | | | |
|
398 | +| | | | | |
|
399 | +| | | | | |
|
400 | + |
|
401 | + |
|
402 | +# 前端详细设计 |
|
403 | + |
|
404 | +## APP开发 |
|
405 | + |
|
406 | +_根据迭代内容选择新建、迭代的模板_ |
|
407 | + |
|
408 | +#### 新建APP(根据迭代选) |
|
409 | + |
|
410 | +##### APP使用文档、介绍信息等等 |
|
411 | + |
|
412 | +写在`README.md`中,在应用市场中展示给用户的信息 |
|
413 | + |
|
414 | +##### APP信息 |
|
415 | + |
|
416 | +在`app.json`中体现 |
|
417 | + |
|
418 | +| 标题 | 内容 | |
|
419 | +| --- | --- | |
|
420 | +| name | APP名 | |
|
421 | +| version | 版本号 | |
|
422 | +| discription | 描述 | |
|
423 | +| keywords | 关键词 | |
|
424 | +| license | 证书 | |
|
425 | +| author | 作者 | |
|
426 | + |
|
427 | + |
|
428 | +#### 依赖项 |
|
429 | +在`package.json`中体现 |
|
430 | +##### 开发环境依赖 |
|
431 | + |
|
432 | +| 依赖 | 版本 | |
|
433 | +| --- | --- | |
|
434 | +| node | >=18 | |
|
435 | +| pnpm | >=8 | |
|
436 | + |
|
437 | + |
|
438 | +##### 平台依赖 |
|
439 | +_用于描述平台安装APP时,依赖其他APP_ |
|
440 | + |
|
441 | +| 平台依赖 | 版本 | |
|
442 | +| --- | --- | |
|
443 | +| 底座 | >1.0.0 | |
|
444 | +| 多租户APP | *(所有版本) | |
|
445 | + |
|
446 | + |
|
447 | +##### 第三方依赖 |
|
448 | +| 依赖 | 版本 | |
|
449 | +| --- | --- | |
|
450 | +| dependencies | npm包依赖,包含平台组件市场的npm包等,第三方依赖 | |
|
451 | +| devDependencies | 开发依赖 | |
|
452 | + |
|
453 | + |
|
454 | +#### 迭代的APP(根据迭代选) |
|
455 | +##### 当前APP信息 |
|
456 | +_系需要体现此次迭代修改的内容_ |
|
457 | + |
|
458 | +##### 平台依赖 |
|
459 | +_用于描述平台安装APP时,依赖其他APP_ |
|
460 | +_系需要体现此次迭代修改的内容_ |
|
461 | + |
|
462 | +| 平台依赖 | 变更前版本 | 变更后 | |
|
463 | +| --- | --- | --- | |
|
464 | +| 底座 | >1.0.0 | >1.5.0 | |
|
465 | +| 多租户APP | * | >2.0.0 | |
|
466 | + |
|
467 | + |
|
468 | +#### 组件 |
|
469 | +##### xxx组件 |
|
470 | +###### 对应的产品文档/原型/设计稿 |
|
471 | +_截图说明_ |
|
472 | +###### 组件扩展性 |
|
473 | + |
|
474 | +- 扩展的属性 |
|
475 | +- 可扩展的方法 |
|
476 | +- 不可扩展的内容注意事项 |
|
477 | + |
|
478 | +###### 组件功能 |
|
479 | +_列出组件功能说明_ |
|
480 | + |
|
481 | +- 搜索功能 |
|
482 | +- 排序功能 |
|
483 | +- 清除输入功能 |
|
484 | + |
|
485 | +必要的组件属性 |
|
486 | + |
|
487 | +| 属性 | 描述 | 类型 | 默认值 | |
|
488 | +| --- | --- | --- | --- | |
|
489 | +| type | 节点类型 | string | - | |
|
490 | +| ... | ... | ... | ... | |
|
491 | + |
|
492 | + |
|
493 | +必要的组件事件 |
|
494 | + |
|
495 | +| 事件 | 描述 | 参数 | 返回值 | |
|
496 | +| --- | --- | --- | --- | |
|
497 | +| click | 点击事件 | name,age | {} | |
|
498 | +| ... | ... | ... | ... | |
|
499 | + |
|
500 | + |
|
501 | +## SDK开发 |
|
502 | +#### SDK描述 |
|
503 | +_版本号_ |
|
504 | +#### 依赖项 |
|
505 | +##### 开发环境依赖 |
|
506 | +| 依赖 | 版本 | |
|
507 | +| --- | --- | |
|
508 | +| node | >=18 | |
|
509 | +| pnpm | >=8 | |
|
510 | + |
|
511 | +##### 第三方依赖 |
|
512 | +| 依赖 | 版本 | |
|
513 | +| --- | --- | |
|
514 | +| 引擎 | ~v1.0.0 | |
|
515 | +| 开发时的依赖项 | | |
|
516 | +| 使用时的依赖项 | | |
|
517 | +| ... | ... | |
|
518 | + |
|
519 | +#### 兼容性 |
|
520 | +_向前兼容时需要注意的地方_ |
|
521 | +#### 开发规范 |
|
522 | +_文件结构,接口规范,不支持的写法等_ |
|
523 | +#### XXXX接口 |
|
524 | +_描述该接口的功能_| |
|
525 | +###### 入口 |
|
526 | +| 参数 | 描述 | 类型 | 是否必填 | 默认值 | |
|
527 | +| --- | --- | --- | --- | --- | |
|
528 | +| comp | 组件配置 | object | 是 | - | |
|
529 | + |
|
530 | +###### 出口 |
|
531 | +| 返回值 | 描述 | 类型 | |
|
532 | +| --- | --- | --- | |
|
533 | +| schema | node树 | object | |
|
534 | + |
|
535 | + |
|
536 | +# 任务拆分 |
|
537 | + |
|
538 | +本次优化迭代内容,工作任务拆分,链接任务拆分基本点 |
\345\246\202\344\275\225\346\224\257\346\214\201\350\277\234\347\250\213\350\260\203\347\224\250.md
\345\270\270\350\247\201\351\227\256\351\242\230.md
... | ... | @@ -0,0 +1,40 @@ |
1 | +- [[常见问题/排查问题前的排查清单]] |
|
2 | +- [[常见问题/分页条数与表格总数不一致]] |
|
3 | +- [[常见问题/重写方法后出现argument-type-mismatch错误]] |
|
4 | +- [[常见问题/selection下拉组件数据过多分页问题处理]] |
|
5 | +- [[/模型如果声明isAutoLog=true属性,会自动生成创建时间,创建人,修改时间修改人四个字段,但是此四个字段无法在前端显示]] |
|
6 | +- [[常见问题/关于重新扩展引擎字段tenant_id注意]] |
|
7 | +- [[常见问题/创建表失败Invliad-default-vlalue-for_update_date]] |
|
8 | +- [[常见问题/模型增加了索引,但是数据库没有新增索引成功]] |
|
9 | +- [[常见问题/分布式id生成优化]] |
|
10 | +- [MI前端扩展不生效问题和Oracle驱动加载失败问题](常见问题/mi-update-front-end-extension-does-not-work.md) |
|
11 | +- [[常见问题/模型支持软删除配置]] |
|
12 | +- [[常见问题/compute-method示例]] |
|
13 | +- [[常见问题/ID生成器]] |
|
14 | +- [[常见问题/properties.add抛出UnsupportedException异常]] |
|
15 | +- [[常见问题/related字段回显中文]] |
|
16 | +- [[常见问题/如何提取一个Filter中的过滤条件到新的Filter中?]] |
|
17 | +- [[常见问题/调用Filter.remove后查询报错]] |
|
18 | +- [[常见问题/teanant]] |
|
19 | +- [[常见问题/租户授权失效]] |
|
20 | +- [[常见问题/修改菜单种子数据的name导致前端扩展不生效]] |
|
21 | +- [[常见问题/Specified key was too long; max key length is 3072 bytes]] |
|
22 | +- [[常见问题/后端如何使用引擎自带crud基础方法实现查询,新增,更新,删除]] |
|
23 | +- [开发配置产品线菜单图标](常见问题/%E5%BC%80%E5%8F%91%E9%85%8D%E7%BD%AE%E4%BA%A7%E5%93%81%E7%BA%BF%E5%92%8C%E8%8F%9C%E5%8D%95%E5%9B%BE%E6%A0%87.md) |
|
24 | +- [[常见问题/在单机模式下清去掉app.install.jar.dir配置]] |
|
25 | +- [[常见问题/one2many related属性如何配置搜索]] |
|
26 | +- [使用SqlProvider适配不同的数据库和手写SQL最佳实践](常见问题/使用SqlProvider适配不同的数据库和手写SQL最佳实践.md) |
|
27 | +- [[常见问题/手动控制嵌套事务]] |
|
28 | +- [安装xxl-job后IDEA旗舰版启动提示javax.management.InstanceAlreadyExistsException](常见问题/InstanceAlreadyExistsException) |
|
29 | +- [[常见问题/视图配置了审计字段不显示]] |
|
30 | +- [[常见问题/方法重载导致接口文档不显示]] |
|
31 | +- [重置种子数据](常见问题/seed-data.md) |
|
32 | +- [后端视图扩展](常见问题/视图扩展.md) |
|
33 | +- [[常见问题/启动加速配置说明]] |
|
34 | +- [[常见问题/MySQL版本和字符集要求]] |
|
35 | +- [[常见问题/返回文件流给浏览器下载]] |
|
36 | +- [[常见问题/利源SMI-数据集模型扩展问题排查.md]] |
|
37 | +- [[常见问题/useDispalyForModel问题排查.md]] |
|
38 | +- [[常见问题/调用update不生效]] |
|
39 | +- [[常见问题/复制扩展菜单]] |
|
40 | +- [[常见问题/查询 ManyToMany]] |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/Filter\345\246\202\344\275\225\346\267\273\345\212\240Filter.FilterOp\345\257\271\350\261\241.txt
... | ... | @@ -0,0 +1,5 @@ |
1 | +如何提取一个Filter中的过滤条件到新的Filter中? |
|
2 | +使用 Filter.parse方法获取新的filter |
|
3 | +例: |
|
4 | +Filter.FilterOp filterOp = filter.getFilterOp("name"); |
|
5 | +Filter f = Filter.parse(Arrays.asList(filterOp)); |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/ID\347\224\237\346\210\220\345\231\250.md
... | ... | @@ -0,0 +1,4 @@ |
1 | +平台统一使用 IdGenerator.nextId() 生成ID。如果想要提前设置 ID,可以使用这个工具类生成一个 ID,然后设置到模型里面,再调用创建方法。 |
|
2 | + |
|
3 | +已知问题 |
|
4 | +1. 不能调用 this.create()。需要调用 rs.call("create") 或 rs.callSuper("create") 方法 |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/InstanceAlreadyExistsException.md
... | ... | @@ -0,0 +1,9 @@ |
1 | +# 安装 xxl-job APP 后,提示 Caused by: javax.management.InstanceAlreadyExistsException |
|
2 | + |
|
3 | +参考 [https://blog.csdn.net/bin_zi_123/article/details/103135584](https://blog.csdn.net/bin_zi_123/article/details/103135584) |
|
4 | + |
|
5 | +安装 xxl-job app 后,项目启动的时候会运行多个 SpringBoot 应用,IDEA 旗舰版会提示上面错误信息 |
|
6 | + |
|
7 | +解决方案:在 SpringBoot 启动项,去掉 Enable launch optimization、Enable JMX agent 的勾选状态 |
|
8 | + |
|
9 | +[[/uploads/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/InstanceAlreadyExistsException/2019111909461725.jpeg]] |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/MySQL\347\211\210\346\234\254\345\222\214\345\255\227\347\254\246\351\233\206\350\246\201\346\261\202.md
... | ... | @@ -0,0 +1,8 @@ |
1 | +# MySQL版本和字符集要求 |
|
2 | + |
|
3 | +1. MySQL版本: >=8.029 |
|
4 | +2. 默认字符集:utf8mb4 |
|
5 | +3. 默认排序规则(不忽略大小写):utf8mb4_bin |
|
6 | + |
|
7 | +注意事项: |
|
8 | +如果旧数据是从其他数据库或者表导入过来的,要检查下2个表的字符集和排序规则是否满足上面的2和3的要求。 |
\345\270\270\350\247\201\351\227\256\351\242\230/Specified key was too long; max key length is 3072 bytes.txt
... | ... | @@ -0,0 +1,2 @@ |
1 | +启动报错信息:java.sql.BatchUpdateException: Specified key was too long; max key length is 3072 bytes |
|
2 | +这是数据库联合索引超过数据库限制长度,具体原因及修改方案可参考:https://zhuanlan.zhihu.com/p/463718964 |
\345\270\270\350\247\201\351\227\256\351\242\230/compute-method\347\244\272\344\276\213.md
... | ... | @@ -0,0 +1,55 @@ |
1 | +```java |
|
2 | +package com.sie.app.newsdk.test.model.demo; |
|
3 | + |
|
4 | +import cn.hutool.core.date.DateUtil; |
|
5 | +import com.sie.snest.sdk.BaseModel; |
|
6 | +import com.sie.snest.sdk.DataType; |
|
7 | +import com.sie.snest.sdk.annotation.meta.MethodService; |
|
8 | +import com.sie.snest.sdk.annotation.meta.Model; |
|
9 | +import com.sie.snest.sdk.annotation.meta.Property; |
|
10 | +import java.util.Date; |
|
11 | + |
|
12 | +/** |
|
13 | + * @author Kris |
|
14 | + */ |
|
15 | +@Model(displayName = "计算属性示例") |
|
16 | +public class ComputePropertyDemo extends BaseModel<ComputePropertyDemo> { |
|
17 | + |
|
18 | + |
|
19 | + @Property(displayName = "姓名", computeMethod = "hello") |
|
20 | + private String name; |
|
21 | + |
|
22 | + @Property(displayName = "出生日期", dataType = DataType.DATE) |
|
23 | + private Date birthday; |
|
24 | + |
|
25 | + @Property(displayName = "年龄", computeMethod = "calAge") |
|
26 | + private Integer age; |
|
27 | + |
|
28 | + /** |
|
29 | + * 如果参数是 String,那么参数是当前字段 name 的值 |
|
30 | + * @param name |
|
31 | + * @return |
|
32 | + */ |
|
33 | + @MethodService |
|
34 | + public String hello(String name) { |
|
35 | + return "hello" + name; |
|
36 | + } |
|
37 | + |
|
38 | + /** |
|
39 | + * 如果参数不是 String |
|
40 | + * 不能跟 setter、getter 方法重名!!! |
|
41 | + * 方法一定是要 public |
|
42 | + * |
|
43 | + * @param model 当前模型 |
|
44 | + * @return 年龄 |
|
45 | + */ |
|
46 | + @MethodService |
|
47 | + public Integer calAge(BaseModel<?> model) { |
|
48 | + Date birthday = model.getDate("birthday"); |
|
49 | + if (birthday == null) { |
|
50 | + return null; |
|
51 | + } |
|
52 | + return DateUtil.age(birthday, DateUtil.date()); |
|
53 | + } |
|
54 | +} |
|
55 | +``` |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/mi-update-front-end-extension-does-not-work.md
... | ... | @@ -0,0 +1,41 @@ |
1 | +# MI前端扩展不生效问题和Oracle驱动加载失败问题 |
|
2 | + |
|
3 | +## 1. MI前端扩展没有生效? |
|
4 | +**1.1 问题原因** |
|
5 | + |
|
6 | +MetaApp.prepareListApp 接口没有返回前端扩展文件给前端. |
|
7 | +MI的前端包tech-smi-ai-1.3.0.zip带了版本号,解压缩后文件名为tech-smi-ai, |
|
8 | +MetaApp.prepareListApps接口通过路径\umdComps\tech-smi-ai-1.3.0\config\app.json去找前端的配置文件找不到,因为路径tech-smi-ai-1.3.0不存在. |
|
9 | + |
|
10 | +**1.2 解决方案** |
|
11 | + |
|
12 | +1.2.1 将泽彬给的mi所有前端包去掉版本号(-1.3.0),以后升级也不要带版本号 |
|
13 | + |
|
14 | +1.2.2 修改数据库的方式 |
|
15 | + |
|
16 | +`SELECT * FROM META_APP WHERE VIEW_FILE LIKE '%1.3.0%';` |
|
17 | + |
|
18 | +然后 VIEW_FILE 去掉版本号-1.3.0保存,tech-smi-ai-1.3.0.zip--->tech-smi-ai.zip |
|
19 | + |
|
20 | +`SELECT * FROM meta_attachment WHERE NAME LIKE '%1.3.0.zip%';` |
|
21 | + |
|
22 | +然后 NAME 去掉版本号保存,tech-smi-ai-1.3.0.zip--->tech-smi-ai.zip |
|
23 | + |
|
24 | +1.2.3 MetaApp.prepareListApps接口需要调整支持前端版本号的问题. |
|
25 | + |
|
26 | + |
|
27 | +## 2. Oracle数据库连接异常? |
|
28 | +**2.1 异常** |
|
29 | + |
|
30 | +`ERROR:java.sql.SQLException: Non supported character set (add orai18n.jar in your classpath): ZHS16GBK |
|
31 | + at oracle.sql.CharacterSetUnknown.failCharsetUnknown(CharacterSetFactoryThin.java:233) |
|
32 | + at oracle.sql.CharacterSetUnknown.convert(CharacterSetFactoryThin.java:194)` |
|
33 | + |
|
34 | +**2.2 解决方案** |
|
35 | + |
|
36 | +oracle加载驱动使用的是线程变量的类加载器,而不是App的类加载器,将oracle驱动的类加载器修改成我们App类加载器 |
|
37 | + |
|
38 | + |
|
39 | + |
|
40 | + |
|
41 | + |
\345\270\270\350\247\201\351\227\256\351\242\230/one2many related\345\261\236\346\200\247\345\246\202\344\275\225\351\205\215\347\275\256\346\220\234\347\264\242.txt
... | ... | @@ -0,0 +1,13 @@ |
1 | + 注意:one2many related属性只支持在前端search视图配置,不支持作为字段展示在grid或者form,因为返回值是集合 |
|
2 | + 注解说明:one2many不设置joinType默认为join或者left join查询方式,如果存在多条数据绑定,join方式会存在重复数据问题 |
|
3 | + 解决方法:joinType = JoinType.SUBQUERY, subQueryType = SubQueryType.ROW使用子查询方式去重(此注解暂时支持one2many) |
|
4 | + |
|
5 | + |
|
6 | + @OneToMany(joinType = JoinType.SUBQUERY, subQueryType = SubQueryType.ROW) |
|
7 | + private List<TestUser> userList; |
|
8 | + |
|
9 | + @Property(displayName = "电话号码",related = "userList.phone") |
|
10 | + private String phone; |
|
11 | + |
|
12 | + @Property(displayName = "用户名",related = "userList.name") |
|
13 | + private String username; |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/properties.add\346\212\233\345\207\272UnsupportedException\345\274\202\345\270\270.md
... | ... | @@ -0,0 +1,10 @@ |
1 | +search 方法的 properties 支持传 Collections.singletonList("*"),代表查询所有字段。 |
|
2 | + |
|
3 | +但是 Collections.singletonList 是一个不可变的 List,所以不能直接调用 properties.add。 |
|
4 | + |
|
5 | +如果重写了 search 方法,想要在 properties 添加额外查询的字段 |
|
6 | + |
|
7 | +```java |
|
8 | +List<String> newProperties = new ArrayList<>(properties); |
|
9 | +newProperties.add("name"); |
|
10 | +``` |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/related\345\255\227\346\256\265\345\233\236\346\230\276\344\270\255\346\226\207.md
... | ... | @@ -0,0 +1,11 @@ |
1 | +如果 related 字段是一个字典,可以使用 @Dict 回显对应的中文值。 |
|
2 | + |
|
3 | +``` |
|
4 | +@ManyToOne(displayName = "客户名称", cascade = {CascadeType.DEL_SET_NULL}) |
|
5 | +@JoinColumn(name = "cus_id") |
|
6 | +private OppmCustomerArchive cus; |
|
7 | + |
|
8 | +@Property(displayName = "新老类型", store = false, related = "cus.customerType") |
|
9 | +@Dict(typeCode = "customerType") |
|
10 | +private String customerType; |
|
11 | +``` |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/seed-data.md
... | ... | @@ -0,0 +1,25 @@ |
1 | +# 种子数据重置和更新 |
|
2 | +### 1. 菜单-已安装应用-重置种子数据 |
|
3 | +超级管理员implementer实施人员才有权限操作,使用场景: |
|
4 | +* 开发阶段; |
|
5 | +* 项目第一次上线; |
|
6 | +* 需要重置整个应用才能使用的。 |
|
7 | + |
|
8 | +<p style="color: red;">种子数据重置会把整个应用的视图,字典、菜单,业务数据都重置为JSON里面的种子数据, |
|
9 | +即使业务数据数据库已经修改了,也会被重置,这种操作比较危险,慎用。</p> |
|
10 | + |
|
11 | + |
|
12 | +### 2. 菜单-已安装应用-更新数据 |
|
13 | +使用场景: |
|
14 | +* 应用有新增的种子数据(包含菜单,字典、视图,业务数据)有新增,会新增到数据库。 |
|
15 | +* 更新应用所有的视图文件,但是不会更新菜单,字典和业务数据。 |
|
16 | + |
|
17 | + |
|
18 | +<p style="color: red;">这个操作不会更新数据库的菜单和业务数据(如果数据库有修改,不会更新)。</p> |
|
19 | + |
|
20 | + |
|
21 | + |
|
22 | +### 3. 菜单-开发者中心-种子数据管理 |
|
23 | +使用场景: |
|
24 | +* 测试环境、生产环境 |
|
25 | +* 重置单个种子数据为JSON里面的数据,包含视图,菜单,字典,业务数据。 |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/selection\344\270\213\346\213\211\347\273\204\344\273\266\346\225\260\346\215\256\350\277\207\345\244\232\345\210\206\351\241\265\351\227\256\351\242\230\345\244\204\347\220\206.txt
... | ... | @@ -0,0 +1,10 @@ |
1 | +selection下拉组件指定method加载数据,如果数据过多,下拉容易崩溃,导致页面卡死崩溃 |
|
2 | +处理方案: |
|
3 | +使用RecordSet接收,获取RecordSet中的分页参数,填充到需要查询的search方法调用代码中,即可实现分页 |
|
4 | + public List<Options> selectMethod(RecordSet rs,Object value){ |
|
5 | + Integer limit = (Integer) rs.getMeta().getArgument("limit"); |
|
6 | + Integer offset = (Integer) rs.getMeta().getArgument("offset"); |
|
7 | + rs.getMeta().get("fixedAssetsInfoModel").search(new Filter(),Arrays.asList("*"),limit,offset,null); |
|
8 | + //业务处理 |
|
9 | + return new ArrayList<>(); |
|
10 | + } |
\345\270\270\350\247\201\351\227\256\351\242\230/teanant.md
... | ... | @@ -0,0 +1,3 @@ |
1 | + |
|
2 | + |
|
3 | +[[(/uploads/Home/17007936754091.png]] |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\344\270\272\344\273\200\344\271\210\345\255\227\346\256\265\346\262\241\346\234\211\346\230\276\347\244\272\344\270\255\346\226\207.md
... | ... | @@ -0,0 +1,80 @@ |
1 | +# 为什么字段没有显示中文 |
|
2 | + |
|
3 | +## 使用 ManyToOne,但是 One 方没有字段标记 displayForModel |
|
4 | + |
|
5 | +例如订单关联物料,我们想要在订单列表显示物料的中文名 |
|
6 | + |
|
7 | +首先物料需要声明使用哪个字段来作为中文名显示 |
|
8 | + |
|
9 | +```java |
|
10 | +@Model |
|
11 | +public class Material extend BaseModel<Material> { |
|
12 | + |
|
13 | + @Property(displayForModel = true) |
|
14 | + private String name; |
|
15 | +} |
|
16 | + |
|
17 | +@Model |
|
18 | +public class Order extend BaseModel<Order> { |
|
19 | + |
|
20 | + @ManyToOne |
|
21 | + @JoinColumn |
|
22 | + private Material material; |
|
23 | +} |
|
24 | +``` |
|
25 | + |
|
26 | +## 确保调用 search 的时候,meta 的 arguments 包含 useDisplayForModel = true |
|
27 | + |
|
28 | +显示中文有多种方式 |
|
29 | + |
|
30 | +1. 使用 @Dict 注解声明字典 |
|
31 | +2. 使用 ManyToOne 关联其他模型 |
|
32 | +3. 使用 @Selection 关联其他模型 |
|
33 | +4. 使用 @Selection 执行 method 动态查询值 |
|
34 | +5. 使用 @Property 的 computeMethod 动态计算值 |
|
35 | + |
|
36 | +为了方便前端查询中文值,前端每次请求都会传入一个参数 useDisplayForModel = true。 |
|
37 | +但是引擎出于性能考虑(上面的方式会涉及到表关联或者方法调用),在每一次调用完查询后, |
|
38 | +都会将 useDisplayForModel 这个标识删除。 |
|
39 | + |
|
40 | +所以如果你重写了 search 方法,例如 |
|
41 | + |
|
42 | +```java |
|
43 | +public List<Material> search(Filter filter, List<String> properties, Integer limit, Integer offset, String order) { |
|
44 | + |
|
45 | + // 调用了其他模型的 search 方法 |
|
46 | + getMeta().get("other_model").search(); |
|
47 | + |
|
48 | + // 最后调用了本模型的 search 方法 |
|
49 | + // 由于上一步的查询,meta 里面 useDisplayForModel 标识被删除了 |
|
50 | + // 所以下面 search 的时候,引擎不会去做关联查询、或者调用 computeMethod |
|
51 | + return super.search(filter, proerties, limit, offset, order); |
|
52 | +} |
|
53 | +``` |
|
54 | + |
|
55 | +你可以明确指定 useDisplayForModel = true |
|
56 | + |
|
57 | +```java |
|
58 | +public List<Material> search(Filter filter, List<String> properties, Integer limit, Integer offset, String order) { |
|
59 | + |
|
60 | + // 调用了其他模型的 search 方法 |
|
61 | + getMeta().get("other_model").search(); |
|
62 | + |
|
63 | + // 最后调用了本模型的 search 方法 |
|
64 | + // 由于上一步的查询,meta 里面 useDisplayForModel 标识被删除了 |
|
65 | + getMeta().addArgument(MetaConstant.USE_DISPLAY_FOR_MODEL, true); |
|
66 | + return super.search(filter, proerties, limit, offset, order); |
|
67 | +} |
|
68 | +``` |
|
69 | + |
|
70 | +## 查询的时候 properties 是 `*` 号 |
|
71 | + |
|
72 | +```java |
|
73 | +public List<Material> search(Filter filter, List<String> properties, Integer limit, Integer offset, String order) { |
|
74 | + |
|
75 | + getMeta().addArgument(MetaConstant.USE_DISPLAY_FOR_MODEL, true); |
|
76 | + // 如果 properties 是 *,那么引擎也不会去做关联查询,或调用 computeMethod |
|
77 | + return super.search(filter, Arrays.asList("*"), limit, offset, order); |
|
78 | +} |
|
79 | +``` |
|
80 | + |
\345\270\270\350\247\201\351\227\256\351\242\230/\344\275\277\347\224\250SqlProvider\351\200\202\351\205\215\344\270\215\345\220\214\347\232\204\346\225\260\346\215\256\345\272\223\345\222\214\346\211\213\345\206\231SQL\346\234\200\344\275\263\345\256\236\350\267\265.md
... | ... | @@ -0,0 +1,352 @@ |
1 | +# 使用SqlProvider适配不同的数据库和手写SQL最佳实践 |
|
2 | + |
|
3 | + |
|
4 | +## 1. 支持的数据库类型 |
|
5 | + |
|
6 | +* Oracle :OracleProvider |
|
7 | +* MySQL:MySqlProvider |
|
8 | +* Dameng国产达梦数据库:DamengProvider,达梦数据库兼容oracle,和oracle的用法和语法基本一致 |
|
9 | + |
|
10 | + |
|
11 | +## 2. Oracle 与 MySQL 语法差异对比 |
|
12 | + |
|
13 | +### 2.1 分页查询 |
|
14 | + |
|
15 | +- **Oracle**: |
|
16 | + ```sql |
|
17 | + SELECT * FROM ( |
|
18 | + SELECT a.*, ROWNUM rn |
|
19 | + FROM your_table a |
|
20 | + WHERE condition |
|
21 | + ORDER BY some_column |
|
22 | + ) |
|
23 | + WHERE rn BETWEEN start_num AND end_num; |
|
24 | + ``` |
|
25 | + |
|
26 | +- **MySQL**: |
|
27 | + ```sql |
|
28 | + SELECT * FROM your_table |
|
29 | + WHERE condition |
|
30 | + LIMIT start_num, row_count; -- row_count = end_num - start_num + 1 |
|
31 | + ``` |
|
32 | + |
|
33 | +### 2.2 字符串连接 |
|
34 | + |
|
35 | +- **Oracle**: |
|
36 | + ```sql |
|
37 | + SELECT CONCAT(column1, ' ', column2) FROM your_table; |
|
38 | + ``` |
|
39 | + |
|
40 | +- **MySQL**: |
|
41 | + ```sql |
|
42 | + SELECT CONCAT(column1, ' ', column2) FROM your_table; |
|
43 | + ``` |
|
44 | + (此处两者语法相同,但请注意在较早版本的 MySQL 中可能使用 `CONCAT_WS` 或 `CONCATENATE` 函数) |
|
45 | + |
|
46 | +### 2.3 自增列(序列) |
|
47 | + |
|
48 | +- **Oracle**: |
|
49 | + 创建序列: |
|
50 | + ```sql |
|
51 | + CREATE SEQUENCE seq_name; |
|
52 | + ``` |
|
53 | + 使用序列: |
|
54 | + ```sql |
|
55 | + INSERT INTO your_table (id, other_columns) |
|
56 | + VALUES (seq_name.NEXTVAL, 'value'); |
|
57 | + ``` |
|
58 | + |
|
59 | +- **MySQL**: |
|
60 | + 在表创建时声明自增列: |
|
61 | + ```sql |
|
62 | + CREATE TABLE your_table ( |
|
63 | + id INT AUTO_INCREMENT PRIMARY KEY, |
|
64 | + other_columns VARCHAR(255) |
|
65 | + ); |
|
66 | + ``` |
|
67 | + |
|
68 | +### 2.4 数据类型差异 |
|
69 | + |
|
70 | +- **Oracle** 支持 `NUMBER(p,s)` 类型,对应 MySQL 的可能是 `DECIMAL(p,s)` 或者 `INT`、`BIGINT` 等。 |
|
71 | + |
|
72 | +- **MySQL** 提供了额外的数据类型如 `SET` 和 `ENUM`,而 Oracle 没有直接对应的类型。 |
|
73 | + |
|
74 | +- **TEXT长文本类型**: |
|
75 | +- **Oracle** 中用于存储大文本数据的主要类型是 `CLOB` (Character Large Object) 和 `NCLOB` (National Character Large Object)。 |
|
76 | + |
|
77 | +- **MySQL** 有明确的 `TEXT` 数据类型,包括 `TINYTEXT`, `TEXT`, `MEDIUMTEXT`, 和 `LONGTEXT`,分别对应不同大小的文本数据范围,最大可存储至 2^32 字节的数据。 |
|
78 | + |
|
79 | + |
|
80 | + |
|
81 | +### 2.5 时间日期函数 |
|
82 | + |
|
83 | +- **Oracle**: |
|
84 | + 获取当前日期时间: |
|
85 | + ```sql |
|
86 | + SELECT SYSDATE FROM dual; |
|
87 | + ``` |
|
88 | + |
|
89 | +- **MySQL**: |
|
90 | + 获取当前日期时间: |
|
91 | + ```sql |
|
92 | + SELECT NOW(); |
|
93 | + ``` |
|
94 | + |
|
95 | +### 2.6 事务处理 |
|
96 | + |
|
97 | +- **Oracle** 默认情况下不自动提交事务,需要显式执行 `COMMIT` 或 `ROLLBACK`。 |
|
98 | + |
|
99 | +- **MySQL** 默认开启了自动提交模式,可以通过 `SET autocommit=0;` 关闭并手动控制事务。 |
|
100 | + |
|
101 | +### 2.7 字符串内引用的分隔符 |
|
102 | + |
|
103 | + - **Oracle** 中,可以使用双引号 (`"`) 来定义标识符(如表名、列名),单引号 (`'`) 用于定义字符串常量。 |
|
104 | + - **MySQL** 中,可以使用反引号 (`` ` ``)、双引号 (`"`) 或者单引号 (`'`) 来定义标识符,但推荐使用反引号以避免与SQL标准冲突;单引号同样用于定义字符串常量。 |
|
105 | + |
|
106 | +### 2.8 Oracle 和 MySQL 在大小写处理方面的差异: |
|
107 | + |
|
108 | + |
|
109 | +**Oracle:** |
|
110 | +- **SQL关键字:** Oracle 不区分大小写,但通常建议大写 SQL 关键字以提高可读性。 |
|
111 | +- **对象名(如表名、列名、序列名等):** **在Linux/Unix环境下是区分大小写的,默认都是大写,返回的结果列默认都是大写******。 |
|
112 | + |
|
113 | +**MySQL:** |
|
114 | +- **SQL关键字:** MySQL 对于SQL关键字也不区分大小写。 |
|
115 | +- **对象名(如表名、列名、数据库名等):** MySQL 默认在所有环境下都不区分大小写。 |
|
116 | +例如: |
|
117 | +- 在Oracle中,`SELECT * FROM MyTable` 和 `select * from mytable` 指的是不同的一个表(在Linux/Unix环境)。 |
|
118 | +- 在MySQL中,`SELECT * FROM MyTable` 和 `select * from mytable` 同样默认视为相同的表名,除非你用反引号定义为 `SELECT * FROM \`MyTable\`;` 与 `select * from \`mytable\`;` 这时它们会被认为是不同的表名。 |
|
119 | + |
|
120 | +**总结来说,对于大小写处理,Oracle区分大小写, 默认都是大写,而MySQL则在所有系统上都默认不区分大小写。** |
|
121 | + |
|
122 | + |
|
123 | + |
|
124 | +## 3. SqlProvider的使用 |
|
125 | + |
|
126 | +### 建议我们的代码中写的SQL都是标准的SQL,如果遇到数据库的差异的SQL,可以使用SqlProvider提供的方法来屏蔽不同数据库之间的差异。 |
|
127 | + |
|
128 | + |
|
129 | +- **SqlProvider的使用**: |
|
130 | + ```java |
|
131 | + RelationDBAccessor da = rs.getMeta().getRelationDBAccessor(); |
|
132 | + //获取SqlProvider |
|
133 | + SqlProvider sqlProvider = da.getSqlProvider(); |
|
134 | + //对关键字添加分隔符:示例: oracle:"identify", sqlserver:[identify], mysql: `identify` |
|
135 | + String joinTable = sqlProvider.quote("tableName"); |
|
136 | + |
|
137 | + |
|
138 | + String sql = "SELECT " + joinTable + "." + id1 + ", " + joinTable + "." + id2 + ", " + table1 + "." + column1 + " FROM " + table1 + " JOIN " + joinTable |
|
139 | + + " ON " + joinTable + "." + id2 + "=" + table1 + ".id" + " AND " + joinTable + "." + id1 + " IN %s "; |
|
140 | + |
|
141 | + |
|
142 | + //分页查询 |
|
143 | + sql = sqlProvider.getPaging(sql, 99999, 0); |
|
144 | + List<Object> params = new ArrayList<Object>(ids); |
|
145 | + //执行sql |
|
146 | + da.executeWithoutAuth(sql, Arrays.asList(params)); |
|
147 | + |
|
148 | + // 读取数据,如果是oracle数据库,则所有的列名都自动转成小写(因为oracle默认都是全部大小,不想转小写的话,请调用fetchMap) |
|
149 | + List<Map<String, Object>> results = da.fetchMapAll(); |
|
150 | + for (Map<String, Object> rows : searchResults) { |
|
151 | + //TODO |
|
152 | + } |
|
153 | + ``` |
|
154 | + |
|
155 | +** 示例2:** |
|
156 | + ```java |
|
157 | + |
|
158 | + RelationDBAccessor da = meta.getRelationDBAccessor(); |
|
159 | + String searchProperties = DataUtil.VIEW_COLUMNS.stream().map(p -> da.getSqlProvider().quote(p)).collect(Collectors.joining(",")); |
|
160 | + da.executeWithoutAuth(String.format( |
|
161 | + " select %s from ui_view_seed where (is_delete is null or is_delete = '0') ", |
|
162 | + searchProperties)); |
|
163 | + for (Map<String, Object> objectMap : da.fetchMapAll()) { |
|
164 | + keyMap.put((String) objectMap.get("key"), objectMap); |
|
165 | + if (objectMap.get("model") != null) { |
|
166 | + String viewKey = StringUtils.join(objectMap.get("model"), ".", objectMap.get("type")); |
|
167 | + List<Map<String, Object>> mapList = modelViews.get(viewKey); |
|
168 | + if (mapList == null) { |
|
169 | + mapList = new ArrayList<>(); |
|
170 | + modelViews.put(viewKey, mapList); |
|
171 | + } |
|
172 | + mapList.add(objectMap); |
|
173 | + } |
|
174 | + } |
|
175 | + |
|
176 | + ``` |
|
177 | + |
|
178 | +** 示例3:** |
|
179 | + ```java |
|
180 | + RelationDBAccessor dataAccessor = meta.getRelationDBAccessor(); |
|
181 | + SqlProvider provider = dataAccessor.getSqlProvider(); |
|
182 | + String sql = "INSERT INTO meta_attachment(id, name, size, md5,content_type, bucket, " + provider.quote("key") |
|
183 | + + ", create_date) VALUES(%s, %s, %s, %s, %s, %s, %s, " + provider.getNowUtc() + ")"; |
|
184 | + |
|
185 | + dataAccessor.executeWithoutAuth(sql); |
|
186 | + |
|
187 | + ``` |
|
188 | + |
|
189 | + |
|
190 | +## 4. SqlProvider接口使用介绍 |
|
191 | + |
|
192 | + |
|
193 | +### 4.1 SqlProvider接口提供的方法 |
|
194 | + |
|
195 | + |
|
196 | + ```java |
|
197 | +public abstract class SqlProvider { |
|
198 | + |
|
199 | + /** |
|
200 | + * 获取数据库时间 |
|
201 | + * |
|
202 | + * @return |
|
203 | + */ |
|
204 | + public abstract String getNowUtc(); |
|
205 | + |
|
206 | + |
|
207 | + /** |
|
208 | + * 为数据库对象名(如表名、列名、数据库名等)添加分隔符:示例:</br> |
|
209 | + * oracle:"identify", sqlserver:[identify], mysql: `identify` |
|
210 | + * |
|
211 | + * @param identify |
|
212 | + * @return |
|
213 | + */ |
|
214 | + public abstract String quote(String identify); |
|
215 | + |
|
216 | + |
|
217 | + /** |
|
218 | + * 为值添加分隔符,示例:'value' |
|
219 | + * @param identify |
|
220 | + * @return |
|
221 | + */ |
|
222 | + public String quoteValue(String identify){ |
|
223 | + return StringUtils.join("'" , identify , "'"); |
|
224 | + } |
|
225 | + |
|
226 | + /** |
|
227 | + * 为as后的别名添加分隔符 |
|
228 | + * |
|
229 | + * @param alias |
|
230 | + * @return |
|
231 | + */ |
|
232 | + public String asQuote(String alias) { |
|
233 | + return quote(alias); |
|
234 | + } |
|
235 | + |
|
236 | + /** |
|
237 | + * AS 关键字,oracle列名可以使用AS,但是表名不能添加AS |
|
238 | + * 建议:oracle 列名和表名都不使用as ; mysql:AS |
|
239 | + * |
|
240 | + * @return |
|
241 | + */ |
|
242 | + public abstract String as(); |
|
243 | + |
|
244 | + /** |
|
245 | + * 生成分页sql |
|
246 | + * |
|
247 | + * @param sql |
|
248 | + * @param limit 总条数 |
|
249 | + * @param offset 偏移量 |
|
250 | + * @return |
|
251 | + */ |
|
252 | + public abstract String getPaging(String sql, Integer limit, Integer offset); |
|
253 | + |
|
254 | + |
|
255 | + /** |
|
256 | + * 使用日期字符串函数,插入日期字符串 |
|
257 | + * @param value |
|
258 | + * @return |
|
259 | + */ |
|
260 | + public String toDate(String value) { |
|
261 | + } |
|
262 | + |
|
263 | + /** |
|
264 | + * 使用日期字符串函数,插入日期时间格式的字符串 |
|
265 | + * @param value |
|
266 | + * @return |
|
267 | + */ |
|
268 | + public String toDateTime(String value) { |
|
269 | + } |
|
270 | + |
|
271 | + /** |
|
272 | + * 使用日期字符串函数,插入时间戳字符串 |
|
273 | + * @param value |
|
274 | + * @return |
|
275 | + */ |
|
276 | + public String toTimestamp(String value) { |
|
277 | + return String.format("'%s'", value); |
|
278 | + } |
|
279 | + |
|
280 | + /** |
|
281 | + * 插入boolean值,存储在数据库的字段为字符串 0和1,true:'1';false:'0' |
|
282 | + * @param value |
|
283 | + * @return |
|
284 | + */ |
|
285 | + public String toBoolean(Boolean value) { |
|
286 | + if (value) { |
|
287 | + return String.format("'%s'", 1); |
|
288 | + } else { |
|
289 | + return String.format("'%s'", 0); |
|
290 | + } |
|
291 | + } |
|
292 | + |
|
293 | + |
|
294 | + |
|
295 | + /** |
|
296 | + * 插入text类型:oracle varchar2最大字节数都是4000,超过4000的话就需要使用clob存储。 |
|
297 | + * 如果属性中是text类型,插入的时候必须使用toText转换成对应数据库的类型 |
|
298 | + * 转成text类型 |
|
299 | + * @param value |
|
300 | + * @return |
|
301 | + */ |
|
302 | + public abstract String toText(String value); |
|
303 | + |
|
304 | + |
|
305 | + /** |
|
306 | + *Oracle toText实现 |
|
307 | + * Oracle的sql语句单引号内部的字节长度不能大于4000 |
|
308 | + * 把传入的body字符串,按宽度splitLength,拆分成多条 |
|
309 | + * 并用to_clob拼接: |
|
310 | + * https://stackoverflow.com/questions/66974557/oracle-clob-data-type-giving-an-error-sql-error-ora-01704-string-literal-too |
|
311 | + */ |
|
312 | + @Override |
|
313 | + public String toText(String content){ |
|
314 | + int maxLen = 1024; |
|
315 | + int count = Math.floorDiv(content.length(), maxLen) + (content.length() % maxLen == 0 ? 0 : 1); |
|
316 | + String toClob = IntStream.range(0, count).mapToObj(i -> { |
|
317 | + String subStr = content.substring(i * 1024, Math.min((i + 1) * maxLen, content.length())); |
|
318 | + subStr = StringUtils.replace(subStr, "'", "''"); |
|
319 | + return "to_clob('" + subStr + "')"; |
|
320 | + }).collect(Collectors.joining("||")); |
|
321 | + return toClob; |
|
322 | + } |
|
323 | + |
|
324 | + |
|
325 | + /** |
|
326 | + * 在 Oracle 和 MySQL 中,转义字符主要用于处理字符串中的特殊字符或通配符。在 SQL 语句中,常见的转义字符是反斜杠 \。 |
|
327 | + * @param content |
|
328 | + * @return |
|
329 | + */ |
|
330 | + @Override |
|
331 | + public String escapeText(String content){ |
|
332 | + String replace = StringUtils.replace(content.toString(), "\\", "\\\\"); |
|
333 | + replace = replace.replace("'","\\'"); |
|
334 | + return replace; |
|
335 | + } |
|
336 | + |
|
337 | +} |
|
338 | + ``` |
|
339 | + |
|
340 | + |
|
341 | +## 5 手写SQL最佳实践: |
|
342 | +建议我们的代码中写的SQL都是标准的SQL,不要使用不兼容的函数,如果遇到数据库的差异的SQL,可以使用SqlProvider提供的方法来屏蔽数据库之间的差异。** |
|
343 | +- **对一些数据库的关键字,对象名(如表名、列名、序列名等),使用`quote`包裹** 。 |
|
344 | +- **Oracle 列名和表名都不使用关键字AS,直接空着不写就行; MySql可以使用AS**。 |
|
345 | +- **对于TXET长文本类型,必须使用`toText`方法进行转换 **。 |
|
346 | +- **分页使用`getPaging`**。 |
|
347 | +- **插入boolean属性使用`toBoolean`**,存储在数据库的字段为字符串 0和1,`true:'1'; false:'0'`,因为oracle没有boolean数据类型,所以统一成字符串类型。 |
|
348 | +- **插入日期格式的字符串时候,使用`toDate`,`toDateTime`,`toTimestamp`**。 |
|
349 | +- **获取数据库的当前时间使用 `getNowUtc`**。 |
|
350 | + |
|
351 | + |
|
352 | + |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\344\277\256\346\224\271\350\217\234\345\215\225\347\247\215\345\255\220\346\225\260\346\215\256\347\232\204name\345\257\274\350\207\264\345\211\215\347\253\257\346\211\251\345\261\225\344\270\215\347\224\237\346\225\210.md
... | ... | @@ -0,0 +1,3 @@ |
1 | +前端会使用菜单的 name 生成一个唯一 ID。然后根据 ID 进行扩展。 |
|
2 | + |
|
3 | +如果后端把菜单种子数据的 name 修改了。会导致前端生成的 ID 发生变化。从而导致前端扩展失效。 |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\205\263\344\272\216\351\207\215\346\226\260\346\211\251\345\261\225\345\274\225\346\223\216\345\255\227\346\256\265tenant_id\346\263\250\346\204\217.txt
... | ... | @@ -0,0 +1,4 @@ |
1 | +tenant_id字段是引擎内置字段,如果需要进行扩展,请保证指向同一个表的多个模型扩展tenant_id的属性一致; |
|
2 | +如果A,B两个模型都指向同一个数据库表,A模型扩展了tenant_id字段,定义为varchar(64),B模型没有扩展tenant_id字段,则默认为varchar(240), |
|
3 | +启动时,AB两个类的加载是不存在顺序先后关系的,因此可能出现,A模型先将tenant_id字段改成varchar(64),然后B模型再将tenant_id字段改成 |
|
4 | +varchar(240),如此反复,如果数据量大,更新已经存在的表的结构,耗时巨大,导致项目启动超时,或长时间等待 |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\210\206\345\270\203\345\274\217id\347\224\237\346\210\220\344\274\230\345\214\226.md
... | ... | @@ -0,0 +1,150 @@ |
1 | +#### 分布式id生成器优化 |
|
2 | + |
|
3 | +#### 已有实现 |
|
4 | + |
|
5 | +- 核心代码 |
|
6 | +```java |
|
7 | + |
|
8 | +public synchronized long nextId() { |
|
9 | + long timestamp = timeGen(); |
|
10 | + |
|
11 | + // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 |
|
12 | + if (timestamp < lastTimestamp) { |
|
13 | + throw new RuntimeException( |
|
14 | + String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", |
|
15 | + lastTimestamp - timestamp)); |
|
16 | + } |
|
17 | + |
|
18 | + // 如果是同一时间生成的,则进行毫秒内序列 |
|
19 | + if (lastTimestamp == timestamp) { |
|
20 | + sequence = (sequence + 1) & sequenceMask; |
|
21 | + // 毫秒内序列溢出 |
|
22 | + if (sequence == 0) { |
|
23 | + // 阻塞到下一个毫秒,获得新的时间戳 |
|
24 | + timestamp = tilNextMillis(lastTimestamp); |
|
25 | + } |
|
26 | + } |
|
27 | + // 时间戳改变,毫秒内序列重置 |
|
28 | + else { |
|
29 | + sequence = 0L; |
|
30 | + } |
|
31 | + |
|
32 | + // 上次生成ID的时间截 |
|
33 | + lastTimestamp = timestamp; |
|
34 | + |
|
35 | + // 移位并通过或运算拼到一起组成64位的ID |
|
36 | + return ((timestamp - twepoch) << timestampLeftShift) |
|
37 | + | (datacenterId << datacenterIdShift) |
|
38 | + | (workerId << workerIdShift) |
|
39 | + | sequence; |
|
40 | +} |
|
41 | +``` |
|
42 | +由上述的代码可知,本质上生成一个分布式id主要包括3段:41bit时间戳 + 10bit机器标识(这里的实现是5bit的机器码+5bit数据中心码)+ 12bit的序列号 |
|
43 | +[[/uploads/Home/id_generator_img.png]] |
|
44 | + |
|
45 | +- 已有实现的问题 |
|
46 | + - 没有使用10bit机器标识。目前的机器码和数据中心码都是写死为1,在所有场景下这10bit完全一样,如果在分布式环境下,遇到一些机器时间漂移的情况,虽然趋势shi递增,但不是绝对递增,那么很可能生成重复的id; |
|
47 | + - 没有充分利用序列号。上述代码遇到不是在同一时间毫秒内,则直接写死序列号为0.同样地很大概率会导致最后的序列号都是0,如果在分布式环境,机器时间漂移,依然容易产生重复的id; |
|
48 | + ``` |
|
49 | + // 时间戳改变,毫秒内序列重置 |
|
50 | + else { |
|
51 | + sequence = 0L; |
|
52 | + } |
|
53 | + ``` |
|
54 | + |
|
55 | + - 对系统时钟回退的误差没有容忍度,而是直接抛出异常 |
|
56 | + |
|
57 | +#### 测试 |
|
58 | +由上述可知,只要时间戳一样,那么生成的id就是一样的,经过测试确实也是如此。 |
|
59 | +```java |
|
60 | + /** |
|
61 | + * 返回以毫秒为单位的当前时间 |
|
62 | + * |
|
63 | + * @return 当前时间(毫秒) |
|
64 | + */ |
|
65 | + protected long timeGen() { |
|
66 | + return twepoch + 1; |
|
67 | + // return System.currentTimeMillis(); |
|
68 | + } |
|
69 | +``` |
|
70 | +为了简单,直接修改timeGen函数,让其返回时间一样,则生成的id都一样。 |
|
71 | + |
|
72 | +[[/uploads/Home/id_gen_test.png]] |
|
73 | + |
|
74 | +#### 优化 |
|
75 | +- 生成机器id。通过获取机器MAC地址来生成 |
|
76 | + ```java |
|
77 | + /** |
|
78 | + * 获取机器ID,使用进程ID配合数据中心ID生成<br> |
|
79 | + * 机器依赖于本进程ID或进程名的Hash值。 |
|
80 | + * |
|
81 | + * <p> |
|
82 | + * 此算法来自于mybatis-plus#Sequence |
|
83 | + * </p> |
|
84 | + * |
|
85 | + * @param datacenterId 数据中心ID |
|
86 | + * @param maxWorkerId 最大的机器节点ID |
|
87 | + * @return ID |
|
88 | + */ |
|
89 | + public static long getWorkerId(long datacenterId, long maxWorkerId) { |
|
90 | + final StringBuilder mpid = new StringBuilder(); |
|
91 | + mpid.append(datacenterId); |
|
92 | + try { |
|
93 | + final String processName = ManagementFactory.getRuntimeMXBean().getName(); |
|
94 | + if (StringUtils.isBlank(processName)) { |
|
95 | + throw new Exception("Process name is blank!"); |
|
96 | + } |
|
97 | + final int atIndex = processName.indexOf('@'); |
|
98 | + int pid; |
|
99 | + if (atIndex > 0) { |
|
100 | + pid = Integer.parseInt(processName.substring(0, atIndex)); |
|
101 | + } else { |
|
102 | + pid = processName.hashCode(); |
|
103 | + } |
|
104 | + mpid.append(pid); |
|
105 | + |
|
106 | + } catch (Exception ex) { |
|
107 | + //ignore |
|
108 | + } |
|
109 | + /* |
|
110 | + * MAC + PID 的 hashcode 获取16个低位 |
|
111 | + */ |
|
112 | + return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1); |
|
113 | + } |
|
114 | + ``` |
|
115 | +- 生成数据中心id。通过获取进程id,并配合机器id来生成 |
|
116 | + ```java |
|
117 | + /** |
|
118 | + * 获取数据中心ID<br> |
|
119 | + * 数据中心ID依赖于本地网卡MAC地址。 |
|
120 | + * <p> |
|
121 | + * 此算法来自于mybatis-plus#Sequence |
|
122 | + * </p> |
|
123 | + * |
|
124 | + * @param maxDatacenterId 最大的中心ID |
|
125 | + * @return 数据中心ID |
|
126 | + */ |
|
127 | + public static long getDataCenterId(long maxDatacenterId) { |
|
128 | + if(maxDatacenterId == Long.MAX_VALUE){ |
|
129 | + maxDatacenterId -= 1; |
|
130 | + } |
|
131 | + long id = 1L; |
|
132 | + SystemInfo systemInfo = new SystemInfo(); |
|
133 | + String str = getDeviceId(systemInfo); |
|
134 | + byte[] mac = str.getBytes(); |
|
135 | + if (null != mac) { |
|
136 | + id = ((0x000000FF & (long) mac[mac.length - 2]) |
|
137 | + | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6; |
|
138 | + id = id % (maxDatacenterId + 1); |
|
139 | + } |
|
140 | + |
|
141 | + return id; |
|
142 | + } |
|
143 | + ``` |
|
144 | + |
|
145 | +- 充分利用序列号段,对于不在同一毫秒内的id,随机生成一个序列号 |
|
146 | + ```java |
|
147 | + public static long randomLong(final long limitExclude) { |
|
148 | + return ThreadLocalRandom.current().nextLong(limitExclude); |
|
149 | + } |
|
150 | + ``` |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\210\206\351\241\265\346\235\241\346\225\260\344\270\216\350\241\250\346\240\274\346\200\273\346\225\260\344\270\215\344\270\200\350\207\264.md
... | ... | @@ -0,0 +1,16 @@ |
1 | +前端查询表格数据的时候,会分别调用 `search` 和 `count` 服务。 |
|
2 | + |
|
3 | +如果重写了 `search` 服务,修改了查询条件,就需要把 `count` 服务也一并重写。 |
|
4 | + |
|
5 | +```java |
|
6 | +public List<Model> search(Filter filter, List<String> properties, Integer limit, Integer offset, String order) { |
|
7 | + |
|
8 | +} |
|
9 | + |
|
10 | +public Long count(Filter filter) { |
|
11 | + |
|
12 | +} |
|
13 | +``` |
|
14 | + |
|
15 | +其实这就是继承带来的问题,所以如果你继承某个父类,一定要对这个父类的接口实现非常清楚。 |
|
16 | +具体可以参考 [effective java 第18条](https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/dev/Chapter-4/Chapter-4-Item-18-Favor-composition-over-inheritance.md) |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\210\233\345\273\272\350\241\250\345\244\261\350\264\245
\345\270\270\350\247\201\351\227\256\351\242\230/\345\210\233\345\273\272\350\241\250\345\244\261\350\264\245Invliad-default-vlalue-for_update_date.md
... | ... | @@ -0,0 +1,63 @@ |
1 | +# 出错现象 |
|
2 | + |
|
3 | +数据库使用的是 MySQL。 |
|
4 | + |
|
5 | +引擎启动的时候会自动进行建表。创建表的时候,日志出现错误信息 1067 - Invliad default vlalue for `update_date`。 |
|
6 | + |
|
7 | +# 问题排查 |
|
8 | + |
|
9 | +使用数据库客户端连接数据库,查询数据库 sql_mode |
|
10 | + |
|
11 | +```sql |
|
12 | +select @@sql_mode |
|
13 | +``` |
|
14 | + |
|
15 | +查询结果中包含 NO_ZERO_IN_DATE、NO_ZERO_DATE |
|
16 | + |
|
17 | +> STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
|
18 | + |
|
19 | +查询变量 |
|
20 | + |
|
21 | +```sql |
|
22 | +show variables like 'explicit_defaults_for_timestamp'; |
|
23 | +``` |
|
24 | + |
|
25 | +查询结果如下 value 为 OFF |
|
26 | + |
|
27 | +> explicit_defaults_for_timestamp OFF |
|
28 | + |
|
29 | +# 问题简单描述 |
|
30 | + |
|
31 | +由于 sql_mode 含有 NO_ZERO_IN_DATE、NO_ZERO_DATE,就是日期和时间类型的字段,不能包含零时间,例如'0000-00-00 00:00:00' |
|
32 | + |
|
33 | +explicit_defaults_for_timestamp 为 OFF,会导致建表语句中,如果有多个 timestamp 字段,并且没有显式地指定为 NULL 或者默认值。MySQL 就会自动给第一个字段之后的字段,加上 DEFAULT '0000-00-00 00:00:0',这跟 NO_ZERO_DATE 是违背的,就会提示最开始的错误。 |
|
34 | + |
|
35 | +例如以下建表语句,在 explicit_defaults_for_timestamp 为 OFF 时报错。 |
|
36 | + |
|
37 | +```sql |
|
38 | +CREATE TABLE test( |
|
39 | + x int(11) NOT NULL AUTO_INCREMENT, |
|
40 | + y timestamp, |
|
41 | + z timestamp, |
|
42 | + w timestamp, |
|
43 | + PRIMARY KEY (x) |
|
44 | +) ENGINE=InnoDB; |
|
45 | +``` |
|
46 | + |
|
47 | +*注意:从 MySQL 8.0.2 开始,explicit_defaults_for_timestamp 默认为 ON。现在部署脚本,安装的 MySQL 版本是 8.0.0。* |
|
48 | + |
|
49 | +# 解决方案 |
|
50 | + |
|
51 | +修改 explicit_defaults_for_timestamp 为 ON |
|
52 | + |
|
53 | +在 Linux 系统上,MySQL 的配置文件通常位于 /etc/my.cnf 或 /etc/mysql/my.cnf;在 Windows 系统上,通常位于 C:\Program Files\MySQL\MySQL Server X.X\my.ini。 |
|
54 | + |
|
55 | +找到配置文件后,使用文本编辑器打开,并找到 [mysqld] 部分。在该部分添加或修改以下行: |
|
56 | + |
|
57 | +explicit_defaults_for_timestamp = ON |
|
58 | + |
|
59 | +# 参考资料 |
|
60 | + |
|
61 | +1. [https://cloud.baidu.com/doc/RDS/s/Ejy5gwvug](https://cloud.baidu.com/doc/RDS/s/Ejy5gwvug) |
|
62 | + |
|
63 | +2. [https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_explicit_defaults_for_timestamp](https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_explicit_defaults_for_timestamp) |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\210\251\346\272\220SMI-\346\225\260\346\215\256\351\233\206\346\250\241\345\236\213\346\211\251\345\261\225\351\227\256\351\242\230\346\216\222\346\237\245.md
... | ... | @@ -0,0 +1,65 @@ |
1 | +# 利源SMI-数据集模型继承扩展调用问题排查 |
|
2 | +## 1. 问题描述 |
|
3 | + 项目:利源项目 |
|
4 | +【利源 测试/生产环境】-SMI应用点击菜单 数据源管理---> 数据集 ,筛选条件选择 类型 type=SQL时,调用查询结果没有数据 |
|
5 | +http://192.168.174.169:30666/iidp/smi/mi_base_root_menu/mi_base_data_source_menu/mi_base_data_set_menu |
|
6 | + |
|
7 | + |
|
8 | + |
|
9 | +## 2. 问题排查顺序: |
|
10 | + |
|
11 | +1. 在本地测试验证,SMI测试环境验证mijiuye环境验证,都是正常的,我们自己的测试环境问题不能复现。 |
|
12 | +2. 首页检查利源测试和生产环境mysql版本,字符集是否为 utf8mb4、排序规则是否为 utf8mb4_bin,确认都是正确的,排除数据库字符集问题。 |
|
13 | +2. 执行SQL排查:将利源环境的表导入到本地,执行SQL成功,SQL是正确的,排查SQL问题。 |
|
14 | +3. 检查SMI的代码:检查模型`mi_base_data_set`模型的`search`方法,`search`方法没有被重写,代码逻辑是正常的。刚开始怀疑是order by字段的问题,设置order by字段后,使用postman测试接口查询, mijiuye环境和mi环境都是正常的,无法复现,排除`order by`的问题。 |
|
15 | +4. 到这一步,字符集是正常的,sql是正常的,代码逻辑是正常的,请求参数是正常的, 一切都看着正常。 |
|
16 | +5. 最后只能怀疑是不是扩展的问题? |
|
17 | + |
|
18 | +点击菜单:开发者中心---模型管理,搜索模型`mi_base_data_set` ,查看模型的继承扩展于: |
|
19 | +```java |
|
20 | +smi-base-datasource.models.dataset.DataSet, |
|
21 | +smi-base-asset.models.asset.AbstractAsset, |
|
22 | +smi-base-datasource.models.dataset.ExcelSet, |
|
23 | +smi-base-datasource.models.dataset.KafkaSet, |
|
24 | +smi-base-datasource.models.dataset.WebSocketSet, |
|
25 | +smi-base-datasource.models.dataset.RabbitMQSet, |
|
26 | +smi-base-datasource.models.dataset.VideoSet, |
|
27 | +smi-base-datasource.models.dataset.JDBCSet, |
|
28 | +smi-base-datasource.models.dataset.ApiSet, |
|
29 | +iiot_datasource.ApiSet, |
|
30 | +iiot_datasource.DataSet, |
|
31 | +base.buss_model |
|
32 | + |
|
33 | +``` |
|
34 | + |
|
35 | + |
|
36 | + |
|
37 | + |
|
38 | +,发现IIOT的 iiot_datasource.DataSet扩展了SMI的`mi_base_data_set`,然后查看IIOT的代码 DataSet,发现 IIOT重写了`mi_base_data_set`的search方法,但是重写错误了,导致查询不出结果。 |
|
39 | + |
|
40 | + |
|
41 | +``` |
|
42 | +package com.sie.iiot.apps.datasource.model; |
|
43 | + |
|
44 | +@SDK.Model(name = "mi_base_data_set", parent = "mi_base_data_set") |
|
45 | +public class DataSet extends Model { |
|
46 | + public List<Map<String, Object>> search(RecordSet rs, Filter filter, List<String> properties, Integer limit, |
|
47 | + Integer offset, String order) {} |
|
48 | +``` |
|
49 | + |
|
50 | + |
|
51 | + |
|
52 | +## 3. 根本原因: |
|
53 | +IIOT重写了SMI 模型`mi_base_data_set`的search方法,但是重写错误了,导致查询不出结果。 |
|
54 | + |
|
55 | +## 4. 如何解决: |
|
56 | +IIOT修改扩展的search方法 |
|
57 | + |
|
58 | +## 5. 如何快速的定位继承扩展问题: |
|
59 | +1. 如何避免跨产品线的扩展,跨产品线的扩展是否需要专门的记录和维护? |
|
60 | +2. 是否可以提供final声明,标明这个方法或者模型不允许扩展。 |
|
61 | +3. 如何快速的查看模型的继承链路,模型的扩展链路?方便快速的定位问题,现在查看模型的继承扩展不清晰。 |
|
62 | +4. 如果扩展了模型的方法,执行方法时,是否可以输出日志打印真正执行的是哪一个APP,哪一个类加载的方法,把具体的className输出? |
|
63 | + |
|
64 | + |
|
65 | + |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\220\216\347\253\257\345\246\202\344\275\225\344\275\277\347\224\250\345\274\225\346\223\216\350\207\252\345\270\246crud\345\237\272\347\241\200\346\226\271\346\263\225\345\256\236\347\216\260\346\237\245\350\257\242\357\274\214\346\226\260\345\242\236\357\274\214\346\233\264\346\226\260\357\274\214\345\210\240\351\231\244.txt
... | ... | @@ -0,0 +1,89 @@ |
1 | +/** |
|
2 | + * crud基础方法的调用方式 |
|
3 | + * call,callSuper,new Object(),super方式调用 |
|
4 | + * filter 过滤器 |
|
5 | + * properties 属性字段,例如要查询id ,name 则properties.add("id");properties.add("name"),查询所有字段用"*",即properties.add("*") |
|
6 | + * limit 分页参数 记录数,设置为 0 或者 nul,表示查询所有纪录 |
|
7 | + * offset 分页参数 初始位置, 设置为 0 或者 nul,表示查询所有纪录 |
|
8 | + * order 排序,例如用年龄字段排序,即"order age asc"或者 "order age desc"; |
|
9 | + */ |
|
10 | + public void test(){ |
|
11 | + |
|
12 | + //模型名 |
|
13 | + String modelName = "TestUser"; |
|
14 | + |
|
15 | + Filter filter = new Filter(); |
|
16 | + List<String> properties = new ArrayList<>(); |
|
17 | + int limit = 0; |
|
18 | + int offset = 0; |
|
19 | + String order = null; |
|
20 | + |
|
21 | + |
|
22 | + //***************************************查询******************************* |
|
23 | + //call方式调用 |
|
24 | + List<Map<String,Object>> list1 = (List<Map<String, Object>>) this.getMeta().get(modelName).call("search",filter,properties,limit,offset,order); |
|
25 | + |
|
26 | + //callSuper方式调用 |
|
27 | + List<Map<String,Object>> list2 = (List<Map<String, Object>>) this.getMeta().get(modelName).callSuper(TestUser.class,"search",filter,properties,limit,offset,order); |
|
28 | + |
|
29 | + //new方式调用,最终具体调用还是调用callSuper方式实现 |
|
30 | + List<TestUser> list3 = new TestUser().search(filter,properties,limit,offset,order); |
|
31 | + |
|
32 | + //super方式调用,此为java的继承调用,继承自BaseModel,最终具体调用还是调用callSuper方式实现 |
|
33 | + List<TestUser> list4 = super.search(filter,properties,limit,offset,order); |
|
34 | + |
|
35 | + |
|
36 | + |
|
37 | + |
|
38 | + //***************************************新增******************************* |
|
39 | + List<Map<String, Object>> valuesList = new ArrayList<>(); |
|
40 | + |
|
41 | + //call方式调用 |
|
42 | + RecordSet recordSet1 = (RecordSet) this.getMeta().get(modelName).call("create",valuesList); |
|
43 | + |
|
44 | + //callSuper方式调用 |
|
45 | + RecordSet recordSet2 = (RecordSet) this.getMeta().get(modelName).callSuper(TestUser.class,"create",valuesList); |
|
46 | + |
|
47 | + //new方式调用,最终具体调用还是调用callSuper方式实现 |
|
48 | + TestUser user = new TestUser(); |
|
49 | + user.put("name","zhangsan"); |
|
50 | + user.create(); |
|
51 | + |
|
52 | + //super方式调用,此为java的继承调用,继承自BaseModel,最终具体调用还是调用callSuper方式实现 |
|
53 | + List<TestUser> userList = new ArrayList<>(); |
|
54 | + userList.add(user); |
|
55 | + super.batchCreate(userList); |
|
56 | + |
|
57 | + |
|
58 | + |
|
59 | + //***************************************更新******************************* |
|
60 | + Map<String, Object> value = new HashMap<>(); |
|
61 | + value.put("id","666"); |
|
62 | + value.put("name","张三"); |
|
63 | + //call方式调用 |
|
64 | + RecordSet recordSet3 = (RecordSet) this.getMeta().get(modelName).call("update",value); |
|
65 | + |
|
66 | + //callSuper方式调用 |
|
67 | + RecordSet recordSet4 = (RecordSet) this.getMeta().get(modelName).callSuper(TestUser.class,"update",value); |
|
68 | + |
|
69 | + //new方式调用,最终具体调用还是调用callSuper方式实现 |
|
70 | + user.put("name","zhangsan"); |
|
71 | + user.update(); |
|
72 | + |
|
73 | + |
|
74 | + |
|
75 | + //***************************************删除******************************* |
|
76 | + //根据id删除 |
|
77 | + RecordSet rs = this.getMeta().get(modelName); |
|
78 | + String id = "666"; |
|
79 | + rs.browse(id).delete(); |
|
80 | + |
|
81 | + List<String> ids = new ArrayList<>(); |
|
82 | + ids.add("666"); |
|
83 | + ids.add("777"); |
|
84 | + rs.browse(ids).delete(); |
|
85 | + |
|
86 | + TestUser user1 = new TestUser(); |
|
87 | + user1.put("id","666"); |
|
88 | + user1.delete(); |
|
89 | + } |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\220\221\345\271\263\345\217\260\344\272\272\345\221\230\346\217\220\351\227\256\344\271\213\345\211\215\347\232\204\345\207\206\345\244\207\345\273\272\350\256\256.md
... | ... | @@ -0,0 +1,9 @@ |
1 | +1、发生问题的背景描述 |
|
2 | + |
|
3 | + 是更换平台前端、后端,业务前端、后端,版本导致的问题,还是突发问题,是线上问题还是测试环境问题 |
|
4 | + |
|
5 | +2、自己定位问题的流程是怎样的,到哪里开始自己无法定位需要协助 |
|
6 | + |
|
7 | + |
|
8 | +3、期望什么时候得到解决 |
|
9 | + |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\220\257\345\212\250\345\212\240\351\200\237\351\205\215\347\275\256\350\257\264\346\230\216.md
... | ... | @@ -0,0 +1,23 @@ |
1 | +### IIDP平台可通过以下两种方式,提升基于idea工具的开发效率(暂不建议在正式环境中配置,因此默认配置分别是engine.model2ddl.mode=CREATE 和 seeder.sync = true,需要通过修改配置提升启动性能) |
|
2 | + |
|
3 | +### 1)ddl和种子数据配置修改能加快启动速度(模型修改的频率应该是很低的,应该在设计阶段做好) |
|
4 | + |
|
5 | + 要求:引擎版本升级到sie-snest-engine v2.2.7.RELEASE或以上 |
|
6 | + |
|
7 | + ddl配置参考如下可提升启动性能,不需要更新表结构(即模型未发生变化): |
|
8 | + |
|
9 | + `engine.model2ddl.mode=NONE` |
|
10 | + |
|
11 | + 忽略种子数据更新(三种类型种子数据,包括菜单、视图、业务数据种子数据),配置参考如下可提升启动性能: |
|
12 | + |
|
13 | + `seeder.sync = false` |
|
14 | + |
|
15 | + 如果在开发时需要调试视图,可以通过在开发者中心-》视图管理,进行修改后调试效果,无误后拷贝到views目录下,这样可以节省启动效率 |
|
16 | + |
|
17 | + **注意:**seeder.sync = false 还会影响权限点的同步,目前平台已经是通配符的方式对租户管理员进行授权,因此即便不同步权限点,也不会影响开发调试;如在正式环境,可能需要更细化的授权,该配置并不适用。 |
|
18 | + |
|
19 | + |
|
20 | +### 2)修改方法体内容,而不是修改签名或者增加方法的情况下,无需重新package即可调试 |
|
21 | + |
|
22 | + 重新编译发布修改java类操作 |
|
23 | + idea菜单Build->Recompile xxx.java |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\234\250\345\215\225\346\234\272\346\250\241\345\274\217\344\270\213\346\270\205\345\216\273\346\216\211app.install.jar.dir\351\205\215\347\275\256.md
... | ... | @@ -0,0 +1,7 @@ |
1 | +启动的时候提示报错 |
|
2 | + |
|
3 | +> 在单机模式下清去掉app.install.jar.dir配置 |
|
4 | + |
|
5 | +这个提示是因为引擎启动的时候没有找到 apps 目录,或者根据 app.install.jar.dir 找不到对应的 apps.json、base app。 |
|
6 | + |
|
7 | +常见原因是 IDEA 打开了项目的父级目录,应用启动的时候所在目录是项目的父级目录,导致根据相对路径找不到 apps 目录。 |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\244\215\345\210\266\346\211\251\345\261\225\350\217\234\345\215\225.md
... | ... | @@ -0,0 +1,88 @@ |
1 | +# 复制扩展菜单 |
|
2 | + |
|
3 | +## 实现功能 |
|
4 | + |
|
5 | +菜单添加扩展功能,一个app里的菜单可以修改其他app菜单的状态。 |
|
6 | + |
|
7 | +## 版本依赖 |
|
8 | + |
|
9 | +引擎 v2.2.2.RELEASE 以上 |
|
10 | + |
|
11 | +## 实现原理 |
|
12 | + |
|
13 | +添加mode、inherit_ids两个属性 |
|
14 | + |
|
15 | +| 字段 | 类型 | 描述 | 可选值 | 默认值 | |
|
16 | +| ----------- | ------ | ------------ | ---------------- | ------ | |
|
17 | +| mode | 字符串 | 模式 | primay\extension | primay | |
|
18 | +| inherit_ids | 字符串 | 扩展的菜单Id | 菜单Id | null | |
|
19 | + |
|
20 | +在下面两个app的菜单种子数据,如果app1和app2同时安装,app2的菜单会扩展修改app1的菜单。当卸载app2时,系统菜单会恢复到app1的菜单状态。 |
|
21 | + |
|
22 | +## 配置示例 |
|
23 | + |
|
24 | +app1的菜单种子数据 |
|
25 | + |
|
26 | +```json |
|
27 | +{ |
|
28 | + "menus": { |
|
29 | + "app1_menu1": { |
|
30 | + "sequence": "1", |
|
31 | + "name": "menu1", |
|
32 | + "display_name": "主菜单" |
|
33 | + }, |
|
34 | + "app1_menu2": { // 在app2的菜单中会复制这个菜单 |
|
35 | + "name": "menu2", |
|
36 | + "sequence": "1", |
|
37 | + "display_name": "子菜单1", |
|
38 | + "view": "from,grid,search", |
|
39 | + "model": "model1", |
|
40 | + "parent_ids": { |
|
41 | + "@ref": "app1_menu1" |
|
42 | + } |
|
43 | + }, |
|
44 | + "app1_menu3": { // 在app2的菜单中会扩展这个菜单 |
|
45 | + "name": "menu3", |
|
46 | + "sequence": "2", |
|
47 | + "display_name": "子菜单2", |
|
48 | + "view": "from,grid,search", |
|
49 | + "model": "model2", |
|
50 | + "parent_ids": { |
|
51 | + "@ref": "app1_menu1" |
|
52 | + } |
|
53 | + } |
|
54 | + } |
|
55 | +} |
|
56 | +``` |
|
57 | + |
|
58 | +app2的菜单种子数据 |
|
59 | + |
|
60 | +```json |
|
61 | +{ |
|
62 | + "menus": { |
|
63 | + "app2_menu1": { |
|
64 | + "sequence": "1", |
|
65 | + "name": "menu4", |
|
66 | + "display_name": "主菜单2" |
|
67 | + }, |
|
68 | + "app2_menu2": { |
|
69 | + "name": "menu5", //复制菜单menu2,放到主菜单2 |
|
70 | + "sequence": "1", //目前,分布式环境下,两个app要装在同一个容器内才有效 |
|
71 | + "display_name": "子菜单1", |
|
72 | + "view": "from,grid,search", |
|
73 | + "model": "model1", |
|
74 | + "parent_ids": { |
|
75 | + "@ref": "app2_menu1" |
|
76 | + } |
|
77 | + }, |
|
78 | + "app2_menu3": { |
|
79 | + "active": false, // 隐藏该菜单 |
|
80 | + "mode": "extension", |
|
81 | + "inherit_ids": { |
|
82 | + "@ref": "app1_menu3" // 扩展菜单app1_menu3 |
|
83 | + } |
|
84 | + } |
|
85 | + } |
|
86 | +} |
|
87 | +``` |
|
88 | + |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\246\202\344\275\225\346\217\220\345\217\226\344\270\200\344\270\252Filter\344\270\255\347\232\204\350\277\207\346\273\244\346\235\241\344\273\266\345\210\260\346\226\260\347\232\204Filter\344\270\255\357\274\237.txt
... | ... | @@ -0,0 +1,5 @@ |
1 | + |
|
2 | +使用 Filter.parse方法获取新的filter |
|
3 | +例: |
|
4 | +Filter.FilterOp filterOp = filter.getFilterOp("name"); |
|
5 | +Filter f = Filter.parse(Arrays.asList(filterOp)); |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\256\211\350\243\205xxl-job\345\220\216IDEA\346\227\227\350\210\260\347\211\210\345\220\257\345\212\250\346\217\220\347\244\272javax.management.InstanceAlreadyExistsException.md
... | ... | @@ -0,0 +1,6 @@ |
1 | +参考 [https://blog.csdn.net/bin_zi_123/article/details/103135584](https://blog.csdn.net/bin_zi_123/article/details/103135584) |
|
2 | + |
|
3 | +如果加入了 xxl-job app,项目启动的时候会启动两个 SpringBoot 应用。如果使用了 IDEA 旗舰版,需要关闭掉 JMX |
|
4 | + |
|
5 | +[[/uploads/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98/%E5%AE%89%E8%A3%85xxl-job%E5%90%8EIDEA%E6%97%97%E8%88%B0%E7%89%88%E5%90%AF%E5%8A%A8%E6%8F%90%E7%A4%BAjavax.management.InstanceAlreadyExistsException/2019111909461724.jpeg]] |
|
6 | + |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\274\200\345\217\221\351\205\215\347\275\256\344\272\247\345\223\201\347\272\277\345\222\214\350\217\234\345\215\225\345\233\276\346\240\207.md
... | ... | @@ -0,0 +1,67 @@ |
1 | +# 开发配置产品线和菜单图标 |
|
2 | +配置方式和view通过staticDownload的action类型下载模板配置相似,只是在前面添加"/fileSystem" |
|
3 | + |
|
4 | +## 下载模板配置回顾 |
|
5 | +```json |
|
6 | +"tbar": [ |
|
7 | + { |
|
8 | + "name": "下载模板", |
|
9 | + "action": "staticDownload", |
|
10 | + "auth": "staticDownload", |
|
11 | + "filename": "/apps/edo/巡检定标设备导入模板.xlsx" |
|
12 | + } |
|
13 | + ] |
|
14 | +``` |
|
15 | +filename的value值为在minio上的路径 |
|
16 | + |
|
17 | +# 产品线配置图标 |
|
18 | +## 1、在minio上传图标文件 |
|
19 | +建议每个app在apps下有自己的路径,避免app相同文件名的冲突,如上传完的路径为"/apps/edo/icon1.png" |
|
20 | + |
|
21 | +## 2、在 app.json 填入配置 |
|
22 | +产品线图标属性是productIcon,给它赋值,上传的路径前添加"/fileSystem",如下: |
|
23 | +```json |
|
24 | +{ |
|
25 | + "name": "onlineIDE", |
|
26 | + "displayName": "在线IDE", |
|
27 | + "author": "onlineIDE", |
|
28 | + "company": "onlineIDE", |
|
29 | + "category": "onlineIDE", |
|
30 | + "product": "在线IDE", |
|
31 | + "description": "onlineIDE", |
|
32 | + "summary": "onlineIDE", |
|
33 | + "type": "SDK", |
|
34 | + "tag": "master", |
|
35 | + "version": "0.0.1", |
|
36 | + "resolved": "com.sie.ide", |
|
37 | + "dependencies": [], |
|
38 | + "application": true, |
|
39 | + "productIcon": "/fileSystem/apps/edo/icon1.png", |
|
40 | + "license": "LGPL 3.0" |
|
41 | +} |
|
42 | +``` |
|
43 | + |
|
44 | +## 3、注意点 |
|
45 | +需刚开始开发app时,就设置productIcon,否则后面设置再重启,不会生效,因为和种子数据不同,app更新时产品线信息不会改动,产品线的修改只能通过界面手动修改(“应用管理”-->“产品线分类”-->编辑-->图标) |
|
46 | + |
|
47 | +# 菜单配置图标 |
|
48 | +## 1、在minio上传图标文件 |
|
49 | +建议每个app在apps下有自己的路径,避免app相同文件名的冲突,如上传完的路径为"/apps/edo/icon1.png" |
|
50 | + |
|
51 | +## 2、在 menu的json文件 填入配置信息 |
|
52 | +菜单图标属性是icon,给它赋值,上传的路径前添加"/fileSystem",如下: |
|
53 | +```json |
|
54 | +"project_menu": { |
|
55 | + "name": "project_menu", |
|
56 | + "display_name": "工程管理", |
|
57 | + "model": "ide_project", |
|
58 | + "view": "ide_project_grid,ide_project_form,ide_project_search", |
|
59 | + "active": true, |
|
60 | + "sequence": 2, |
|
61 | + "icon": "/fileSystem/apps/edo/icon2.png" |
|
62 | + } |
|
63 | +``` |
|
64 | + |
|
65 | +## 3、注意点 |
|
66 | +菜单的修改,需要重置种子数据才生效 |
|
67 | + |
\345\270\270\350\247\201\351\227\256\351\242\230/\345\277\253\351\200\237\345\220\257\345\212\250\351\205\215\347\275\256.md
... | ... | @@ -0,0 +1,23 @@ |
1 | +### IIDP平台可通过以下两种方式,提升基于idea工具的开发效率(暂不建议在正式环境中配置,因此默认配置分别是engine.model2ddl.mode=CREATE 和 seeder.sync = true,需要通过修改配置提升启动性能) |
|
2 | + |
|
3 | +### 1)ddl和种子数据配置修改能加快启动速度(模型修改的频率应该是很低的,应该在设计阶段做好) |
|
4 | + |
|
5 | + 要求:引擎版本升级到sie-snest-engine v2.2.7.RELEASE或以上 |
|
6 | + |
|
7 | + ddl配置参考如下可提升启动性能,不需要更新表结构(即模型未发生变化): |
|
8 | + |
|
9 | + `engine.model2ddl.mode=NONE` |
|
10 | + |
|
11 | + 忽略种子数据更新(三种类型种子数据,包括菜单、视图、业务数据种子数据),配置参考如下可提升启动性能: |
|
12 | + |
|
13 | + `seeder.sync = false` |
|
14 | + |
|
15 | + 如果在开发时需要调试视图,可以通过在开发者中心-》视图管理,进行修改后调试效果,无误后拷贝到views目录下,这样可以节省启动效率 |
|
16 | + |
|
17 | + **注意:**seeder.sync = false 还会影响权限点的同步,目前平台已经是通配符的方式对租户管理员进行授权,因此即便不同步权限点,也不会影响开发调试;如在正式环境,可能需要更细化的授权,该配置并不适用。 |
|
18 | + |
|
19 | + |
|
20 | +### 2)修改方法体内容,而不是修改签名或者增加方法的情况下,无需重新package即可调试 |
|
21 | + |
|
22 | + 重新编译发布修改java类操作 |
|
23 | + idea菜单Build->Recompile xxx.java |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\346\211\213\345\212\250\346\216\247\345\210\266\345\265\214\345\245\227\344\272\213\345\212\241.txt
... | ... | @@ -0,0 +1,53 @@ |
1 | +1.目前只支持手动控制嵌套事务 |
|
2 | +2.事务控制由上下文BaseContextHandler中的mata控制 |
|
3 | + (1) //获取meta |
|
4 | + Meta meta = BaseContextHandler.getMeta(); |
|
5 | + (2) //新建一个事务点,oracle事务点名称规则有限制可以使用String point1 = "SP"+IdGenerator.nextId(); |
|
6 | + String point1 = UUID.randomUUID().toString(); |
|
7 | + (3) //子事务的回滚或者提交,如不主动提交则跟随后续meta.commit一起提交 |
|
8 | + try { |
|
9 | + meta.setSavepoint(point1); |
|
10 | + validates(v); |
|
11 | + //执行成功,直接commit(会提交整个point及之前的代码事务) |
|
12 | + meta.commit(); |
|
13 | + }catch (Exception e){ |
|
14 | + //失败,可以回滚当前事务点的代码 |
|
15 | + meta.rollback(point1); |
|
16 | + } |
|
17 | + |
|
18 | + (4)可运行demo: |
|
19 | + public RecordSet create(List<Map<String, Object>> valuesList) throws SQLException { |
|
20 | + //获取meta |
|
21 | + Meta meta = BaseContextHandler.getMeta(); |
|
22 | + List<Map<String, Object>> v = new ArrayList<>(); |
|
23 | + Map<String,Object> m = valuesList.get(0); |
|
24 | + m.remove("upload_field_images"); |
|
25 | + v.add(m); |
|
26 | + RecordSet rs = (RecordSet) this.getMeta().get("TestUser").callSuper(TestUser.class,"create",v); |
|
27 | + //新建一个事务点,oracle事务点名称规则有限制可以使用String point1 = "SP"+IdGenerator.nextId(); |
|
28 | + String point1 = UUID.randomUUID().toString(); |
|
29 | + try { |
|
30 | + meta.setSavepoint(point1); |
|
31 | + validates(v); |
|
32 | + //执行成功,直接commit(会提交整个point及之前的代码事务) |
|
33 | + meta.commit(); |
|
34 | + }catch (Exception e){ |
|
35 | + //失败,可以回滚当前事务点的代码,不影响其他代码事务执行 |
|
36 | + meta.rollback(point1); |
|
37 | + } |
|
38 | + //commit事务后,后续操作会开启新事务 |
|
39 | + meta.commit(); |
|
40 | + if(StringUtils.isBlank((String) valuesList.get(0).get("phone"))){ |
|
41 | + throw new RuntimeException("手机号码不能为空。。。。"); |
|
42 | + } |
|
43 | + rs = (RecordSet) this.getMeta().get("TestUser").callSuper(TestUser.class,"create",v); |
|
44 | + return rs; |
|
45 | + } |
|
46 | + |
|
47 | + |
|
48 | + public void validates(List<Map<String, Object>> valuesList){ |
|
49 | + if(StringUtils.isBlank((String) valuesList.get(0).get("phone"))){ |
|
50 | + throw new RuntimeException("手机号码不能为空。。。。"); |
|
51 | + } |
|
52 | + this.getMeta().get("TestUser").callSuper(TestUser.class,"create",valuesList); |
|
53 | + } |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\346\216\222\346\237\245\351\227\256\351\242\230\345\211\215\347\232\204\346\216\222\346\237\245\346\270\205\345\215\225.md
... | ... | @@ -0,0 +1,26 @@ |
1 | +# 排查问题前的排查清单 |
|
2 | + |
|
3 | +# 版本 |
|
4 | + |
|
5 | +1. base.json 有没有配置多租户 APP? |
|
6 | + |
|
7 | +# 配置 |
|
8 | + |
|
9 | +## 多租户 |
|
10 | + |
|
11 | +1. base.json 有没有配置多租户 APP |
|
12 | + |
|
13 | +## 分布式 |
|
14 | + |
|
15 | +查看 application-dev.properties |
|
16 | + |
|
17 | +1. hazelcast.network.join.auto-detection.enabled = true # 是否开启自动组网 |
|
18 | +2. hazelcast.network.join.multicast.enabled = false |
|
19 | +3. hazelcast.network.join.kubernetes.enabled = true |
|
20 | +4. hazelcast.network.join.kubernetes.namespace = iiot # 命名空间是否正确? |
|
21 | + |
|
22 | +# 数据库 |
|
23 | + |
|
24 | +1. 字符集是否为 utf8mb4 |
|
25 | +2. 排序规则是否为 utf8mb4_bin |
|
26 | + |
\345\270\270\350\247\201\351\227\256\351\242\230/\346\226\271\346\263\225\351\207\215\350\275\275\345\257\274\350\207\264\346\216\245\345\217\243\346\226\207\346\241\243\344\270\215\346\230\276\347\244\272.md
... | ... | @@ -0,0 +1,3 @@ |
1 | +# 方法重载导致接口文档不显示 |
|
2 | + |
|
3 | +IIDP 不支持方法重载。因此方法名注意不要跟 get 方法同名。同一个模型不要出现同名方法,否则引擎解析不成功。 |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\346\227\245\345\277\227\351\227\256\351\242\230\357\274\232\351\200\232\350\277\207\345\260\206\351\207\215\350\246\201\346\227\245\345\277\227\345\206\231\345\205\245\346\226\207\344\273\266\350\247\243\345\206\263idea\346\227\245\345\277\227\350\242\253\350\246\206\347\233\226\347\232\204\351\227\256\351\242\230.md
... | ... | @@ -0,0 +1,96 @@ |
1 | +合理配置日志级别,将重要日志写入文件,通过查看文件中错误信息,不会产生idea控制台错误日志被滚动覆盖问题 |
|
2 | +server下的logback.xml可参考配置如下: |
|
3 | +```xml |
|
4 | +<?xml version="1.0" encoding="UTF-8"?> |
|
5 | +<!-- 配置文件修改时重新加载,默认true --> |
|
6 | +<configuration scan="true"> |
|
7 | + |
|
8 | + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> |
|
9 | + <encoder> |
|
10 | + <pattern><![CDATA[%n[%d{yyyy-MM-dd HH:mm:ss.SSS}] [level: %p] [Thread: %t] [ Class:%c >> Method: %M:%L ]%n%p:%m%n]]></pattern> |
|
11 | + </encoder> |
|
12 | + </appender> |
|
13 | + <appender name="metalogfile" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
|
14 | + <encoder> |
|
15 | + <pattern><![CDATA[%n[%d{yyyy-MM-dd HH:mm:ss.SSS}] [level: %p] [Thread: %t] [ Class:%c >> Method: %M:%L ]%n%p:%m%n]]></pattern> |
|
16 | + </encoder> |
|
17 | + <file>logs/metalogfile.log</file> |
|
18 | + <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
|
19 | + <fileNamePattern>logs/metalogfile.-%d{yyyyMMdd}.%i.log</fileNamePattern> |
|
20 | + <!-- 每天一个日志文件,当天的日志文件超过10MB时,生成新的日志文件,当天的日志文件数量超过totalSizeCap/maxFileSize,日志文件就会被回滚覆盖。 --> |
|
21 | + <maxFileSize>10MB</maxFileSize> |
|
22 | + <maxHistory>30</maxHistory> |
|
23 | + <totalSizeCap>10GB</totalSizeCap> |
|
24 | + </rollingPolicy> |
|
25 | + </appender> |
|
26 | + |
|
27 | + <appender name="sqllogfile" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
|
28 | + <encoder> |
|
29 | + <pattern>%d %-5level [%thread] %logger{0}: %msg%n</pattern> |
|
30 | + </encoder> |
|
31 | + <file>logs/sqllogfile.log</file> |
|
32 | + <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
|
33 | + <fileNamePattern>logs/sqllogfile.-%d{yyyyMMdd}.%i.log</fileNamePattern> |
|
34 | + <!-- 每天一个日志文件,当天的日志文件超过10MB时,生成新的日志文件,当天的日志文件数量超过totalSizeCap/maxFileSize,日志文件就会被回滚覆盖。 --> |
|
35 | + <maxFileSize>10MB</maxFileSize> |
|
36 | + <maxHistory>30</maxHistory> |
|
37 | + <totalSizeCap>10GB</totalSizeCap> |
|
38 | + </rollingPolicy> |
|
39 | + </appender> |
|
40 | + |
|
41 | + <appender name="businesslogfile" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
|
42 | + <encoder> |
|
43 | + <pattern><![CDATA[%n[%d{yyyy-MM-dd HH:mm:ss.SSS}] [level: %p] [Thread: %t] [ Class:%c >> Method: %M:%L ]%n%p:%m%n]]></pattern> |
|
44 | + </encoder> |
|
45 | + <file>logs/businesslogfile.log</file> |
|
46 | + <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
|
47 | + <fileNamePattern>logs/businesslogfile.-%d{yyyyMMdd}.%i.log</fileNamePattern> |
|
48 | + <!-- 每天一个日志文件,当天的日志文件超过10MB时,生成新的日志文件,当天的日志文件数量超过totalSizeCap/maxFileSize,日志文件就会被回滚覆盖。 --> |
|
49 | + <maxFileSize>10MB</maxFileSize> |
|
50 | + <maxHistory>30</maxHistory> |
|
51 | + <totalSizeCap>10GB</totalSizeCap> |
|
52 | + </rollingPolicy> |
|
53 | + </appender> |
|
54 | + |
|
55 | + |
|
56 | + <appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> |
|
57 | + <encoder> |
|
58 | + <pattern><![CDATA[%n[%d{yyyy-MM-dd HH:mm:ss.SSS}] [level: %p] [Thread: %t] [ Class:%c >> Method: %M:%L ]%n%p:%m%n]]></pattern> |
|
59 | + </encoder> |
|
60 | + <file>logs/root.log</file> |
|
61 | + <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
|
62 | + <fileNamePattern>logs/root.-%d{yyyyMMdd}.%i.log</fileNamePattern> |
|
63 | + <!-- 每天一个日志文件,当天的日志文件超过10MB时,生成新的日志文件,当天的日志文件数量超过totalSizeCap/maxFileSize,日志文件就会被回滚覆盖。 --> |
|
64 | + <maxFileSize>10MB</maxFileSize> |
|
65 | + <maxHistory>30</maxHistory> |
|
66 | + <totalSizeCap>10GB</totalSizeCap> |
|
67 | + </rollingPolicy> |
|
68 | + </appender> |
|
69 | + |
|
70 | + <!-- 以下定义的logger是一个过滤器链, appender-ref指定输出方式--> |
|
71 | + <!-- sql error日志 --> |
|
72 | + <logger name="sql_logger" level="ERROR" additivity="false"> |
|
73 | + <appender-ref ref="STDOUT"/> |
|
74 | + <appender-ref ref="sqllogfile"/> |
|
75 | + </logger> |
|
76 | + |
|
77 | + <!-- 引擎error日志 --> |
|
78 | + <logger name="com.sie.snest.engine" level="ERROR" additivity="false"> |
|
79 | + <appender-ref ref="STDOUT"/> |
|
80 | + <appender-ref ref="metalogfile"/> |
|
81 | + </logger> |
|
82 | + |
|
83 | + <!-- com包的error日志 --> |
|
84 | + <logger name="com" level="ERROR" additivity="false"> |
|
85 | + <appender-ref ref="STDOUT"/> |
|
86 | + <appender-ref ref="businesslogfile"/> |
|
87 | + </logger> |
|
88 | + |
|
89 | + <!-- 除com包之外的其他包debug日志 --> |
|
90 | + <root level="DEBUG"> |
|
91 | + <appender-ref ref="STDOUT"/> |
|
92 | + <appender-ref ref="LOG_FILE"/> |
|
93 | + </root> |
|
94 | +</configuration> |
|
95 | + |
|
96 | +``` |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\346\227\245\351\222\242\351\241\271\347\233\256\345\207\272\347\216\260\345\242\236\345\212\240\346\250\241\345\236\213index\347\264\242\345\274\225\357\274\214\346\225\260\346\215\256\345\272\223\346\262\241\346\234\211\346\226\260\345\242\236\347\264\242\345\274\225\346\210\220\345\212\237\347\216\260\350\261\241.txt
... | ... | @@ -0,0 +1,3 @@ |
1 | +新增索引不成功有以下可能 |
|
2 | +(1)数据量过多,索引耗时巨大,短时间内无法新增索引完成,并且会造成数据库锁表问题,建议如果已经存在数据的表,模型增加索引后,手动在数据库进行索引新增 |
|
3 | +(2)如果表中已经存在不符合索引唯一校验的数据,启动时新增索引会失败,打印类似日志Duplicate entry 'xxx' for key 'test_user.unique_name,因此在新增索引前建议先检查数据唯一性 |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\346\250\241\345\236\213\345\242\236\345\212\240\344\272\206\347\264\242\345\274\225\357\274\214\344\275\206\346\230\257\346\225\260\346\215\256\345\272\223\346\262\241\346\234\211\346\226\260\345\242\236\347\264\242\345\274\225\346\210\220\345\212\237.txt
... | ... | @@ -0,0 +1,3 @@ |
1 | +新增索引不成功有以下可能 |
|
2 | +(1)数据量过多,索引耗时巨大,短时间内无法新增索引完成,并且会造成数据库锁表问题,建议如果已经存在数据的表,模型增加索引后,手动在数据库进行索引新增 |
|
3 | +(2)如果表中已经存在不符合索引唯一校验的数据,启动时新增索引会失败,打印类似日志Duplicate entry 'xxx' for key 'test_user.unique_name,因此在新增索引前建议先检查数据唯一性 |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\346\250\241\345\236\213\346\224\257\346\214\201\350\275\257\345\210\240\351\231\244\351\205\215\347\275\256.txt
... | ... | @@ -0,0 +1,19 @@ |
1 | +1.模型配置支持软删除 |
|
2 | + |
|
3 | +@Model(isLogicDelete = Bool.True) |
|
4 | + |
|
5 | +在模型上声明软删除后 |
|
6 | +(1)模型自动增加deleteFlag字段,数据库表自动增加delete_flag字段 |
|
7 | +(2)模型如果标记为软删除,那原先的crud接口应自动采用软删除的接口,如 |
|
8 | +新增:软删除字段值为0 |
|
9 | +删除:软删除字段值更新为1 |
|
10 | +修改:无改变 |
|
11 | +查询:自动过滤软删除的记录 |
|
12 | +(3)unique校验和数据库索引indexes校验自动增加deleteFlag<>1条件过滤 |
|
13 | + |
|
14 | +2.注意事项 |
|
15 | +(1)软删除配置只支持业务模型 |
|
16 | +(2)软删除配置后由hardDelete(不支持重写)接口提供硬删除功能,参数和原删除方法delete一致 |
|
17 | +(3)如果存在indexes唯一索引,增加软删除后,需要开发者自行将数据库唯一索引,更改为普通索引 |
|
18 | +(4)软删除字段不受权限控制 |
|
19 | +(5)关联模型删除,例如A和B关联,删除A,自动删除B; A和B的删除是物理删除还是逻辑删除取决A和B模型本身是否软删除模型,A和B互不影响 |
\345\270\270\350\247\201\351\227\256\351\242\230/\347\247\215\345\255\220\346\225\260\346\215\256\344\270\255\346\230\216\346\230\216\351\205\215\347\275\256\344\272\206\350\217\234\345\215\225\357\274\214\347\224\250\347\256\241\347\220\206\345\221\230\347\231\273\351\231\206\345\234\250\351\241\265\351\235\242\344\270\255\350\217\234\345\215\225\346\211\276\344\270\215\345\210\260.md
... | ... | @@ -0,0 +1,5 @@ |
1 | +1、查看日志,看是否是菜单种子数据解析错误,比如模型名、属性名写错,json格式有问题等,该错误可自行解决 |
|
2 | + |
|
3 | +2、浏览器按f12,看LoadMenu的服务返回是否有该菜单数据(按关键字搜索) |
|
4 | + |
|
5 | +3、查看种子数据表,数据是否存在,如果不存在说明可能是安装/重启后逻辑有误,未能正常安装/重启,需要联系平台开发人员解决 |
\345\270\270\350\247\201\351\227\256\351\242\230/\347\247\237\346\210\267\346\216\210\346\235\203\345\244\261\346\225\210.md
... | ... | @@ -0,0 +1,14 @@ |
1 | +### MI遇到租户授权APP失效情况,经排查发现,对应的租户管理员角色(赛意MI租户管理员角色)未授予上对应APP所有权限,表象为租户授权失效 |
|
2 | + |
|
3 | +### 经过仔细数据分析,以及本地调式等手段,终于发现rbac_role表中出现两个租户管理员角色tenant_id都是同一租户,存在数据异常(下面图为修改后情况,原本为两个租户租户管理员角色tenant_id都是(03bxfrvsstzh6)) |
|
4 | +[[/uploads/Home/17007936754091.png]] |
|
5 | + |
|
6 | +### 解析:一个租户内两个租户管理员,所以导致租户授权中,查到了另外一个租户管理员角色来赋予上对应APP权限,因此出现租户授权APP失效的现象,其实是给另外一个租户管理员角色赋予上了对应APP权限 |
|
7 | + |
|
8 | +[[/uploads/Home/17007958497118.png]] |
|
9 | + |
|
10 | + |
|
11 | + |
|
12 | + |
|
13 | + |
|
14 | + |
\345\270\270\350\247\201\351\227\256\351\242\230/\350\247\206\345\233\276\346\211\251\345\261\225.md
... | ... | @@ -0,0 +1,271 @@ |
1 | +# 后端视图扩展 |
|
2 | + |
|
3 | +B app 对 A app 的视图文件进行扩展。例如增加字段、增加标签页 |
|
4 | + |
|
5 | +## 1. 编写 A 视图 |
|
6 | + |
|
7 | +## 声明菜单,view 指定视图的类型 |
|
8 | + |
|
9 | +```json |
|
10 | +"mom_material_menu": { |
|
11 | + "name": "mom_material_menu", |
|
12 | + "display_name": "物料", |
|
13 | + "model": "Material", |
|
14 | + "view": "material_grid,material_search,material_form", |
|
15 | + "sequence": 2, |
|
16 | + "active": true, |
|
17 | + "parent_ids": { |
|
18 | + "@ref": "mom_config_menu" |
|
19 | + } |
|
20 | +} |
|
21 | +``` |
|
22 | + |
|
23 | +## 声明 A 的表单视图,这里定义了一个标签页 |
|
24 | + |
|
25 | +```json |
|
26 | +{ |
|
27 | + "material_form": { |
|
28 | + "body": { |
|
29 | + "columns": [], |
|
30 | + "tabs": [ |
|
31 | + { |
|
32 | + "header": "基本资料", |
|
33 | + "rowspan": 1, |
|
34 | + "name": "item_info_tab", |
|
35 | + "body": { |
|
36 | + "type": "formPart", |
|
37 | + "model": "Material", |
|
38 | + "columns": [] |
|
39 | + } |
|
40 | + } |
|
41 | + { |
|
42 | + "header": "设计资料", |
|
43 | + "rowspan": 1, |
|
44 | + "name": "item_design_info_tab", |
|
45 | + "body": { |
|
46 | + "type": "formPart", |
|
47 | + "model": "Material", |
|
48 | + "columns": [ |
|
49 | + ] |
|
50 | + } |
|
51 | + } |
|
52 | + ], |
|
53 | + "type": "form" |
|
54 | + }, |
|
55 | + "mode": "primary", |
|
56 | + "model": "Material", |
|
57 | + "name": "物料-表单", |
|
58 | + "type": "form" |
|
59 | + } |
|
60 | +} |
|
61 | +``` |
|
62 | + |
|
63 | +## 2. 在 B app 声明视图 |
|
64 | + |
|
65 | + |
|
66 | + |
|
67 | +```json |
|
68 | +{ |
|
69 | + "views": { |
|
70 | + "aps_buyer_tab_form_ext": { |
|
71 | + "name": "mom-item扩展-采购资料tab", |
|
72 | + "model": "Material", |
|
73 | + "type": "form", |
|
74 | + "mode": "extension", # 填扩展类型 |
|
75 | + "inherit_ids": { |
|
76 | + "@ref": "siemes.material_form" # 指定对应 siemes app 要扩展的视图 |
|
77 | + }, |
|
78 | + "body": { |
|
79 | + "jsonpath": [ |
|
80 | + { |
|
81 | + "expr": "tabs[?(@.name=='item_info_tab')]..columns.desc", # 扩展的位置,通过 name 找到 tab 里面的字段 |
|
82 | + "position": "before", # 扩展的位置 |
|
83 | + "body": [ |
|
84 | + "zhichengluxiam" # 扩展的内容,新增一个字段 |
|
85 | + ] |
|
86 | + }, |
|
87 | + { |
|
88 | + "expr": "tabs[?(@.name=='item_info_tab')]..columns.shengchantiqianqi", |
|
89 | + "position": "after", |
|
90 | + "body": [ |
|
91 | + "gongyicanshufenlei" |
|
92 | + ] |
|
93 | + }, |
|
94 | + { |
|
95 | + "has_not": "tabs", # 判断是否有 tabs |
|
96 | + "expr": "tabs[?(@.name=='item_design_info_tab')]", # 找到指定的 tab |
|
97 | + "position": "after", |
|
98 | + "body": [ # 扩展的内容 |
|
99 | + { |
|
100 | + "header": "采购资料", |
|
101 | + "body": { |
|
102 | + "type": "formPart", |
|
103 | + "model": "Material", |
|
104 | + "columns": [ |
|
105 | + "caigouziliao", |
|
106 | + "caigouyuan", |
|
107 | + "caigoutiqianqi" |
|
108 | + ] |
|
109 | + } |
|
110 | + } |
|
111 | + ] |
|
112 | + } |
|
113 | + ] |
|
114 | + } |
|
115 | + } |
|
116 | + } |
|
117 | +} |
|
118 | +``` |
|
119 | + |
|
120 | + |
|
121 | +## 3. 示例: |
|
122 | + |
|
123 | +1. 扩展表单,给表单的一个字段后面新增一个字段 |
|
124 | +2. 给表单的tab页新增一个字段 |
|
125 | + |
|
126 | +```json |
|
127 | +{ |
|
128 | + "views": { |
|
129 | + "test_eam_fault_maintenance_order_form_ext": { |
|
130 | + "mode": "extension", |
|
131 | + "inherit_ids": { |
|
132 | + "@ref": "newSdkApp.test_eam_fault_maintenance_order_form" |
|
133 | + }, |
|
134 | + "model": "test_eam_fault_maintenance_order", |
|
135 | + "name": "故障工单-表单", |
|
136 | + "type": "form", |
|
137 | + "body": { |
|
138 | + "jsonpath": [ |
|
139 | + { |
|
140 | + "expr": "columns.repairDate", |
|
141 | + "position": "after", |
|
142 | + "body": [ |
|
143 | + { |
|
144 | + "displayName": "报修单号2扩展", |
|
145 | + "name": "reportNo2", |
|
146 | + "groupConf": { |
|
147 | + "name": "故障信息", |
|
148 | + "id": "repairInfoGroup" |
|
149 | + } |
|
150 | + } |
|
151 | + ] |
|
152 | + }, |
|
153 | + { |
|
154 | + "expr": "tabs[?(@.name=='maintenanceInformationTab')]..columns.responseTime", |
|
155 | + "position": "after", |
|
156 | + "body": [ |
|
157 | + { |
|
158 | + "displayName": "报修单号acceptedBy扩展", |
|
159 | + "name": "reportNo2" |
|
160 | + } |
|
161 | + ] |
|
162 | + } |
|
163 | + ] |
|
164 | + } |
|
165 | + } |
|
166 | + } |
|
167 | +} |
|
168 | + |
|
169 | +``` |
|
170 | + |
|
171 | + |
|
172 | +## 4. JsonPath语法 |
|
173 | + |
|
174 | +JsonPath的语法相对简单,它采用开发语言友好的表达式形式,如果你了解类C语言,对JsonPath就不会感到不适应。 |
|
175 | + |
|
176 | +JsonPath语法要点: |
|
177 | + |
|
178 | +- `$` 表示文档的根元素 |
|
179 | +- `@` 表示文档的当前元素 |
|
180 | +- `.node_name` 或 `['node_name']` 匹配下级节点 |
|
181 | +- `[index]` 检索数组中的元素 |
|
182 | +- `[start:end:step]` 支持数组切片语法 |
|
183 | +- `*` 作为通配符,匹配所有成员 |
|
184 | +- `..` 子递归通配符,匹配成员的所有子元素 |
|
185 | +- `(<expr>)` 使用表达式 |
|
186 | +- `?(<boolean expr>)`进行数据筛选 |
|
187 | + |
|
188 | +下表将列举所有支持的语法,并对XPath进行比较: |
|
189 | + |
|
190 | +| XPath | JsonPath | 说明 | |
|
191 | +| ----- | ------------------ | ------------------------------------------------------------ | |
|
192 | +| `/` | `$` | 文档根元素 | |
|
193 | +| `.` | `@` | 当前元素 | |
|
194 | +| `/` | `.`或`[]` | 匹配下级元素 | |
|
195 | +| `..` | `N/A` | 匹配上级元素,JsonPath不支持此操作符 | |
|
196 | +| `//` | `..` | 递归匹配所有子元素 | |
|
197 | +| `*` | `*` | 通配符,匹配下级元素 | |
|
198 | +| `@` | `N/A` | 匹配属性,JsonPath不支持此操作符 | |
|
199 | +| `[]` | `[]` | 下标运算符,根据索引获取元素,**XPath索引从1开始,JsonPath索引从0开始** | |
|
200 | +| `|` | `[,]` | 连接操作符,将多个结果拼接成数组返回,可以使用索引或别名 | |
|
201 | +| `N/A` | `[start:end:step]` | 数据切片操作,XPath不支持 | |
|
202 | +| `[]` | `?()` | 过滤表达式 | |
|
203 | +| `N/A` | `()` | 脚本表达式,使用底层脚本引擎,XPath不支持 | |
|
204 | +| `()` | `N/A` | 分组,JsonPath不支持 | |
|
205 | + |
|
206 | +注意: |
|
207 | + |
|
208 | +- JsonPath的索引从0开始计数 |
|
209 | +- JsonPath中字符串使用单引号表示,例如:`$.store.book[?(@.category=='reference')]`中的`'reference'` |
|
210 | + |
|
211 | +## JsonPath示例 |
|
212 | + |
|
213 | +下面是相应的JsonPath的示例,代码来源于[https://goessner.net/articles/JsonPath/](https://links.jianshu.com/go?to=https%3A%2F%2Fgoessner.net%2Farticles%2FJsonPath%2F),JSON文档如下: |
|
214 | + |
|
215 | + |
|
216 | + |
|
217 | +```json |
|
218 | +{ |
|
219 | + "store": { |
|
220 | + "book": [{ |
|
221 | + "category": "reference", |
|
222 | + "author": "Nigel Rees", |
|
223 | + "title": "Sayings of the Century", |
|
224 | + "price": 8.95 |
|
225 | + }, { |
|
226 | + "category": "fiction", |
|
227 | + "author": "Evelyn Waugh", |
|
228 | + "title": "Sword of Honour", |
|
229 | + "price": 12.99 |
|
230 | + }, { |
|
231 | + "category": "fiction", |
|
232 | + "author": "Herman Melville", |
|
233 | + "title": "Moby Dick", |
|
234 | + "isbn": "0-553-21311-3", |
|
235 | + "price": 8.99 |
|
236 | + }, { |
|
237 | + "category": "fiction", |
|
238 | + "author": "J. R. R. Tolkien", |
|
239 | + "title": "The Lord of the Rings", |
|
240 | + "isbn": "0-395-19395-8", |
|
241 | + "price": 22.99 |
|
242 | + } |
|
243 | + ], |
|
244 | + "bicycle": { |
|
245 | + "color": "red", |
|
246 | + "price": 19.95 |
|
247 | + } |
|
248 | + } |
|
249 | +} |
|
250 | +``` |
|
251 | + |
|
252 | +接下来我们看一下如何对这个文档进行解析: |
|
253 | + |
|
254 | +| XPath | JsonPath | Result | |
|
255 | +| ---------------------- | ------------------------------------------ | ---------------------------------------- | |
|
256 | +| `/store/book/author` | `$.store.book[*].author` | 所有book的author节点 | |
|
257 | +| `//author` | `$..author` | 所有author节点 | |
|
258 | +| `/store/*` | `$.store.*` | store下的所有节点,book数组和bicycle节点 | |
|
259 | +| `/store//price` | `$.store..price` | store下的所有price节点 | |
|
260 | +| `//book[3]` | `$..book[2]` | 匹配第3个book节点 | |
|
261 | +| `//book[last()]` | `$..book[(@.length-1)]`,或 `$..book[-1:]` | 匹配倒数第1个book节点 | |
|
262 | +| `//book[position()<3]` | `$..book[0,1]`,或 `$..book[:2]` | 匹配前两个book节点 | |
|
263 | +| `//book[isbn]` | `$..book[?(@.isbn)]` | 过滤含isbn字段的节点 | |
|
264 | +| `//book[price<10]` | `$..book[?(@.price<10)]` | 过滤`price<10`的节点 | |
|
265 | +| `//*` | `$..*` | 递归匹配所有子节点 | |
|
266 | + |
|
267 | + |
|
268 | + |
|
269 | +链接:https://www.jianshu.com/p/9808ab64fc0c |
|
270 | + |
|
271 | + |
\345\270\270\350\247\201\351\227\256\351\242\230/\350\247\206\345\233\276\346\226\260\351\205\215\347\275\256\346\214\211\351\222\256\344\270\215\346\230\276\347\244\272.md
... | ... | @@ -0,0 +1 @@ |
1 | +111 |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\350\247\206\345\233\276\351\205\215\347\275\256\344\272\206\345\256\241\350\256\241\345\255\227\346\256\265\344\270\215\346\230\276\347\244\272.md
... | ... | @@ -0,0 +1,11 @@ |
1 | +# 视图配置了审计字段不显示 |
|
2 | + |
|
3 | +因为审计字段的 display 属性默认为 false,如果需要显示,需要在模型显式指定为 true。 |
|
4 | + |
|
5 | +```java |
|
6 | +@Property(displayName = "申请时间", display = true) |
|
7 | +private Date create_date; |
|
8 | + |
|
9 | +@Property(displayName = "更新时间", display = true) |
|
10 | +private Date update_date; |
|
11 | +``` |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\350\247\206\345\233\276\351\227\256\351\242\230\357\274\232\350\217\234\345\215\225\350\203\275\347\202\271\345\207\273\357\274\214\344\275\206\350\277\224\345\233\236\347\251\272\347\231\275\351\241\265.md
... | ... | @@ -0,0 +1,6 @@ |
1 | +1、按F12看loadView是否能正常返回视图数据 |
|
2 | + |
|
3 | +2、如能正常返回视图,则可能是平台前端渲染问题,请@前端平台人员 |
|
4 | +如未能正常返回,请查看日志,看是否视图种子数据加载报错导致,如视图数据Json格式错误、模型/属性填写错误等 |
|
5 | + |
|
6 | +3、如日志未有异常信息,请@后端平台人员 |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\350\260\203\347\224\250Filter.remove\345\220\216\346\237\245\350\257\242\346\212\245\351\224\231.md
... | ... | @@ -0,0 +1,19 @@ |
1 | +Filter 是继承了 ArrayList。 |
|
2 | + |
|
3 | +Filter 新加了几个 remove 方法,用于移除前端查询条件中的特殊字段。但是 remove(Object o) 是 ArrayList 的方法,调用该方法去移除可能会导致 Filter 语义错误。 |
|
4 | + |
|
5 | +正确的方法 |
|
6 | + |
|
7 | +```java |
|
8 | +Filter newFilterA = filter.remove("name"); |
|
9 | +Filter newFilterB = filter.remove(Arrays.asList("name", "age")); |
|
10 | +``` |
|
11 | + |
|
12 | +错误的用法 |
|
13 | + |
|
14 | +```java |
|
15 | +FilterOp name = filter.getFilterOp("name"); |
|
16 | +filter.remove(name); |
|
17 | +``` |
|
18 | + |
|
19 | +后续版本,调用 remove(Object) 会抛出异常。 |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\350\260\203\347\224\250update\344\270\215\347\224\237\346\225\210.md
... | ... | @@ -0,0 +1,21 @@ |
1 | +# 跨模型调用 update 方法不生效 |
|
2 | + |
|
3 | +以下写法,即使 values 里面有 id,也不会更新成功。 |
|
4 | + |
|
5 | +```java |
|
6 | +Map<String, Object> values = new HashMap<>(); |
|
7 | +// 修改 values 的值 |
|
8 | +getMeta().get("model").call("update", values); |
|
9 | +``` |
|
10 | + |
|
11 | +正确写法 |
|
12 | + |
|
13 | +因为 `getMeta().get()` 是返回了一个新的 RecordSet。但是这个 RecordSet 里面并没有 ids。 |
|
14 | +所以引擎更新的时候不知道要更新哪一条数据。 |
|
15 | + |
|
16 | +```java |
|
17 | +String[] ids = ["1"] |
|
18 | +Map<String, Object> values = new HashMap<>(); |
|
19 | +// 修改 values 的值 |
|
20 | +getMeta().get("model").browse(ids).call("update", values); |
|
21 | +``` |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\350\277\224\345\233\236\346\226\207\344\273\266\346\265\201\347\273\231\346\265\217\350\247\210\345\231\250\344\270\213\350\275\275.md
... | ... | @@ -0,0 +1,37 @@ |
1 | +```java |
|
2 | +@MethodService |
|
3 | +public void downloadFile() throws MalformedURLException { |
|
4 | + String urlString = "http://127.0.0.1:8081/test.jar"; |
|
5 | + // 创建URL对象 |
|
6 | + URL url = new URL(urlString); |
|
7 | + |
|
8 | + ByteArrayOutputStream outputStream = getMeta().getResponse().getByteArrayoutputStream(); |
|
9 | + try (InputStream inputStream = url.openStream()) { |
|
10 | + // 打开并获取输入流 |
|
11 | + if (inputStream != null) { |
|
12 | + // 现在可以从输入流中读取数据,这里只是简单打印 |
|
13 | + byte[] buffer = new byte[1024]; |
|
14 | + int read; |
|
15 | + while ((read = inputStream.read(buffer)) != -1) { |
|
16 | + outputStream.write(buffer, 0, read); |
|
17 | + } |
|
18 | + } else { |
|
19 | + outputStream.write("无法打开URL连接或读取资源".getBytes()); |
|
20 | + } |
|
21 | + } catch (IOException ignore) { |
|
22 | + } |
|
23 | + String encodedFileName = null; |
|
24 | + try { |
|
25 | + encodedFileName = URLEncoder.encode("test.jar", StandardCharsets.UTF_8.toString()); |
|
26 | + } catch (UnsupportedEncodingException ignore) { |
|
27 | + } |
|
28 | + |
|
29 | + // 设置Content-Disposition头,让浏览器以附件形式下载,并指定了文件名 |
|
30 | + getMeta().getResponse().setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\""); |
|
31 | + |
|
32 | + // 指定 ContentType |
|
33 | + getMeta().getResponse().setContentType("application/java-archive"); |
|
34 | + // 需要设置 @file_return 参数,才会返回文件流给前端,否则统一返回 JSON |
|
35 | + getMeta().addArgument("@file_return", "true"); |
|
36 | +} |
|
37 | +``` |
|
... | ... | \ No newline at end of file |
\345\270\270\350\247\201\351\227\256\351\242\230/\351\207\215\345\206\231\346\226\271\346\263\225\345\220\216\345\207\272\347\216\260argument-type-mismatch\351\224\231\350\257\257.md
... | ... | @@ -0,0 +1,24 @@ |
1 | +Model 可以重写默认的方法,例如 `delete` 方法。 |
|
2 | + |
|
3 | +查看 `BussModelDataAccess` 的 `delete` 方法签名是 `public boolean delete(RecordSet rs)` |
|
4 | + |
|
5 | +因此重写 `delete` 方法的时候也要保持方法签名一致 |
|
6 | + |
|
7 | +正例 |
|
8 | + |
|
9 | +```java |
|
10 | +public boolean delete(RecordSet rs) { |
|
11 | + String[] ids = rs.getIds(); |
|
12 | + // 其他逻辑 |
|
13 | +} |
|
14 | +``` |
|
15 | + |
|
16 | +反例 |
|
17 | + |
|
18 | +下面的方法会导致方法调用的时候,提示 `argument type mismatch` 错误 |
|
19 | + |
|
20 | +```java |
|
21 | +public boolean delete(List<String> ids) { |
|
22 | + // 其他逻辑 |
|
23 | +} |
|
24 | +``` |
|
... | ... | \ No newline at end of file |
\345\271\263\345\217\260\345\222\214app\346\216\210\346\235\203.md
... | ... | @@ -0,0 +1,8 @@ |
1 | +[[平台和app授权/平台和app授权设计与实现.md]] |
|
2 | + |
|
3 | + |
|
4 | + |
|
5 | +[[平台和app授权/部署文档.md]] |
|
6 | + |
|
7 | +[[平台和app授权/操作文档.md]] |
|
8 | + |
\345\271\263\345\217\260\345\222\214app\346\216\210\346\235\203/\345\271\263\345\217\260\345\222\214app\346\216\210\346\235\203\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md
... | ... | @@ -0,0 +1,268 @@ |
1 | +### 1,需求分析 |
|
2 | +在实际的软件部署流程中,为了防止一套售出的软件被随意在多个平台进行部署,那么就需要对软件系统进行授权,只有在获取到授权后才能使用,常用的方法无非两种, |
|
3 | +其一是软件认证, |
|
4 | +其二是硬件绑定。 |
|
5 | +软件认证,顾名思义就是在软件层面的一种认证手段,常用的方法就是注册账号设置密码。 |
|
6 | +只要账号密码正确,在任何设备上都可使用。 |
|
7 | +硬件绑定,就是将软件和硬件设备进行捆绑,也就是说一旦完成捆绑后,该软件就只能在该硬件设备上使用了。 |
|
8 | +两种授权方法各有优劣,因应用场景的不同而选择不同的方案,在此就不多做讨论了, |
|
9 | +本文主要探讨的是硬件绑定的方法,以在PC机的软件授权为背景进行授权码的设计。 |
|
10 | + |
|
11 | +### 2,方案选型 |
|
12 | + |
|
13 | +方案一:硬件绑定。考虑到虚拟机部署,无法确定具体的硬件信息,但是通过挂在宿主机的/sys/class/net/eth0/address文件,则可以获取宿主机的mac地址。 |
|
14 | +方案二:结合方案一,同时通过限制部署节点的个数、cpu mem使用情况来防止大规模的部署,在可控范围内起到一定的限制作用。 |
|
15 | + |
|
16 | +授权机制的过程和原理: |
|
17 | +- 生成密钥对,包含私钥和公钥,私钥签名,公钥验签。 |
|
18 | +- 授权者保留私钥,使用私钥对授权信息诸如使用截止日期、mac地址、限定的资源和appID等内容生成 license签名证书。 |
|
19 | +- 公钥给使用者,放在代码中使用,用于验证 license 签名证书是否符合使用条件,比如部署节点个数是否在限定范围内。 |
|
20 | +- 各个部署节点分别定时向鉴权服务注册自己的信息,包括设备id,cpu mem等资源情况。 |
|
21 | +- 如果设备下线或者不适用,则请求反注册接口,释放自己的资源。如果节点长时间(一天)没有上报信息则自动过期。 |
|
22 | +- 授权服务统计上报节点的各资源信息并统计,如果已经超过了license规定的资源限定值则返回授权失败。 |
|
23 | + |
|
24 | +### 3,技术实现 |
|
25 | + |
|
26 | +### 编译命令行工具 |
|
27 | + |
|
28 | +编译工具需要安装[Go1.19](https://go.dev/dl/)和`make`命令,设置[Go模块代理](https://goproxy.cn/) |
|
29 | + |
|
30 | +```shell |
|
31 | +# 切换到本项目根目录,执行以下命令编译工具 |
|
32 | +make build_tool |
|
33 | +make build_api # 编译生成 api 服务 |
|
34 | + |
|
35 | +# 默认命令行工具将会输出build目录,切换到build目录,执行以下命令 |
|
36 | +cd build |
|
37 | +autool #显示全部命令 |
|
38 | +autool version #显示工具版本号 |
|
39 | +autool help version #显示version命令帮助信息 |
|
40 | + |
|
41 | +# 如果是在linux环境,可能需要执行以下命令 |
|
42 | +chmod +x autool |
|
43 | +./autool #显示全部命令 |
|
44 | +``` |
|
45 | + |
|
46 | + |
|
47 | + |
|
48 | +### 创建授权证书&编译程序库 |
|
49 | + |
|
50 | +创建授权证书基本步骤: |
|
51 | + |
|
52 | +- 创建密钥文件,或者使用已有密钥文件 |
|
53 | +- 修改命令行工具配置文件`tool.toml`,设置授权信息 |
|
54 | +``` |
|
55 | +[license] |
|
56 | +User = '1'#授权用户 |
|
57 | +ExpiredAt = '20240101' # 授权到期日期,格式:YYYYMMDD |
|
58 | +EngineVersion = 'v1.0.0' # 引擎版本 |
|
59 | +FingerPrint = 'xxx' # 指纹 |
|
60 | + |
|
61 | +MacAddress = '00:ff:42:c3:58:9a' # 网卡Mac地址,多个逗号分隔 |
|
62 | +PhysicalID = '178BFBFF00A50F00' # cpu序列号 |
|
63 | + |
|
64 | +# 资源限定 |
|
65 | +Cpu = 1024 # cpu核数 |
|
66 | +Mem = 1024 # 内存k |
|
67 | +Node = 10 # 节点个数 |
|
68 | + |
|
69 | +CompanyName = "test" # 企业名称 |
|
70 | +ContractID = "fdfdj12345jjfjd" # 合同id |
|
71 | +LesseeID = 123 # 租户id |
|
72 | +AppID = [1,2,3,4] # 授权的app id,多个以逗号隔开 |
|
73 | +``` |
|
74 | +- 创建&加密授权证书 |
|
75 | +- 编译程序库,嵌入加密后证书 |
|
76 | + |
|
77 | + |
|
78 | + |
|
79 | +```shell |
|
80 | +# 项目包含1份示例配置文件,见`conf/tool.toml` |
|
81 | +# 以下假设autool已经存在于bulid目录下 |
|
82 | +# 打开命令行窗口 |
|
83 | +cd build |
|
84 | +mkdir -o conf |
|
85 | +cp ../conf/tool.toml ./conf/ |
|
86 | + |
|
87 | +# 创建密钥文件,文件默认输出到: conf/key.pem, conf/key_pub.pem |
|
88 | +autool newkey |
|
89 | + |
|
90 | +# 修改tool.toml,根据实际情况设置授权证书信息,见"[license]" |
|
91 | +# 如果设置mac地址,则调用接口时会校验服务器mac地址是否已授权 |
|
92 | +# 可执行以下命令查看本机mac地址 |
|
93 | +autool mac |
|
94 | + |
|
95 | +# 创建加密授权证书,文件默认输出到:conf/license |
|
96 | +autool newlic |
|
97 | + |
|
98 | +# 编译程序库,加密证书和对应的公钥文件会嵌入程序库里 |
|
99 | +# 如果需要使用已有证书和公钥,可修改tool.toml->PubKeyFile/LicenseFile |
|
100 | +# windows环境,库文件输出到:parser.dll |
|
101 | +# linux环境,库文件输出到:libparser.so |
|
102 | +# 注:Go目前不支持通过设置GOOS来编译不同环境下的动态库文件 |
|
103 | +autool newlib |
|
104 | + |
|
105 | +# 启动api服务 |
|
106 | +chmod +x api |
|
107 | +./api |
|
108 | + |
|
109 | +``` |
|
110 | + |
|
111 | +### api 接口 |
|
112 | +##### aes加密方案 |
|
113 | +所有请求数据和返回数据均通过aes加密,32位秘钥是:Y8uCrLL8SavXyiUzpnU+Lmn4mODprYLo |
|
114 | +所以,发送和接收到的body,都是经过加密后的数据,比如: |
|
115 | +``` |
|
116 | +实际请求: |
|
117 | +{ |
|
118 | + "device_id": "3", |
|
119 | + "cpu": 4, |
|
120 | + "mem": 1024, |
|
121 | + "engine_version": "v1.0.0" |
|
122 | +} |
|
123 | +加密后的请求: |
|
124 | +��2.�&%Y���g�|�Q��-V�ݧa��%yIO;��6��ֵYb1}���5���ޭ��Pޕ<Q8�ڍ���j��E�ܯ��缰BsbV`�0�F0w |
|
125 | + |
|
126 | +同理,接收到的body也是加密的 |
|
127 | +实际返回: |
|
128 | +{ |
|
129 | + "code": 0, |
|
130 | + "err": "", |
|
131 | + "data": "" |
|
132 | +} |
|
133 | +加密后的返回: |
|
134 | +(w�`�'�gDk� |
|
135 | +: 4$C������n |
|
136 | +]�����[rr���fl�ff��sk`J |
|
137 | + |
|
138 | +``` |
|
139 | + |
|
140 | +- 注册 |
|
141 | +``` |
|
142 | +POST localhost:8080/api/v1/register |
|
143 | +请求body: |
|
144 | +{ |
|
145 | + "device_id": "3", |
|
146 | + "cpu": 4, |
|
147 | + "mem": 1024, |
|
148 | + "engine_version": "v1.0.0" |
|
149 | +} |
|
150 | +返回: |
|
151 | +{ |
|
152 | + "code": 0, |
|
153 | + "err": "", |
|
154 | + "data": "" |
|
155 | +} |
|
156 | +``` |
|
157 | +- 反注册 |
|
158 | +``` |
|
159 | +POST localhost:8080/api/v1/unregister |
|
160 | +请求body: |
|
161 | +{ |
|
162 | + "device_id": "2" |
|
163 | +} |
|
164 | + |
|
165 | +返回: |
|
166 | +{ |
|
167 | + "code": 0, |
|
168 | + "err": "", |
|
169 | + "data": "" |
|
170 | +} |
|
171 | +``` |
|
172 | +- 鉴权 |
|
173 | +``` |
|
174 | +POST localhost:8080/api/v1/auth |
|
175 | +请求: |
|
176 | +{ |
|
177 | + "app_id": ["1","2","3"] |
|
178 | +} |
|
179 | + |
|
180 | +返回成功: |
|
181 | +{ |
|
182 | + "code": 0, |
|
183 | + "err": "", |
|
184 | + "data": { |
|
185 | + "user": "1", |
|
186 | + "engine_version": "v1.0.0", |
|
187 | + "expired_at": "20240101", |
|
188 | + "finger_print": "xxx", |
|
189 | + "mac_address": "", |
|
190 | + "physical_id": "", |
|
191 | + "cpu": 1024, |
|
192 | + "mem": 1024, |
|
193 | + "node": 10, |
|
194 | + "app_id": [ "1", "2", "3" ] |
|
195 | + } |
|
196 | +} |
|
197 | + |
|
198 | +授权失败返回: |
|
199 | +{ |
|
200 | + "code": 121, |
|
201 | + "err": "unauthorized failed", |
|
202 | + "data": null |
|
203 | +} |
|
204 | +``` |
|
205 | + |
|
206 | +### web前端接口 |
|
207 | +参考下图: |
|
208 | + |
|
209 | + |
|
210 | + |
|
211 | +- 获取服务信息 |
|
212 | +``` |
|
213 | +POST localhost:8080/api/v1/service_info |
|
214 | +请求: |
|
215 | +{ |
|
216 | + "lease_id": "123456", |
|
217 | + "app_id": ["1","2", "oppmApp"], |
|
218 | + "cpu": 1024, |
|
219 | + "mem": 1024, |
|
220 | + "node": 100, |
|
221 | + "engine_version": "v1.0.0", |
|
222 | + "expired_at": "20240101" |
|
223 | +} |
|
224 | + |
|
225 | +返回成功: |
|
226 | +{ |
|
227 | + "code": 0, |
|
228 | + "err": "", |
|
229 | + "data": "HWqwL0TBDyULAiZPv0u5zRst6OHfnv8AC7gDoxeXA97ceSh2OeG80hX0ZaxoAHoR7I2M/WlsojCq2KK/ao4UyRm711YVM+jS+X+b67dBYE5eNXHrMpzWpPryd3fh09f0w7ghnp2ZJ++THIBTio/hJp3Nk2gwlLTnM4jLkb3ZD8Yb/+kOJrirDN4QKzGguejK+95aiCPbH7VzW9sVfJQsijBWFyzDto2Qp2Rr7o0uESFMC4HWE1yrNQzAoeQUy7Qr4yBsF04UGZWPrjGwGGChIms6zosFOKAgvzKMwUX94TfCDfP5RsdG4EVIe7cxkk86j175hlLn162kglZGRI+PsA==" |
|
230 | +} |
|
231 | + |
|
232 | +授权失败返回: |
|
233 | +{ |
|
234 | + "code": 121, |
|
235 | + "err": "", |
|
236 | + "data": null |
|
237 | +} |
|
238 | +``` |
|
239 | + |
|
240 | +- 提交授权许可 |
|
241 | +``` |
|
242 | +POST localhost:8080/api/v1/submit |
|
243 | +请求: |
|
244 | +{ |
|
245 | + "license": "xxxxxxx", |
|
246 | +} |
|
247 | + |
|
248 | +返回成功: |
|
249 | +{ |
|
250 | + "code": 0, |
|
251 | + "err": "", |
|
252 | + "data": null |
|
253 | +} |
|
254 | + |
|
255 | +授权失败返回: |
|
256 | +{ |
|
257 | + "code": 121, |
|
258 | + "err": "submit err", |
|
259 | + "data": null |
|
260 | +} |
|
261 | +``` |
|
262 | + |
|
263 | +### 计划 |
|
264 | + |
|
265 | +- 3月27-3月29 |
|
266 | + 完成整个授权码功能的后端接口开发并自测 |
|
267 | +- 3月30-3月31 |
|
268 | + 完成联调 |
|
... | ... | \ No newline at end of file |
\345\271\263\345\217\260\345\222\214app\346\216\210\346\235\203/\346\223\215\344\275\234\346\226\207\346\241\243.md
... | ... | @@ -0,0 +1,29 @@ |
1 | +### 操作手册 |
|
2 | + |
|
3 | +#### 前端操作 |
|
4 | +- 进入前端页面,比如在176环境地址是:http://192.168.168.176:8888/snest/base/base_overview_menu?page=apiRegister |
|
5 | +- 根据表单项中内容,填写对应的服务器信息,由于服务器信息是授权的凭证,请务必确保填写信息的正确性,如果需要修改信息,则需要重新提交申请 |
|
6 | +授权码 |
|
7 | +- 确保服务器信息填写完毕后,点击"获取服务器信息"按钮,生成加密后的服务器信息 |
|
8 | +- 拷贝生成后的服务器信息内容,咨询产品经理,以邮件形式发起申请授权码 |
|
9 | +- 如果申请通过,将授权码填写对应位置,点击"提交授权许可",无任何错误提示信息后,即可完成注册。 |
|
10 | + |
|
11 | +[[/uploads/grant/grant_1.png]] |
|
12 | + |
|
13 | +[[/uploads/grant/grant_3.png]] |
|
14 | +#### 字段说明 |
|
15 | +1. 客户ID:暂时没有使用(多租户上线后使用,代表一个租户)。 |
|
16 | +2. 应用清单:授权的应用名称。授权多个应用使用英文逗号分隔。 |
|
17 | + 1.  |
|
18 | + 2. base、file、dict 默认已授权。 |
|
19 | + 3. 填写 * 号,代表授权所以应用。 |
|
20 | +3. 内存(G):授权所有平台实例的内存使用限制。例如授权 10G,如果只启动一个引擎,当引擎当前使用的内存超过 10G,就会注册失败。如果在高可用模式下,启动多个引擎,当所有引擎当前使用的内存加起来超过 10G,那么引擎注册失败。 |
|
21 | +4. 节点数:引擎在高可用模式或分布式模式启动的实例数量。 |
|
22 | +5. 引擎版本:暂时没有使用。 |
|
23 | +6. 到期时间:授权的最后有效日期。包含当天。 |
|
24 | + |
|
25 | +#### 授权码生成 |
|
26 | +- 打开授权码生成工具所在文件夹,一个可执行文件,一个conf配置文件,conf配置文件包含生成授权码过程中所需要的key,需要妥善保管好 |
|
27 | +- 点击可执行文件 license_generator.exe,输入服务器信息,点击submit提交,即可获取生成的授权码。 |
|
28 | + |
|
29 | +[[/uploads/grant/grant_2.png]] |
|
... | ... | \ No newline at end of file |
\345\271\263\345\217\260\345\222\214app\346\216\210\346\235\203/\351\203\250\347\275\262\346\226\207\346\241\243.md
... | ... | @@ -0,0 +1,155 @@ |
1 | +### 授权服务部署 |
|
2 | + |
|
3 | +#### 裸机部署 |
|
4 | +下载可执行程序:[[grant_deploy.rar|/uploads/grant/grant_deploy.rar]] |
|
5 | +- 获取完整的安装压缩包 ```grant_deploy.tar```,并解压到当前目录下 |
|
6 | +``` |
|
7 | + tar -xf grant_deploy.tar |
|
8 | +``` |
|
9 | +- 进入```grant_deploy```文件夹,查看当前的文件,共2个文件:conf 和 grant |
|
10 | +``` |
|
11 | + ls |
|
12 | + conf grant |
|
13 | +``` |
|
14 | + |
|
15 | +- 执行程序会自动建表。直接进入conf 目录,编辑配置文件,填写当前的数据库host等信息,以及用户名和密码 |
|
16 | +``` |
|
17 | + cd .. |
|
18 | + cd conf |
|
19 | + ls |
|
20 | + config.properties key_pub.pem |
|
21 | + vim config.properties |
|
22 | +``` |
|
23 | +``` |
|
24 | +url=jdbc:mysql://192.168.168.176:3306/iidp?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToN ull&useSSL=false&allowPublicKeyRetrieval=true |
|
25 | +username=root |
|
26 | +password=123456 |
|
27 | +serverPort=8080 # 服务监听的端口 |
|
28 | +``` |
|
29 | + |
|
30 | +- Oracle数据库与mysql配置类似 |
|
31 | +``` |
|
32 | +url=jdbc:oracle:thin:@192.168.168.177:1521:HELOWIN |
|
33 | +username=sie_chun |
|
34 | +password=123456 |
|
35 | +``` |
|
36 | +- 回到根目录,执行 `nohup ./grant > grant.log 2>&1 & |
|
37 | +`,grant.log 日志出现`Listening and serving HTTP on :8080`日志,即表示运行成功。同时需要查看是否grant进程存在。 |
|
38 | + |
|
39 | +#### 容器部署 |
|
40 | +下载镜像:[[grant-v2.tar|/uploads/grant/grant-v2.tar]] |
|
41 | + |
|
42 | +- docker部署,在```/app/build/conf```配置好相关配置文件(参考裸机配置,但docker配置需要自行挂载配置文件,参考下面的 -v 命令,其中```/app/build/conf``` 配置文件目录是固定死的,容器外的路径可自行配置),并执行以下docker命令 |
|
43 | +``` |
|
44 | + docker stop grant |
|
45 | + docker rm grant |
|
46 | + docker run -itd --name grant -p 8080:8080 -v /app/build/conf:/app/build/conf dockerhub.kubekey.local/release/grant:v2 |
|
47 | +``` |
|
48 | +如果无法获取 `dockerhub.kubekey.local/release/grant:v2` 镜像,可以创建一个 Dockerfile,内容如下: |
|
49 | +``` |
|
50 | +FROM ubuntu:latest |
|
51 | + |
|
52 | +COPY grant /app/grant |
|
53 | + |
|
54 | +WORKDIR /app |
|
55 | + |
|
56 | +CMD ["./grant"] |
|
57 | +``` |
|
58 | +执行命令 `docker build . -t grant:latest` 构建镜像 `grant:latest`。然后使用以下命令运行镜像 |
|
59 | +``` |
|
60 | +docker run -itd --restart=always --name grant -p 8080:8080 -v /app/build/conf:/app/conf grant:v2 |
|
61 | +``` |
|
62 | + |
|
63 | +- docker-compose部署,编写```docker-compose.yaml```文件(如下),执行```docker-compose -d up```. |
|
64 | +``` |
|
65 | +version: '3.6' |
|
66 | +services: |
|
67 | + grant: |
|
68 | + image: 'dockerhub.kubekey.local/release/grant:v2' |
|
69 | + restart: always |
|
70 | + container_name: 'grant' |
|
71 | + ports: |
|
72 | + - '8080:8080' |
|
73 | + volumes: |
|
74 | + - /app/build/conf:/app/build/conf |
|
75 | +``` |
|
76 | + |
|
77 | +- k8s部署。由同目录下的 ```iidp平台项目部署文档``` 可知,已经在客户机上安装了一整套k8s平台和镜像仓库harbor, |
|
78 | +那么安装和部署grant服务就相对简单,按照上述预置条件,创建好数据库和配置文件,直接执行```kubectl -n yournamespace apply -f deployment.yaml``` |
|
79 | +``` |
|
80 | +apiVersion: apps/v1 |
|
81 | +kind: Deployment |
|
82 | +metadata: |
|
83 | + name: grant-deployment |
|
84 | +spec: |
|
85 | + replicas: 3 |
|
86 | + selector: |
|
87 | + matchLabels: |
|
88 | + app: grant |
|
89 | + template: |
|
90 | + metadata: |
|
91 | + annotations: |
|
92 | + dapr.io/enabled: "false" # 是否开启dapr |
|
93 | + dapr.io/app-id: "grant" |
|
94 | + dapr.io/app-port: "8080" |
|
95 | + labels: |
|
96 | + app: grant |
|
97 | + spec: |
|
98 | + containers: |
|
99 | + - name: grant |
|
100 | + image: dockerhub.kubekey.local/release/grant:v2 # 镜像地址 |
|
101 | + imagePullPolicy: IfNotPresent |
|
102 | + ports: |
|
103 | + - containerPort: 80 |
|
104 | + volumeMounts: |
|
105 | + - name: config-volume |
|
106 | + mountPath: /app/build/conf |
|
107 | + volumes: |
|
108 | + - name: config-volume |
|
109 | + configMap: |
|
110 | + name: grant-cm-db |
|
111 | +--- |
|
112 | +apiVersion: v1 |
|
113 | +kind: Service |
|
114 | +metadata: |
|
115 | + labels: |
|
116 | + version: v1 |
|
117 | + app: grant-svc |
|
118 | + name: grant-svc |
|
119 | +spec: |
|
120 | + selector: |
|
121 | + app: grant |
|
122 | + ports: |
|
123 | + - name: tcp-8080 |
|
124 | + protocol: TCP |
|
125 | + port: 8080 |
|
126 | + targetPort: 8080 |
|
127 | + |
|
128 | +--- |
|
129 | + |
|
130 | +apiVersion: v1 |
|
131 | +kind: ConfigMap |
|
132 | +metadata: |
|
133 | + labels: |
|
134 | + app: grant |
|
135 | + name: grant-cm |
|
136 | +data: |
|
137 | + config.properties: | |
|
138 | + url=jdbc:mysql://192.168.168.176:3306/snest?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true |
|
139 | + username=root |
|
140 | + password=123456 |
|
141 | + serverPort=8080 |
|
142 | + |
|
143 | + key_pub.pem: | |
|
144 | + -----BEGIN RSA PUBLIC KEY----- |
|
145 | + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuWHxKR0aDQXEKthIMhe7 |
|
146 | + L8jNs1Wti0ZNUzDIOqLnO+uL6gaXdvelp9orE5lN+J2NqQTRjAUm8cNIG8w97y+e |
|
147 | + UeG9JaC8Wp1LOjsHzE1GfwlGkCXKo8uy893WKC0kr8LHSUiOgL82q61BJ3gH/od1 |
|
148 | + zzeEXT1I7DzK7ZidY7++6/vnPB0C6B5BFN7ZT1a29BZFk3GsRxxuUzK5EKvoYF3n |
|
149 | + P62IKku3CQh211DEy6MWXMfpWbheExvqn54IeaZrAV9NuZoDF5P7CMlksEMUapQh |
|
150 | + CJs2cxypMRi96jaeRc4oLecM6y1Kz3x4ZFoEmAHnyc+2rgHSbif0nYzRkbJAUDD5 |
|
151 | + WQIDAQAB |
|
152 | + -----END RSA PUBLIC KEY----- |
|
153 | + |
|
154 | + |
|
155 | +``` |
|
... | ... | \ No newline at end of file |
\345\271\263\345\217\260\345\222\214app\346\216\210\346\235\203\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md
... | ... | @@ -0,0 +1,2 @@ |
1 | +### [[平台和app授权/平台和app授权设计与实现]] |
|
2 | + |
\345\274\200\345\217\221\346\211\213\345\206\214/\345\220\216\347\253\257\345\274\200\345\217\221\347\216\257\345\242\203\346\220\255\345\273\272.md
... | ... | @@ -0,0 +1,118 @@ |
1 | +# 开发工具准备 |
|
2 | + |
|
3 | +1. MySQL 8.0+ |
|
4 | +2. IDEA |
|
5 | +3. maven |
|
6 | +4. iidp-demo 项目 |
|
7 | +5. 前端项目包 snest-ui.zip |
|
8 | +6. settings.xml 文件。maven 的配置文件 |
|
9 | +7. 确保已经连上公司 Wi-Fi |
|
10 | + |
|
11 | +# 配置 maven 仓库 |
|
12 | + |
|
13 | +下载 settings.xml 文件。拷贝到你的 maven home。一般是用户目录下的 .m2 目录。 |
|
14 | + |
|
15 | +或者你可以手动添加仓库地址到你的 settings.xml 文件,内容如下。 |
|
16 | + |
|
17 | +```xml |
|
18 | +<?xml version="1.0" encoding="UTF-8"?> |
|
19 | + |
|
20 | +<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" |
|
21 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd"> |
|
22 | + <localRepository>${user.home}/.m2/repository</localRepository> |
|
23 | + |
|
24 | + <mirrors> |
|
25 | + <mirror> |
|
26 | + <id>nexus</id> |
|
27 | + <mirrorOf>central</mirrorOf> |
|
28 | + <url>http://192.168.168.156:8081/repository/maven-public/</url> |
|
29 | + </mirror> |
|
30 | + <mirror> |
|
31 | + <id>nexus-aliyun</id> |
|
32 | + <mirrorOf>central</mirrorOf> |
|
33 | + <name>Nexus aliyun</name> |
|
34 | + <url>http://maven.aliyun.com/nexus/content/groups/public</url> |
|
35 | + </mirror> |
|
36 | + </mirrors> |
|
37 | + |
|
38 | + <profiles> |
|
39 | + <profile> |
|
40 | + <id>nexus</id> |
|
41 | + <repositories> |
|
42 | + <repository> |
|
43 | + <id>nexus</id> |
|
44 | + <name>Nexus</name> |
|
45 | + <url>http://192.168.168.156:8081/repository/maven-public/</url> |
|
46 | + <releases> |
|
47 | + <enabled>true</enabled> |
|
48 | + <!--<always>true</always>--> |
|
49 | + <updatePolicy>always</updatePolicy> |
|
50 | + </releases> |
|
51 | + <snapshots> |
|
52 | + <enabled>true</enabled> |
|
53 | + <!--<always>true</always>--> |
|
54 | + </snapshots> |
|
55 | + </repository> |
|
56 | + </repositories> |
|
57 | + <pluginRepositories> |
|
58 | + <pluginRepository> |
|
59 | + <id>nexus</id> |
|
60 | + <name>Nexus</name> |
|
61 | + <url>http://192.168.168.156:8081/repository/maven-snapshots/</url> |
|
62 | + <releases> |
|
63 | + <enabled>true</enabled> |
|
64 | + <!--<always>true</always>--> |
|
65 | + </releases> |
|
66 | + <snapshots> |
|
67 | + <enabled>true</enabled> |
|
68 | + <!--<always>true</always>--> |
|
69 | + </snapshots> |
|
70 | + </pluginRepository> |
|
71 | + </pluginRepositories> |
|
72 | + </profile> |
|
73 | + |
|
74 | + |
|
75 | + <profile> |
|
76 | + <id>nexus-aliyun</id> |
|
77 | + <repositories> |
|
78 | + <repository> |
|
79 | + <id>nexus-aliyun</id> |
|
80 | + <name>nexus-aliyun</name> |
|
81 | + <url>https://maven.aliyun.com/repository/public</url> |
|
82 | + <releases> |
|
83 | + <enabled>true</enabled> |
|
84 | + </releases> |
|
85 | + <snapshots> |
|
86 | + <enabled>true</enabled> |
|
87 | + </snapshots> |
|
88 | + </repository> |
|
89 | + </repositories> |
|
90 | + <pluginRepositories> |
|
91 | + <pluginRepository> |
|
92 | + <id>nexus-aliyun</id> |
|
93 | + <name>nexus-aliyun</name> |
|
94 | + <url>https://maven.aliyun.com/repository/public/</url> |
|
95 | + <releases> |
|
96 | + <enabled>true</enabled> |
|
97 | + </releases> |
|
98 | + <snapshots> |
|
99 | + <enabled>true</enabled> |
|
100 | + </snapshots> |
|
101 | + </pluginRepository> |
|
102 | + </pluginRepositories> |
|
103 | + </profile> |
|
104 | + |
|
105 | + </profiles> |
|
106 | + |
|
107 | + <activeProfiles> |
|
108 | + <activeProfile>nexus-aliyun</activeProfile> |
|
109 | + <activeProfile>nexus</activeProfile> |
|
110 | + </activeProfiles> |
|
111 | +</settings> |
|
112 | +``` |
|
113 | + |
|
114 | +# 使用 IDEA 打开 iidp-demo 项目 |
|
115 | + |
|
116 | +*直接打开 iidp-demo 这个目录,不要打开父级目录* |
|
117 | + |
|
118 | +[[/uploads/%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C/%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/iidp-demo目录.png]] |
|
... | ... | \ No newline at end of file |
\346\216\245\345\217\243APP.md
... | ... | @@ -0,0 +1,2 @@ |
1 | +## 接口管理操作手册 |
|
2 | +[[接口管理操作手册V1.2.docx|/uploads/%E6%8E%A5%E5%8F%A3APP/接口管理操作手册 - 1.1.docx]] |
|
... | ... | \ No newline at end of file |
\346\223\215\344\275\234\346\227\245\345\277\227app\350\256\276\350\256\241\344\270\216\345\256\236\347\216\260.md
... | ... | @@ -0,0 +1,73 @@ |
1 | +## 版本管理 |
|
2 | + |
|
3 | +## <font color=green>v3.0.0-RELEASE</font> |
|
4 | +### change log |
|
5 | +- 兼容 elastic search 存储操作日志 |
|
6 | + |
|
7 | +### 介绍视频 |
|
8 | + |
|
9 | +<video width="100%" height="100%" src="http://192.168.175.198:10003/iidpminio/operator-log/%E6%93%8D%E4%BD%9C%E6%97%A5%E5%BF%97app-v3.0.0-%E5%85%BC%E5%AE%B9es%E5%AD%98%E5%82%A8.mp4" controls="true" /> |
|
10 | + |
|
11 | +<hr> |
|
12 | + |
|
13 | + |
|
14 | +## 操作日志设计与实现 |
|
15 | + |
|
16 | +为了方便开发人员定位线上的问题,设计并实现了这个操作日志app,具体需求参考: |
|
17 | +http://192.168.175.55:9888/appDev/sie-nest-log/issues/1 |
|
18 | +该需求目前分为两期,第一期是只实现了rpc层面的请求参数和返回结果的记录, 第二期实现了rpc请求过程中涉及到的各个方法调用的详情,包括方法执行前参数、 调用后的结果数据、请求速率限制、请求黑白名单配置等功能。 |
|
19 | + |
|
20 | +### 设计 |
|
21 | + |
|
22 | +- 日志功能需求 |
|
23 | + 由 http://192.168.175.55:9888/appDev/sie-nest-log/issues/1 需求可知, 需要记录rpc请求参数、执行过程详情和返回结果等需求。 |
|
24 | +- 性能需求 |
|
25 | + 由于日志是非常频繁的,为了尽量影响用户的rpc请求,需要异步执行存储日志,防止大量日志影响用户的请求。 目前使用异步队列,日志生成后放入队列,消费方在独立的线程中批处理或者定时处理日志并存储。 |
|
26 | +- 黑名单需求 |
|
27 | + 为了过滤掉一些非常频繁的rpc请求,需要对这些频繁的请求进行日志数据的限制, 同时为了保活用户要求的rpc方法防止因频率过高而被放置到黑名单,因为提供了黑白名单的功能,黑名单的方法不会保存日志,白名单的方法一定会保存日志。 |
|
28 | + |
|
29 | +### 实现 |
|
30 | + |
|
31 | +- ###### 日志功能 |
|
32 | + |
|
33 | +1. 引擎部分 |
|
34 | + |
|
35 | +[[/uploads/operatorLog/image.png]] |
|
36 | + |
|
37 | +[[/uploads/operatorLog/image1.png]] |
|
38 | + 如上图所示,主要在rpc的入口和finally出口进行数据采集并写入日志队列。 |
|
39 | + |
|
40 | + 另外,需要采集rpc请求过程中的详情,那么就需要在 bussModelDataAccess create/update/delete 三个方法中记录相关日志 |
|
41 | + |
|
42 | +[[/uploads/operatorLog/image2.png]] |
|
43 | +[[/uploads/operatorLog/image3.png]] |
|
44 | +[[/uploads/operatorLog/image4.png]] |
|
45 | + |
|
46 | + 可以发现在执行多个操作方法时,是按照执行的顺序,依次追加进context中的operatorData中的, 这个追加顺序就是链路的顺序。 |
|
47 | + |
|
48 | +2. app部分 |
|
49 | + app源码仓库参考:http://192.168.175.55:9888/appDev/sie-nest-log |
|
50 | + 首先在app中定义了三个模型,分别是:操作日志模型、日志详情模型和黑白名单模型 这三个模型对应这两张数据库表,保存对应的数据内容。其中操作日志和日志详情表,是通过操作日志表的主键去关联日志详情表, |
|
51 | + 也就是说,一条操作日志可能会关联多条日志详情记录,日志详情记录了该操作日志操作过程中的create/update/delete前后的数据,由于在追加的时候已经维护了操作顺序,那么就可以通过生成spanID来在表中维护它们之间的顺序。 |
|
52 | + 可参考下图所示代码: |
|
53 | + |
|
54 | +[[/uploads/operatorLog/image5.png]] |
|
55 | + |
|
56 | +- ###### 性能 |
|
57 | + |
|
58 | +1. 异步阻塞队列 考虑到写入日志的性能,维护了一个异步阻塞队列 |
|
59 | + ```ArrayBlockingQueue<Map<String, Object>> queue = new ArrayBlockingQueue<>(5000);``` |
|
60 | + 任何日志产生后都写入该队列,独立线程进行阻塞消费该队列,触发消费行为由两个方面触发,一个是日志已经满200条,另一个是超时时间10s. |
|
61 | + |
|
62 | +[[/uploads/operatorLog/image6.png]] |
|
63 | + |
|
64 | +- ###### 黑白名单 |
|
65 | + |
|
66 | +黑白名单是为了让用户配置哪些方法需要记录日志,哪些方法不需要记录日志。 此外,该日志app还会自动检测哪些方法的打印日志频率超过了给定的阈值,也会自动加入到黑名单,但是如果该方法已经配置了白名单, |
|
67 | +那么不会再加入白名单了。简单来说判断顺序是:```白名单 > app自动检测 > 黑名单``` |
|
68 | + |
|
69 | +另外,考虑到查询黑白名单的性能,做了进程内缓存,会将使用到的黑白名单缓存在本地内存,并设置缓存的过期时间,也会定时扫描清楚过期的缓存 由于本地缓存与数据库的数据一致性,所以会存在一定的延迟,但最终是一致的。 |
|
70 | + |
|
71 | +[[/uploads/operatorLog/data.png]] |
|
72 | +### web页面效果 |
|
73 | +[[/uploads/operatorLog/web.png]] |
|
... | ... | \ No newline at end of file |
\346\225\260\346\215\256\346\235\203\351\231\220\347\233\270\345\205\263\345\274\200\345\217\221\345\273\272\350\256\256.md
... | ... | @@ -0,0 +1,5 @@ |
1 | +为了减少采用硬编码进行数据权限控制的开发工作量,建议设计、开发人员注意如下: |
|
2 | + |
|
3 | +一、需要按仓库进行数据权限控制的模型,建议统一增加仓库ID:warehouse_id,命名保持一致 |
|
4 | + |
|
5 | +二、需要按组织进行数据权限控制的模型,建议统一增加组织ID:site_id,命名保持一致 |
\346\226\271\346\241\210\344\270\216\350\256\276\350\256\241.md
... | ... | @@ -0,0 +1,16 @@ |
1 | +### [[平台和app授权]] |
|
2 | + |
|
3 | +### [[操作日志app设计与实现]] |
|
4 | + |
|
5 | +### [[Hazelcast分布式内存同步设计与实现.md]] |
|
6 | +### [[hazelcast集群安装]] |
|
7 | + |
|
8 | +### [[Hazelcast性能测试.md]] |
|
9 | + |
|
10 | +### [[JUnit5单元测试.md]] |
|
11 | + |
|
12 | +### [[sonar-lint使用]] |
|
13 | + |
|
14 | +### [[阿里通义千问]] |
|
15 | + |
|
16 | +### [[ORM增强语法功能设计]] |
|
... | ... | \ No newline at end of file |
\346\226\271\346\241\210\346\226\207\346\241\243.md
\346\240\207\345\207\206API\346\216\245\345\217\243\350\275\254\345\217\221APP(Apiadapt).md
... | ... | @@ -0,0 +1,296 @@ |
1 | + 标准API接口转发APP\(Apiadapt\) |
|
2 | + |
|
3 | +## 需求设计: |
|
4 | + |
|
5 | +# ⼀.k8s安装APP(仅在k8s环境中的功能点) |
|
6 | + |
|
7 | +## 功能点名称:服务配置 |
|
8 | + |
|
9 | +### 原型: |
|
10 | + |
|
11 | + |
|
12 | + |
|
13 | +- |
|
14 | + 1. __功能点描述:__ |
|
15 | + |
|
16 | +此功能点是为了设置k8s pod中的服务状态和设置提供服务的副本数,需要注意的是,此功能要在系统中判断是否是k8s的分布式环境 |
|
17 | + |
|
18 | +### 实现步骤: |
|
19 | + |
|
20 | +1. 点击应⽤市场中的“安装”按钮,弹出服务配置 |
|
21 | +2. 在服务配置窗⼝中选择“有状态”或者是“⽆状态”(默认是⽆状态),输⼊副本数(仅允许输⼊正整数,默认是 |
|
22 | + |
|
23 | +1) |
|
24 | + |
|
25 | +1. 点“确认”按钮,后续进⾏常规安装 |
|
26 | + |
|
27 | +### 调⽤接⼝ |
|
28 | + |
|
29 | +#### 输⼊参数: |
|
30 | + |
|
31 | +需要在原来install接⼝服务中增加 kind 和 replicas,分别代表状态和副本数 kind有两种状态分别是: |
|
32 | + |
|
33 | +kind: stateful kind: unStateful |
|
34 | + |
|
35 | +__例如如下⼊参json__ |
|
36 | + |
|
37 | +\{ |
|
38 | + |
|
39 | +"id": "guid", |
|
40 | + |
|
41 | +"jsonrpc": "2\.0", |
|
42 | + |
|
43 | +"method": "service", "params": \{ |
|
44 | + |
|
45 | +"args": \{ |
|
46 | + |
|
47 | +"ids": \[ |
|
48 | + |
|
49 | +"02oj3ppkv9r0g" |
|
50 | + |
|
51 | +\], |
|
52 | + |
|
53 | +"apps":\[ |
|
54 | + |
|
55 | +\{"id": "02oj3ppkv9r0g", "name": "mom\-item"\} |
|
56 | + |
|
57 | +\], |
|
58 | + |
|
59 | +"kind": "stateful", |
|
60 | + |
|
61 | +"replicas": 3 |
|
62 | + |
|
63 | +\}, |
|
64 | + |
|
65 | +"context": \{ |
|
66 | + |
|
67 | +"uid": "", |
|
68 | + |
|
69 | +"lang": "zh\_CN", "useDisplayForModel": true |
|
70 | + |
|
71 | +\}, |
|
72 | + |
|
73 | +"model": "meta\_app\_store", "tag": "master", "service": "installApp" |
|
74 | + |
|
75 | +### 回调接⼝ |
|
76 | + |
|
77 | +\} |
|
78 | + |
|
79 | +\} |
|
80 | + |
|
81 | +http://127\.0\.0\.1:4500/callback |
|
82 | + |
|
83 | +#### 调⽤⽅式:post回调输⼊参数: |
|
84 | + |
|
85 | +保留以上接⼝⼊参的所有参数,增加dependencies、extend、caller,如下: |
|
86 | + |
|
87 | +\{ |
|
88 | + |
|
89 | +"id": "guid", |
|
90 | + |
|
91 | +"jsonrpc": "2\.0", |
|
92 | + |
|
93 | +"method": "service", "params": \{ |
|
94 | + |
|
95 | +"args": \{ |
|
96 | + |
|
97 | +"ids": \[ |
|
98 | + |
|
99 | +"02oj3ppkv9r0g" |
|
100 | + |
|
101 | +\], |
|
102 | + |
|
103 | +"apps": \[\{ |
|
104 | + |
|
105 | +"id": "0279nkacdvpj4", "name": "app3" |
|
106 | + |
|
107 | +\}\], |
|
108 | + |
|
109 | +"kind": "stateful", "replicas": 3, |
|
110 | + |
|
111 | +"dependencies": \["app1", "app2"\], |
|
112 | + |
|
113 | +"extend": \["app1", "app2"\], "caller": "uninstall" |
|
114 | + |
|
115 | +\}, |
|
116 | + |
|
117 | +"service": "", "context": \{ |
|
118 | + |
|
119 | +"uid": "", |
|
120 | + |
|
121 | +"lang": "zh\_CN" |
|
122 | + |
|
123 | +\}, |
|
124 | + |
|
125 | +"model": "meta\_app", |
|
126 | + |
|
127 | +"tag": "master" |
|
128 | + |
|
129 | +\} |
|
130 | + |
|
131 | +\} |
|
132 | + |
|
133 | +## 功能点名称:服务列表 |
|
134 | + |
|
135 | +### 原型 |
|
136 | + |
|
137 | + |
|
138 | + |
|
139 | +- |
|
140 | + 1. __功能点描述__ |
|
141 | + |
|
142 | +列出⼀安装服务的服务列表 |
|
143 | + |
|
144 | +### 实现步骤 |
|
145 | + |
|
146 | +1. 点击左边菜单的“运维管理”下的”服务列表“菜单,右边显示服务列表 |
|
147 | +2. 点击列表“操作”列的“设置副本数”,可以设置该服务的副本数,服务数需要校验,只允许输⼊整数,⼤⼩控制在5 |
|
148 | + |
|
149 | +以内(可配置) |
|
150 | + |
|
151 | +### 调⽤边⻋接⼝ |
|
152 | + |
|
153 | +#### 服务列表 |
|
154 | + |
|
155 | +http://127\.0\.0\.1:4500/getHosts |
|
156 | + |
|
157 | +(与如下的docker 主机列表相同) |
|
158 | + |
|
159 | +#### 调⽤⽅式:get输⼊参数: |
|
160 | + |
|
161 | +⽆输⼊参数 |
|
162 | + |
|
163 | +#### 输出参数: |
|
164 | + |
|
165 | +\[\{ |
|
166 | + |
|
167 | +"extend": \["iiot\_base", "net", "iiot\_thing", "iiot\_project", "redis"\], "caller": "start", |
|
168 | + |
|
169 | +"replicas": 1, //副本数 |
|
170 | + |
|
171 | +"kind": "stateful", |
|
172 | + |
|
173 | +"ids": \["032sscz5p8cu8", "032swsetcxssg", "032sxxbpv0v0g", "032sxxycue8e8", "032sxyju4ym0w", "032sxzdpb981s", "032sy0lve4c1s", "032sy2fogbi0w", "032sy32u5xgqo", "032sy3qxg6ark"\], |
|
174 | + |
|
175 | +"fromStream": false, |
|
176 | + |
|
177 | +"model": "iiot\_alarm\_monitor", |
|
178 | + |
|
179 | +"svcName": "iiot", // 服务名 |
|
180 | + |
|
181 | +"tag": "master", |
|
182 | + |
|
183 | +"state": "installing", //状态: 有这个参数表示安装中,没有表示已安装 |
|
184 | + |
|
185 | +"apps": \[\{ //安装的应⽤ |
|
186 | + |
|
187 | +"name": "apiadapt", "id": "032sscz5p8cu8" |
|
188 | + |
|
189 | +\}, \{ |
|
190 | + |
|
191 | +"name": "iiot\_datasource", "id": "032swsetcxssg" |
|
192 | + |
|
193 | +\}, \{ |
|
194 | + |
|
195 | +"name": "iiot\_access", "id": "032sxxbpv0v0g" |
|
196 | + |
|
197 | +\},\{ |
|
198 | + |
|
199 | +"name": "iiot\_thing", "id": "032sy3qxg6ark" |
|
200 | + |
|
201 | +\}\], |
|
202 | + |
|
203 | +"dependencies": \["iiot\_base", "iiot\_thing", "iiot\_task\_scheduler", "iiot\_store", "iiot\_project", "net", "redis"\] |
|
204 | + |
|
205 | +\}, |
|
206 | + |
|
207 | +\{ |
|
208 | + |
|
209 | +"replicas": 1, "kind": "unStateful", "fromStream": false, "svcName": "doc", |
|
210 | + |
|
211 | +"dependencies": \[\], |
|
212 | + |
|
213 | +"extend": \[\], |
|
214 | + |
|
215 | +"caller": "start", "useDisplayForModel": true, "ids": \["032sxvu6dvhmo"\], |
|
216 | + |
|
217 | +"model": "ui\_menu", |
|
218 | + |
|
219 | +"tag": "master", |
|
220 | + |
|
221 | +"apps": \[\{ |
|
222 | + |
|
223 | +"name": "doc", |
|
224 | + |
|
225 | +"id": "032sxvu6dvhmo" |
|
226 | + |
|
227 | +\}\] |
|
228 | + |
|
229 | +\} |
|
230 | + |
|
231 | +注:以上输出参数对应两个服务,对应⻚⾯的字段清查看注释 |
|
232 | + |
|
233 | +#### 设置副本数调⽤⽅式:post |
|
234 | + |
|
235 | +http://127\.0\.0\.1:4500/setReplicas |
|
236 | + |
|
237 | +__输⼊参数:__ |
|
238 | + |
|
239 | +\{ |
|
240 | + |
|
241 | +"svcName": "app1", "replicas": 2 |
|
242 | + |
|
243 | +\} |
|
244 | + |
|
245 | +__输出参数:__ |
|
246 | + |
|
247 | +true/false |
|
248 | + |
|
249 | +## 功能点名称:安装app加⼊现有服务 |
|
250 | + |
|
251 | +### 原型 |
|
252 | + |
|
253 | + |
|
254 | + |
|
255 | +- |
|
256 | + 1. __功能点描述__ |
|
257 | + |
|
258 | +### 把需要新安装的app加⼊到现有的服务pod中 |
|
259 | + |
|
260 | +### 实现步骤 |
|
261 | + |
|
262 | +- |
|
263 | + - |
|
264 | + 1. 在安装app的过程中可以选择“新建服务”或者“加⼊现有服务” |
|
265 | + 2. “新建服务“与原来界⾯相同,”加⼊现有服务“可选择已经存在的服务名 |
|
266 | + |
|
267 | +### 调⽤接⼝ |
|
268 | + |
|
269 | +“服务名” 调⽤如上的服务列表可得到服务名列表 |
|
270 | + |
|
271 | +“确认” 与原来的安装接⼝相同 |
|
272 | + |
|
273 | +注:这个功能后端没有⼯作量,只是前端界⾯的改变 |
|
274 | + |
|
275 | +# 功能实现: |
|
276 | + |
|
277 | +## 功能点名称:服务列表 |
|
278 | + |
|
279 | +App:apiadapt |
|
280 | + |
|
281 | +Model:service\_node |
|
282 | + |
|
283 | +Service:search |
|
284 | + |
|
285 | + |
|
286 | + |
|
287 | +## 功能点名称:安装app加⼊现有服务 |
|
288 | + |
|
289 | +App:base |
|
290 | + |
|
291 | +Model:installed\_service |
|
292 | + |
|
293 | +Service:search |
|
294 | + |
|
295 | + |
|
296 | + |
\346\250\241\345\236\213\345\246\202\346\236\234\345\243\260\346\230\216isAutoLog=true\345\261\236\346\200\247\357\274\214\344\274\232\350\207\252\345\212\250\347\224\237\346\210\220\345\210\233\345\273\272\346\227\266\351\227\264\357\274\214\345\210\233\345\273\272\344\272\272\357\274\214\344\277\256\346\224\271\346\227\266\351\227\264\344\277\256\346\224\271\344\272\272\345\233\233\344\270\252\345\255\227\346\256\265\357\274\214\344\275\206\346\230\257\346\255\244\345\233\233\344\270\252\345\255\227\346\256\265\346\227\240\346\263\225\345\234\250\345\211\215\347\253\257\346\230\276\347\244\272.md
... | ... | @@ -0,0 +1,3 @@ |
1 | +# 模型如果声明isAutoLog=true |
|
2 | + |
|
3 | +可以定义一个新的字段,指定字段数据库列名为创建时间,创建人,修改时间或修改人字段,将新定义的字段配置在视图中即可显示 |
|
... | ... | \ No newline at end of file |
\346\250\241\345\236\213\345\261\236\346\200\247/Selection.md
... | ... | @@ -0,0 +1,10 @@ |
1 | +# 自定义方法多选 |
|
2 | + |
|
3 | +```java |
|
4 | +@Selection(method = "selectType", multiple = true) |
|
5 | +private String type; |
|
6 | + |
|
7 | +public List<Option> selectType(String[] value) { |
|
8 | + |
|
9 | +} |
|
10 | +``` |
|
... | ... | \ No newline at end of file |
\347\211\210\346\234\254\345\217\221\345\270\203/\345\211\215\345\220\216\347\253\257\347\211\210\346\234\254\346\233\264\346\226\260\344\277\241\346\201\257.md
... | ... | @@ -0,0 +1,1779 @@ |
1 | +# 版本发布 |
|
2 | +tips: |
|
3 | +- v1.x.x.RELEASE是无多租户和审批流的正式版 |
|
4 | +- v2.x.x.RELEASE是有多租户和审批流的正式版 |
|
5 | +- 首次部署请使用如下部署包 |
|
6 | +- k8s部署包 http://192.168.175.54:9000/k8s-snest-deployment/k8s-snest-master-v2.2.2.RELEASE.tar.gz |
|
7 | +- docker部署包 http://192.168.175.54:9000/docker-snest-deployment/docker-snest-master-v2.2.RELEASE.tar.gz |
|
8 | +- [前端K8S配置及升级底座的相关操作](http://iidp.chinasie.com:9999/iidpdoc/pages/bc053c/#k8s%E7%9B%B8%E5%85%B3%E6%93%8D%E4%BD%9C) |
|
9 | +- [[分布式部署 K8S 升级步骤|/k8s-distributed-upgrade-guide]] |
|
10 | +- [升级方法指引](http://iidp.chinasie.com:9999/iidpwiki/upgrad) |
|
11 | + |
|
12 | +# v2.3.9.RELEASE |
|
13 | + |
|
14 | +<table> |
|
15 | + <tr> |
|
16 | + <th>类型</th> |
|
17 | + <th>插件版本</th> |
|
18 | + <th>镜像</th> |
|
19 | + </tr> |
|
20 | + <tr> |
|
21 | + <td style="width:10%">前端</td> |
|
22 | + <td style="width:40%"> |
|
23 | + "@tech/t-base": "2.3.9"<br/> |
|
24 | + "@tech/t-build": "1.0.14"<br/> |
|
25 | + "@tech/t-core": "2.3.4"<br/> |
|
26 | + "@tech/t-el-ui": "2.3.7"<br/> |
|
27 | + </td> |
|
28 | + <td style="width:50%"> |
|
29 | + 镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.3.9.release<br/> |
|
30 | + 下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.3.9.release.tar<br/> |
|
31 | + </td> |
|
32 | + </tr> |
|
33 | + <tr> |
|
34 | + <td style="width:10%">后端</td> |
|
35 | + <td style="width:40%"> |
|
36 | + 更新项:<br/> |
|
37 | + sie-snest-sdk <br/> |
|
38 | + v2.3.3.RELEASE <br/> |
|
39 | + sie-snest-engine <br/> |
|
40 | + v2.3.3.RELEASE <br/> |
|
41 | + </td> |
|
42 | + <td style="width:50%"> |
|
43 | + 镜像地址: harbor.sieiot.com/iidp/sie-snest:v2.3.3.RELEASE |
|
44 | +下载地址: http://idp.chinasie.com/download/repository/sie-snest-v2.3.3.RELEASE.tar |
|
45 | + </td> |
|
46 | + </tr> |
|
47 | +</table> |
|
48 | + |
|
49 | + |
|
50 | +## 更新日期 |
|
51 | +2024/5/15 |
|
52 | + |
|
53 | +## 主要BUG修复 |
|
54 | +1. 修复分布式逻辑删除不可用 |
|
55 | +2. 调整default limit默认上限为5000,可配置 |
|
56 | +3. 修复取审计字段日期报错 |
|
57 | +4. 修复引擎因为apps.json填错启动不起来问题 |
|
58 | +5. 修复安装按钮 |
|
59 | +6. <font color=red>修复切换镜像时,内置APP扩展方法调用链混乱的问题</font> |
|
60 | +7. 查询条件中looup多选拼接.id导致回填问题修复 |
|
61 | +8. 解决表格updateEr情况下表单回显问题 |
|
62 | +9. 去掉了已捕获的错误打印信息 |
|
63 | +10. 树表格还原分页状态 |
|
64 | +11. 添加了自定义处理vue内部错误打印 |
|
65 | +12. 增加删除表格钩子 |
|
66 | +13. lookup查询参数修复 |
|
67 | +14. openview 层级优化 |
|
68 | +15. 修复了懒加载重新加载资源加克隆断开extendView引用地址 |
|
69 | +16. 修复了message组件使用$t报红问题 |
|
70 | +17. 添加了打开菜单路由如果原来有路由则先移除 |
|
71 | +18. 搜索按钮翻译 |
|
72 | +19. 添加了打开菜单路由如果原来有路由则先移除 |
|
73 | + |
|
74 | + |
|
75 | +# v2.3.8.RELEASE |
|
76 | + |
|
77 | +<table> |
|
78 | + <tr> |
|
79 | + <th>类型</th> |
|
80 | + <th>插件版本</th> |
|
81 | + <th>镜像</th> |
|
82 | + </tr> |
|
83 | + <tr> |
|
84 | + <td style="width:10%">前端</td> |
|
85 | + <td style="width:40%"> |
|
86 | + "@tech/t-base":"@2.3.7"<br/> |
|
87 | + "@tech/t-build":"@1.0.14"<br/> |
|
88 | + "@tech/t-core":"@2.3.3"<br/> |
|
89 | + "@tech/t-el-ui":"@2.3.6"<br/> |
|
90 | + </td> |
|
91 | + <td style="width:50%"> |
|
92 | + 镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.3.8.release <br/> |
|
93 | + 下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.3.8.release.tar<br/> |
|
94 | + </td> |
|
95 | + </tr> |
|
96 | + <tr> |
|
97 | + <td style="width:10%">后端</td> |
|
98 | + <td style="width:40%"> |
|
99 | + 多租户APP:<br/> |
|
100 | + sie-snest-tenant-v2.3.5-RELEASE.jar<br/> |
|
101 | + 国际化APP:<br/> |
|
102 | + sie-snest-i18n-v1.0.0-RELEASE.jar<br/> |
|
103 | + 接口APP:<br/> |
|
104 | + sie-snest-interface-v1.2.0-RELEASE.jar<br/> |
|
105 | + 工作流APP:<br/> |
|
106 | + sie-snest-workflow-v1.0.0-RELEASE.jar<br/> |
|
107 | + 日志APP:<br/> |
|
108 | + sie-snest-log-v2.2.0-RELEASE.jar<br/> |
|
109 | + sie-snest-base-v2.3.0.RELEASE.jar<br/> |
|
110 | + sie-snest-cache-v2.3.0.RELEASE.jar<br/> |
|
111 | + sie-snest-file-v2.3.0.RELEASE.jar<br/> |
|
112 | + sie-snest-datasource-v2.3.0.RELEASE.jar<br/> |
|
113 | + sie-snest-mail-v2.3.0.RELEASE.jar<br/> |
|
114 | + sie-snest-dict-v2.3.0.RELEASE.jar<br/> |
|
115 | + 其他:<br/> |
|
116 | + sie-snest-engine<br/> |
|
117 | + v2.3.0.RELEASE<br/> |
|
118 | + sdk<br/> |
|
119 | + sie-snest-sdk<br/> |
|
120 | + v2.3.0.RELEASE<br/> |
|
121 | + 旧版sdk<br/> |
|
122 | + v1.0.17.RELEASE<br/> |
|
123 | + sie-snest-maven-plugin 插件<br/> |
|
124 | + v1.0.0.RELEASE<br/> |
|
125 | + </td> |
|
126 | + <td style="width:50%"> |
|
127 | + 镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.3.1.RELEASE<br/><br/> |
|
128 | + 下载地址: http://idp.chinasie.com/download/repository/sie-snest-v2.3.1.RELEASE.tar<br/><br/> |
|
129 | + </td> |
|
130 | + </tr> |
|
131 | + <tr> |
|
132 | + <td style="width:10%">边车 |
|
133 | + </td> |
|
134 | + <td style="width:40%"> |
|
135 | + |
|
136 | + </td> |
|
137 | + <td style="width:50%"> |
|
138 | + 镜像地址: harbor.sieiot.com/iidp/distributed-engine:v2.0.7.RELEASE <br/> |
|
139 | + 下载地址: http://idp.chinasie.com/download/repository/distributed-engine-v2.0.7.RELEASE.tar<br/> |
|
140 | + </td> |
|
141 | + </tr> |
|
142 | + <tr> |
|
143 | + <td colspan="3"> |
|
144 | + 前后端包APP包: http://docs-iidp.sieiot.com/index.php/s/2SpBjOaTPN7l2co |
|
145 | + </td> |
|
146 | + </tr> |
|
147 | +</table> |
|
148 | + |
|
149 | + |
|
150 | +## 更新日期 |
|
151 | +2024/5/10 |
|
152 | + |
|
153 | +## 不兼容项 |
|
154 | +1. 接口APP菜单需重置种子数据后新功能才生效,种子数据管理中重置api_third_platform_menu数据 |
|
155 | +2. 缓存APP sie-snest-cache-v2.3.0.RELEASE.jar 必须安装 |
|
156 | + |
|
157 | +## 注意事项 |
|
158 | +1. K8S base.json需要修改多租户和工作流的版本,其他的不需要修改 |
|
159 | +```json |
|
160 | +{ |
|
161 | + "loaders": { |
|
162 | + "API": "", |
|
163 | + "SDK": "" |
|
164 | + }, |
|
165 | + "apiToken": "", |
|
166 | + "apps": { |
|
167 | + "SDK": [ |
|
168 | + "sie-snest-tenant-v2.3.5-RELEASE.jar", |
|
169 | + "sie-snest-workflow-v1.0.0-RELEASE.jar" |
|
170 | + ] |
|
171 | + } |
|
172 | +} |
|
173 | +``` |
|
174 | + |
|
175 | +2. 修改边车配置文件下段内容中的变量为 $SERVER_PORT: |
|
176 | + |
|
177 | +``` |
|
178 | +readinessProbe: |
|
179 | + httpGet: |
|
180 | + path: /checkhealth |
|
181 | + port: $SERVER_PORT |
|
182 | + scheme: HTTP |
|
183 | +``` |
|
184 | + |
|
185 | +## 新增 |
|
186 | +1. 国际化APP [操作说明](http://docs-iidp.sieiot.com/index.php/s/1NFspFNlUv6fEbz) |
|
187 | + * 多语言管理 - 新增多语言管理,支持新增、启用、禁用、删除语种,设置默认语种。 |
|
188 | + * 翻译管理 - 新增翻译管理,支持在线翻译资源(菜单、视图等)。 |
|
189 | + * 导入导出 - 新增导入导出功能,支持将译文导入导出,进行批量翻译。 |
|
190 | + * 语种切换 - 新增多语言切换功能,支持在登录页和导览行切换多语言,切换后按照切换的语种展示系统界面。 |
|
191 | +2. 接口APP [操作说明](http://docs-iidp.sieiot.com/index.php/s/xwkolnHOiiJ0hqN) |
|
192 | + * 错误码 - 新增支持错误码多级解析。 |
|
193 | + * 请求头格式转换 - 新增支持post请求且body为XML、Text时,请求体可以进行转换,满足对接微信公众号平台的需求。 |
|
194 | + * 平台管理 - 新增支持统一管理第三方平台的授权信息,包括3种授权方式:固定Token、OAuth 2.0密码模式、Bearer Token。 |
|
195 | + * <font color=red>注意:菜单关联视图改变了,升级后要重置api_third_platform_menu种子数据才生效</font> |
|
196 | +3. 缓存APP |
|
197 | + * 优化登录缓存和RPC缓存逻辑 |
|
198 | + * <font color=red>注意:此为必装项,sie-snest-cache-v2.3.0.RELEASE.jar</font> |
|
199 | + |
|
200 | + |
|
201 | +## 修改及优化 |
|
202 | +1. 租户APP [操作说明](http://docs-iidp.sieiot.com/index.php/s/1NFspFNlUv6fEbz) |
|
203 | + * 作用域 - 优化切换作用域的展示逻辑,切换作用域后,支持根据作用域关联的角色,展示当前用户所属角色的菜单。 |
|
204 | + * 内存 - 优化多租户内存,优化功能权限的注入和循环次数,减少放到内存的权限点。优化分布式内存过高问题,优化引擎启动速度 |
|
205 | +。 |
|
206 | + * 导入导出 - 新增导入导出功能,支持将译文导入导出,进行批量翻译。 |
|
207 | + * 语种切换 - 新增多语言切换功能,支持在登录页和导览行切换多语言,切换后按照切换的语种展示系统界面。 |
|
208 | +2. 接口APP [操作说明](http://docs-iidp.sieiot.com/index.php/s/xwkolnHOiiJ0hqN) |
|
209 | + * 对外接口 - 优化get请求、返回支持的类型,不限制请求头的Content-Type,且不限制返回协议模板为json格式,支持直接返回统一接口的data。 |
|
210 | + * 请求头格式转换 - 新增支持post请求且body为XML、Text时,请求体可以进行转换,满足对接微信公众号平台的需求。 |
|
211 | + * 平台管理 - 新增支持统一管理第三方平台的授权信息,包括3种授权方式:固定Token、OAuth 2.0密码模式、Bearer Token。 |
|
212 | +3. 优化登录缓存和RPC缓存逻辑 |
|
213 | +4. 应用市场APP |
|
214 | + * 批量上架 |
|
215 | + * 可重复安装 |
|
216 | + * 产品线支持多选筛选 |
|
217 | +5. 高可用 |
|
218 | + * 影响服务不可用问题 |
|
219 | + * 修复容器健康检查 |
|
220 | + * 边车bug修复 |
|
221 | + * 修复加入新的应用到容器 |
|
222 | + * 滚动更新 |
|
223 | +6. 分布式JVM内存优化 |
|
224 | + * 优化分布式内存同步 |
|
225 | + * 优化元模型序列化和反序列化 |
|
226 | + * 优化元模型结构 |
|
227 | + |
|
228 | +## 主要BUG修复 |
|
229 | +1. ORM-支持exists子查询 |
|
230 | +2. ORM-修复分库分表软删除别名异常问题;添加分表字段校验 |
|
231 | +3. 解决ManyToOne 必须添加dependencies应用的问题,但是应用必须安装在一起 |
|
232 | +4. 工作流-解决常用流程search和count分页数量不一致;根据流程实例id获取当前任务分组审批人 |
|
233 | +5. 解决SMI提出栈溢出问题 |
|
234 | +6. 修复表单Many2Many字段保存问题 |
|
235 | +7. 修复新增若干字段后更新App,新增的字段列权限缺失导致列表中无法显示和查出数据的bug |
|
236 | +8. base APP界面优化:;菜单管理,模型管理,视图管理,种子数据管理页面添加筛选条件和排序 |
|
237 | + |
|
238 | + |
|
239 | +# v2.2.24.RELEASE |
|
240 | + |
|
241 | +<table> |
|
242 | + <tr> |
|
243 | + <th>类型</th> |
|
244 | + <th>插件版本</th> |
|
245 | + <th>镜像</th> |
|
246 | + </tr> |
|
247 | + <tr> |
|
248 | + <td style="width:10%">前端</td> |
|
249 | + <td style="width:30%">"@tech/t-base":"@2.2.24" <br/>"@tech/t-build":"@1.0.12" <br/>"@tech/t-core":"@1.0.40" <br/>"@tech/t-el-ui":"@2.2.16"</td> |
|
250 | + <td style="width:60%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.24.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.24.release.tar<br/></td> |
|
251 | + </tr> |
|
252 | + <tr> |
|
253 | + <td style="width:8%">后端</td> |
|
254 | + <td style="width:25%">sie-snest-engine-v2.2.8_04.beta.jar<br/>sie-snest-sdk-v2.2.8_04.beta.jar<br/>sie-snest-tenant-v2.4.3-BETA.jar </td> |
|
255 | + <td style="width:77%">镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.2.8_04.beta<br/> |
|
256 | +下载地址:http://idp.chinasie.com/download/repository/sie-snest-v2.2.8_04.beta.tar<br/> |
|
257 | +</td> |
|
258 | + </tr> |
|
259 | +</table> |
|
260 | + |
|
261 | + |
|
262 | +### 更新日期 |
|
263 | +2024/4/26 |
|
264 | + |
|
265 | +### 前端Bug修复及优化 |
|
266 | +新增 |
|
267 | + 暂无 |
|
268 | + |
|
269 | +修复 |
|
270 | +1.表格行内编辑lookup的__values清除 |
|
271 | +2.条件树按钮层级优化 |
|
272 | +3.解决工作流请求数据问题 |
|
273 | +4.openView里主表单返回列表优化 |
|
274 | +5.对扩展中before类型的处理优化 |
|
275 | +6.修复了view协议的添加异步触发 |
|
276 | + |
|
277 | +# v2.2.23.RELEASE |
|
278 | + |
|
279 | +<table> |
|
280 | + <tr> |
|
281 | + <th>类型</th> |
|
282 | + <th>插件版本</th> |
|
283 | + <th>镜像</th> |
|
284 | + </tr> |
|
285 | + <tr> |
|
286 | + <td style="width:10%">前端</td> |
|
287 | + <td style="width:30%">"@tech/t-base":"@2.2.23" <br/>"@tech/t-build":"@1.0.12" <br/>"@tech/t-core":"@1.0.39" <br/>"@tech/t-el-ui":"@2.2.15"</td> |
|
288 | + <td style="width:60%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.23.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.23.release.tar<br/></td> |
|
289 | + </tr> |
|
290 | + <tr> |
|
291 | + <td style="width:8%">后端</td> |
|
292 | + <td style="width:25%">sie-snest-engine-v2.2.8_04.beta.jar<br/>sie-snest-sdk-v2.2.8_04.beta.jar<br/>sie-snest-tenant-v2.4.3-BETA.jar </td> |
|
293 | + <td style="width:77%">镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.2.8_04.beta<br/> |
|
294 | +下载地址:http://idp.chinasie.com/download/repository/sie-snest-v2.2.8_04.beta.tar<br/> |
|
295 | +</td> |
|
296 | + </tr> |
|
297 | +</table> |
|
298 | + |
|
299 | + |
|
300 | +### 更新日期 |
|
301 | +2024/4/25 |
|
302 | + |
|
303 | +### 前端Bug修复及优化 |
|
304 | +新增 |
|
305 | + 暂无 |
|
306 | + |
|
307 | +修复 |
|
308 | +1.主表单返回按钮双击异常修复 |
|
309 | +2.datetime转换修复 |
|
310 | +3.按钮分组优化 |
|
311 | +4.lookup的customFilter优化 |
|
312 | + |
|
313 | +# v2.2.21.RELEASE |
|
314 | + |
|
315 | +<table> |
|
316 | + <tr> |
|
317 | + <th>类型</th> |
|
318 | + <th>插件版本</th> |
|
319 | + <th>镜像</th> |
|
320 | + </tr> |
|
321 | + <tr> |
|
322 | + <td style="width:10%">前端</td> |
|
323 | + <td style="width:30%">"@tech/t-base":"@2.2.21" <br/>"@tech/t-build":"@1.0.12" <br/>"@tech/t-core":"@1.0.39" <br/>"@tech/t-el-ui":"@2.2.14"</td> |
|
324 | + <td style="width:60%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.21.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.21.release.tar<br/></td> |
|
325 | + </tr> |
|
326 | + <tr> |
|
327 | + <td style="width:8%">后端</td> |
|
328 | + <td style="width:25%">sie-snest-engine-v2.2.8_04.beta.jar<br/>sie-snest-sdk-v2.2.8_04.beta.jar<br/>sie-snest-tenant-v2.4.3-BETA.jar </td> |
|
329 | + <td style="width:77%">镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.2.8_04.beta<br/> |
|
330 | +下载地址:http://idp.chinasie.com/download/repository/sie-snest-v2.2.8_04.beta.tar<br/> |
|
331 | +</td> |
|
332 | + </tr> |
|
333 | +</table> |
|
334 | + |
|
335 | + |
|
336 | +### 更新日期 |
|
337 | +2024/4/23 |
|
338 | + |
|
339 | +### 前端Bug修复及优化 |
|
340 | +新增 |
|
341 | + 暂无 |
|
342 | + |
|
343 | +修复 |
|
344 | +1.修复formPart的activeTab问题 |
|
345 | +2.修复openView的表格总条数查询 |
|
346 | +3.表格新增行同步data.tableData和$ds.tableData数据 |
|
347 | +4.修复新增状态下子表刷新入参 |
|
348 | +5.应用市场安装不调接口修复 |
|
349 | + |
|
350 | +# v2.2.20.RELEASE |
|
351 | + |
|
352 | +<table> |
|
353 | + <tr> |
|
354 | + <th>类型</th> |
|
355 | + <th>插件版本</th> |
|
356 | + <th>镜像</th> |
|
357 | + </tr> |
|
358 | + <tr> |
|
359 | + <td style="width:10%">前端</td> |
|
360 | + <td style="width:30%">"@tech/t-base":"@2.2.20" <br/>"@tech/t-build":"@1.0.12" <br/>"@tech/t-core":"@1.0.39" <br/>"@tech/t-el-ui":"@2.2.14"</td> |
|
361 | + <td style="width:60%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.20.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.20.release.tar<br/></td> |
|
362 | + </tr> |
|
363 | + <tr> |
|
364 | + <td style="width:8%">后端</td> |
|
365 | + <td style="width:25%">sie-snest-engine-v2.2.8_04.beta.jar<br/>sie-snest-sdk-v2.2.8_04.beta.jar<br/>sie-snest-tenant-v2.4.3-BETA.jar </td> |
|
366 | + <td style="width:77%">镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.2.8_04.beta<br/> |
|
367 | +下载地址:http://idp.chinasie.com/download/repository/sie-snest-v2.2.8_04.beta.tar<br/> |
|
368 | +</td> |
|
369 | + </tr> |
|
370 | +</table> |
|
371 | + |
|
372 | + |
|
373 | +### 更新日期 |
|
374 | +2024/4/19 |
|
375 | + |
|
376 | +### 前端Bug修复及优化 |
|
377 | +新增 |
|
378 | +1.表格勾选同步全局配置 |
|
379 | +2.openView不缓存全局配置 |
|
380 | + |
|
381 | +修复 |
|
382 | +1.openView中searchByMainTable参数拼接 |
|
383 | + |
|
384 | +# v2.2.19.RELEASE |
|
385 | + |
|
386 | +<table> |
|
387 | + <tr> |
|
388 | + <th>类型</th> |
|
389 | + <th>插件版本</th> |
|
390 | + <th>镜像</th> |
|
391 | + </tr> |
|
392 | + <tr> |
|
393 | + <td style="width:10%">前端</td> |
|
394 | + <td style="width:30%">"@tech/t-base":"@2.2.20" <br/>"@tech/t-build":"@1.0.12" <br/>"@tech/t-core":"@1.0.38" <br/>"@tech/t-el-ui":"@2.2.13"</td> |
|
395 | + <td style="width:60%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.19.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.19.release.tar<br/></td> |
|
396 | + </tr> |
|
397 | + <tr> |
|
398 | + <td style="width:8%">后端</td> |
|
399 | + <td style="width:25%">sie-snest-engine-v2.2.8_04.beta.jar<br/>sie-snest-sdk-v2.2.8_04.beta.jar<br/>sie-snest-tenant-v2.4.3-BETA.jar </td> |
|
400 | + <td style="width:77%">镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.2.8_04.beta<br/> |
|
401 | +下载地址:http://idp.chinasie.com/download/repository/sie-snest-v2.2.8_04.beta.tar<br/> |
|
402 | +</td> |
|
403 | + </tr> |
|
404 | +</table> |
|
405 | + |
|
406 | + |
|
407 | +### 更新日期 |
|
408 | +2024/4/17 |
|
409 | + |
|
410 | +### 前端Bug修复及优化 |
|
411 | +新增 |
|
412 | +1.oepnView的app传参 |
|
413 | +2弹框选择器添加清除事件 |
|
414 | + |
|
415 | +修复 |
|
416 | +1.lookup赋值触发changeHandler修复 |
|
417 | + |
|
418 | +# v2.2.18.RELEASE |
|
419 | + |
|
420 | +<table> |
|
421 | + <tr> |
|
422 | + <th>类型</th> |
|
423 | + <th>插件版本</th> |
|
424 | + <th>镜像</th> |
|
425 | + </tr> |
|
426 | + <tr> |
|
427 | + <td style="width:10%">前端</td> |
|
428 | + <td style="width:30%">"@tech/t-base":"@2.2.18" <br/>"@tech/t-build":"@1.0.9" <br/>"@tech/t-core":"@1.0.38" <br/>"@tech/t-el-ui":"@2.2.12"</td> |
|
429 | + <td style="width:60%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.18.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.18.release.tar<br/></td> |
|
430 | + </tr> |
|
431 | + <tr> |
|
432 | + <td style="width:8%">后端</td> |
|
433 | + <td style="width:25%">sie-snest-engine-v2.2.8_04.beta.jar<br/>sie-snest-sdk-v2.2.8_04.beta.jar<br/>sie-snest-tenant-v2.4.3-BETA.jar </td> |
|
434 | + <td style="width:77%">镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.2.8_04.beta<br/> |
|
435 | +下载地址:http://idp.chinasie.com/download/repository/sie-snest-v2.2.8_04.beta.tar<br/> |
|
436 | +</td> |
|
437 | + </tr> |
|
438 | +</table> |
|
439 | + |
|
440 | + |
|
441 | +### 更新日期 |
|
442 | +2024/4/10 |
|
443 | + |
|
444 | +### 前端Bug修复及优化 |
|
445 | +新增 |
|
446 | +1.视图配置转Json |
|
447 | +2.后端视图放前端工程调试功能 |
|
448 | +3.自定义按钮增加actionAfterFn配置 |
|
449 | +4.searchByMainTable增加app入参 |
|
450 | +5.弹窗选择器相关组件添加清除功能 |
|
451 | +6.文件预览添加视频 |
|
452 | + |
|
453 | +修复 |
|
454 | +1.表格行内按钮重复问题 |
|
455 | +2.表格自定义表格列按钮修复 |
|
456 | +3.表单提交JSON校验 |
|
457 | +4.lookup多选回填修复 |
|
458 | +5.表单分组对custom问题修复 |
|
459 | +6.表格选中数据清除修复 |
|
460 | +7.子表行内编辑新增行,传参修复 |
|
461 | +8.openView的表单取消默认查询 |
|
462 | +9.表单提交对空字符串转换null |
|
463 | +10.表格滚动条鼠标移入样式优化 |
|
464 | +11.表单中弹窗选择器select多选回填修复 |
|
465 | +12.预览组件图片格式预览修改 |
|
466 | + |
|
467 | +# v2.2.17.RELEASE |
|
468 | + |
|
469 | +<table> |
|
470 | + <tr> |
|
471 | + <th>类型</th> |
|
472 | + <th>插件版本</th> |
|
473 | + <th>镜像</th> |
|
474 | + </tr> |
|
475 | + <tr> |
|
476 | + <td style="width:10%">前端</td> |
|
477 | + <td style="width:30%">"@tech/t-base":"@2.2.17" <br/>"@tech/t-build":"@1.0.9" <br/>"@tech/t-core":"@1.0.37" <br/>"@tech/t-el-ui":"@2.2.11"</td> |
|
478 | + <td style="width:60%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.17.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.17.release.tar<br/></td> |
|
479 | + </tr> |
|
480 | + <tr> |
|
481 | + <td style="width:8%">后端</td> |
|
482 | + <td style="width:25%">sie-snest-engine-v2.2.8_04.beta.jar<br/>sie-snest-sdk-v2.2.8_04.beta.jar<br/>sie-snest-tenant-v2.4.3-BETA.jar </td> |
|
483 | + <td style="width:77%">镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.2.8_04.beta<br/> |
|
484 | +下载地址:http://idp.chinasie.com/download/repository/sie-snest-v2.2.8_04.beta.tar<br/> |
|
485 | +</td> |
|
486 | + </tr> |
|
487 | +</table> |
|
488 | + |
|
489 | + |
|
490 | +### 更新日期 |
|
491 | +2024/3/27 |
|
492 | +### 前端Bug修复及优化 |
|
493 | + |
|
494 | +1. 导入接口拼接自定义参数 |
|
495 | +2. 操作列按钮自定义参数解析修复 |
|
496 | +3. oepnView的行内编辑按钮显示异常修复 |
|
497 | + |
|
498 | +### 后端Bug修复 |
|
499 | + |
|
500 | +1. 修复MBM提出 filter过滤日期时间null的问题 |
|
501 | +2. 修复MBM提出的many2many selection字段多选保存不更新问题 |
|
502 | +3. 修复孚能项目提出的后端视图扩展不生效问题 |
|
503 | +4. 修复多租户APP,视图支持配置视图白名单(处理无需登录却能loaView访问标准视图;) |
|
504 | +5. 修复多租户APP,授权,支持仅配置查询权限(表格下新增详情按钮,授权详情,则只拥有查看权限); |
|
505 | +6. 修复多租户APP,处理兼容通配符发版后问题,新增,菜单隐藏能力,配置显/隐(处理openView访问标准视图,不显示菜单,却需要拥有菜单底下视图权限) |
|
506 | + |
|
507 | + |
|
508 | +# v2.2.15.RELEASE(使用下个版本) |
|
509 | + |
|
510 | +<table> |
|
511 | + <tr> |
|
512 | + <th>类型</th> |
|
513 | + <th>插件版本</th> |
|
514 | + <th>镜像</th> |
|
515 | + </tr> |
|
516 | + <tr> |
|
517 | + <td style="width:10%">前端</td> |
|
518 | + <td style="width:30%">"@tech/t-base":"@2.2.15" <br/>"@tech/t-build":"@1.0.9" <br/>"@tech/t-core":"@1.0.36" <br/>"@tech/t-el-ui":"@2.2.10"</td> |
|
519 | + <td style="width:60%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.15.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.15.release.tar<br/></td> |
|
520 | + </tr> |
|
521 | + <tr> |
|
522 | + <td style="width:8%">后端</td> |
|
523 | + <td style="width:25%">sie-snest-engine-v2.2.7.RELEASE.jar<br/>sie-snest-sdk-v2.2.7.RELEASE.jar<br/> |
|
524 | +sie-snest-tenant-v2.4.3-BETA.jar</td> |
|
525 | + <td style="width:77%">镜像地址:<br/> |
|
526 | +下载地址:<br/> |
|
527 | +</td> |
|
528 | + </tr> |
|
529 | +</table> |
|
530 | + |
|
531 | + |
|
532 | +### 更新日期 |
|
533 | +2024/3/25 |
|
534 | +### 新增功能 |
|
535 | + |
|
536 | +1. 行内编辑增加editChange配置 |
|
537 | +2. 视图支持配置视图白名单(处理无需登录却能loaView访问标准视图;) |
|
538 | + 1. 在 application.properties 添加配置 `view.whiteList=model.*` 代表放行该模型所有视图 |
|
539 | + 2. 配置 `view.whiteList=model.viewIdA,model.viewIdB` 代表放行该模型指定视图。可以使用逗号分隔指定多个视图。 |
|
540 | +3. 授权,支持仅配置查询权限(表格下新增详情按钮,授权详情,则只拥有查看权限); |
|
541 | +4. 多租户权限控制页面添加控制菜单的开关按钮,处理通配符优化发版后问题,新增,菜单隐藏能力,配置开/关 对应 显/隐(处理openView访问标准视图,不显示菜单,却需要拥有菜单底下视图权限)); |
|
542 | + |
|
543 | +### Bug修复及优化 |
|
544 | + |
|
545 | +1. openView行内编辑表格的删除按钮重复显示问题 |
|
546 | +2. 上下表子表过滤fpormpart |
|
547 | +3. 表格默认开启横向虚拟滚动 |
|
548 | +4. 搜索条件缓存修复 |
|
549 | +5. openView内视图操作增加标识 |
|
550 | +6. 分布式可重复安装应用逻辑判断 |
|
551 | + |
|
552 | +# v2.2.14.RELEASE |
|
553 | + |
|
554 | +<table> |
|
555 | + <tr> |
|
556 | + <th>类型</th> |
|
557 | + <th>插件版本</th> |
|
558 | + <th>镜像</th> |
|
559 | + </tr> |
|
560 | + <tr> |
|
561 | + <td style="width:10%">前端</td> |
|
562 | + <td style="width:30%">"@tech/t-base":"@2.2.14" <br/>"@tech/t-build":"@1.0.9" <br/>"@tech/t-core":"@1.0.36" <br/>"@tech/t-el-ui":"@2.2.9"</td> |
|
563 | + <td style="width:60%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.14.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.14.release.tar<br/></td> |
|
564 | + </tr> |
|
565 | + <tr> |
|
566 | + <td style="width:8%">后端</td> |
|
567 | + <td style="width:25%">sie-snest-engine-v2.2.7.RELEASE.jar<br/>sie-snest-sdk-v2.2.7.RELEASE.jar<br/> |
|
568 | +sie-snest-tenant-v2.3.3-RELEASE.jar</td> |
|
569 | + <td style="width:77%">镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.2.7.RELEASE<br/> |
|
570 | +下载地址:http://idp.chinasie.com/download/repository/sie-snest-v2.2.7.RELEASE.tar<br/> |
|
571 | +</td> |
|
572 | + </tr> |
|
573 | +</table> |
|
574 | + |
|
575 | + |
|
576 | +### 更新日期 |
|
577 | +2024/3/22 |
|
578 | + |
|
579 | +### 新增功能 |
|
580 | +1.表格工具栏导入功能自定义入参 |
|
581 | +2.表格工具栏自定义按钮自定义入参 |
|
582 | +3.子表addEr添加关联弹框已选择数据自定义显示内容 |
|
583 | +4.表单项增加过滤前后空格配置 |
|
584 | + |
|
585 | +### Bug修复及优化 |
|
586 | +1.筛选条件异常 |
|
587 | +2.弹框选择器数据回填异常 |
|
588 | +3.表格导出功能优化 |
|
589 | + |
|
590 | +# v2.2.13.RELEASE |
|
591 | + |
|
592 | +<table> |
|
593 | + <tr> |
|
594 | + <th>类型</th> |
|
595 | + <th>插件版本</th> |
|
596 | + <th>镜像</th> |
|
597 | + </tr> |
|
598 | + <tr> |
|
599 | + <td style="width:10%">前端</td> |
|
600 | + <td style="width:30%">"@tech/t-base":"@2.2.13" <br/>"@tech/t-build":"@1.0.8" <br/>"@tech/t-core":"@1.0.35" <br/>"@tech/t-el-ui":"@2.2.8"</td> |
|
601 | + <td style="width:60%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.13.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.13.release.tar<br/></td> |
|
602 | + </tr> |
|
603 | + <tr> |
|
604 | + <td style="width:8%">后端</td> |
|
605 | + <td style="width:25%">sie-snest-engine-v2.2.7.RELEASE.jar<br/>sie-snest-sdk-v2.2.7.RELEASE.jar<br/> |
|
606 | +sie-snest-tenant-v2.3.3-RELEASE.jar</td> |
|
607 | + <td style="width:77%">镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.2.7.RELEASE<br/> |
|
608 | +下载地址:http://idp.chinasie.com/download/repository/sie-snest-v2.2.7.RELEASE.tar<br/> |
|
609 | +</td> |
|
610 | + </tr> |
|
611 | +</table> |
|
612 | + |
|
613 | + |
|
614 | +### 更新日期 |
|
615 | +2024/3/19 |
|
616 | + |
|
617 | +### Bug修复 |
|
618 | +1. 子表保存id异常,导致数据被删除问题修复 |
|
619 | +2. 树组件保存时易受扩展影响导致数据保存后错乱问题修复 |
|
620 | + |
|
621 | + |
|
622 | +# v2.2.11.RELEASE |
|
623 | + |
|
624 | +<table> |
|
625 | + <tr> |
|
626 | + <th>类型</th> |
|
627 | + <th>插件版本</th> |
|
628 | + <th>镜像</th> |
|
629 | + </tr> |
|
630 | + <tr> |
|
631 | + <td style="width:10%">前端</td> |
|
632 | + <td style="width:30%">"@tech/t-base":"@2.2.10" <br/>"@tech/t-build":"@1.0.8" <br/>"@tech/t-core":"@1.0.34" <br/>"@tech/t-el-ui":"@2.2.7"</td> |
|
633 | + <td style="width:60%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.10.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.10.release.tar<br/></td> |
|
634 | + </tr> |
|
635 | + <tr> |
|
636 | + <td style="width:8%">后端</td> |
|
637 | + <td style="width:25%">sie-snest-engine-v2.2.7.RELEASE.jar<br/>sie-snest-sdk-v2.2.7.RELEASE.jar<br/> |
|
638 | +sie-snest-tenant-v2.3.3-RELEASE.jar</td> |
|
639 | + <td style="width:77%">镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.2.7.RELEASE<br/> |
|
640 | +下载地址:http://idp.chinasie.com/download/repository/sie-snest-v2.2.7.RELEASE.tar<br/> |
|
641 | +</td> |
|
642 | + </tr> |
|
643 | +</table> |
|
644 | + |
|
645 | + |
|
646 | +### 更新日期 |
|
647 | +2024/3/18 |
|
648 | + |
|
649 | +### 新增 |
|
650 | +1. 增加seeder.sync: true/false 默认为true,设置是否同步种子数据;不同步种子数据则权限信息也不会更新 |
|
651 | +2. 增加前端可写后端视图的能力,方便调试 |
|
652 | +3. 增加切换作用域可切换菜单的能力 |
|
653 | + |
|
654 | +### Bug修复 |
|
655 | +1. 修复排序未生效的bug |
|
656 | +2. 修复从配置中心获取配置时,null 变成了“null”的问题 |
|
657 | +3. 修复many2many中间表建模型后自动生成id以及租户id字段导致匹配条件和查询字段冲突bug |
|
658 | +4. 修复逻辑删除的时候使用别名的问题 |
|
659 | + |
|
660 | +### 多租户更新 |
|
661 | + |
|
662 | +作用域优化: |
|
663 | +1、作用域与角色关联,根据用户关联的作用域限制角色范围,从而实现控制用户菜单权限; |
|
664 | + |
|
665 | +多租户APP性能提升: |
|
666 | + |
|
667 | +引入种子数据同步开关(seeder.sync: true/false)机制, |
|
668 | + |
|
669 | + |
|
670 | +当设置为true时,多租户APP会分析并构建安装APP时的多租户权限树,并将其注入引擎,以保证初始化权限数据的完整性和一致性。 |
|
671 | +若设置为false,系统将不再执行分析构建权限树的过程,转而直接采用数据库中已存在的多租户数据权限树,此举旨在减少不必要的计算开销,有效提升多租户环境下APP的启动性能。 |
|
672 | + |
|
673 | +(**温馨提醒**:1、空库启动,seeder.sync禁止设置为false;2、seeder.sync设置为false,则重启应用或应用市场安装app,都需要在已安装应用点击更新种子数据,再到多租户-权限点明细,点击刷新权限) |
|
674 | + |
|
675 | +# v2.2.10.RELEASE |
|
676 | + |
|
677 | +<table> |
|
678 | + <tr> |
|
679 | + <th>类型</th> |
|
680 | + <th>插件版本</th> |
|
681 | + <th>镜像</th> |
|
682 | + </tr> |
|
683 | + <tr> |
|
684 | + <td style="width:10%">前端</td> |
|
685 | + <td style="width:30%">"@tech/t-base":"@2.2.10" <br/>"@tech/t-build":"@1.0.8" <br/>"@tech/t-core":"@1.0.34" <br/>"@tech/t-el-ui":"@2.2.7"</td> |
|
686 | + <td style="width:60%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.10.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.10.release.tar<br/></td> |
|
687 | + </tr> |
|
688 | + <tr> |
|
689 | + <td style="width:8%">后端</td> |
|
690 | + <td style="width:25%">sie-snest-engine-v2.2.6.RELEASE.jar<br/> |
|
691 | +sie-snest-tenant-v2.2.3-RELEASE.jar</td> |
|
692 | + <td style="width:77%">镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.2.6.RELEASE<br/> |
|
693 | +下载地址:http://idp.chinasie.com/download/repository/sie-snest-v2.2.6.RELEASE.tar<br/> |
|
694 | +</td> |
|
695 | + </tr> |
|
696 | +</table> |
|
697 | + |
|
698 | + |
|
699 | +### 更新日期 |
|
700 | +2024/3/15 |
|
701 | + |
|
702 | +### Bug修复 |
|
703 | +- 修复openView子表数据异常问题 |
|
704 | +- 修复部分组件使用问题 |
|
705 | + |
|
706 | +# v2.2.6.RELEASE |
|
707 | + |
|
708 | +<table> |
|
709 | + <tr> |
|
710 | + <th>类型</th> |
|
711 | + <th>插件版本</th> |
|
712 | + <th>镜像</th> |
|
713 | + </tr> |
|
714 | + <tr> |
|
715 | + <td style="width:10%">前端</td> |
|
716 | + <td style="width:30%">"@tech/t-base":"@2.2.6" <br/>"@tech/t-build":"@1.0.8" <br/>"@tech/t-core":"@1.0.32" <br/>"@tech/t-el-ui":"@2.2.3"</td> |
|
717 | + <td style="width:60%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.6.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.6.release.tar<br/></td> |
|
718 | + </tr> |
|
719 | + <tr> |
|
720 | + <td style="width:8%">后端</td> |
|
721 | + <td style="width:25%">sie-snest-engine-v2.2.6.RELEASE.jar<br/> |
|
722 | +sie-snest-tenant-v2.2.3-RELEASE.jar</td> |
|
723 | + <td style="width:77%">镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.2.6.RELEASE<br/> |
|
724 | +下载地址:http://idp.chinasie.com/download/repository/sie-snest-v2.2.6.RELEASE.tar<br/> |
|
725 | +</td> |
|
726 | + </tr> |
|
727 | +</table> |
|
728 | + |
|
729 | + |
|
730 | +### 更新日期 |
|
731 | +2024/3/8 |
|
732 | + |
|
733 | +### 新增 |
|
734 | + |
|
735 | +- 工作流-孚能项目提出提供API接口完成用户任务,指定多个审批人,获取用户任务列表 |
|
736 | + |
|
737 | + |
|
738 | +### Bug修复 |
|
739 | + |
|
740 | +- 修复视图种子数据重置报别名不能为空的问题(MBM) |
|
741 | +- 修复期望单一记录问题,回退 store update方法(MBM) |
|
742 | +- 修复MySQL delete不要使用别名,修复版本不兼容问题(HA)不需要测试(HA环境) |
|
743 | +- 修复工作流id传参问题,这里的id会被审批流服务认为是流程实例的id(利源) |
|
744 | +- 修复:利源环境日志问题,去掉线程变量拷贝日志(利源) |
|
745 | +- 修复接口app对外接口,模型转换不支持null和对象返回的问题(通威) |
|
746 | +- 修复oracle环境孚能项目重置种子数据异常问题(孚能) |
|
747 | +- 修复oracle环境孚能项目ManyToMany查询异常问题(孚能) |
|
748 | +- 修复IIOT物模型扩展不起作用的问题(利源,IDP) |
|
749 | +- 前端修复:工作流-利源项目工作流tab打开异常问题(利源) |
|
750 | + |
|
751 | + |
|
752 | + |
|
753 | + |
|
754 | +### 升级注意事项 |
|
755 | +1. base.json 修改sie-snest-tenant APP 版本为sie-snest-tenant-v2.2.3-RELEASE.jar |
|
756 | + |
|
757 | + ```json |
|
758 | + { |
|
759 | + // ...其他配置 |
|
760 | + "apps": { |
|
761 | + "SDK": [ |
|
762 | + // ...其他 APP |
|
763 | + "sie-snest-tenant-v2.2.3-RELEASE.jar", // 多租户应用 |
|
764 | + ] |
|
765 | + } |
|
766 | + } |
|
767 | + ``` |
|
768 | + |
|
769 | +2.Maven仓库地址:http://192.168.168.156:8081/#browse/search/maven=format%3Dmaven2%20AND%20attributes.maven2.groupId%3Dcom.sie.meta%20AND%20version%3Dv2.2.6* |
|
770 | + |
|
771 | +*** |
|
772 | + |
|
773 | +# v2.2.2.RELEASE |
|
774 | + |
|
775 | +<table> |
|
776 | + <tr> |
|
777 | + <th>类型</th> |
|
778 | + <th>插件版本</th> |
|
779 | + <th>镜像</th> |
|
780 | + </tr> |
|
781 | + <tr> |
|
782 | + <td style="width:8%">前端</td> |
|
783 | + <td style="width:25%">"@tech/t-base":"2.2.2" <br/>"@tech/t-build":"1.0.7" <br/>"@tech/t-core":"1.0.30" <br/>"@tech/t-el-ui":"2.2.0"</td> |
|
784 | + <td style="width:77%">镜像地址: harbor.sieiot.com/iidp/snest-nginx:v2.2.2.release <br/><br/>下载地址: http://idp.chinasie.com/download/repository/snest-nginx-v2.2.2.release.tar<br/>webgme镜像地址: harbor.sieiot.com/iidp/webgme:v1.0.1</td> |
|
785 | + </tr> |
|
786 | + <tr> |
|
787 | + <td style="width:8%">后端</td> |
|
788 | + <td style="width:25%">sie-snest-engine-v2.2.0.RELEASE.jar<br/> |
|
789 | +sie-snest-sdk-v2.2.0.RELEASE.jar<br/>sie-snest-tenant-v2.1.1-RELEASE.jar<br/>sie-snest-api-adapt-v1.0.0-RELEASE.jar</td> |
|
790 | + <td style="width:77%">镜像地址:harbor.sieiot.com/iidp/sie-snest:v2.2.0.RELEASE<br/> |
|
791 | +下载地址:http://idp.chinasie.com/download/repository/sie-snest-v2.2.0.RELEASE.tar</td> |
|
792 | + </tr> |
|
793 | +</table> |
|
794 | + |
|
795 | + |
|
796 | +### 更新日期 |
|
797 | +2024/3/1 |
|
798 | + |
|
799 | +### 新增 |
|
800 | + |
|
801 | +- 实现了标准API接口转发应用的功能集成(接口APP) |
|
802 | +- 应用市场增加了批量导入和上架功能 |
|
803 | +- 标准模板新增:树+详情页 |
|
804 | +- 无界性能模式 |
|
805 | +- webgme汉化 + 权限拉通 |
|
806 | +- 菜单添加扩展功能 |
|
807 | +- 数据权限和作用域能力增强 |
|
808 | + - 新增三种操作符 |
|
809 | + - in:包含 |
|
810 | + - not in:不包含 |
|
811 | + - child_of:当前节点以及所有子节点 |
|
812 | + - 新增三个内置变量 |
|
813 | + - 当前用户所属集团及以下 |
|
814 | + - 当前用户所属单位及以下 |
|
815 | + - 当前用户所属部门及以下 |
|
816 | + |
|
817 | + |
|
818 | + |
|
819 | + |
|
820 | +### 优化 |
|
821 | + |
|
822 | +#### IIDP后台优化 |
|
823 | + |
|
824 | +* 多租户用户信息 |
|
825 | +* 多租户-租户授权性能 |
|
826 | +* 多租户功能权限授权性能 |
|
827 | +* 多租户功能权限菜单树查询性能 |
|
828 | +* 多租户权限配置拆分功能权限、服务权限、工作流权限、移动端权限等 |
|
829 | + |
|
830 | + |
|
831 | + |
|
832 | +#### ORM优化和能力升级 |
|
833 | + |
|
834 | + - 性能优化:提升了查询接口性能,优化了多对多查询,减少了SQL查询次数,合并前端用户权限查询接口 |
|
835 | + |
|
836 | + - 新增支持Filter:实现了对单表两列的比较支持 |
|
837 | + |
|
838 | + - 新增支持根据流程实例ID获取当前执行任务的审批人 |
|
839 | + |
|
840 | + - 新增对达梦数据库的支持 |
|
841 | + |
|
842 | + - 新增DDL建表模式配置。提供ddl建表模式配置,控制建表语句执行,用于在开发中提升项目启动速度,也可用于项目上线后建表语句由dba执行 |
|
843 | + |
|
844 | + - 新增OneToMany关系中related字段的查询 |
|
845 | + |
|
846 | + - 新增ManyToMany cascadeType级联删除支持 |
|
847 | + |
|
848 | + - 新增增加savepoint控制嵌套事务判空 |
|
849 | + |
|
850 | + - 新增OneToMany支持行子查询(Row Subquery)配置 |
|
851 | + |
|
852 | +### 修复 |
|
853 | + |
|
854 | +#### Bug修复 |
|
855 | + |
|
856 | + - 修复唯一校验空值处理问题 |
|
857 | + - 修复DateTime属性精度提升到纳秒级别 |
|
858 | + - 修复传参id为null时插入SQL异常的问题 |
|
859 | + - 修复设备管理-定标使用审批流驳回时需填写“审核意见”的问题 |
|
860 | + - 修复分布式行内编辑问题 |
|
861 | + - 修复SQL输出完整的带参数的SQL问题 |
|
862 | + - 修复校验提示中去掉英文字符的问题 |
|
863 | + - 修复锁表问题,通过从基类获取显示名称避免频繁生成DDL语句 |
|
864 | + - 修复软删除异常问题,软删除默认添加审计字段 |
|
865 | + - 修复oracle批量删除突破in值不超过一千的限制问题 |
|
866 | + - 修复工作流中提出的撤回操作的id错误问题 |
|
867 | + - 修复工作流处理模型不存在的问题,返回null app的问题 |
|
868 | + - 修复分布式APP apiadapt安装异常 |
|
869 | + |
|
870 | + |
|
871 | + |
|
872 | +### 升级注意事项 |
|
873 | + |
|
874 | +1. 审批流: |
|
875 | + |
|
876 | + * 使用旧数据:升级后不重置种子数据 |
|
877 | + * 出现重复菜单:删除多余菜单 |
|
878 | + * 依赖webgme |
|
879 | + |
|
880 | +2. webgme: |
|
881 | + |
|
882 | + 1. 系统部署:使用webgme镜像v1.1.0 |
|
883 | + |
|
884 | + 2. nginx配置:转发/webgme/ |
|
885 | + |
|
886 | + ```nginx |
|
887 | + # websocket需要增加该配置 |
|
888 | + map $http_upgrade $connection_upgrade { |
|
889 | + default keep-alive; |
|
890 | + 'websocket' upgrade; |
|
891 | + } |
|
892 | + |
|
893 | + # 在server中配置转发 |
|
894 | + location ^~/webgme/ { |
|
895 | + proxy_http_version 1.1; |
|
896 | + # host地址与webgme访问地址一致 |
|
897 | + proxy_pass http://dev.snest.com:31815/webgme/; |
|
898 | + client_max_body_size 200m; |
|
899 | + } |
|
900 | + ``` |
|
901 | + |
|
902 | + 后端项目 |
|
903 | + |
|
904 | + 3. application-dev.properties增加以下配置: |
|
905 | + |
|
906 | + ```nginx |
|
907 | + webgme服务配置: |
|
908 | + |
|
909 | + # 后端要访问的地址,webgme部署的服务器ip地址 |
|
910 | + webgme.url=http://192.168.168.25:30139 |
|
911 | + |
|
912 | + # 应答给前端的地址,url的host要与iidp访问的host一致 |
|
913 | + webgme.url.front=http://{iidp的host}/{nginx配置解析webgme的关键词} |
|
914 | + onlineIDE.designUrl=/idedev |
|
915 | + ``` |
|
916 | + |
|
917 | + 4. base.json 修改 tenat APP 版本、增加 interface APP 配置、增加 webgme APP 配置 |
|
918 | + |
|
919 | + ```json |
|
920 | + { |
|
921 | + // ...其他配置 |
|
922 | + "apps": { |
|
923 | + "SDK": [ |
|
924 | + // ...其他 APP |
|
925 | + "sie-snest-tenant-v2.1.1-RELEASE.jar", // 多租户应用 |
|
926 | + "sie-snest-interface-v1.0.0-RELEASE.jar" // 接口App |
|
927 | + ], |
|
928 | + "API": [ |
|
929 | + { |
|
930 | + "name": "APP_1", |
|
931 | + "tag": "master", |
|
932 | + "resolved": "{webgme的访问地址}/routers/sierouter/getBranchesProject/{webgme的用户名}+{该用户的项目名}" //url中的host需要与application-dev.properties配置的webgme.url一致 |
|
933 | + } |
|
934 | + ] |
|
935 | + } |
|
936 | + } |
|
937 | + ``` |
|
938 | + |
|
939 | + |
|
940 | + |
|
941 | + |
|
942 | +# v2.1.12.RELEASE |
|
943 | + |
|
944 | +<table> |
|
945 | + <tr> |
|
946 | + <th>类型</th> |
|
947 | + <th>插件版本</th> |
|
948 | + <th>镜像</th> |
|
949 | + </tr> |
|
950 | + <tr> |
|
951 | + <td style="width:8%">前端</td> |
|
952 | + <td style="width:25%">"@tech/t-base":"2.1.12" <br/>"@tech/t-build":"1.0.6" <br/>"@tech/t-core":"1.0.27" <br/>"@tech/t-el-ui":"2.0.16"</td> |
|
953 | + <td style="width:77%">harbor.sieiot.com/iidp/snest-nginx:v2.1.12.release <br/><br/>http://idp.chinasie.com/download/repository/snest-nginx-v2.1.12.release.tar</td> |
|
954 | + </tr> |
|
955 | + <tr> |
|
956 | + <td style="width:8%">后端</td> |
|
957 | + <td style="width:25%"></td> |
|
958 | + <td style="width:77%"></td> |
|
959 | + </tr> |
|
960 | +</table> |
|
961 | + |
|
962 | + |
|
963 | +### 更新日期 |
|
964 | +2024/2/4 |
|
965 | + |
|
966 | +### 修改点 |
|
967 | + |
|
968 | +- openView表格翻页bug修复 |
|
969 | +- |
|
970 | + |
|
971 | +# v2.1.11.RELEASE |
|
972 | + |
|
973 | +<table> |
|
974 | + <tr> |
|
975 | + <th>类型</th> |
|
976 | + <th>插件版本</th> |
|
977 | + <th>镜像</th> |
|
978 | + </tr> |
|
979 | + <tr> |
|
980 | + <td style="width:8%">前端</td> |
|
981 | + <td style="width:25%">"@tech/t-base":"2.1.11" <br/>"@tech/t-build":"1.0.6" <br/>"@tech/t-core":"1.0.27" <br/>"@tech/t-el-ui":"2.0.16"</td> |
|
982 | + <td style="width:77%">harbor.sieiot.com/iidp/snest-nginx:v2.1.11.release <br/><br/>http://idp.chinasie.com/download/repository/snest-nginx-v2.1.11.release.tar</td> |
|
983 | + </tr> |
|
984 | + <tr> |
|
985 | + <td style="width:8%">后端</td> |
|
986 | + <td style="width:25%"></td> |
|
987 | + <td style="width:77%"></td> |
|
988 | + </tr> |
|
989 | +</table> |
|
990 | + |
|
991 | + |
|
992 | +### 更新日期 |
|
993 | +2024/2/1 |
|
994 | + |
|
995 | +### 修改点 |
|
996 | + |
|
997 | +- 1.子表数据删除不掉修复 |
|
998 | +- 2. openView表格保存异常提示 |
|
999 | + |
|
1000 | +# v2.1.10.RELEASE |
|
1001 | + |
|
1002 | +<table> |
|
1003 | + <tr> |
|
1004 | + <th>类型</th> |
|
1005 | + <th>插件版本</th> |
|
1006 | + <th>镜像</th> |
|
1007 | + </tr> |
|
1008 | + <tr> |
|
1009 | + <td style="width:8%">前端</td> |
|
1010 | + <td style="width:25%">"@tech/t-base":"2.1.10" <br/>"@tech/t-build":"1.0.6" <br/>"@tech/t-core":"1.0.27" <br/>"@tech/t-el-ui":"2.0.16"</td> |
|
1011 | + <td style="width:77%">harbor.sieiot.com/iidp/snest-nginx:v2.1.10.release <br/><br/>http://idp.chinasie.com/download/repository/snest-nginx-v2.1.10.release.tar</td> |
|
1012 | + </tr> |
|
1013 | + <tr> |
|
1014 | + <td style="width:8%">后端</td> |
|
1015 | + <td style="width:25%"></td> |
|
1016 | + <td style="width:77%"></td> |
|
1017 | + </tr> |
|
1018 | +</table> |
|
1019 | + |
|
1020 | + |
|
1021 | +### 更新日期 |
|
1022 | +2024/1/31 |
|
1023 | + |
|
1024 | +### 修改点 |
|
1025 | + |
|
1026 | +- 1.平台bug修复 |
|
1027 | + |
|
1028 | +# v2.1.9.RELEASE |
|
1029 | + |
|
1030 | +<table> |
|
1031 | + <tr> |
|
1032 | + <th>类型</th> |
|
1033 | + <th>插件版本</th> |
|
1034 | + <th>镜像</th> |
|
1035 | + </tr> |
|
1036 | + <tr> |
|
1037 | + <td style="width:8%">前端</td> |
|
1038 | + <td style="width:25%">"@tech/t-base":"2.1.9" <br/>"@tech/t-build":"1.0.6" <br/>"@tech/t-core":"1.0.25" <br/>"@tech/t-el-ui":"2.0.15"</td> |
|
1039 | + <td style="width:77%">harbor.sieiot.com/iidp/snest-nginx:v2.1.9.release <br/><br/>http://idp.chinasie.com/download/repository/snest-nginx-v2.1.9.release.tar</td> |
|
1040 | + </tr> |
|
1041 | + <tr> |
|
1042 | + <td style="width:8%">后端</td> |
|
1043 | + <td style="width:25%"></td> |
|
1044 | + <td style="width:77%"></td> |
|
1045 | + </tr> |
|
1046 | +</table> |
|
1047 | + |
|
1048 | + |
|
1049 | +### 更新日期 |
|
1050 | +2024/1/25 |
|
1051 | + |
|
1052 | +### 修改点 |
|
1053 | + |
|
1054 | +- 1.搜索条件优化 |
|
1055 | +- 2.平台bug修复 |
|
1056 | + |
|
1057 | + |
|
1058 | + |
|
1059 | +### 影响范围 |
|
1060 | + |
|
1061 | + |
|
1062 | +### 不兼容 |
|
1063 | + |
|
1064 | +* 无 |
|
1065 | +* |
|
1066 | + |
|
1067 | +# v2.1.8.RELEASE |
|
1068 | + |
|
1069 | +<table> |
|
1070 | + <tr> |
|
1071 | + <th>类型</th> |
|
1072 | + <th>插件版本</th> |
|
1073 | + <th>镜像</th> |
|
1074 | + </tr> |
|
1075 | + <tr> |
|
1076 | + <td style="width:8%">前端</td> |
|
1077 | + <td style="width:25%">"@tech/t-base":"2.1.8" <br/>"@tech/t-build":"1.0.6" <br/>"@tech/t-core":"1.0.25" <br/>"@tech/t-el-ui":"2.0.14"</td> |
|
1078 | + <td style="width:77%">dockerhub.kubekey.local/onlybuildimg-web/snest-nginx:v2.1.8.release <br/><br/>http://idp.chinasie.com/download/repository/snest-nginx-v2.1.8.release.tar</td> |
|
1079 | + </tr> |
|
1080 | + <tr> |
|
1081 | + <td style="width:8%">后端</td> |
|
1082 | + <td style="width:25%"></td> |
|
1083 | + <td style="width:77%"></td> |
|
1084 | + </tr> |
|
1085 | +</table> |
|
1086 | + |
|
1087 | + |
|
1088 | +### 更新日期 |
|
1089 | +2024/1/22 |
|
1090 | + |
|
1091 | +### 修改点 |
|
1092 | + |
|
1093 | +- 1.登录记住密码 2.表单submitChanged修改 (bd5fd39) |
|
1094 | +- 1.修复审批流loadview数据源问题2.流程中心跳转时没有相应菜单则return (24b4a6a) |
|
1095 | + |
|
1096 | + |
|
1097 | + |
|
1098 | +### 影响范围 |
|
1099 | + |
|
1100 | + |
|
1101 | +### 不兼容 |
|
1102 | + |
|
1103 | +* 无 |
|
1104 | +* |
|
1105 | + |
|
1106 | + |
|
1107 | +# v2.1.5.RELEASE |
|
1108 | + |
|
1109 | +<table> |
|
1110 | + <tr> |
|
1111 | + <th>类型</th> |
|
1112 | + <th>插件版本</th> |
|
1113 | + <th>镜像</th> |
|
1114 | + </tr> |
|
1115 | + <tr> |
|
1116 | + <td style="width:8%">前端</td> |
|
1117 | + <td style="width:25%">"@tech/t-base":"2.1.5" <br/>"@tech/t-build":"1.0.6" <br/>"@tech/t-core":"1.0.24" <br/>"@tech/t-el-ui":"2.0.13"</td> |
|
1118 | + <td style="width:77%">http://idp.chinasie.com/download/repository/snest-nginx-v2.1.5.release.tar</td> |
|
1119 | + </tr> |
|
1120 | + <tr> |
|
1121 | + <td style="width:8%">后端</td> |
|
1122 | + <td style="width:25%"></td> |
|
1123 | + <td style="width:77%"></td> |
|
1124 | + </tr> |
|
1125 | +</table> |
|
1126 | + |
|
1127 | + |
|
1128 | +### 更新日期 |
|
1129 | +2024/1/16 |
|
1130 | + |
|
1131 | +### 修改点 |
|
1132 | + |
|
1133 | + |
|
1134 | +- 作用域配置表单显示出来 (2deb0af) |
|
1135 | + |
|
1136 | + |
|
1137 | +### 影响范围 |
|
1138 | + |
|
1139 | +- 作用域 |
|
1140 | +- 登录 |
|
1141 | +- 依赖库 |
|
1142 | + |
|
1143 | +### 不兼容 |
|
1144 | + |
|
1145 | +* 无 |
|
1146 | +* |
|
1147 | + |
|
1148 | + |
|
1149 | +# v2.1.2.RELEASE |
|
1150 | + |
|
1151 | +<table> |
|
1152 | + <tr> |
|
1153 | + <th>类型</th> |
|
1154 | + <th>插件版本</th> |
|
1155 | + <th>镜像</th> |
|
1156 | + </tr> |
|
1157 | + <tr> |
|
1158 | + <td style="width:8%">前端</td> |
|
1159 | + <td style="width:25%">"@tech/t-base":"2.1.2" <br/>"@tech/t-build":"1.0.6" <br/>"@tech/t-core":"1.0.24" <br/>"@tech/t-el-ui":"2.0.13"</td> |
|
1160 | + <td style="width:77%">http://idp.chinasie.com/download/repository/snest-nginx-v2.1.2.release.tar</td> |
|
1161 | + </tr> |
|
1162 | + <tr> |
|
1163 | + <td style="width:8%">后端</td> |
|
1164 | + <td style="width:25%"></td> |
|
1165 | + <td style="width:77%"></td> |
|
1166 | + </tr> |
|
1167 | +</table> |
|
1168 | + |
|
1169 | + |
|
1170 | +### 更新日期 |
|
1171 | +2024/1/12 |
|
1172 | + |
|
1173 | +### 修改点 |
|
1174 | + |
|
1175 | + |
|
1176 | +- 作用域配置表单显示出来 (2deb0af) |
|
1177 | + |
|
1178 | + |
|
1179 | +### 影响范围 |
|
1180 | + |
|
1181 | +- 作用域 |
|
1182 | +- 登录 |
|
1183 | +- 依赖库 |
|
1184 | + |
|
1185 | +### 不兼容 |
|
1186 | + |
|
1187 | +* 无 |
|
1188 | +* |
|
1189 | + |
|
1190 | +*** |
|
1191 | + |
|
1192 | +# v2.1.3.RELEASE(后端) |
|
1193 | + |
|
1194 | +| 类型 | 插件版本 | 镜像 | |
|
1195 | +| -- | ---------------- | ------ | |
|
1196 | +| 后端 | | dockerhub.kubekey.local/release/sie-snest:v2.1.3.RELEASE<br/>http://idp.chinasie.com/download/repository/sie-snest-v2.1.3.RELEASE.tar | |
|
1197 | + |
|
1198 | +### 更新日期 |
|
1199 | +2024/1/26 |
|
1200 | +### 发布说明 |
|
1201 | +后端v2.1.3.RELEASE发布,发布说明如下: |
|
1202 | +* 1.Bug修复,修复引擎审计字段排序的问题(java.lang.NullPointerException at BussModelDataAccess.generateOrderByInner) |
|
1203 | +* 2.Bug修复,修复微服务调用模型服务失败的问题(调用模型(encoder rule),服务getEncoderApi)失败,期望参数列表[name=encoderlnputDTO,descriptions=编码生成) |
|
1204 | + |
|
1205 | + |
|
1206 | +# v2.1.1.RELEASE(后端) |
|
1207 | + |
|
1208 | +| 类型 | 插件版本 | 镜像 | |
|
1209 | +| -- | ---------------- | ------ | |
|
1210 | +| 后端 | | dockerhub.kubekey.local/release/sie-snest:v2.1.1.RELEASE<br/>http://idp.chinasie.com/download/repository/sie-snest-v2.1.1.RELEASE.tar | |
|
1211 | + |
|
1212 | +### 更新日期 |
|
1213 | +2024/1/12 |
|
1214 | +### 发布说明 |
|
1215 | +后端v2.1.1.RELEASE发布,发布说明如下: |
|
1216 | +* 1.Bug修复:修复分布式环境刷新工作流的问题 |
|
1217 | + |
|
1218 | + |
|
1219 | +# v2.1.0.RELEASE(后端) |
|
1220 | + |
|
1221 | +| 类型 | 插件版本 | 镜像 | |
|
1222 | +| -- | ---------------- | ------ | |
|
1223 | +| 后端 | | dockerhub.kubekey.local/release/sie-snest:v2.1.0.RELEASE<br/>http://idp.chinasie.com/download/repository/sie-snest-v2.1.0.RELEASE.tar | |
|
1224 | + |
|
1225 | +### 更新日期 |
|
1226 | +2024/1/5 |
|
1227 | +### 发布说明 |
|
1228 | +后端v2.1.0.RELEASE发布,发布说明如下: |
|
1229 | +* 1.增加数据源APP,支持mysql分表分区,提供分表和分区两种模式,可以按年、按月、按列表三种方式对数据进行分表或者分区(可选app) |
|
1230 | +* 2.支持聚集函数,支持group by分组、having分组过滤滤、sum、count、max、min、avg聚合函数查询 |
|
1231 | +* 3.增加附件种子数据能力 |
|
1232 | +* 4.修复分布式app重置种子数据失败问题 |
|
1233 | +* 5.orm部分性能优化 |
|
1234 | + many2one 根据子表条件查询主表采用 join;many2many支持一层查询 ;get性能问题 |
|
1235 | +* 6、优化以下内容 |
|
1236 | + 优化文件上传提示、审批流分布式能力优化、平台重启后已经修改的菜单不会被还原;增加角色关联用户功能;处理分组校验bug |
|
1237 | + |
|
1238 | +### 升级说明如下 |
|
1239 | + |
|
1240 | +包括引擎和SDK需要升级到v2.1.0.RELEASE,以下内置APP也需要升级 |
|
1241 | +(下载地址http://iidp.chinasie.com:9999/maven/#browse/browse:maven-releases): |
|
1242 | + |
|
1243 | +* sie-snest-base-v2.1.0.RELEASE.jar |
|
1244 | +* sie-snest-dict-v2.1.0.RELEASE.jar |
|
1245 | +* sie-snest-file-v2.1.0.RELEASE.jar |
|
1246 | +* sie-snest-tenant-v2.1.0.RELEASE.jar |
|
1247 | +* sie-snest-workflow-v2.1.0.RELEASE.jar(审批流必选) |
|
1248 | +* sie-snest-datasource-v2.1.0.RELEASE.jar(mysql分表分区必选) |
|
1249 | + |
|
1250 | + |
|
1251 | +### 新能力使用参考文档 |
|
1252 | + |
|
1253 | +分表分区使用说明 :http://iidp.chinasie.com:9999/iidpdoc/pages/da25fe/ |
|
1254 | + |
|
1255 | +聚集函数使用说明:http://iidp.chinasie.com:9999/iidpdoc/pages/96e5ee/ |
|
1256 | + |
|
1257 | +# v2.0.15.RELEASE |
|
1258 | + |
|
1259 | +| 类型 | 插件版本 | 镜像 | |
|
1260 | +| -- | ---------------- | ------ | |
|
1261 | +| 前端 |"@tech/t-base":"2.0.15",<br/>"@tech/t-build":"1.0.6",<br/>"@tech/t-core":"1.0.23",<br/>"@tech/t-el-ui":"2.0.9", | http://idp.chinasie.com/download/repository/snest-nginx-v2.0.15.tar | |
|
1262 | +| 后端 | | | |
|
1263 | + |
|
1264 | +### 更新日期 |
|
1265 | +2024/1/3 |
|
1266 | + |
|
1267 | +### 修改点 |
|
1268 | + |
|
1269 | +- 单点登录不显示用户名问题 (1153fc4) |
|
1270 | +- 登录不显示用户名问题 (cc1e4e2) |
|
1271 | +- 登录后获取profile接口信息 (c058112) |
|
1272 | +- 库地址改为http://iidp.chinasie.com:9999/maven/repository/npm-group/,该地址代理了新库地址 (bee383c) |
|
1273 | +- 增加beforeDestroy钩子 (9ec4eb4) |
|
1274 | +- 作用域配置表单显示出来 (2deb0af) |
|
1275 | + |
|
1276 | + |
|
1277 | +### 影响范围 |
|
1278 | + |
|
1279 | +- 作用域 |
|
1280 | +- 登录 |
|
1281 | +- 依赖库 |
|
1282 | + |
|
1283 | +### 不兼容 |
|
1284 | + |
|
1285 | +* 无 |
|
1286 | +* |
|
1287 | + |
|
1288 | +# v1.0.38.RELEASE |
|
1289 | + |
|
1290 | +| 类型 | 插件版本 | 镜像 | |
|
1291 | +| -- | ---------------- | ------ | |
|
1292 | +| 前端 |"@tech/t-base": "1.0.38",<br/>"@tech/t-build": "1.0.6",<br/>"@tech/t-core": "1.0.24",<br/>"@tech/t-el-ui": "1.0.28", |http://idp.chinasie.com/download/repository/snest-nginx-v1.0.38.release.tar| |
|
1293 | +| 后端 | | | |
|
1294 | + |
|
1295 | +## 前端版本说明 |
|
1296 | + |
|
1297 | +### 更新日期 |
|
1298 | +2024/1/4 |
|
1299 | +### 修改点 |
|
1300 | + |
|
1301 | +- 1.upload组件配置show-file-list 2.dialog配置customMiddleButtons (c2e2ea7) |
|
1302 | +- 编辑器增加自定义hint方法 (1e077d5) |
|
1303 | +- 编辑器增加自定义hint方法增加参数 (a1f6b24) |
|
1304 | +- 处理upload多样性和多选冲突 (375ee15) |
|
1305 | +- 穿梭框功能完善 (b56ab72) |
|
1306 | +- 单元格值比较新旧值时先把数值类型转成字符串类型 (ea5cf88) |
|
1307 | +- 多图片上传回显 (d3ade91) |
|
1308 | +- 解决非编辑态点击单元格时单元格内容换行问题 (d75e09f) |
|
1309 | +- 库地址改为http://iidp.chinasie.com:9999/maven/repository/npm-group/,该地址代理了新库地址 (96d9194) |
|
1310 | +- 目录图标错误 (29219a5) |
|
1311 | +- 删除log (342b733) |
|
1312 | +- 上传添加预览功能配置 (6c3f5d2) |
|
1313 | + |
|
1314 | +### 不兼容 |
|
1315 | + |
|
1316 | +* 无 |
|
1317 | + |
|
1318 | + |
|
1319 | +# v1.0.36.RELEASE |
|
1320 | + |
|
1321 | +| 类型 | 插件版本 | 镜像 | |
|
1322 | +| -- | ---------------- | ------ | |
|
1323 | +| 前端 |"@tech/t-base": "1.0.36",<br/>"@tech/t-build": "1.0.5",<br/>"@tech/t-core": "1.0.18",<br/>"@tech/t-el-ui": "1.0.26", || |
|
1324 | +| 后端 | | | |
|
1325 | + |
|
1326 | +## 前端版本说明 |
|
1327 | + |
|
1328 | +### 更新日期 |
|
1329 | +2023/12/29 |
|
1330 | +### 修改点 |
|
1331 | +- 修复initForm方法的拷贝方法 (05a406c) |
|
1332 | + |
|
1333 | + |
|
1334 | +### 影响范围 |
|
1335 | + |
|
1336 | +表单模板初始化 |
|
1337 | + |
|
1338 | +### 不兼容 |
|
1339 | + |
|
1340 | +* 无 |
|
1341 | + |
|
1342 | + |
|
1343 | + |
|
1344 | +# v2.0.13.RELEASE |
|
1345 | + |
|
1346 | +| 类型 | 插件版本 | 镜像 | |
|
1347 | +| -- | ---------------- | ------ | |
|
1348 | +| 前端 |"@tech/t-base": "2.0.13",<br/>"@tech/t-build": "1.0.5",<br/>"@tech/t-core": "1.0.22",<br/>"@tech/t-el-ui": "2.0.8", | | |
|
1349 | +| 后端 | | | |
|
1350 | + |
|
1351 | +### 更新日期 |
|
1352 | +2023/12/29 |
|
1353 | + |
|
1354 | +### 修改点 |
|
1355 | + |
|
1356 | +视图配置defaultCancelEdit时自动显示取消编辑(不受按钮禁用状态影响) (95cd57d) |
|
1357 | +- scopeRequestAddargs (fb8d7ec) |
|
1358 | + |
|
1359 | + |
|
1360 | +# v2.0.12.RELEASE |
|
1361 | + |
|
1362 | +| 类型 | 插件版本 | 镜像 | |
|
1363 | +| -- | ---------------- | ------ | |
|
1364 | +| 前端 |"@tech/t-base": "2.0.12",<br/>"@tech/t-build": "1.0.5",<br/>"@tech/t-core": "1.0.22",<br/>"@tech/t-el-ui": "2.0.7", | | |
|
1365 | +| 后端 | | | |
|
1366 | + |
|
1367 | +### 更新日期 |
|
1368 | +2023/12/22 |
|
1369 | + |
|
1370 | +### 修改点 |
|
1371 | + |
|
1372 | +* - - 单点登录不显示用户名问题 (115 |
|
1373 | +* - 登录不显示用户名问题 (cc1e4e2) |
|
1374 | +* - 登录后获取profile接口信息 (c058112) |
|
1375 | +* - 增加beforeDestroy钩子 (9ec4eb4) |
|
1376 | +* - 作用域配置表单显示出来 (2deb0af) |
|
1377 | + |
|
1378 | + |
|
1379 | +# v2.0.7.RELEASE |
|
1380 | + |
|
1381 | +| 类型 | 插件版本 | 镜像 | |
|
1382 | +| -- | ---------------- | ------ | |
|
1383 | +| 前端 |"@tech/t-base": "2.0.9",<br/>"@tech/t-build": "1.0.5",<br/>"@tech/t-core": "1.0.22",<br/>"@tech/t-el-ui": "2.0.6", | | |
|
1384 | +| 后端 | | | |
|
1385 | + |
|
1386 | +### 更新日期 |
|
1387 | +2023/12/22 |
|
1388 | + |
|
1389 | +### 修改点 |
|
1390 | + |
|
1391 | +* - 登录后获取profile接口信息 (c058112) |
|
1392 | + |
|
1393 | +# v2.0.25.beta(后端) |
|
1394 | + |
|
1395 | +| 类型 | 插件版本 | 镜像 | |
|
1396 | +| -- | ---------------- | ------ | |
|
1397 | +| 后端 | | dockerhub.kubekey.local/release/sie-snest:v2.0.25.beta<br/>http://idp.chinasie.com/download/repository/sie-snest-v2.0.25.beta.tar | |
|
1398 | + |
|
1399 | +### 更新日期 |
|
1400 | +2023/12/15 |
|
1401 | +### 修改点(开发人员无需修改本地版本) |
|
1402 | +* 处理分布式场景下作用域选择部分属性时报错的bug |
|
1403 | + |
|
1404 | + |
|
1405 | +# v2.0.7.RELEASE |
|
1406 | + |
|
1407 | +| 类型 | 插件版本 | 镜像 | |
|
1408 | +| -- | ---------------- | ------ | |
|
1409 | +| 前端 |"@tech/t-base": "2.0.7",<br/>"@tech/t-build": "1.0.5",<br/>"@tech/t-core": "1.0.19",<br/>"@tech/t-el-ui": "2.0.6", | | |
|
1410 | +| 后端 | | | |
|
1411 | + |
|
1412 | +## 前端版本说明 |
|
1413 | +### 更新日期 |
|
1414 | +2023/12/19 |
|
1415 | +### 修改点 |
|
1416 | +* 表格搜索项manytomany类型配置默认值时tag不显示问题修复 |
|
1417 | + |
|
1418 | + |
|
1419 | +### 影响范围 |
|
1420 | + |
|
1421 | +*表格搜索 |
|
1422 | + |
|
1423 | +### 不兼容 |
|
1424 | + |
|
1425 | +* 无 |
|
1426 | + |
|
1427 | +# v2.0.6.RELEASE |
|
1428 | + |
|
1429 | +| 类型 | 插件版本 | 镜像 | |
|
1430 | +| -- | ---------------- | ------ | |
|
1431 | +| 前端 |"@tech/t-base": "2.0.6",<br/>"@tech/t-build": "1.0.5",<br/>"@tech/t-core": "1.0.18",<br/>"@tech/t-el-ui": "2.0.6", | http://idp.chinasie.com/download/repository/snest-nginx-v2.0.6.release.tar | |
|
1432 | +| 后端 | | | |
|
1433 | + |
|
1434 | +## 前端版本说明 |
|
1435 | +### 更新日期 |
|
1436 | +2023/12/18 |
|
1437 | +### 修改点 |
|
1438 | +* 添加了拼接id如何name属性非数字字母下划线连接符情况下就不拼接name逻辑 |
|
1439 | +* 自定义权限接口数据缓存 |
|
1440 | +* 视图配置defaultCancelEdit时自动显示取消编辑(不受按钮禁用状态影响) |
|
1441 | + |
|
1442 | + |
|
1443 | + |
|
1444 | +### 影响范围 |
|
1445 | + |
|
1446 | +* profile接口 |
|
1447 | +* name包含中文的组件例如tabs组件 |
|
1448 | +* 行内编辑 |
|
1449 | + |
|
1450 | +### 不兼容 |
|
1451 | + |
|
1452 | +* 无 |
|
1453 | + |
|
1454 | + |
|
1455 | +# v1.0.5.RELEASE |
|
1456 | + |
|
1457 | +| 类型 | 插件版本 | 镜像 | |
|
1458 | +| -- | ---------------- | ------ | |
|
1459 | +| 前端 |"@tech/t-base": "1.0.29",<br/>"@tech/t-build": "1.0.5",<br/>"@tech/t-core": "1.0.18",<br/>"@tech/t-el-ui": "1.0.26", |http://idp.chinasie.com/download/repository/snest-nginx-v1.0.29.release.tar| |
|
1460 | +| 后端 | | | |
|
1461 | + |
|
1462 | +## 前端版本说明 |
|
1463 | + |
|
1464 | +### 更新日期 |
|
1465 | +2023/12/18 |
|
1466 | +### 修改点 |
|
1467 | +* 修复抽屉表单确认数据丢失 |
|
1468 | +* 添加了tabs的displayName显示 |
|
1469 | +* 登录后只保留一个profile接口 |
|
1470 | +* 添加了拼接id如何name属性非数字字母下划线连接符情况下就不拼接name逻辑 |
|
1471 | + |
|
1472 | + |
|
1473 | +### 影响范围 |
|
1474 | + |
|
1475 | +* profile接口 |
|
1476 | +* name包含中文的组件例如tabs组件 |
|
1477 | +* 主表单的抽屉抽屉表单 |
|
1478 | + |
|
1479 | + |
|
1480 | +### 不兼容 |
|
1481 | + |
|
1482 | +* 无 |
|
1483 | + |
|
1484 | + |
|
1485 | + |
|
1486 | + |
|
1487 | + |
|
1488 | +# v2.0.21.beta(后端) |
|
1489 | + |
|
1490 | +| 类型 | 插件版本 | 镜像 | |
|
1491 | +| -- | ---------------- | ------ | |
|
1492 | +| 后端 | sie-snest-base-v2.0.21.beta.jar<br/>sie-snest-dict-v2.0.21.beta.jar<br/>sie-snest-file-v2.0.21.beta.jar<br/>sie-snest-tenant-v2.0.21.beta.jar<br/>sie-snest-workflow-v2.0.21.beta.jar<br/>sie-snest-sdk v2.0.21.beta.jar<br/> | dockerhub.kubekey.local/release/sie-snest:v2.0.21.beta | |
|
1493 | + |
|
1494 | +### 更新日期 |
|
1495 | +2023/12/15 |
|
1496 | +### 修改点 |
|
1497 | +* 优化功能权限查询性能 |
|
1498 | +* 处理分组校验bug |
|
1499 | +* 优化文件上传提示 |
|
1500 | +* 审批流分布式优化 |
|
1501 | +* 平台重启后已经修改的菜单不会被还原 |
|
1502 | + |
|
1503 | +# v1.0.4.RELEASE |
|
1504 | + |
|
1505 | +| 类型 | 插件版本 | 镜像 | |
|
1506 | +| -- | ---------------- | ------ | |
|
1507 | +| 前端 |"@tech/t-base": "1.0.27",<br/>"@tech/t-build": "1.0.5",<br/>"@tech/t-core": "1.0.16",<br/>"@tech/t-el-ui": "1.0.25", | http://idp.chinasie.com/download/repository/snest-nginx-v1.0.25.release.tar| |
|
1508 | +| 后端 | | | |
|
1509 | + |
|
1510 | +## 前端版本说明 |
|
1511 | +### 更新日期 |
|
1512 | +2023/12/14 |
|
1513 | +### 修改点 |
|
1514 | +* 修复了dropdown组件里面的内存溢出 (1837aed) |
|
1515 | +* Upload组件不显示问题 (27ae88e) |
|
1516 | + |
|
1517 | + |
|
1518 | + |
|
1519 | +### 影响范围 |
|
1520 | + |
|
1521 | +* 上传组件 |
|
1522 | +* dropdown组件 |
|
1523 | + |
|
1524 | +### 不兼容 |
|
1525 | + |
|
1526 | +* 无 |
|
1527 | + |
|
1528 | + |
|
1529 | + |
|
1530 | +# v2.0.4.RELEASE |
|
1531 | + |
|
1532 | +| 类型 | 插件版本 | 镜像 | |
|
1533 | +| -- | ---------------- | ------ | |
|
1534 | +| 前端 |"@tech/t-base": "2.0.4",<br/>"@tech/t-build": "1.0.5",<br/>"@tech/t-core": "1.0.16",<br/>"@tech/t-el-ui": "2.0.5", | http://http://idp.chinasie.com/download/repository/snest-nginx-v2.0.5.release.tar| |
|
1535 | +| 后端 | | | |
|
1536 | + |
|
1537 | +## 前端版本说明 |
|
1538 | +### 更新日期 |
|
1539 | +2023/12/14 |
|
1540 | +### 修改点 |
|
1541 | +* -切换作用域——不显示租户作用域 (cb6170d) |
|
1542 | +* 行内编辑的编辑按钮受权限控制 (99a3749) |
|
1543 | +* 增加自定义权限的iframe类型协议 (0e71651) |
|
1544 | +* 只保留一个profile,接口增加loading (46da10e) |
|
1545 | +* 自定义权限接口数据缓存 (55deeab) |
|
1546 | + |
|
1547 | + |
|
1548 | +### 影响范围 |
|
1549 | + |
|
1550 | +* 行内编辑 |
|
1551 | +* 作用域 |
|
1552 | +* 自定义权限 |
|
1553 | +* iframe |
|
1554 | +* profile接口 |
|
1555 | + |
|
1556 | +### 不兼容 |
|
1557 | + |
|
1558 | +* 无 |
|
1559 | +# v2.0.3.RELEASE |
|
1560 | + |
|
1561 | +| 类型 | 插件版本 | 镜像 | |
|
1562 | +| -- | ---------------- | ------ | |
|
1563 | +| 前端 |"@tech/t-base": "2.0.3",<br/>"@tech/t-build": "1.0.5",<br/>"@tech/t-core": "1.0.15",<br/>"@tech/t-el-ui": "2.0.3", | http://idp.chinasie.com/download/repository/snest-nginx-v2.0.3.release.tar| |
|
1564 | +| 后端 | | | |
|
1565 | + |
|
1566 | +## 前端版本说明 |
|
1567 | +### 更新日期 |
|
1568 | +2023/12/13 |
|
1569 | +### 修改点 |
|
1570 | +* 行内编辑的编辑按钮受权限控制 |
|
1571 | +* 切换作用域——不显示租户作用域 |
|
1572 | +* 增加自定义权限的iframe类型协议 |
|
1573 | +* 自定义权限接口数据缓存 |
|
1574 | + |
|
1575 | + |
|
1576 | + |
|
1577 | + |
|
1578 | +### 影响范围 |
|
1579 | + |
|
1580 | +* 行内编辑 |
|
1581 | +* 作用域 |
|
1582 | +* 自定义权限 |
|
1583 | +* iframe |
|
1584 | + |
|
1585 | +### 不兼容 |
|
1586 | + |
|
1587 | +* 无 |
|
1588 | +# v2.0.2.RELEASE |
|
1589 | + |
|
1590 | +| 类型 | 插件版本 | 镜像 | |
|
1591 | +| -- | ---------------- | ------ | |
|
1592 | +| 前端 |"@tech/t-base": "2.0.2",<br/>"@tech/t-build": "1.0.5",<br/>"@tech/t-core": "1.0.15",<br/>"@tech/t-el-ui": "2.0.2", | http://idp.chinasie.com/download/repository/snest-nginx-v2.0.2.tar| |
|
1593 | +| 后端 | | | |
|
1594 | + |
|
1595 | +## 前端版本说明 |
|
1596 | +### 更新日期 |
|
1597 | +2023/12/12 |
|
1598 | +### 修改点 |
|
1599 | +* 添加了view协议inheritApp参数配置 分布式调接口app参数强制继承行为节点的app参数 |
|
1600 | +* 退出登录或返回到登录页面时先清空loadView缓存 |
|
1601 | +* 修改了app参数赋值判断位置 |
|
1602 | +* 无界添加样式转换开关 |
|
1603 | +* goTo方法跳转参数修改 |
|
1604 | + |
|
1605 | + |
|
1606 | + |
|
1607 | +### 影响范围 |
|
1608 | + |
|
1609 | +* openView添加app传参 |
|
1610 | +* goTo方法 |
|
1611 | +* 无界 |
|
1612 | +* loadView缓存 |
|
1613 | + |
|
1614 | +### 不兼容 |
|
1615 | + |
|
1616 | +* 无 |
|
1617 | +# v2.0.1.RELEASE |
|
1618 | + |
|
1619 | +| 类型 | 插件版本 | 镜像 | |
|
1620 | +| -- | ---------------- | ------ | |
|
1621 | +| 前端 |"@tech/t-base": "2.0.1",<br/>"@tech/t-build": "1.0.5",<br/>"@tech/t-core": "1.0.14",<br/>"@tech/t-el-ui": "2.0.1", | http://idp.chinasie.com/download/repository/snest-nginx-37.tar | |
|
1622 | +| 后端 | | | |
|
1623 | + |
|
1624 | +## 前端版本说明 |
|
1625 | +### 更新日期 |
|
1626 | +2023/12/11 |
|
1627 | +### 修改点 |
|
1628 | +* 修复退出登录刷新目录 |
|
1629 | +* 修复了sidebar内存溢出 |
|
1630 | +* 修复了表单中openview的数据继承问题 |
|
1631 | + |
|
1632 | + |
|
1633 | + |
|
1634 | +### 影响范围 |
|
1635 | + |
|
1636 | +* 表单中openview的请求接口 |
|
1637 | +* 退出登录后菜单重置 |
|
1638 | + |
|
1639 | + |
|
1640 | +### 不兼容 |
|
1641 | + |
|
1642 | +* 无 |
|
1643 | + |
|
1644 | + |
|
1645 | +# v2.0.0.RELEASE |
|
1646 | + |
|
1647 | +| 类型 | 插件版本 | 镜像 | |
|
1648 | +| -- | ---------------- | ------ | -- | |
|
1649 | +| 前端 |"@tech/t-base": "1.1.5-beta.26",<br/>"@tech/t-build": "1.0.5-beta.0",<br/>"@tech/t-core": "1.0.13-beta.0",<br/>"@tech/t-el-ui": "1.1.1-beta.2", | http://idp.chinasie.com/download/repository/snest-nginx-28.tar | |
|
1650 | +| 后端 | sie-snest-base-v2.0.0.RELEASE.jar<br/>sie-snest-config-v2.0.0.RELEASE.jar<br/>sie-snest-dict-v2.0.0.RELEASE.jar<br/>sie-snest-file-v2.0.0.RELEASE.jar<br/>sie-snest-tenant-v2.0.0.RELEASE.jar<br/>sie-snest-workflow-v2.0.0.RELEASE.jar<br/>sie-snest-sdk v2.0.0.RELEASE.jar<br/> | dockerhub.kubekey.local/release/sie-snest:v2.0.0.RELEASE | |
|
1651 | + |
|
1652 | +## 前端版本说明 |
|
1653 | +### 更新日期 |
|
1654 | +2023/12/8 |
|
1655 | +### 修改点 |
|
1656 | +* 修复了跳转页面资源回收 |
|
1657 | +* 上传组件类型判断,添加提示 |
|
1658 | + |
|
1659 | + |
|
1660 | + |
|
1661 | +### 影响范围 |
|
1662 | + |
|
1663 | +* 页面跳转 |
|
1664 | +* 上传文件的类型添加判断和提示 |
|
1665 | + |
|
1666 | + |
|
1667 | +### 不兼容 |
|
1668 | + |
|
1669 | +* 无 |
|
1670 | + |
|
1671 | + |
|
1672 | +# v2.0.11.beta |
|
1673 | + |
|
1674 | +| 类型 | 插件版本 | 镜像 | |
|
1675 | +| -- | ---------------- | ------ | -- | |
|
1676 | +| 前端 | "@tech/t-base": "1.1.5-beta.26",<br/>"@tech/t-build": "1.0.5-beta.0",<br/>"@tech/t-core": "1.0.12-beta.1",<br/>"@tech/t-el-ui": "1.1.1-beta.1", | http://idp.chinasie.com/download/repository/snest-nginx-27.tar | |
|
1677 | +| 后端 | | | |
|
1678 | + |
|
1679 | +## 前端版本说明 |
|
1680 | +### 更新日期 |
|
1681 | +2023/12/8 |
|
1682 | +### 修改点 |
|
1683 | +* 多租户作用域配置显示上一个 (5c4ddda) |
|
1684 | +* 获取自定义权限阻塞页面展示,添加trycatch (e0cbe1a) |
|
1685 | +* 详情页保存按钮是否显示控制逻辑修改 |
|
1686 | +* search模块去掉required |
|
1687 | +* 搜索菜单-点击跳转错误问题 |
|
1688 | + |
|
1689 | + |
|
1690 | + |
|
1691 | +### 影响范围 |
|
1692 | + |
|
1693 | +* 菜单搜索 |
|
1694 | +* 多租户控制详情页保存按钮显示 |
|
1695 | +* search |
|
1696 | + |
|
1697 | + |
|
1698 | +### 不兼容 |
|
1699 | + |
|
1700 | +* 无 |
|
1701 | + |
|
1702 | + |
|
1703 | + |
|
1704 | + |
|
1705 | + |
|
1706 | + |
|
1707 | + |
|
1708 | +# v2.0.10.beta |
|
1709 | + |
|
1710 | +| 类型 | 插件版本 | 镜像 | |
|
1711 | +| -- | ---------------- | ------ | -- | |
|
1712 | +| 前端 | "@tech/t-base": "1.1.5-beta.25",<br/>"@tech/t-build": "1.0.5-beta.0",<br/>"@tech/t-core": "1.0.12-beta.0",<br/>"@tech/t-el-ui": "1.1.1-beta.0", | dockerhub.kubekey.local/onlybuildimg-web/snest-nginx:25 | |
|
1713 | +| 后端 | sie-snest-sdk:v2.0.10.beta<br/>sie-snest-engine:v2.0.10.beta<br/>sie-snest-base-v2.0.10.jar<br/>sie-snest-file-v2.0.10.jar<br/>sie-snest-dict-v2.0.10.jar | dockerhub.kubekey.local/release/sie-snest:v2.0.10.beta | |
|
1714 | + |
|
1715 | +## 前端版本说明 |
|
1716 | +### 更新日期 |
|
1717 | +2023/12/6 |
|
1718 | +### 修改点 |
|
1719 | +* 子表tab切换增加配置searchByMainTable更新子表 |
|
1720 | +* 关于弹窗展示插件版本 (1e3524e) |
|
1721 | +* 子表抽屉弹窗保存报错不关闭抽屉 |
|
1722 | +* 作用域错误消息格式修改 (3650494) |
|
1723 | +* 修复tbar删除按钮的disabled设置 2.解决进入表单后返回列表报错 (140ad59) |
|
1724 | +* 修改goTo方法跨应用挑战 2.修复useOpenView搜索项展开 (ff0dbf3) |
|
1725 | +* 多租户作用域增加显示隐藏条件 (ac4db6a) |
|
1726 | +* 列权限数据回显 (7cfb0ea) |
|
1727 | +* 删除重复方法 (162b4fa) |
|
1728 | +* 关于弹窗展示插件版本 (1e3524e) |
|
1729 | +* 弹出收起菜单bug修改 (58f358c) |
|
1730 | +* 添加了非iframe模式内部情况 如果url参数上有 token与userId则去掉后replace (1a9f9ee) |
|
1731 | +* 行内编辑自增溢出报错解决 (f1812fe) |
|
1732 | +* 处理upload多样性和多选冲突 (375ee15) |
|
1733 | + |
|
1734 | +### 影响范围 |
|
1735 | + |
|
1736 | +* 菜单展开收起 |
|
1737 | +* url参数 |
|
1738 | +* 行内编辑 |
|
1739 | +* 关于弹窗 |
|
1740 | +* 上传组件 |
|
1741 | +* 多租户作用域接口消息格式处理 |
|
1742 | + |
|
1743 | +### 不兼容 |
|
1744 | + |
|
1745 | +* 无 |
|
1746 | + |
|
1747 | +## 后端版本说明(2023-12-09) |
|
1748 | +### 修改点 |
|
1749 | +v2.0.0.RELEASE 发版: |
|
1750 | +1. 最新引擎 |
|
1751 | +2. 核心APP,包括多租户、审批流、授权v2.0,其中授权v2.0支持按租户数量控制、对app单独授权; |
|
1752 | +3. 支持分布式、单机部署 |
|
1753 | +4. 兼容oracle、mysql |
|
1754 | +5. 操作手册、培训视频 |
|
1755 | +6. 自定义权限(含PC端前端扩展、移动端) |
|
1756 | +7. 1.x版本升级到v2.0.0.RELEASE的升级手册 |
|
1757 | +8. iiot场景性能优化 |
|
1758 | +优化注入模型的内存占用、优化DataModel的调用性能 |
|
1759 | +9. 增加配置中心app,分离server统一配置到app中 |
|
1760 | + |
|
1761 | +### 影响范围 |
|
1762 | +所有APP |
|
1763 | +### 不兼容 |
|
1764 | +无 |
|
1765 | +### 相关链接 |
|
1766 | +1. 内置app |
|
1767 | +http://iidp.chinasie.com:9999/maven/#browse/browse:maven-releases:com%2Fsie%2Fmeta |
|
1768 | +2. 部署配置相关调整 |
|
1769 | + |
|
1770 | +分布式: |
|
1771 | + |
|
1772 | +[多租户2.0升级部署文档](http://iidp.chinasie.com:9999/iidpwiki/%E5%A4%9A%E7%A7%9F%E6%88%B72.0%E5%8D%87%E7%BA%A7%E9%83%A8%E7%BD%B2%E6%96%87%E6%A1%A3.md) |
|
1773 | + |
|
1774 | +3. 1.x升级到最新引擎说明 联系平台组 |
|
1775 | + |
|
1776 | +[IIDP引擎多租户&流程引擎升级指引](https://doc.weixin.qq.com/doc/w3_AbUAigYwAIsajpZU0OrRt0werjJHZ?scode=AAIAKAcJAAcV11eNAjAbUAigYwAIs&version=4.1.13.6002&platform=win) |
|
1777 | + |
|
1778 | +4. [前端开发文档](http://iidp.chinasie.com:9999/iidpdoc/pages/ffe696/) |
|
1779 | +5. [后端开发文档](http://iidp.chinasie.com:9999/iidpdoc/pages/12ea54/) |
\347\231\275\345\220\215\345\215\225\351\205\215\347\275\256.md
... | ... | @@ -0,0 +1,39 @@ |
1 | +# URL 白名单 |
|
2 | + |
|
3 | +可以在 application.properties 中配置 |
|
4 | + |
|
5 | +URL白名单(只校验入口,其他调用链节点放行),格式:appName.modelName.service 支持通配符(*) |
|
6 | + |
|
7 | +``` |
|
8 | +url.whitelist=*.rbac login model.* base.rbac user.login |
|
9 | +``` |
|
10 | + |
|
11 | +还可以在 app.json 中配置 |
|
12 | + |
|
13 | +```json |
|
14 | +"globalConfig": { |
|
15 | + "urlWhiteList": { |
|
16 | + "value": "sie-snest-tenant.custom_view_model.queryCustomPermission" |
|
17 | + } |
|
18 | +} |
|
19 | +``` |
|
20 | + |
|
21 | +# 作用域白名单 |
|
22 | + |
|
23 | +sg白名单,即租户隔离白名单,如果有模型不需要进行租户数据隔离或由自动程序写入,无法正确生成表中tenant_id数据的可以配置此项 |
|
24 | + |
|
25 | +可以在 application.properties 中配置。格式:appName.modelName 支持通配符(*) (按需配置,非必填) |
|
26 | + |
|
27 | +``` |
|
28 | +sg.whiteList=appName.modelName |
|
29 | +``` |
|
30 | + |
|
31 | +可以在 app.json 中配置 |
|
32 | + |
|
33 | +```json |
|
34 | +"globalConfig": { |
|
35 | + "sgWhiteList": { |
|
36 | + "value": "*.custom_view_model" |
|
37 | + } |
|
38 | +} |
|
39 | +``` |
|
... | ... | \ No newline at end of file |
\350\265\233\346\204\217.md
... | ... | @@ -0,0 +1,140 @@ |
1 | +# 自动化测试 |
|
2 | + |
|
3 | +## 1.1、安装 |
|
4 | + |
|
5 | +- 初始化安装 |
|
6 | + |
|
7 | +```js |
|
8 | +// 首先安装node.js nodejs版本>=12 |
|
9 | +// 然后任一位置新建文件夹playwright,进入playwright文件夹运行以下命令 |
|
10 | + |
|
11 | +// 使用以下安装方式,推荐下面这种 会帮生成一些需要的配置文件不需要自己再创建 |
|
12 | +// 输入命令后会有一些确认选择,选择默认的就行,按回车就可以 |
|
13 | +npm init playwright@latest |
|
14 | + |
|
15 | +``` |
|
16 | + |
|
17 | +- 打开 packgae.json 文件 里面配置 |
|
18 | + |
|
19 | +```js |
|
20 | +"scripts": { |
|
21 | + "test": "npx playwright test", |
|
22 | + "log": "npx playwright show-report" |
|
23 | +} |
|
24 | + |
|
25 | +npm run test //运行所有的用例脚本 |
|
26 | +npm run test addUser.spec.js //运行某一个用例脚本 |
|
27 | +npm run log //查看最近一条用例的运行日志 |
|
28 | +``` |
|
29 | + |
|
30 | +- 录制测试脚本 |
|
31 | + |
|
32 | +```js |
|
33 | +// 创建一个存放录制脚本的文件夹useCase放置录制的测试脚本 |
|
34 | +//useCase/demo.js 录制的脚本保存位置和文件名称,文件名称可自定义 |
|
35 | +//chromium:录制使用浏览器 http://localhost:8085:录制打开的网页链接 |
|
36 | +npx playwright codegen --target javascript -o 'useCase/demo.js' -b chromium http://localhost:8085 |
|
37 | +// 录制后的脚本并不能直接跑,还需要添加一些等待的脚本代码 |
|
38 | +await page.waitForTimeout(1000); |
|
39 | +``` |
|
40 | + |
|
41 | +- 示例-从登录到新增用户 |
|
42 | + |
|
43 | +```js |
|
44 | +test('add user', async () => { |
|
45 | + const browser = await chromium.launch({ |
|
46 | + headless: false //有头无头模式设置,无头模式,脚本内部运行,不显示浏览器;有头模式,弹出浏览器运行测试脚本,能看到操作步骤 |
|
47 | + }) |
|
48 | + const context = await browser.newContext() |
|
49 | + const page = await context.newPage() |
|
50 | + await page.goto('http://192.168.168.81:30888/iidp/') |
|
51 | + await page.getByPlaceholder('请输入用户名').click() |
|
52 | + await page.getByPlaceholder('请输入用户名').type('admin', { delay: 50 }) |
|
53 | + await page.getByPlaceholder('请输入密码').click() |
|
54 | + await page.getByPlaceholder('请输入密码').type('admin', { delay: 50 }) |
|
55 | + await page.waitForTimeout(1000) |
|
56 | + // 断言 |
|
57 | + const customerCountInnerText = await page |
|
58 | + .getByPlaceholder('请输入密码') |
|
59 | + .inputValue() |
|
60 | + expect(customerCountInnerText).toBe('admin') |
|
61 | + await page.getByRole('button', { name: '登录' }).click() |
|
62 | + // 延迟等待 |
|
63 | + await page.waitForTimeout(1000) |
|
64 | + await page.locator('#sidebar_app_sdk2_button').click() |
|
65 | + await page.waitForTimeout(1000) |
|
66 | + await page.locator('#sdk_test_user_table_toolbar_create').click() |
|
67 | + await page |
|
68 | + .locator('#sdk_test_user_form_main_detail_top_common_items_0_items_2') |
|
69 | + .getByRole('textbox') |
|
70 | + .click() |
|
71 | + await page |
|
72 | + .locator('#sdk_test_user_form_main_detail_top_common_items_0_items_0') |
|
73 | + .getByRole('textbox') |
|
74 | + .click() |
|
75 | + await page |
|
76 | + .locator('#sdk_test_user_form_main_detail_top_common_items_0_items_0') |
|
77 | + .getByRole('textbox') |
|
78 | + .type('sdfsdf', { delay: 50 }) |
|
79 | + await page |
|
80 | + .locator('#sdk_test_user_form_main_detail_top_common_items_0_items_1') |
|
81 | + .getByRole('textbox') |
|
82 | + .click() |
|
83 | + await page |
|
84 | + .locator('#sdk_test_user_form_main_detail_top_common_items_0_items_1') |
|
85 | + .getByRole('textbox') |
|
86 | + .type('456@qq.com', { delay: 50 }) |
|
87 | + await page |
|
88 | + .locator('#sdk_test_user_form_main_detail_top_common_items_0_items_2') |
|
89 | + .getByRole('textbox') |
|
90 | + .click() |
|
91 | + await page |
|
92 | + .locator('#sdk_test_user_form_main_detail_top_common_items_0_items_3') |
|
93 | + .getByRole('textbox') |
|
94 | + .click() |
|
95 | + await page |
|
96 | + .locator('#sdk_test_user_form_main_detail_top_common_items_0_items_3') |
|
97 | + .getByRole('textbox') |
|
98 | + .type('18', { delay: 50 }) |
|
99 | + await page.locator('div').filter({ hasText: '密码' }).nth(2).click() |
|
100 | + await page.locator('input[type="password"]').type('123456', { delay: 50 }) |
|
101 | + await page.getByText('9-联动-省 清空 1-30').click() |
|
102 | + await page.waitForTimeout(1000) |
|
103 | + await page.getByRole('listitem').filter({ hasText: '北京市' }).click() |
|
104 | + const provinceVal = await page |
|
105 | + .locator( |
|
106 | + '#sdk_test_user_form_main_detail_top_common_items_0_items_18 input' |
|
107 | + ) |
|
108 | + .inputValue() |
|
109 | + expect(provinceVal).not.toBe('') |
|
110 | + await page |
|
111 | + .locator('#sdk_test_user_form_main_detail_top_common_items_0_items_19') |
|
112 | + .getByPlaceholder('请选择') |
|
113 | + .click() |
|
114 | + await page.waitForTimeout(1000) |
|
115 | + await page.getByRole('listitem').getByText('北京市').click() |
|
116 | + await page |
|
117 | + .locator('#sdk_test_user_form_main_detail_top_common_items_0_items_20') |
|
118 | + .getByPlaceholder('请选择') |
|
119 | + .click() |
|
120 | + await page.waitForTimeout(1000) |
|
121 | + await page.getByRole('listitem').filter({ hasText: '朝阳区' }).click() |
|
122 | + await page.getByRole('button', { name: '保存', exact: true }).click() |
|
123 | + await page.waitForTimeout(2000) |
|
124 | + |
|
125 | + // 多tab模式 |
|
126 | + const page2 = await context.newPage() |
|
127 | + await page2.goto('http://192.168.175.198:30600/iidp/') |
|
128 | + // 下面写第二个tab页的测试用例 |
|
129 | + |
|
130 | + await page.close() |
|
131 | + // --------------------- |
|
132 | + await context.close() |
|
133 | + await browser.close() |
|
134 | +}) |
|
135 | +``` |
|
136 | + |
|
137 | +- 文档链接 |
|
138 | + |
|
139 | + 英文文档 https://playwright.dev/docs/intro |
|
140 | + 中文文档 https://zhuanlan.zhihu.com/p/604028393 |
|
... | ... | \ No newline at end of file |
\351\205\215\347\275\256\344\270\255\345\277\203.md
... | ... | @@ -0,0 +1,156 @@ |
1 | +# 配置中心 |
|
2 | + |
|
3 | +配置 APP 仓库地址 [sie-snest-config-project](http://192.168.175.55:9888/caiqijun/sie-snest-config-project) |
|
4 | + |
|
5 | +# 需求 |
|
6 | + |
|
7 | +之前 IIDP 的配置都来自于 application.properties。如果 APP 想要增加自己的配置,也需要添加到 application.properties,而这样会导致配置文件堆积大量的配置。并且有可能导致冲突。 |
|
8 | + |
|
9 | +配置中心目前实现三点功能 |
|
10 | + |
|
11 | +1. APP 可以将配置写到自己的 APP 里面,从而避免跟其他 APP 的配置造成命名冲突。 |
|
12 | +2. 提供配置中心页面,可以在页面上动态修改配置,从而实现不停机动态修改 APP 的行为。 |
|
13 | +3. 必填校验。APP 可以可以将配置项声明为必填项,那么从应用市场安装的时候会校验配置是否有值,如果没有填写值,将会安装失败。 |
|
14 | + |
|
15 | +# 使用例子 |
|
16 | + |
|
17 | +- [[白名单配置]] |
|
18 | + |
|
19 | +# 实现设计 |
|
20 | + |
|
21 | +## 声明配置 |
|
22 | + |
|
23 | +在 app.json 填入配置 |
|
24 | + |
|
25 | +```json |
|
26 | +"globalConfig": { |
|
27 | + "backendUrl": { |
|
28 | + "desc": "微服务后端URL", |
|
29 | + "value": "http://localhost" |
|
30 | + } |
|
31 | +}, |
|
32 | +"appConfig": { |
|
33 | + "test": { |
|
34 | + "desc": "测试配置", |
|
35 | + "value": "123", |
|
36 | + "required": true |
|
37 | + } |
|
38 | +} |
|
39 | +``` |
|
40 | + |
|
41 | +配置分为两类 |
|
42 | + |
|
43 | +1. globalConfig |
|
44 | + a. 由平台定义,一般是引擎有特殊用途的配置。 |
|
45 | +2. appConfig |
|
46 | + a. 由 APP 自行定义,由 APP 业务上使用。 |
|
47 | + |
|
48 | +配置项说明 |
|
49 | + |
|
50 | +- desc: 配置项的说明 |
|
51 | + - 对于 globalConfig,desc 并不是必须的,因为平台开发者在引擎声明 GlobalConfig 的同时就写了描述。 |
|
52 | +- value: 配置值,必须为字符串 |
|
53 | +- required: 配置是否必填。如果为 true,那么应用安装的时候需要先填写配置项才能安装 |
|
54 | + |
|
55 | +# 使用示例 |
|
56 | + |
|
57 | +## 升级说明 |
|
58 | + |
|
59 | +在 apps 的 apps.json 添加 sie-snest-config 。 |
|
60 | + |
|
61 | +```json |
|
62 | +"apps": { |
|
63 | + "SDK": [ |
|
64 | + "sie-snest-base-1.0-SNAPSHOT.jar", |
|
65 | + "sie-snest-file-1.0-SNAPSHOT.jar", |
|
66 | + "sie-snest-dict-1.0-SNAPSHOT.jar", |
|
67 | + "sie-snest-log-2.0-SNAPSHOT.jar", |
|
68 | + "sie-snest-config-1.0-SNAPSHOT.jar" |
|
69 | + ] |
|
70 | +} |
|
71 | +``` |
|
72 | + |
|
73 | +## 系统配置 |
|
74 | + |
|
75 | +### 声明配置 |
|
76 | + |
|
77 | +平台开发人员如果想要新增配置,在可以在 com.sie.snest.engine.config.GlobalConfig 这个枚举类中添加配置项。 |
|
78 | + |
|
79 | +因为 GlobalConfig 这个类是在引擎工程里,所以只有平台开发人员可以声明配置项。 |
|
80 | + |
|
81 | +### 使用配置 |
|
82 | + |
|
83 | +```java |
|
84 | +Optional<String> frontendUrl = ConfigUtils.getGlobalConfig(appName, appTag, GlobalConfig.FRONTEND_URL); |
|
85 | +``` |
|
86 | + |
|
87 | +### APP 设置系统配置 |
|
88 | + |
|
89 | +在 app.json 里面设置值 |
|
90 | + |
|
91 | +```json |
|
92 | +"globalConfig": { |
|
93 | + "frontendUrl": { |
|
94 | + "desc": "微服务前端URL", |
|
95 | + "value": "http://localhost" |
|
96 | + } |
|
97 | +} |
|
98 | +``` |
|
99 | + |
|
100 | +系统配置也可以设置为 required = true,这样安装 APP 的时候会校验是否填写了配置值。 |
|
101 | + |
|
102 | +## 应用配置 |
|
103 | + |
|
104 | +### 声明配置 |
|
105 | + |
|
106 | +APP 的配置只需要在 app.json 里面声明。 |
|
107 | + |
|
108 | +```json |
|
109 | +"appConfig": { |
|
110 | + "test": { |
|
111 | + "desc": "测试配置", |
|
112 | + "value": "", |
|
113 | + "required": true |
|
114 | + } |
|
115 | +} |
|
116 | +``` |
|
117 | + |
|
118 | +如果 value 为空字符串,required = true。应用上架的时候,该配置项默认为空,需要在配置中心填写了配置值后,才能安装。这样保证了应用安装完成后,在启动事件也能拿到对应的值。 |
|
119 | + |
|
120 | +### 使用配置 |
|
121 | + |
|
122 | +```json |
|
123 | +Optional<String> test = ConfigUtils.get(appName, appTag, "test"); |
|
124 | +``` |
|
125 | + |
|
126 | +# 配置中心 |
|
127 | + |
|
128 | +在开发者中心 - 配置中心,可以动态修改所有配置。并且配置会保存到数据库。 |
|
129 | + |
|
130 | +[[/uploads/config_center/配置中心列表.png]] |
|
131 | + |
|
132 | +[[/uploads/config_center/配置中心编辑.png]] |
|
133 | + |
|
134 | +## 必填配置 |
|
135 | + |
|
136 | +如果配置项的 required 为 true。上架的时候,会校验 value 是否有值,如果没有值会提示先填写配置值。在配置中心修改保存的时候,也会校验是否有填写值。 |
|
137 | + |
|
138 | +```json |
|
139 | +"appConfig": { |
|
140 | + "test": { |
|
141 | + "desc": "测试配置", |
|
142 | + "value": "", |
|
143 | + "required": true |
|
144 | + } |
|
145 | +} |
|
146 | +``` |
|
147 | + |
|
148 | +安装前校验 |
|
149 | + |
|
150 | +[[/uploads/config_center/配置安装校验.png]] |
|
151 | + |
|
152 | +修改配置时校验 |
|
153 | + |
|
154 | +[[/uploads/config_center/配置编辑校验.png]] |
|
155 | + |
|
156 | + |
\351\207\215\345\206\231\346\250\241\345\236\213CRUD\346\226\271\346\263\225.md
... | ... | @@ -0,0 +1,50 @@ |
1 | +一、扩展中RecordSet参数可以无缝替换为ids,但必须声明为数组,RecordSet比ids多携带了模型信息,相当于代理了ids和模型信息。 |
|
2 | +如:rs.call("xxx"),可以直接代替this.getMeta().get("模型名"),显得更为简单 |
|
3 | + |
|
4 | +二、扩展中的Map<String,Object>参数可以由模型类来代替,因为在iidp平台中,每个sdk声明的模型都是个Map |
|
5 | + |
|
6 | +创建扩展: |
|
7 | +```java |
|
8 | +public RecordSet create(RecordSet rs, @Spec(doc = "k v") List<Map<String, Object>> valuesList) { |
|
9 | +} |
|
10 | + |
|
11 | +public RecordSet create(String[] ids, @Spec(doc = "k v") List<XXModel> valuesList) { |
|
12 | +} |
|
13 | +``` |
|
14 | + |
|
15 | + |
|
16 | + |
|
17 | +更新扩展: |
|
18 | +```java |
|
19 | +public RecordSet update(RecordSet rs, @Spec(doc = "k v") Map<String, Object> values) { |
|
20 | + |
|
21 | + } |
|
22 | + |
|
23 | +public RecordSet update(String[] ids, Map<String, Object> values) { |
|
24 | + |
|
25 | + } |
|
26 | + |
|
27 | +public RecordSet update(String[] ids, xxx values) { |
|
28 | + |
|
29 | + } |
|
30 | +``` |
|
31 | + |
|
32 | +删除扩展: |
|
33 | +```java |
|
34 | +public boolean delete(RecordSet rs) {} |
|
35 | +public boolean delete(String[] ids) {} |
|
36 | +``` |
|
37 | + |
|
38 | +search扩展: |
|
39 | + |
|
40 | +```java |
|
41 | +public List<Map<String, Object>> search(RecordSet rs, |
|
42 | + @Spec(doc = "过滤器") Filter filter, |
|
43 | + @Spec(doc = "多个属性") List<String> properties, |
|
44 | + @Spec(doc = "记录数") Integer limit, |
|
45 | + @Spec(doc = "初始位置") Integer offset, |
|
46 | + @Spec(doc = "排序") String order) {} |
|
47 | +``` |
|
48 | + |
|
49 | + |
|
50 | + |
\351\222\210\345\257\271\344\273\243\347\240\201\345\210\206\345\261\202\347\273\223\346\236\204\347\232\204\344\274\230\345\214\226.md
... | ... | @@ -0,0 +1 @@ |
1 | +[请点击查看](https://www.yuque.com/cuiguiyang-x3oor/yob7un/ze7o8f7ybwcyxxvs?singleDoc# 《针对代码分层结构的优化》) |
|
... | ... | \ No newline at end of file |
\351\230\277\351\207\214\351\200\232\344\271\211\345\215\203\351\227\256.md
... | ... | @@ -0,0 +1,50 @@ |
1 | +### 需求 |
|
2 | +本文档主要介绍 ChatGPT-Next-Web 通过 one-api 来对接阿里通义千问的部署详细过程,并提供了一个可测试的在线服务示例。 |
|
3 | +### 部署 one-api |
|
4 | +one-api 是 OpenAI 接口管理 & 分发系统,支持 Azure、Anthropic Claude、Google PaLM 2 & Gemini、智谱 ChatGLM、百度文心一言、讯飞星火认知、阿里通义千问、360 智脑以及腾讯混元。具体参考[github地址](https://github.com/songquanpeng/one-api): |
|
5 | + |
|
6 | + |
|
7 | +我们这里主要是对接阿里通义千问,采用docker方式进行部署,支持两种后端数据库部署,具体参考如下dockers命令: |
|
8 | +``` |
|
9 | + # 使用 SQLite 的部署命令: |
|
10 | + docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api |
|
11 | + # 使用 MySQL 的部署命令,在上面的基础上添加 `-e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi"`,请自行修改数据库连接参数,不清楚如何修改请参见下面环境变量一节。 |
|
12 | + # 例如: |
|
13 | + docker run --name one-api -d --restart always -p 3000:3000 -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api |
|
14 | +``` |
|
15 | +我们这里为了简单使用sqlite进行部署,执行: |
|
16 | +docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /root/data/one-api:/data justsong/one-api (挂载的卷可自定义) |
|
17 | + |
|
18 | +部署完成以后,我们打开页面,如能看到下图,即表示部署成功。 |
|
19 | +[图片] |
|
20 | +然后创建一个通道,主要有两个地方需要填写:类型选择阿里通义千问,以及对应的密钥,同时需要注意选定的模型,如下图所示: |
|
21 | +[图片] |
|
22 | + |
|
23 | +继续添加令牌,用于获得访问呢one-api的权限,并获取令牌: |
|
24 | +[图片] |
|
25 | +### 部署 ChatGPT-Next-Web |
|
26 | +ChatGPT-Next-Web 是跨平台 ChatGPT 应用,具体参考[github地址](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web) |
|
27 | + |
|
28 | +我们可以直接执行docker命令: |
|
29 | +``` |
|
30 | + docker run -d --name chatgpt-next-web -p 3001:3000 -e BASE_URL=http://192.168.168.176:3000/ -e CUSTOM_MODELS=-all,qwen-turbo -e OPENAI_API_KEY=sk-exiXwTlhPKMp93dfasfdsB11e0a4bA5Ec32Cb yidadaa/chatgpt-next-web 环境变量需要根据实机部署环境进行调整 |
|
31 | + docker run -d --name chatgpt-next-web -p 3001:3000 -e BASE_URL=http://10.18.101.53:7000/ -e CUSTOM_MODELS=-all,+qwen-turbo,+qwen-plus -e OPENAI_API_KEY=sk-exiXwTlhPKMp93dfasfdsB11e0a4bA5Ec32Cb yidadaa/chatgpt-next-web |
|
32 | +``` |
|
33 | +需要注意的几个点,BASE_URL 需要填写上面one-api的部署地址和端口,CUSTOM_MODELS 指定的模型需要添加qwen-turbo,以及OPENAI_API_KEY 设置为one-api中的令牌。 |
|
34 | + |
|
35 | +完成上述部署后,我们就可以访问chatgpt-next-web所在地址了,在打开的对话框中输入"你是谁",回复 "我是通义千问,由阿里云开发的人工智能助手。我被设计用来回答各种问题、提供信息和与用户进行对话。有什么我可以帮助你的吗?" 则说明我们已经成功对接了通义千问,如下图: |
|
36 | +[图片] |
|
37 | +### 示例 |
|
38 | +为了给大家一个可在线测试环境,这边部署了一套示例服务: |
|
39 | +[示例](http://182.16.72.122:3001/) |
|
40 | + |
|
41 | +### 自定义插件 |
|
42 | + |
|
43 | +[[/uploads/iidp-plugin/tongyiqianwen-3.png]] |
|
44 | + |
|
45 | + |
|
46 | +### 对话机器人 |
|
47 | + |
|
48 | +[[/uploads/iidp-plugin/tongyiqianwen-1.png]] |
|
49 | + |
|
50 | +[[/uploads/iidp-plugin/tongyiqianwen-2.png]] |
|
... | ... | \ No newline at end of file |
\351\230\277\351\207\214\351\200\232\344\271\211\345\215\203\351\227\256\346\220\255\345\273\272.md
... | ... | @@ -0,0 +1,32 @@ |
1 | +需求 |
|
2 | +本文档主要介绍 ChatGPT-Next-Web 通过 one-api 来对接阿里通义千问的部署详细过程,并提供了一个可测试的在线服务示例。 |
|
3 | +部署 one-api |
|
4 | +one-api 是 OpenAI 接口管理 & 分发系统,支持 Azure、Anthropic Claude、Google PaLM 2 & Gemini、智谱 ChatGLM、百度文心一言、讯飞星火认知、阿里通义千问、360 智脑以及腾讯混元。具体参考github地址:https://github.com/songquanpeng/one-api |
|
5 | +我们这里主要是对接阿里通义千问,采用docker方式进行部署,支持两种后端数据库部署,具体参考如下dockers命令: |
|
6 | +# 使用 SQLite 的部署命令: |
|
7 | +docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api |
|
8 | +# 使用 MySQL 的部署命令,在上面的基础上添加 `-e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi"`,请自行修改数据库连接参数,不清楚如何修改请参见下面环境变量一节。 |
|
9 | +# 例如: |
|
10 | +docker run --name one-api -d --restart always -p 3000:3000 -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" -e TZ=Asia/Shanghai -v /home/ubuntu/data/one-api:/data justsong/one-api |
|
11 | +我们这里为了简单使用sqlite进行部署,执行: |
|
12 | +docker run --name one-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /root/data/one-api:/data justsong/one-api (挂载的卷可自定义) |
|
13 | + |
|
14 | +部署完成以后,我们打开页面,如能看到下图,即表示部署成功。 |
|
15 | +[图片] |
|
16 | +然后创建一个通道,主要有两个地方需要填写:类型选择阿里通义千问,以及对应的密钥,同时需要注意选定的模型,如下图所示: |
|
17 | +[图片] |
|
18 | + |
|
19 | +继续添加令牌,用于获得访问呢one-api的权限,并获取令牌: |
|
20 | +[图片] |
|
21 | +部署 ChatGPT-Next-Web |
|
22 | +ChatGPT-Next-Web 是跨平台 ChatGPT 应用,具体参考github地址:https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web |
|
23 | +我们可以直接执行docker命令: |
|
24 | +docker run -d --name chatgpt-next-web -p 3001:3000 -e BASE_URL=http://192.168.168.176:3000/ -e CUSTOM_MODELS=-all,qwen-turbo -e OPENAI_API_KEY=sk-exiXwTlhPKMp93dfasfdsB11e0a4bA5Ec32Cb yidadaa/chatgpt-next-web 环境变量需要根据实机部署环境进行调整 |
|
25 | +docker run -d --name chatgpt-next-web -p 3001:3000 -e BASE_URL=http://10.18.101.53:7000/ -e CUSTOM_MODELS=-all,+qwen-turbo,+qwen-plus -e OPENAI_API_KEY=sk-exiXwTlhPKMp93dfasfdsB11e0a4bA5Ec32Cb yidadaa/chatgpt-next-web |
|
26 | +需要注意的几个点,BASE_URL 需要填写上面one-api的部署地址和端口,CUSTOM_MODELS 指定的模型需要添加qwen-turbo,以及OPENAI_API_KEY 设置为one-api中的令牌。 |
|
27 | + |
|
28 | +完成上述部署后,我们就可以访问chatgpt-next-web所在地址了,在打开的对话框中输入"你是谁",回复 "我是通义千问,由阿里云开发的人工智能助手。我被设计用来回答各种问题、提供信息和与用户进行对话。有什么我可以帮助你的吗?" 则说明我们已经成功对接了通义千问,如下图: |
|
29 | +[图片] |
|
30 | +示例 |
|
31 | +为了给大家一个可在线测试环境,这边部署了一套示例服务: |
|
32 | +http://182.16.72.122:3001/ |
|
... | ... | \ No newline at end of file |