挡板测试其实是MOCK程序的一种,用于超大型项目~

就是仅提供一个或者几个功能测试的沙盒beta 版本
首先,挡板测试在测试领域提到的不是很普遍。挡板测试大概就是:在一些跨系统的性能测试项目中,往往由于客观因素的限制(测试硬件资源有限、多系统之间的协调等),我们无法搭建一个完整的测试环境来完成测试工作。此时,我们一般会搭建出被测系统,然后采用软件程序来模拟其他相关系统的功能。该软件程序一般被称为挡板。在BAT(百度阿里腾讯)公司有很多类似的挡板,但是内部并不是叫这个名词。他们会叫:“线下系统”、“沙盒系统”为了区分线上正式运营的系统。所以,挡板测试在平时测试联调级联模块显得极其的重要了

公司的挡板测试就是模拟接口返回值来测试,模拟服务或功能的返回值。有时候真实的返回值覆盖不了所有的测试场景,可以通过挡板来模拟。
还有真实的调用有可能产生费用,就使用模拟,比如短信服务。

 


背景:
在项目开发中,会有调用第三方接口的场景。当开发时,对方不愿意提供测试服务器给我们调用,或者有的接口会按调用次数进行计费。当联调时,第三方的测试服务器也可能会出现不稳定,如果他们的服务挂了,我们就一直等着服务恢复,那么这就相当影响效率了。如果我们在开发时,就定义一个挡板或者mock服务,在发起调用时,不直接调到第三方接口,而是调到我们自己的挡板代码或者mock服务,这样就可以避免这些问题了。

优势:

挡板代码,不需要侵入业务代码,可以根据入参做一些动态结果返回
不需要专门开发一个挡板服务,并且在每次启动客户端都先启动挡板服务
可以自由选择使用挡板还是Mock数据
Demo详细代码,已经提交到Github,欢迎star

Demo地址: https://github.com/Seifon/FeignStubMock

一、下面我就以一个第三方SMS短信接口来做演示:​

首先,我们写一个Feign客户端接口,正常调用第三方接口:

1.定义一个SMS短信的Feign客户端接口:
import cn.seifon.example.feignstubmock.dto.YunxunSmsReqDto;
import cn.seifon.example.feignstubmock.dto.YunxunSmsRespDto;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

/**
* @Author: Seifon
* @Description:
* @Date: Created in 10:24 2019/1/7
*/
@FeignClient(name = "smsclient", url = "${sms.url}", primary = false)
public interface YunxunSmsFeign {

/**
*
* @param request
* @return {"code":"0","failNum":"0","successNum":"1","msgId":"19012516213625881","time":"20190125162136","errorMsg":""}
* @return {"code":"107","msgId":"","time":"20190125162358","errorMsg":"手机号码格式错误"}
*/
@PostMapping("/msg/variable/json")
YunxunSmsRespDto send(@RequestBody YunxunSmsReqDto request);
}

注意:@FeignClient注解里面的primary属性一定要设置为false,这是为了防止在开启Feign挡板时,出现多个Feign客户端导致启动报错。

