diff --git a/README.md b/README.md index dbcd7daa16edf6e6ba12a5b73b9e389abf353705..e8f859b3f08cc4d8150f7fd569a60483bd1f7350 100644 --- a/README.md +++ b/README.md @@ -4,28 +4,324 @@ AMC(Automated Mock Component)自动化Mock组件:对HTTP请求进行Mock、拦截超时、指定返回结果,方便研发、QA进行测试和回归 -## 软件架构 +## 什么是自动化Mock组件:AMC(Automated Mock Component) -软件架构说明 +- 在我们的项目开发的过程中,对接外部系统,经常会请求其接口或者服务。在很多情况下,我们在开发完毕的时候需要对业务代码进行自测、联调 +- 但是会出现一种情况,需要造数据,来测试各种场景的业务逻辑是否可以正常执行。但是真实的情况下,很难快速的模拟各种正常、异常数据 +- 来验证系统的稳定性,甚至在QA同学在进行测试的时候,都会使用一些网络工具或者Mock工具进行Mock,经常会在Mock上耗费非常多的时间。 +- 现在我来举例经常出现的一些问题,比如某个接口返回100种错误码,但是这些错误码非常难Mock,因此可能需要下很多种场景 +- 甚至拦截或者是在代码中进行处理,因此会有风险,也有隐患。因此如果能够实现无侵入的方式,那么则是友好的。但是无侵入的方式很难,因为对接的外部接口的方式不是同一的标准,因此,做不同的适配是不合适的 +- 服务的接入方式:提供Mock-Cloud的方式进行数据交互 -## 安装教程 +## 什么是Mock-Cloud -1. xxxx -2. xxxx -3. xxxx +- Mock-Cloud,是AMC的一种服务提供方式。在上文中,我们提到,依赖方可能不想有对Jar的依赖,因此只需要提供对外暴露的HTTP接口即可,通过对AMC的封装,可以实现任意场景的Mock +- 并且,组件会对Mock的进行实现,记录"案发现场",实际上,针对这些重复的调用,每个会生成唯一的记录,这样即可进行数据的溯源,也可以复用执行请求 -## 使用说明 +## 功能设计 -1. xxxx -2. xxxx -3. xxxx +- 能够支持客户端和HTTP服务端通信的方式 +- 能够根据项目进行划分,区分不同项目的执行数据 +- 能够对历史的执行请求进行记录,请求和返回,以及业务执行日志 +- 可以Mock指定的数据,系统架构中设计指定的请求执行返回结果 +- 定义执行契约,对数据进行处理 +- 页面UI设计 -## 参与贡献 +## 系统架构设计 -1. Fork 本仓库 -2. 新建 Feat_xxx 分支 -3. 提交代码 -4. 新建 Pull Request +- 因为Mock是一个测试系统,所以我们这里使用简洁的架构方案,使用DDK进行通知,来完成更新的功能 + +![amc](./doc/img/AMC.png) + +## 设计要点 + +- 表结构设计,分为配置数据和日志数据;目前配置数据都写在MongoDB +- 分布式雪花ID,基于Hutool的工具实现 +- 此AMC和Mock Cloud的设计,基于AP模型。只要有一个服务端可用,那么整体就是可用的 +- 在实际的业务开发中,可能这种通知不仅仅在项目中实现,也会在业务方定制的SDK中使用,那么这种情况,就需要考虑同一个项目使用多个SDK的问题,是否需要限制多个SDK的启动?但是实际上项目在启动的时候,所有配置都是基于SpringBean实现的 +- 对于通知配置,Admin提供了通知接口。在请求达到的时候,会进行发送处理,其实就是发布,发布之后会通过DDK进行通知,然后调用更新的方法,进行更新数据,因此AMC-Cloud依赖于DDK +- 对Mock-Cloud进行开发,提供Facade接口的方式进行提供服务,并且记录埋点 +- 总体上来说,系统分为3个实现,第一个是Admin配置站点,第二个是Facade服务站点;还有一个测试项目,作为测试使用 +- 但是实际上,无需设计的这么复杂,对于Mock的场景来说,对于数据的更新,我们可以通过Core提供接口来进行刷新数据,但是存在一种问题, +- 也就是在集群的情况下,接口的请求并不是平均分配,因此此种方式不能保证集群的每一台机器都刷新本地的缓存数据。 +- 当然了,这种方式我们可以通过分布式注册中心,进行通知,但是本质上这种服务我并不想设计的太过复杂,也就是说不需要依赖太多的组件,简单易用就可以, +- 因此一种简单的设计方式是,写更新数据到通知表,然后各个Facade服务,各自轮询这个数据,进行数据的更新,但是这种方式有一种问题,那就是如果判断数据是最新的数据, +- 并轮询也会消耗CPU的部分性能,尽管当代计算机这种消耗已经可以忽略不计,因此,我们还是和之前一样,通过手动实现注册中心的方式进行消息通知, +- 后期会将注册中心进行单独设计,将其抽象出来 +- 但是之前做的DDK,已经可以完成通知的功能,因此,此次将使用DDK去完成通知功能,而不用在手写注册中心那哪些套路了 + +## 技术与架构选型 + +- 存储技术选型:MongoDB +- 开发框架:SpringBoot 2.2.2.RELEASE、Vue2.0、Netty4.x、Vue-Template-Admin +- 数据请求:Hutool HttpClient;超时与重试,基于线程池和FutureTask实现 + +## AMC&Mock-Cloud要素 + +### 事业群 + +- 事业群是最高平级维度,一个公司可能有多个事业部 + +### 项目组 + +- 一个事业群下有多个项目组 + +### 项目 + +- 一个项目组有多个项目:项目的标识是唯一的 + +### 项目MockTest配置 + +- 对于Mock来说,会写一些执行脚本,因此,我们需要将一些常用的或者复杂的配置数据先行保存,方便后面在进行测试的时候进行处理,因此有这个前置数据模块 + +### 项目Mock配置 + +- 在一个项目中有多个Mock场景配置,我们可以将其进行分类,然后编写对应的配置,然后定义契约即可进行访问 + +### 项目Mock回调配置 + +- 在某些情况下,我们需要进行mock回调,也就是需要处理接入系统被调用的情况,因此会有这个Mock回调功能,但是第一个版本暂时不做处理 + +### Mock报告 + +- 对于研发和QA来说,我们实现的功能是能够记录到案发现场,因此我们需要做一些记录和埋点,记录请求参数和返回参数,然后通过文件导出, +- 并尝试借助这种方式来补充我们的提测报告,Mock报告的导出方式分别为markdown、pdf、excel文档的形式,设计成markdown是为了能够对文档进行修改,导出为pdf则是直接使用,但是第一版本的实现,只实现了excel的导出 + +## 数据表设计 + +- 数据表设计,根据动态字典键&注册中心要素可以发现聚合了5张表,有些数据是固化的,每5张表数据都有的,如下表,因此这些字段不再单独列出,具体的可在存储的数据结构中看到 + +| **字段名称** | **类型** | **备注** | +| --- | --- | --- | +| id | Object | MongoDB数据库自增id | +| uuid | String | 雪花算法随机UUID | +| desc | String | 功能描述 | +| createTime | Date | 创建时间 | +| updateTime | Date | 更新时间 | +| isDelete | int | 状态 1删除,0未删除 | +| env | String | 环境 | + +### 事业群 + +- 表名字:amc-group + +| **字段名称** | **类型** | **备注** | +| --- | --- | --- | +| groupId | String | 事业群组id,唯一 | +| groupName | String | 组名字 | + +### 项目组 + +- 表名字:amc-team + +| **字段名称** | **类型** | **备注** | +| --- | --- | --- | +| teamId | String | 项目组id,唯一 | +| teamName | String | 项目组名字 | +| groupUuid | String | 事业群关联uuid | + +### 项目组 + +- 表名字:amc-app + +| **字段名称** | **类型** | **备注** | +| --- | --- | --- | +| appId | String | 项目id,全局唯一 | +| appName | String | 项目名字 | +| teamUuid | String | 项目组关联uuid | + +### 项目MockTest配置 + +- 表名字:amc-app-mock-call-test + +| **字段名称** | **类型** | **备注** | +| --- | --- | --- | +| mockTestName | String | 配置Test名称 | +| testJson | String | 执行的测试内容 | +| appUuid | String | 项目关联uuid | + +### 项目Mock配置 + +- 表名字:amc-app-mock-call + +| **字段名称** | **类型** | **备注** | +| --- | --- | --- | +| mockName | String | 配置名称 | +| scriptType | String | 脚本执行类型 | +| script | String | 执行的脚本内容 | +| appUuid | String | 项目关联uuid | + +### 项目Mock回调配置 + +- 表名字:amc-app-mock-callback +- 一期版本没有 + +### Mock报告 + +- 记录触发条件、执行结果的现场数据,为Mock执行的结果进行数据聚合,规整成为标准文档 +- 表名字:amc-app-mock-call-report + +| **字段名称** | **类型** | **备注** | +| --- | --- | --- | +| success | boolean | mock过程是否成功 | +| mockName | String | mockName | +| mockUuid | String | mockUuid | +| mockRequest | String | mock请求 | +| runtime | long | mock调用耗时 | +| mockErrorMessage | String | mock调用异常 | +| mockResponse | String | mock调用返回结果 | + +## AMC-Cloud消息更新 + +- 消息更新使用DDK进行触发配置,AMC-Cloud会将自己注册到DDK,然后通过发布将数据请求到DDK,由DDK接管数据的更新功能 +- 具体的yml配置如下 + +```yaml +# 接入方需要上报自己的信息给注册中心 +ddk: + env: test + load: true + client-port: 12000 + server-ips: 127.0.0.1 + server-port: 9998 + app-id: lsi.bs.java.amc +``` + +- 具体的通知配置资源如下,`AmcMockCallRepository` 内部逻辑会判断是否是第一次加载,如果是则全量加载,然后加载配置通知值;否则就只加载配置 + +```java +@Service @DdkResource("#mock.cloud.notice") public class DDKResource { + + private final Logger logger = LoggerFactory.getLogger(DDKResource.class); + + @Resource private AmcMockCallRepository amcMockCallRepository; + + @DdkBeforeCall private synchronized void ddkBeforeCall(String mockCallUuid) { + // No Op + } + + @DdkCall private synchronized void ddkCall(String mockCallUuid) { + logger.info("[DDKResource][ddkCall] mockCallUuid:{}, now is Start", mockCallUuid); + amcMockCallRepository.refresh(mockCallUuid); + logger.info("[DDKResource][ddkCall] mockCallUuid:{}, now is End", mockCallUuid); + } + + @DdkAfterCall private synchronized void ddkAfterCall(String mockCallUuid) { + // No Op + } +} + +``` + +## 页面展示 + +### 事业群 + +![amc](./doc/img/group.png) +![amc](./doc/img/group1.png) + +### 项目组 + +![amc](./doc/img/team.png) +![amc](./doc/img/team1.png) + +### 项目 + +![amc](./doc/img/app.png) +![amc](./doc/img/app1.png) + +### Mock配置 + +![amc](./doc/img/mockjson.png) +![amc](./doc/img/mockjson1.png) + +### MockCall配置 + +![amc](./doc/img/mockcall.png) +![amc](./doc/img/mockcall1.png) + +### MockCall报告 + +![amc](./doc/img/mockcallreport.png) +![amc](./doc/img/mockcallreport1.png) + +### Mock-Cloud在DDK的注册 + +![amc](./doc/img/ddk.png) + +## 接入方式 + +- 启动DDK,动态字典键和配置中心 +- 启动AMC-Admin,进行相关的配置 +- 启动AMC-Cloud,加载数据到缓存 +- 启动接入项目,接入项目只需要引入依赖模型包即可,如下 + +```xml + +0.0.0.1-SNAPSHOT + +1.0-SNAPSHOT + + + cn.icanci.loopstack + lsi-api + ${lsi.version} + + + cn.icanci.loopstack.amc + cloud-common + ${amc.version} + +``` + +- 数据请求测试如下,在cloud-common包中提供`WrapperUtils` ,可将Wrapper对象解析为各种类型,具体的转换参见WrapperUtils即可 + +```java +@Service +public class MockCallServiceImpl implements MockCallService { + private static final Logger logger = LoggerFactory.getLogger(MockCallServiceImpl.class); + + @Resource + private Client httpClient; + + @Value("${amc.url}") + private String amcUrl; + + @Override + public void testMockCall(String mockUuid) { + MockCallRequest request = new MockCallRequest(); + request.setTraceId(UUID.randomUUID().toString()); + request.setMockCallUuid(mockUuid); + HashMap obj = new HashMap<>(); + obj.put("uuid", UUID.randomUUID().toString()); + request.setMockRequest(JSONUtil.toJsonStr(obj)); + Client.RpcRequest rpcRequest = new Client.RpcRequest(amcUrl, request, Maps.newHashMap(), Method.POST, 3, TimeUnit.SECONDS, 0); + logger.info("[MockCallService][testMockCall] req:{}", JSONUtil.toJsonStr(request)); + MockCallResponse call = httpClient.call(rpcRequest, MockCallResponse.class); + // MockCallWrapper wrapper = call.getWrapper(); + // Object mockResponse = wrapper.getMockResponse(); + // + // System.out.println(WrapperUtils.toStringValue(mockResponse)); + // Quota x = WrapperUtils.toObjectBean(mockResponse, Quota.class); + // System.out.println(x); + + logger.info("[MockCallService][testMockCall] resp:{}", JSONUtil.toJsonStr(call.getWrapper())); + System.out.println(); + } +} + +``` + +- 有的童鞋可能对为什么要使用DDK进行通知,原因是因为AMC-Cloud可能是集群部署的,因此,在集群部署情况下,需要进行分布式的通知,因此需要使用DDK进行消息通知 + +## 迭代版本 + +- 第一个版本完成了配置的更新、和DDK的整合、Mock报告的现场还原,完成了对数据的管理等 + +## 项目地址 +- AMC地址:https://gitee.com/loopstack/amc +- AMC-Cloud:https://gitee.com/loopstack/amc-cloud +- AMC-Sample:https://gitee.com/loopstack/amc-sample ## 备注 diff --git a/doc/.gitkeep b/doc/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/doc/img/AMC.png b/doc/img/AMC.png new file mode 100644 index 0000000000000000000000000000000000000000..40ca4b137f2cd97ea786bf3fa05ace20bb8950d2 Binary files /dev/null and b/doc/img/AMC.png differ diff --git a/doc/img/app.png b/doc/img/app.png new file mode 100644 index 0000000000000000000000000000000000000000..36ebe945d3593af354e01d7c7cd0068d62b7f9c1 Binary files /dev/null and b/doc/img/app.png differ diff --git a/doc/img/app1.png b/doc/img/app1.png new file mode 100644 index 0000000000000000000000000000000000000000..c26106c72341deb548183ab6724f69e9937b4442 Binary files /dev/null and b/doc/img/app1.png differ diff --git a/doc/img/ddk.png b/doc/img/ddk.png new file mode 100644 index 0000000000000000000000000000000000000000..1b6a0bfa7ba74398be69efeea66f47114517cba4 Binary files /dev/null and b/doc/img/ddk.png differ diff --git a/doc/img/group.png b/doc/img/group.png new file mode 100644 index 0000000000000000000000000000000000000000..6f061c1158188e6de72197afe40d2dc12c654184 Binary files /dev/null and b/doc/img/group.png differ diff --git a/doc/img/group1.png b/doc/img/group1.png new file mode 100644 index 0000000000000000000000000000000000000000..4ef47287c8811c0e4b9a8e86549a7d30e9a5a233 Binary files /dev/null and b/doc/img/group1.png differ diff --git a/doc/img/mockcall.png b/doc/img/mockcall.png new file mode 100644 index 0000000000000000000000000000000000000000..7007c28a9fde18681e0ab4ca921dea76267a24b6 Binary files /dev/null and b/doc/img/mockcall.png differ diff --git a/doc/img/mockcall1.png b/doc/img/mockcall1.png new file mode 100644 index 0000000000000000000000000000000000000000..da86bca539bbdb3fb9eccc3e3b5a2a6a3501c99a Binary files /dev/null and b/doc/img/mockcall1.png differ diff --git a/doc/img/mockcallreport.png b/doc/img/mockcallreport.png new file mode 100644 index 0000000000000000000000000000000000000000..45ed9fd773eabab5d75b007c38d0bdfffc219cad Binary files /dev/null and b/doc/img/mockcallreport.png differ diff --git a/doc/img/mockcallreport1.png b/doc/img/mockcallreport1.png new file mode 100644 index 0000000000000000000000000000000000000000..8d584f99384a00ce28bacb314c28de1d2a37c73f Binary files /dev/null and b/doc/img/mockcallreport1.png differ diff --git a/doc/img/mockjson.png b/doc/img/mockjson.png new file mode 100644 index 0000000000000000000000000000000000000000..8696315d00c7a8837755acb53fe6e6584ca20dfc Binary files /dev/null and b/doc/img/mockjson.png differ diff --git a/doc/img/mockjson1.png b/doc/img/mockjson1.png new file mode 100644 index 0000000000000000000000000000000000000000..eb20fec9073c6d029bcacfa5ddaf2851ab6dc9bb Binary files /dev/null and b/doc/img/mockjson1.png differ diff --git a/doc/img/team.png b/doc/img/team.png new file mode 100644 index 0000000000000000000000000000000000000000..1ec958450b8dfc3717f3d97ce4c15b80addfedbc Binary files /dev/null and b/doc/img/team.png differ diff --git a/doc/img/team1.png b/doc/img/team1.png new file mode 100644 index 0000000000000000000000000000000000000000..e2e086ea9c0c18ff8ba4f9bcc8f41c2c716681d0 Binary files /dev/null and b/doc/img/team1.png differ