diff --git a/README.md b/README.md
index b32a67cd2879c2dfd8a74159e9ca4934d81923f0..4fc4c59c202f7719384844a1445bda0dd41d4ab3 100644
--- a/README.md
+++ b/README.md
@@ -16,14 +16,11 @@
- rec-admin-views:视图模块
- rec-admin-web:对前端接口模块
- rec-common:基础模型、枚举模块、DTO模型、规则聚合等
-- rec-core:核心独立部署服务
- - 加载所有域进行流程处理,提供Facade接口和MQ的方式进行数据处理
- - MQ方式需自行处理,本项目不提供支持(MQ接入非常简单,不再赘言)
- rec-engine:规则引擎处理器
- rec-engine-script:规则引擎脚本处理器
- rec-engine-sdk:规则引擎SDK、SpringBootStarter模块(此处可能不太优雅)
- rec-engine-sdk-http:规则引擎SDK的SPI实现,加载数据,HTTP实现
- - rec-engine-sdk-netty:规则引擎SDK的SPI实现,加载数据,Netty实现
+- rec-spi:内部事件机制
@@ -45,57 +42,92 @@
- 方便运营进行操作,只需页面配置,然后发布,即可动态变更数据。
- 而对于页面的操作,将具备完善的文档,方便快速接入,并且理解配置,使得配置不具备复杂性。
- 可以通过SDK的方式进行接入,支持服务端、客户端通信进行处理。
-
## 规则引擎要素
-
-
### 域
- **域**作为一个业务单元的概念,尤其是在分布式系统中,域可作为原子系统的边界,也可以理解为每个单独的应用,是一个`**业务域**`
- 每个域的数据相互隔离,在进行数据发送的时候,按照域进行加载和发送,之后的所有配置都依托域来进行配置
-
### 场景
- 域下业务节点,比如淘宝买东西,业务节点有:下单成功、物流通知、收货成功、评价等等,这些都是域中,也就是应用中的业务节点,那么这些业务节点可以称之为**场景**,那么`**域加上场景**`,就确定了一个唯一的业务节点。既然可以确定唯一的业务节点,就可以进行接下来的配置
-
### 元数据
- 元数据是一些基本的数据,比如常用的,不会经常变更的,其类似于Java中的枚举,是一组数据的集合,用来归类一些通用的配置项
-
### 操作符
- 操作符即比较符,是一些数学上的概念,比如大于、小于、等于、不等于、包含、不包含,通过操作符,判断左右值,即可判断出当前执行的是否成功,从而判断是否执行下面的节点
-
### 基础数据
- 基础数据是取值上的概念,通过多种脚本执行引擎,可以通过编写脚本来进行取值,来和目标值进行比较运算
-
### 数据源
- 数据源,在执行规则判断的时候,可能存在一些计算得到的数据源,并不是仅仅传参,这样就可以动态的组织起始数据
-
### 策略组
- 策略组是多组策略的集合
-
### 决策树
- 对策略组进行编排,构建决策树,因为策略组具备多个可能的执行流程分支,执行分支可能是且、或、存在等的关系,通过决策树执行最后的结果,可定义简单返回类型,然后后续业务逻辑会根据返回类型进行决断
-
## 规则引擎操作页面
- 操作页面按照规则引擎要素进行划分,分为域配置、场景配置、元数据配置、基础数据配置、策略组配置,具体的页面UI等开发完毕之后进行贴图即可
-
+
+### 域配置
+
+- 域的作用主要是用来划分业务系统和原子系统,本质上是个聚合的分布式系统上下文的概念。在域中,可以配置场景,并且发布等,发布是通知客户端进行更新最新的数据
+
+
+
+
+
+### 场景配置
+
+- 场景依托于域存在,场景指的是这个业务系统的某些需要动态配置的节点,比如物理系统中的消息通知等等,可在场景配置中配置
+
+
+
+### 元数据配置
+
+- 元数据是一类Key-Value的集合,对应映射到Java语言中,应是枚举,这些是一些不会经常变更、比较稳定的数据
+
+
+
+
+
+### 基础数据配置
+
+- 基础数据是根据指定的入参或者无参进行取值,取值的方式通过脚本取值,这样就能满足动态的取值逻辑,但是也会有接入复杂度,可能脚本会写的比较复杂,并且有进程风险,因为脚本完全是动态的
+
+
+
+
+
+### 数据源配置
+
+- 数据源指的是规则执行的前置数据,当然了,客户端也可以传入数据,系统会自动对数据进行聚合,优先以传入的数据为准,进行合并数据,然后将合并的数据交给规则引擎处理
+- 获取值的方式支持HTTP、Groovy、Mvel2.0等
+
+
+
+
+
+### 策略组
+
+- 策略组就是一些配置项目,真正进行规则判断的地方,现在只支持简单的配置类型,但是支持复杂规则模式,简单规则模式只会返回布尔值,复杂规则模式支持执行中断,并返回目标值
+
+
+
+
## 规则引擎单节点执行逻辑
@@ -103,25 +135,26 @@
- 业务节点触发规则执行,执行的时候先判断一些数据的验证逻辑,通过之后才能将数据推到`Rule Engine Calculation`进行执行,在此过程中判断当前业务节点的决策树是否构建完毕,如果构建完毕,则从本地缓存中取出数据,否则重新构建决策树,然后存储到本地缓存执行,最终返回执行结果