2.写一个单元测试:
import cn.seifon.example.feignstubmock.dto.YunxunSmsReqDto;
import cn.seifon.example.feignstubmock.dto.YunxunSmsRespDto;
import cn.seifon.example.feignstubmock.feign.YunxunSmsFeign;
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class FeignStubMockApplicationTests {
@Autowired
private YunxunSmsFeign yunxunSmsFeign;

@Test
public void feignStubMockTest() {
YunxunSmsReqDto yunxunSmsReqDto=new YunxunSmsReqDto();
yunxunSmsReqDto.setAccount("XXXXXXX");
yunxunSmsReqDto.setPassword("XXXXXXX");
yunxunSmsReqDto.setMsg("登录验证码:{$var},请不要对非本人透露。");
yunxunSmsReqDto.setParams("13011112222,123456");
yunxunSmsReqDto.setReport("true");

YunxunSmsRespDto send = yunxunSmsFeign.send(yunxunSmsReqDto);

//打印结果
System.out.println(JSON.toJSON(send));
}

}
3.1.我们输入一个正确的手机号,拿一个成功的结果:
2019-01-28 11:17:56.718 DEBUG 6920 --- [           main] c.s.e.f.feign.YunxunSmsFeign             : [YunxunSmsFeign#send] ---> POST http://smssh1.253.com/msg/variable/json HTTP/1.1
2019-01-28 11:17:56.719 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] Content-Type: application/json;charset=UTF-8
2019-01-28 11:17:56.720 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] Content-Length: 160
2019-01-28 11:17:56.720 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send]
2019-01-28 11:17:56.721 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] {"account":"XXXXXX","password":"XXXXXXX","msg":"登录验证码:{$var},请不要对非本人透露。","params":"17311112222,123456","report":"true"}
2019-01-28 11:17:56.721 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] ---> END HTTP (160-byte body)
2019-01-28 11:17:56.958 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] <--- HTTP/1.1 200 OK (236ms)
2019-01-28 11:17:56.960 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] connection: keep-alive
2019-01-28 11:17:56.962 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] content-length: 109
2019-01-28 11:17:56.963 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] content-type: application/json;charset=UTF-8
2019-01-28 11:17:56.965 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] date: Mon, 28 Jan 2019 03:17:56 GMT
2019-01-28 11:17:56.966 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send]
2019-01-28 11:17:56.971 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] {"code":"0","failNum":"0","successNum":"1","msgId":"19012811175621982","time":"20190128111756","errorMsg":""}
2019-01-28 11:17:56.972 DEBUG 6920 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] <--- END HTTP (109-byte body)
{"code":"0","failNum":"0","successNum":"1","msgId":"19012811175621982","time":"20190128111756","errorMsg":""}
3.2.我们输入一个错误的手机号,拿一个失败的结果:
2019-01-28 11:21:15.300 DEBUG 5288 --- [           main] c.s.e.f.feign.YunxunSmsFeign             : [YunxunSmsFeign#send] ---> POST http://smssh1.253.com/msg/variable/json HTTP/1.1
2019-01-28 11:21:15.301 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] Content-Type: application/json;charset=UTF-8
2019-01-28 11:21:15.302 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] Content-Length: 152
2019-01-28 11:21:15.302 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send]
2019-01-28 11:21:15.303 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] {"account":"XXXXX","password":"XXXXXXX","msg":"登录验证码:{$var},请不要对非本人透露。","params":"173,123456","report":"true"}
2019-01-28 11:21:15.303 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] ---> END HTTP (152-byte body)
2019-01-28 11:21:15.470 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] <--- HTTP/1.1 200 OK (165ms)
2019-01-28 11:21:15.471 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] connection: keep-alive
2019-01-28 11:21:15.473 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] content-length: 87
2019-01-28 11:21:15.474 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] content-type: application/json;charset=UTF-8
2019-01-28 11:21:15.476 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] date: Mon, 28 Jan 2019 03:21:15 GMT
2019-01-28 11:21:15.477 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send]
2019-01-28 11:21:15.483 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] {"code":"107","msgId":"","time":"20190128112115","errorMsg":"手机号码格式错误"}
2019-01-28 11:21:15.484 DEBUG 5288 --- [ main] c.s.e.f.feign.YunxunSmsFeign : [YunxunSmsFeign#send] <--- END HTTP (87-byte body)
{"code":"107","msgId":"","time":"20190128112115","errorMsg":"手机号码格式错误"}

当我们知道了两种情况下出现的结果,那么我们就可以模拟响应结果啦。小技巧:我们可以先跟对方调接口,把各种响应报文保存下来,方便后面直接mock数据

二、接下来进入挡板编写环节:​

1.编写一个YunxunSmsFeignStub类,并实现YunxunSmsFeign接口:
import cn.seifon.example.feignstubmock.dto.YunxunSmsReqDto;
import cn.seifon.example.feignstubmock.dto.YunxunSmsRespDto;
import cn.seifon.example.feignstubmock.feign.YunxunSmsFeign;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
* @Author: Seifon
* @Description:
* @Date: Created in 10:24 2019/1/7
*/
@Primary //注意:需要在原Feign接口@FeignClient注解加入primary = false 属性
@Component
@ConditionalOnProperty(name = "feign-stub.yunxun.sms.mode", havingValue = "stub")
public class YunxunSmsFeignStub implements YunxunSmsFeign {
private static final Logger LOG = LoggerFactory.getLogger(YunxunSmsFeignStub.class);

@Override
public YunxunSmsRespDto send(YunxunSmsReqDto request) {
YunxunSmsRespDto yunxunSmsRespDto = new YunxunSmsRespDto();

//模拟正常响应结果
yunxunSmsRespDto.setCode("0");
yunxunSmsRespDto.setFailNum("0");
yunxunSmsRespDto.setSuccessNum("1");
yunxunSmsRespDto.setMsgId(String.valueOf(RandomUtils.nextLong(19000000000000000L, 19999999999999999L)));
yunxunSmsRespDto.setTime(DateFormatUtils.format(new Date(), "yyyyMMddHHmmss"));
yunxunSmsRespDto.setErrorMsg("");

String params = request.getParams();
String[] paramSplit = StringUtils.split(params, ",");
if (paramSplit[0].length() != 11) {
//模拟错误响应结果
yunxunSmsRespDto.setCode("107");
yunxunSmsRespDto.setMsgId("");
yunxunSmsRespDto.setErrorMsg("手机号码格式错误");
}
return yunxunSmsRespDto;
}
}

注意:必须标注@Primary注解,否则启动会报错。@ConditionalOnProperty的作用就是根据application.yaml配置的相关属性,判断是否注入Spring容器

2.application.yaml文件,加入下面的配置:
sms:
url: 'http://smssh1.253.com'

#yunxun:代表第三方系统名称,sms:代表业务名称,mode:代表Stub模式,url:代表mock服务地址
feign-stub:
yunxun:
sms:
mode: 'stub'
3.为了区分返回的内容是挡板结果,我们可以写一个AOP切面打印日志:
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
* @Author: Seifon
* @Description:
* @Date: Created in 10:24 2019/1/7
*/
@Aspect
@Component
public class FeignStubAspect {

private static final Logger LOG = LoggerFactory.getLogger(FeignStubAspect.class);

@Pointcut("execution(* cn.seifon.example.feignstubmock..stub.*.*(..))")
public void pointCut(){}

@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp){
String name = StringUtils.join(pjp.getTarget().getClass().getName(), ".", pjp.getSignature().getName());
LOG.info("-----【{}】---- 进入挡板模式... request: 【{}】", name, JSON.toJSON(pjp.getArgs()));
try {
Object proceed = pjp.proceed();
LOG.info("-----【{}】---- 退出挡板模式... request: 【{}】, response: 【{}】", name, JSON.toJSON(pjp.getArgs()), JSON.toJSON(proceed));
return proceed;
} catch (Throwable e) {
e.printStackTrace();
}

return null;
}

}
4.1.运行之前写的单元测试代码(输入一个正确的手机号):
2019-01-28 11:32:51.255  INFO 7488 --- [           main] c.s.e.f.aspect.FeignStubAspect           : -----【cn.seifon.example.feignstubmock.feign.stub.YunxunSmsFeignStub.send】---- 进入挡板模式... request: 【[{"msg":"登录验证码:{$var},请不要对非本人透露。","password":"XXXXXXX","report":"true","params":"13011112222,123456","account":"XXXXXXX"}]】
2019-01-28 11:32:51.975 INFO 7488 --- [ main] c.s.e.f.aspect.FeignStubAspect : -----【cn.seifon.example.feignstubmock.feign.stub.YunxunSmsFeignStub.send】---- 退出挡板模式... request: 【[{"msg":"登录验证码:{$var},请不要对非本人透露。","password":"XXXXXXX","report":"true","params":"13011112222,123456","account":"XXXXXXX"}]】, response: 【{"code":"0","failNum":"0","successNum":"1","msgId":"19148964234899564","time":"20190128113251","errorMsg":""}】
{"code":"0","failNum":"0","successNum":"1","msgId":"19148964234899564","time":"20190128113251","errorMsg":""}
4.2.运行之前写的单元测试代码(输入一个错误的手机号):
2019-01-28 11:35:27.177  INFO 15204 --- [           main] c.s.e.f.aspect.FeignStubAspect           : -----【cn.seifon.example.feignstubmock.feign.stub.YunxunSmsFeignStub.send】---- 进入挡板模式... request: 【[{"msg":"登录验证码:{$var},请不要对非本人透露。","password":"XXXXXXX","report":"true","params":"130,123456","account":"XXXXXXX"}]】
2019-01-28 11:35:27.900 INFO 15204 --- [ main] c.s.e.f.aspect.FeignStubAspect : -----【cn.seifon.example.feignstubmock.feign.stub.YunxunSmsFeignStub.send】---- 退出挡板模式... request: 【[{"msg":"登录验证码:{$var},请不要对非本人透露。","password":"XXXXXXX","report":"true","params":"130,123456","account":"XXXXXXX"}]】, response: 【{"code":"107","failNum":"0","successNum":"1","msgId":"","time":"20190128113527","errorMsg":"手机号码格式错误"}】
{"code":"107","failNum":"0","successNum":"1","msgId":"","time":"20190128113527","errorMsg":"手机号码格式错误"}

