# AMC-自动化Mock组件
**Repository Path**: loopstack/amc
## Basic Information
- **Project Name**: AMC-自动化Mock组件
- **Description**: AMC(Automated Mock Component)自动化Mock组件:对HTTP请求进行Mock、拦截超时、指定返回结果,方便研发、QA进行测试和回归
- **Primary Language**: Java
- **License**: Apache-2.0
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 5
- **Forks**: 2
- **Created**: 2023-01-14
- **Last Updated**: 2025-08-31
## Categories & Tags
**Categories**: Uncategorized
**Tags**: VueBoot, Mock, Netty, 降本增效
## README
# AMC-自动化Mock组件
## 介绍
AMC(Automated Mock Component)自动化Mock组件:对HTTP请求进行Mock、拦截超时、指定返回结果,方便研发、QA进行测试和回归
## 什么是自动化Mock组件:AMC(Automated Mock Component)
- 在我们的项目开发的过程中,对接外部系统,经常会请求其接口或者服务。在很多情况下,我们在开发完毕的时候需要对业务代码进行自测、联调
- 但是会出现一种情况,需要造数据,来测试各种场景的业务逻辑是否可以正常执行。但是真实的情况下,很难快速的模拟各种正常、异常数据
- 来验证系统的稳定性,甚至在QA同学在进行测试的时候,都会使用一些网络工具或者Mock工具进行Mock,经常会在Mock上耗费非常多的时间。
- 现在我来举例经常出现的一些问题,比如某个接口返回100种错误码,但是这些错误码非常难Mock,因此可能需要下很多种场景
- 甚至拦截或者是在代码中进行处理,因此会有风险,也有隐患。因此如果能够实现无侵入的方式,那么则是友好的。但是无侵入的方式很难,因为对接的外部接口的方式不是同一的标准,因此,做不同的适配是不合适的
- 服务的接入方式:提供Mock-Cloud的方式进行数据交互
## 什么是Mock-Cloud
- Mock-Cloud,是AMC的一种服务提供方式。在上文中,我们提到,依赖方可能不想有对Jar的依赖,因此只需要提供对外暴露的HTTP接口即可,通过对AMC的封装,可以实现任意场景的Mock
- 并且,组件会对Mock的进行实现,记录"案发现场",实际上,针对这些重复的调用,每个会生成唯一的记录,这样即可进行数据的溯源,也可以复用执行请求
## 功能设计
- 能够支持客户端和HTTP服务端通信的方式
- 能够根据项目进行划分,区分不同项目的执行数据
- 能够对历史的执行请求进行记录,请求和返回,以及业务执行日志
- 可以Mock指定的数据,系统架构中设计指定的请求执行返回结果
- 定义执行契约,对数据进行处理
- 页面UI设计
## 系统架构设计
- 因为Mock是一个测试系统,所以我们这里使用简洁的架构方案,使用DDK进行通知,来完成更新的功能

## 设计要点
- 表结构设计,分为配置数据和日志数据;目前配置数据都写在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
}
}
```
## 页面展示
### 事业群


### 项目组


### 项目


### Mock配置


### MockCall配置


### MockCall报告


### Mock-Cloud在DDK的注册

## 接入方式
- 启动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