-
-## 规则引擎架构设计
+## 技术与架构选型
-- 架构主要分为两个方面,一方面是页面的规则配置,另外一方面是`**仓储(本地缓存)**`的设计
-- 页面的操作通过管理员进行操作,然后可选择域进行发布,发布之后就会更新对应域的仓储数据
-- 而接入方可以选择使用SDK方式的接入,但是这种接入方式限制了开发语言为Java;也可以使用HTTP接口的方式提供服务
-- 通过SDK的方式需要在当前JVM进程加载当前域所在的配置数据;而通过HTTP接口的方式,则会在HTTP服务端加载所有仓储的数据。当然,在更新的时候也是按需更新
+- 存储技术选型:MongoDB。因为是一些配置的数据,有一些的数据可能经常的发生改动,使用MySQL不方便经常的变更数据结构,因此使用文档数据库MongoDB
+- 开发框架:SpringBoot 2.2.2.RELEASE、Vue2.0、Netty4.x、Vue-Template-Admin
+- 数据请求:Hutool Http
+- 脚本执行引擎:Groovy、JavaScript、Mvel2.0等
+- 分布式锁实现:基于MongoDB实现
-
-
+## 架构设计与落地
-## 技术与架构选项
+- 架构主要分为两个方面,一方面是页面的规则配置,另外一方面是仓储(本地缓存,JVM进程级别的缓存)的设计
+- 页面进行操作,然后可选择域进行发布,发布之后就会更新对应域的仓储数据
+- 接入方可以选择使用SDK方式的接入,但是这种接入方式限制了开发语言为Java;也可以使用HTTP接口的方式提供服务(单独开启服务加载所有域的数据);
+ - 但是使用HTTP接口的方式,需要注意序列化和反序列化问题,因为在JVM进程中,SDK接入的方式,进行脚本执行的时候,是使用的指定对象,但是经过序列化和反序列化之后,对象可能发生了变化,导致执行的结果和预期不一致
+ - 实际上,在页面上进行测试的时候,使用的是String类型的JSON数据,本质上也是序列化执行的,如果能够保证最终执行数据一致,那么则不会有问题
+- 通过SDK的方式需要在当前JVM进程加载当前域所在的配置数据;而通过HTTP接口的方式,则会在HTTP服务端加载所有仓储的数据。当然在更新的时候也是按需更新
+- 当配置触发发布的时候,会将数据推送到触发线程队列,然后由线程池消费队列,触发客户端的消息更新
-- 存储技术选型:MongoDB,因为是一些配置的数据,有一些的数据可能经常的发生改动,使用MySQL不方便经常的变更数据,因此使用文档数据库MongoDB
-- 开发框架:SpringBoot、Vue2.0、Netty4.x、Vue-Template-Admin
-- 数据请求:OKHTTP
-- 其他暂无
-
+
## 数据表设计
@@ -158,7 +191,7 @@
| --- | --- | --- |
| domainCode | String | 域Code |
| metadataName | String | 元数据名称 |
-| metadataList | String(JSON格式) | 元数据枚举值 |
+| metadataPairs | List | 元数据枚举值 |
- 基础数据(文档名称:rec-basedata)
@@ -167,6 +200,7 @@
| domainCode | String | 域Code |
| fieldName | String | 基础数据名称 |
| dataType | String | 数据类型(布尔、字符串、数值、日期、元数据等) |
+| metadataUuid | String | 关联的元数据uuid |
| scriptType | String | 脚本执行类型 |
| scriptContent | String | 脚本内容 |
| resultType | String | 脚本执行返回类型(只能是基本数据类型) |
@@ -175,6 +209,7 @@
| **字段名称** | **类型** | **备注** |
| --- | --- | --- |
+| domainCode | String | 域Code |
| dataSourceName | String | 数据源名称 |
| dataSourceType | String | 数据源类型(脚本、HTTP、SQL) |
| scriptInfo | Object | 数据源为脚本的执行数据集 |
@@ -188,29 +223,107 @@
| domainCode | String | 域Code |
| sceneCode | String | 场景Code |
| strategyName | String | 策略组名称 |
-| dataSourceType | String | 数据源类型(脚本、接口、SQL) |
-| dataSourceCode | String | 数据源关联Code |
+| dataSourceUuid | String | 数据源关联uuid |
| ruleType | String | 规则配置类型(默认为List) |
+| ruleMode | String | 规则模式 默认为simple |
| ruleListInfo | Object | 规则配置类型为List时候的规则数据 |
| ruleTreeInfo | Object | 规则配置类型为Tree时候的规则数据 |
-
+## 注册中心设计
-## 项目地址
+- 为什么需要注册中心,因为SDK是在客户端使用,如果需要动态变更数据,那么就需要能够通知到相应的SDK进行数据变更,在规则引擎中,没有使用第三方的注册中心实现,所有就有自己进行编写注册中心
+- 总体逻辑比较简单,在SDK所在的客户端启动的时候,会主动将自身的服务ip、服务端口、应用信息等发送给Admin(注册中心),然后注册中心会对这些数据进行注册,写到注册表中进行物理维护,并且定时触发心跳,判断SDK客户端是否还在线,并且维护状态
+- 而当需要数据更新的时候,则需要触发消息通知,就会对注册中心的注册信息进行轮询,然后进行服务通知,相关的数据交互,参见架构设计与落地部分
+- 在向Admin注册的时候,会将所有可用的域Code发送,假设当前机器需要加载100个域,那么注册中心将会有100条记录,区别就是domain不一致,这样做是为了方便消息通知
+- **注册表**设计如下
-- [https://gitee.com/icanci/rec](https://gitee.com/icanci/rec)
-- 分支: dev_vue_admin_template
-
+| **字段名称** | **类型** | **备注** |
+| --- | --- | --- |
+| id | object(String) | mongodb 自带id |
+| uuid | String | 雪花算法随机UUID |
+| desc | String | 功能描述 |
+| createTime | Date | 创建时间 |
+| updateTime | Date | 更新时间 |
+| isDelete | int | 状态 1有效,0无效 |
+| env | String | 环境 |
+| clientAddress | String | SDK 服务ip地址 |
+| clientPort | int | SDK 服务端口 |
+| appName | String | SDK 服务服务名字 |
+| registerTime | Date | 服务注册时间 |
+| lastUpdateTime | Date | 上次注册更新时间 |
+| domain | String | 注册绑定的domain |
+
+
+
+## 接入方式
+
+- 引入依赖,其中 `rec-engine-sdk-http` 是加载本地缓存的默认实现方式
+
+```xml
+
+
0&&(x<(S+=y)&&(x=S),S=0)}A.height=w,A.depth=x,v+=w,A.pos=v,v+=x+S,h[o]=A}var E,z,D=v/2+l.metrics.axisHeight,q=e.value.cols||[],T=[];for(a=0,z=0;a
=p)){var F;(a>0||e.value.hskipBeforeAndAfter)&&0!==(F=u.deflt(M.pregap,d))&&((E=c(["arraycolsep"],[])).style.width=F+"em",T.push(E));var N=[];for(o=0;o >16&255,l[c++]=t>>8&255,l[c++]=255&t;2===s&&(t=o[e.charCodeAt(r)]<<2|o[e.charCodeAt(r+1)]>>4,l[c++]=255&t);1===s&&(t=o[e.charCodeAt(r)]<<10|o[e.charCodeAt(r+1)]<<4|o[e.charCodeAt(r+2)]>>2,l[c++]=t>>8&255,l[c++]=255&t);return l},t.fromByteArray=function(e){for(var t,r=e.length,o=r%3,i=[],a=0,s=r-o;a |$))/i,/<\/(script|pre|style)>/i,!0],[/^/,!0],[/^<\?/,/\?>/,!0],[/^/,!0],[/^/,!0],[new RegExp("^?("+n.join("|")+")(?=(\\s|/?>|$))","i"),/^$/,!0],[new RegExp(o.source+"\\s*$"),/^$/,!1]];e.exports=function(e,t,r,n){var o,a,s,l,u=e.bMarks[t]+e.tShift[t],c=e.eMarks[t];if(!e.md.options.html)return!1;if(60!==e.src.charCodeAt(u))return!1;for(l=e.src.slice(u,c),o=0;on.height+n.depth+h&&(h=(h+m-n.height-n.depth)/2);var g,_=-(n.height+h+s)+d.height;if(d.style.top=_+"em",d.height-=_,d.depth+=_,g=0===n.height&&0===n.depth?c():i.makeVList([{type:"elem",elem:n},{type:"kern",size:h},{type:"elem",elem:u},{type:"kern",size:s}],"firstBaseline",null,t),e.value.index){var y=b(e.value.index,t.withStyle(o.SCRIPTSCRIPT)),v=c([t.style.reset(),o.SCRIPTSCRIPT.cls()],[y]),k=.6*(Math.max(d.height,g.height)-Math.max(d.depth,g.depth)),w=i.makeVList([{type:"elem",elem:v}],"shift",-k,t),x=c(["root"],[w]);return c(["sqrt","mord"],[x,d,g])}return c(["sqrt","mord"],[d,g])},_.sizing=function(e,t,r){var n=p(e.value.value,t.withSize(e.value.size),r),o=c(["mord"],[c(["sizing","reset-"+t.size,e.value.size,t.style.cls()],n)]),a=i.sizingMultiplier[e.value.size];return o.maxFontSize=a*t.style.sizeMultiplier,o},_.styling=function(e,t,r){var n={display:o.DISPLAY,text:o.TEXT,script:o.SCRIPT,scriptscript:o.SCRIPTSCRIPT}[e.value.style],i=p(e.value.value,t.withStyle(n),r);return c([t.style.reset(),n.cls()],i)},_.font=function(e,t,r){var n=e.value.font;return b(e.value.body,t.withFont(n),r)},_.delimsizing=function(e,t,r){var n=e.value.value;return"."===n?c([h[e.value.delimType]]):c([h[e.value.delimType]],[a.sizedDelim(n,e.value.size,t,e.mode)])},_.leftright=function(e,t,r){for(var n,o,i=p(e.value.body,t.reset()),s=0,l=0,u=0;u
\n':'
\n')+'\n'}function i(){return"
\nw((l-r)/(v=n+1))&&A("overflow"),r+=(s-t)*v,t=s,a=0;as?s:a+16383));1===o?(t=e[r-1],i.push(n[t>>2]+n[t<<4&63]+"==")):2===o&&(t=(e[r-2]<<8)+e[r-1],i.push(n[t>>10]+n[t>>4&63]+n[t<<2&63]+"="));return i.join("")};for(var n=[],o=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s=0,l=a.length;s
\n"},a.fence=function(e,t,r,n,a){var s,l,u,c,p=e[t],h=p.info?o(p.info).trim():"",f="";return h&&(f=h.split(/\s+/g)[0]),0===(s=r.highlight&&r.highlight(p.content,f)||i(p.content)).indexOf(""+i(e[t].content)+"
\n"):""+s+"
\n"},a.image=function(e,t,r,n,o){var i=e[t];return i.attrs[i.attrIndex("alt")][1]=o.renderInlineAsText(i.children,r,n),o.renderToken(e,t,r)},a.hardbreak=function(e,t,r){return r.xhtmlOut?""+s+"
\n":"
\n"},a.softbreak=function(e,t,r){return r.breaks?r.xhtmlOut?"
\n":"
\n":"\n"},a.text=function(e,t){return i(e[t].content)},a.html_block=function(e,t){return e[t].content},a.html_inline=function(e,t){return e[t].content},s.prototype.renderAttrs=function(e){var t,r,n;if(!e.attrs)return"";for(n="",t=0,r=e.attrs.length;t=r)return l;if(34!==(i=e.charCodeAt(t))&&39!==i&&40!==i)return l;for(t++,40===i&&(i=41);t>1,c=-7,p=r?o-1:0,h=r?-1:1,f=e[t+p];for(p+=h,i=f&(1<<-c)-1,f>>=-c,c+=s;c>0;i=256*i+e[t+p],p+=h,c-=8);for(a=i&(1<<-c)-1,i>>=-c,c+=n;c>0;a=256*a+e[t+p],p+=h,c-=8);if(0===i)i=1-u;else{if(i===l)return a?NaN:1/0*(f?-1:1);a+=Math.pow(2,n),i-=u}return(f?-1:1)*a*Math.pow(2,i-n)},t.write=function(e,t,r,n,o,i){var a,s,l,u=8*i-o-1,c=(1<>1,h=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,f=n?0:i-1,d=n?1:-1,m=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(s=isNaN(t)?1:0,a=c):(a=Math.floor(Math.log(t)/Math.LN2),t*(l=Math.pow(2,-a))<1&&(a--,l*=2),(t+=a+p>=1?h/l:h*Math.pow(2,1-p))*l>=2&&(a++,l/=2),a+p>=c?(s=0,a=c):a+p>=1?(s=(t*l-1)*Math.pow(2,o),a+=p):(s=t*Math.pow(2,p-1)*Math.pow(2,o),a=0));o>=8;e[r+f]=255&s,f+=d,s/=256,o-=8);for(a=a<