以上代码就完成了一个stub挡板功能,可有时候,我们已经拿到第三方接口的返回报文,并切不想去写一大段Stub代码。那么这个时候,我们就可以选择下面的Mock方式去完成我们的功能。

三、接下来进入Mock环节:​

1. 首先准备一个mock服务,这里我就用自己比较喜欢的一个mock工具(mock-json-server)给大家演示:
1.1 安装nodejs:
参看官网:http://nodejs.cn/
1.2 安装mock-json-server:
npm install -g mock-json-server
1.3 新建mock数据文件(命名为:data.json):
{
"/msg/variable/json": {
"post": {
"code":"0",
"failNum":"0",
"successNum":"1",
"msgId":"19012516213625881",
"time":"20190125162136",
"errorMsg":""
}
}
}
1.4 运行:
mock-json-server {path}/data.json --port=1240

{path}替换为存放data.json的绝对路径
1.5 如果显示如下结果,就代表mock服务运行成功:
JSON Server running at http://localhost:1240/
mock-json-server具体使用文档,请参考:https://www.npmjs.com/package/mock-json-server
2. 准备工作做好后,接下来,就进入Mock正式环节:
2.1 首先,我们定义一个YunxunSmsFeignMock接口,并且继承YunxunSmsFeign接口
import cn.seifon.example.feignstubmock.feign.YunxunSmsFeign;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

/**
* @Author: Seifon
* @Description:
* @Date: Created in 10:24 2019/1/7
*/
@Primary //注意:需要在原Feign接口@FeignClient注解加入primary = false 属性
@Component
@ConditionalOnProperty(name = "feign-stub.yunxun.sms.mode", havingValue = "mock")
@FeignClient(name = "smsclient-mock", url = "${feign-stub.yunxun.sms.mockUrl}" ,path = "/")
public interface YunxunSmsFeignMock extends YunxunSmsFeign {

}

注意:必须标注@Primary注解,否则启动时会报错。@FeignClient里的name属性不能跟原Feign接口名称相同,如果相同会启动报错。@ConditionalOnProperty的作用就是根据application.yaml配置的相关属性,判断是否注入Spring容器

2.2 application.yaml文件,加入下面的配置:
sms:
url: 'http://smssh1.253.com'

#生产环境请勿添加此配置。mode说明:''-不开启, 'mock'-mock模式, 'stub'-stub模式。url说明:只有mock模式需要配置调试url。fund为第三方机构,repayment是业务名称
#yunxun:代表第三方系统名称,sms:代表业务名称,mode:代表挡板模式,url:代表mock服务地址
feign-stub:
yunxun:
sms:
mode: 'mock'
mockUrl: "http://localhost:1240"
2.3 为了区分返回的内容是Mock结果,我们可以写一个AOP切面打印日志:
import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
* @Author: Seifon
* @Description:
* @Date: Created in 10:24 2019/1/7
*/
@Aspect
@Component
public class FeignMockAspect {

private static final Logger LOG = LoggerFactory.getLogger(FeignMockAspect.class);

@Pointcut("execution(* cn.seifon.example.feignstubmock..mock.*.*(..))")
public void pointCut(){}

@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp){
String name = StringUtils.join(pjp.getTarget().getClass().getName(), ".", pjp.getSignature().getName());
LOG.info("-----【{}】---- 进入Mock模式... request: 【{}】", name, JSON.toJSON(pjp.getArgs()));
try {
Object proceed = pjp.proceed();
LOG.info("-----【{}】---- 退出Mock模式... request: 【{}】, response: 【{}】", name, JSON.toJSON(pjp.getArgs()), JSON.toJSON(proceed));
return proceed;
} catch (Throwable e) {
e.printStackTrace();
}

return null;
}

}
2.4 运行之前的单元测试类,得到如下结果:
2019-01-28 16:16:35.567  INFO 8976 --- [           main] c.s.e.f.aspect.FeignMockAspect           : -----【com.sun.proxy.$Proxy95.send】---- 进入Mock模式... request: 【[{"msg":"登录验证码:{$var},请不要对非本人透露。","password":"XXXXXXX","report":"true","params":"13011112222,123456","account":"XXXXXXX"}]】
2019-01-28 16:16:35.934 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] ---> POST http://localhost:1240/msg/variable/json HTTP/1.1
2019-01-28 16:16:35.935 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] Content-Type: application/json;charset=UTF-8
2019-01-28 16:16:35.936 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] Content-Length: 152
2019-01-28 16:16:35.936 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send]
2019-01-28 16:16:35.937 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] {"account":"XXXXXXX","password":"XXXXXXX","msg":"登录验证码:{$var},请不要对非本人透露。","params":"13011112222,123456","report":"true"}
2019-01-28 16:16:35.937 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] ---> END HTTP (152-byte body)
2019-01-28 16:16:36.021 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] <--- HTTP/1.1 200 OK (82ms)
2019-01-28 16:16:36.021 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] access-control-allow-origin: *
2019-01-28 16:16:36.022 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] connection: keep-alive
2019-01-28 16:16:36.023 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] content-length: 109
2019-01-28 16:16:36.023 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] content-type: application/json; charset=utf-8
2019-01-28 16:16:36.024 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] date: Mon, 28 Jan 2019 08:16:36 GMT
2019-01-28 16:16:36.024 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] etag: W/"6d-XqhLoZB8r6IRF2Lb6CWoIVVNhIQ"
2019-01-28 16:16:36.025 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] x-content-type-options: nosniff
2019-01-28 16:16:36.026 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] x-powered-by: Express
2019-01-28 16:16:36.027 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send]
2019-01-28 16:16:36.030 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] {"code":"0","failNum":"0","successNum":"1","msgId":"19012516213625881","time":"20190125162136","errorMsg":""}
2019-01-28 16:16:36.030 DEBUG 8976 --- [ main] c.s.e.f.feign.mock.YunxunSmsFeignMock : [YunxunSmsFeignMock#send] <--- END HTTP (109-byte body)
2019-01-28 16:16:36.227 INFO 8976 --- [ main] c.s.e.f.aspect.FeignMockAspect : -----【com.sun.proxy.$Proxy95.send】---- 退出Mock模式... request: 【[{"msg":"登录验证码:{$var},请不要对非本人透露。","password":"XXXXXXX","report":"true","params":"13011112222,123456","account":"XXXXXXX"}]】, response: 【{"code":"0","failNum":"0","successNum":"1","msgId":"19012516213625881","time":"20190125162136","errorMsg":""}】
{"code":"0","failNum":"0","successNum":"1","msgId":"19012516213625881","time":"20190125162136","errorMsg":""}

四、结语:​

如果有什么需要改进的地方,或者不正确的地方,请在评论里面提出并指正。谢谢!

Demo详细代码,已经提交到Github,欢迎star

Demo地址: ​​https://github.com/Seifon/FeignStubMock

Feign挡板和Mock_feign

 


 

打印debug日志的配置:

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfig {

@Bean
public Logger.Level feignLoggerLevel() {
return feign.Logger.Level.FULL;
}
}


 

不才写了本使用Spring Cloud玩转微服务架构的书,书名是《Spring Cloud与Docker微服务架构实战》 - 周立,已于2017-01-12交稿。不少朋友想先看看源码,现将代码放出。

本次放出的代码:

  • 共计70+个DEMO
  • 覆盖Eureka、Ribbon、Feign、Hystrix、Zuul、Spring Cloud Config、Spring Cloud Bus、Spring Cloud Sleuth、Docker、Docker Compose等。

1-11章代码地址:

12-14章配套代码地址:

书目录

详见: ​​http://www.itmuch.com/advertisment/my-spring-book/​

版权说明

本文采用 CC BY 3.0 CN协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。如转载至微信公众号,请在文末添加作者公众号二维码。

关注我

博客:​​http://www.itmuch.com​

 

相关技能树:

第1章 基础知识 1

什么是微服务架构 1

-- 与单体系统的区别 1

-- 如何实施微服务 2

为什么选择Spring Cloud 6

Spring Cloud简介 7

版本说明 8

第2章 微服务构建:Spring Boot 11

框架简介 12

快速入门 13

-- 项目构建与解析 13

-- 实现RESTful API 17

配置详解 20

-- 配置文件 20

-- 自定义参数 22

-- 参数引用 22

-- 使用随机数 23

-- 命令行参数 23

-- 多环境配置 24

-- 加载顺序 25

监控与管理 26

-- 初识actuator 27

-- 原生端点 28

小结 38

第3章 服务治理:Spring Cloud Eureka 39

服务治理 39

-- Netflix Eureka 40

--搭建服务注册中心 41

--注册服务提供者 43

--高可用注册中心 46

--服务发现与消费 48

Eureka详解 51

--基础架构 52

--服务治理机制 52

--源码分析 56

配置详解 65

--服务注册类配置 65

--服务实例类配置 67

跨平台支持 71

第4章 客户端负载均衡:Spring Cloud Ribbon 73

客户端负载均衡 73

RestTemplate详解 75

-- GET请求 75

-- POST请求 77

-- PUT请求 79

-- DELETE请求 79

源码分析 80

-- 负载均衡器 91

-- 负载均衡策略 109

配置详解 123

--自动化配置 124

-- Camden版本对RibbonClient配置的优化 125

-- 参数配置 127

-- 与Eureka结合 127

重试机制 128

第5章 服务容错保护:Spring Cloud Hystrix 130

快速入门 131

原理分析 135

-- 工作流程 135

-- 断路器原理 144

-- 依赖隔离 148

使用详解 151

-- 创建请求命令 151

-- 定义服务降级 154

-- 异常处理 157

-- 命令名称、分组以及线程池划分 158

-- 请求缓存 159

-- 请求合并 166

属性详解 172

-- Command属性 174

-- collapser属性 184

-- threadPool属性 185

Hystrix仪表盘 187

Turbine集群监控 192

-- 构建监控聚合服务 192

-- 与消息代理结合 196

第6章 声明式服务调用:Spring Cloud Feign 199

快速入门 200

参数绑定 202

继承特性 205

Ribbon配置 209

全局配置 209

指定服务配置 209

重试机制 210

Hystrix配置 211

全局配置 211

禁用Hystrix 211

指定命令配置 212

服务降级配置 212

其他配置 214

第7章 API网关服务:Spring Cloud Zuul 217

快速入门 219

-- 构建网关 220

-- 请求路由 221

-- 请求过滤 223

路由详解 226

-- 传统路由配置 226

-- 服务路由配置 228

-- 服务路由的默认规则 229

-- 自定义路由映射规则 229

-- 路径匹配 230

-- 路由前缀 233

-- 本地跳转 234

-- Cookie与头信息 235

-- Hystrix和Ribbon支持 236

过滤器详解 238

-- 过滤器 238

-- 请求生命周期 239

-- 核心过滤器 240

-- 异常处理 244

-- 禁用过滤器 256

动态加载 257

-- 动态路由 257

-- 动态过滤器 261

第8章 分布式配置中心:Spring Cloud Config 267

快速入门 267

-- 构建配置中心 268

-- 配置规则详解 269

-- 客户端配置映射 272

服务端详解 274

-- 基础架构 274

-- Git配置仓库 276

-- SVN配置仓库 279

-- 本地仓库 279

-- 本地文件系统 279

-- 健康监测 280

-- 属性覆盖 281

-- 安全保护 281

-- 加密解密 282

-- 高可用配置 286

客户端详解 286

-- URI指定配置中心 287

-- 服务化配置中心 287

-- 失败快速响应与重试 290

-- 获取远程配置 292

-- 动态刷新配置 293

第9章 消息总线:Spring Cloud Bus 295

消息代理 295

RabbitMQ实现消息总线 296

-- 基本概念 297

-- 安装与使用 298

-- 快速入门 302

-- 整合Spring Cloud Bus 306

-- 原理分析 307

-- 指定刷新范围 308

-- 架构优化 309

-- RabbitMQ配置 310

Kafka实现消息总线 312

-- Kafka简介 312

-- 快速入门 313

--整合Spring Cloud Bus 315

-- Kafka配置 318

深入理解 318

-- 源码分析 320

-- 其他消息代理的支持 342

第10章 消息驱动的微服务:Spring Cloud Stream 344

快速入门 344

核心概念 349

-- 绑定器 350

-- 发布-订阅模式 351

-- 消费组 353

-- 消息分区 354

使用详解 355

-- 开启绑定功能 355

-- 绑定消息通道 356

-- 消息生产与消费 360

-- 响应式编程 366

-- 消费组与消息分区 368

-- 消息类型 370

绑定器详解 373

-- 绑定器SPI 373

-- 自动化配置 374

-- 多绑定器配置 374

-- RabbitMQ与Kafka绑定器 376

配置详解 376

-- 基础配置 377

-- 绑定通道配置 377

-- 绑定器配置 379

第11章 分布式服务跟踪:Spring Cloud Sleuth 386

快速入门 386

-- 准备工作 386

-- 实现跟踪 389

跟踪原理 390

抽样收集 392

与Logstash整合 394

与Zipkin整合 397

-- HTTP收集 398

-- 消息中间件收集 402

-- 收集原理 404

-- 数据存储 414

-- API接口 417

附录A Starter POMs 419

后记 421

 

1 微服务架构概述. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

1.1 单体应用架构存在的问题1

1.2 如何解决单体应用架构存在的问题3

1.3 什么是微服务3

1.4 微服务架构的优点与挑战5

1.4.1 微服务架构的优点5

1.4.2 微服务架构面临的挑战5

1.5 微服务设计原则6

1.6 如何实现微服务架构7

1.6.1 技术选型7

1.6.2 架构图及常用组件8

2 微服务开发框架——Spring Cloud . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

2.1 Spring Cloud 简介10

2.2 Spring Cloud 特点10

2.3 Spring Cloud 版本11

2.3.1 版本简介11

2.3.2 子项目一览12

2.3.3 Spring Cloud/Spring Boot 版本兼容性13

3 开始使用Spring Cloud 实战微服务. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

3.1 Spring Cloud 实战前提14

3.1.1 技术储备14

3.1.2 工具及软件版本15

3.2 服务提供者与服务消费者16

3.3 编写服务提供者16

3.3.1 手动编写项目17

3.3.2 使用Spring Initializr 快速创建Spring Boot 项目21

3.4 编写服务消费者23

3.5 为项目整合Spring Boot Actuator 25

3.6 硬编码有哪些问题27

4 微服务注册与发现. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

4.1 服务发现简介29

4.2 Eureka 简介31

4.3 Eureka 原理31

4.4 编写Eureka Server 33

4.5 将微服务注册到Eureka Server 上35

4.6 Eureka Server 的高可用36

4.6.1 将应用注册到Eureka Server 集群上38

4.7 为Eureka Server 添加用户认证39

4.7.1 将微服务注册到需认证的Eureka Server 40

4.8 理解Eureka 的元数据41

4.8.1 改造用户微服务41

4.8.2 改造电影微服务41

4.9 Eureka Server 的REST 端点43

4.9.1 示例45

4.9.2 注销微服务实例49

4.10 Eureka 的自我保护模式51

4.11 多网卡环境下的IP 选择52

4.11.1 忽略指定名称的网卡52

4.11.2 使用正则表达式,指定使用的网络地址52

4.11.3 只使用站点本地地址53

4.11.4 手动指定IP 地址53

4.12 Eureka 的健康检查53

5 使用Ribbon 实现客户端侧负载均衡. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

5.1 Ribbon 简介56

5.2 为服务消费者整合Ribbon 57

5.3 使用Java 代码自定义Ribbon 配置60

5.4 使用属性自定义Ribbon 配置63

5.5 脱离Eureka 使用Ribbon 64

6 使用Feign 实现声明式REST 调用. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66

6.1 Feign 简介67

6.2 为服务消费者整合Feign 67

6.3 自定义Feign 配置69

6.4 手动创建Feign 72

6.4.1 修改用户微服务72

6.4.2 修改电影微服务76

6.5 Feign 对继承的支持78

6.6 Feign 对压缩的支持79

6.7 Feign 的日志80

6.8 使用Feign 构造多参数请求82

6.8.1 GET 请求多参数的URL 82

6.8.2 POST 请求包含多个参数83

7 使用Hystrix 实现微服务的容错处理. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85

7.1 实现容错的手段85

7.1.1 雪崩效应85

7.1.2 如何容错86

7.2 使用Hystrix 实现容错88

7.2.1 Hystrix 简介88

7.2.2 通用方式整合Hystrix 89

7.2.3 Hystrix 断路器的状态监控与深入理解91

7.2.4 Hystrix 线程隔离策略与传播上下文93

7.2.5 Feign 使用Hystrix 96

7.3 Hystrix 的监控101

7.3.1 Feign 项目的Hystrix 监控102

7.4 使用Hystrix Dashboard 可视化监控数据103

7.5 使用Turbine 聚合监控数据105

7.5.1 Turbine 简介105

7.5.2 使用Turbine 监控多个微服务105

7.5.3 使用消息中间件收集数据108

8 使用Zuul 构建微服务网关. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113

8.1 为什么要使用微服务网关113

8.2 Zuul 简介115

8.3 编写Zuul 微服务网关115

8.4 Zuul 的路由端点118

8.5 Zuul 的路由配置详解119

8.6 Zuul 的安全与Header 122

8.6.1 敏感Header 的设置122

8.6.2 忽略Header 123

8.7 使用Zuul 上传文件124

8.7.1 编写文件上传微服务124

8.8 Zuul 的过滤器127

8.8.1 过滤器类型与请求生命周期127

8.8.2 编写Zuul 过滤器128

8.8.3 禁用Zuul 过滤器130

8.9 Zuul 的容错与回退130

8.9.1 为Zuul 添加回退131

8.10 Zuul 的高可用133

8.10.1 Zuul 客户端也注册到了Eureka Server 上133

8.10.2 Zuul 客户端未注册到Eureka Server 上133

8.11 使用Sidecar 整合非JVM 微服务134

8.11.1 编写Node.js 微服务135

8.11.2 编写Sidecar 136

8.11.3 Sidecar 的端点138

8.11.4 Sidecar 与Node.js 微服务分离部署139

8.11.5 Sidecar 原理分析139

9 使用Spring Cloud Config 统一管理微服务配置. . . . . . . . . . . . . . . . . . . . . . . 142

9.1 为什么要统一管理微服务配置142

9.2 Spring Cloud Config 简介143

9.3 编写Config Server 144

9.3.1 Config Server 的端点145

9.4 编写Config Client 147

9.5 Config Server 的Git 仓库配置详解149

9.6 Config Server 的健康状况指示器152

9.7 配置内容的加解密153

9.7.1 安装JCE 153

9.7.2 Config Server 的加解密端点153

9.7.3 对称加密153

9.7.4 存储加密的内容154

9.7.5 非对称加密155

9.8 使用/refresh 端点手动刷新配置155

9.9 使用Spring Cloud Bus 自动刷新配置157

9.9.1 Spring Cloud Bus 简介157

9.9.2 实现自动刷新158

9.9.3 局部刷新159

9.9.4 架构改进159

9.9.5 跟踪总线事件160

9.10 Spring Cloud Config 与Eureka 配合使用161

9.11 Spring Cloud Config 的用户认证162

9.11.1 Config Client 连接需用户认证的Config Server 163

9.12 Config Server 的高可用164

9.12.1 Git 仓库的高可用164

9.12.2 RabbitMQ 的高可用164

9.12.3 Config Server 自身的高可用165

10 使用Spring Cloud Sleuth 实现微服务跟踪. . . . . . . . . . . . . . . . . . . . . . . . . . . . 167

10.1 为什么要实现微服务跟踪167

10.2 Spring Cloud Sleuth 简介168

10.3 整合Spring Cloud Sleuth 170

10.4 Spring Cloud Sleuth 与ELK 配合使用172

10.5 Spring Cloud Sleuth 与Zipkin 配合使用176

10.5.1 Zipkin 简介176

10.5.2 编写Zipkin Server 176

10.5.3 微服务整合Zipkin 178

10.5.4 使用消息中间件收集数据181

10.5.5 存储跟踪数据183

11 Spring Cloud 常见问题与总结. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186

11.1 Eureka 常见问题186

11.1.1 Eureka 注册服务慢186

11.1.2 已停止的微服务节点注销慢或不注销187

11.1.3 如何自定义微服务的Instance ID 188

11.1.4 Eureka 的UNKNOWN 问题总结与解决189

11.2 Hystrix/Feign 整合Hystrix 后首次请求失败190

11.2.1 原因分析191

11.2.2 解决方案191

11.3 Turbine 聚合的数据不完整191

11.3.1 解决方案192

11.4 Spring Cloud 各组件配置属性193

11.4.1 Spring Cloud 的配置193

11.4.2 原生配置193

11.5 Spring Cloud 定位问题思路总结194

12 Docker 入门. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197

12.1 Docker 简介197

12.2 Docker 的架构197

12.3 安装Docker 199

12.3.1 系统要求199

12.3.2 移除非官方软件包199

12.3.3 设置Yum 源199

12.3.4 安装Dokcer 200

12.3.5 卸载Docker 201

12.4 配置镜像加速器201

12.5 Docker 常用命令202

12.5.1 Docker 镜像常用命令202

12.5.2 Docker 容器常用命令204

13 将微服务运行在Docker 上. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209

13.1 使用Dockerfile 构建Docker 镜像209

13.1.1 Dockerfile 常用指令210

13.1.2 使用Dockerfile 构建镜像215

13.2 使用Docker Registry 管理Docker 镜像217

13.2.1 使用Docker Hub 管理镜像217

13.2.2 使用私有仓库管理镜像219

13.3 使用Maven 插件构建Docker 镜像220

13.3.1 快速入门221

13.3.2 插件读取Dockerfile 进行构建222

13.3.3 将插件绑定在某个phase 执行223

13.3.4 推送镜像224

13.4 常见问题与总结226

14 使用Docker Compose 编排微服务. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227

14.1 Docker Compose 简介227

14.2 安装Docker Compose 227

14.2.1 安装Compose 228

14.2.2 安装Compose 命令补全工具228

14.3 Docker Compose 快速入门229

14.3.1 基本步骤229

14.3.2 入门示例229

14.3.3 工程、服务、容器230

14.4 docker-compose.yml 常用命令230

14.4.1 build 230

14.4.2 command 231

14.4.3 dns 231

14.4.4 dns_search 231

14.4.5 environment 231

14.4.6 env_file 232

14.4.7 expose 232

14.4.8 external_links 232

14.4.9 image 232

14.4.10 links 232

14.4.11 networks 233

14.4.12 network_mode 233

14.4.13 ports 233

14.4.14 volumes 233

14.4.15 volumes_from 234

14.5 docker-compose 常用命令234

14.5.1 build 234

14.5.2 help 235

14.5.3 kill 235

14.5.4 logs 235

14.5.5 port 235

14.5.6 ps 235

14.5.7 pull 235

14.5.8 rm 236

14.5.9 run 236

14.5.10 scale 236

14.5.11 start 236

14.5.12 stop 236

14.5.13 up 236

14.6 Docker Compose 网络设置237

14.6.1 基本概念237

14.6.2 更新容器237

14.6.3 links 238

14.6.4 指定自定义网络238

14.6.5 配置默认网络239

14.6.6 使用已存在的网络239

14.7 综合实战:使用Docker Comose 编排Spring Cloud 微服务240

14.7.1 编排Spring Cloud 微服务240

14.7.2 编排高可用的Eureka Server 243

14.7.3 编排高可用Spring Cloud 微服务集群及动态伸缩245

14.8 常见问题与总结247