在項目開發中,會有調用第三方接口的場景。當開發時,對方不肯意提供測試服務器給咱們調用,或者有的接口會按調用次數進行計費。當聯調時,第三方的測試服務器也可能會出現不穩定,若是他們的服務掛了,咱們就一直等着服務恢復,那麼這就至關影響效率了。若是咱們在開發時,就定義一個擋板或者mock服務,在發起調用時,不直接調到第三方接口,而是調到咱們本身的擋板代碼或者mock服務,這樣就能夠避免這些問題了。java
優點:
Demo詳細代碼,已經提交到Github,歡迎star
Demo地址: https://github.com/Seifon/Fei...node
首先,咱們寫一個Feign客戶端接口,正常調用第三方接口:git
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客戶端致使啓動報錯。
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)); } }
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":""}
此時,咱們能夠根據日誌,看到請求的地址也是第三方的urlgithub
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數據web
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容器
sms: url: 'http://smssh1.253.com' #yunxun:表明第三方系統名稱,sms:表明業務名稱,mode:表明Stub模式,url:表明mock服務地址 feign-stub: yunxun: sms: mode: 'stub'
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; } }
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":""}
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方式去完成咱們的功能。spring
參看官網:http://nodejs.cn/
npm install -g mock-json-server
{ "/msg/variable/json": { "post": { "code":"0", "failNum":"0", "successNum":"1", "msgId":"19012516213625881", "time":"20190125162136", "errorMsg":"" } } }
mock-json-server {path}/data.json --port=1240 {path}替換爲存放data.json的絕對路徑
JSON Server running at http://localhost:1240/
mock-json-server具體使用文檔,請參考: https://www.npmjs.com/package...
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容器apache
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"
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; } }
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":""}
說明:此時咱們根據日誌,會發現feign調用的url已經變爲咱們的Mock服務地址了。同理,若是要返回失敗結果,只須要修改data.json文件,再次調用後,便可獲得咱們想要的結果了。npm
若是有什麼須要改進的地方,或者不正確的地方,請在評論裏面提出並指正。謝謝!json
Demo詳細代碼,已經提交到Github,歡迎star
Demo地址: https://github.com/Seifon/Fei...服務器
項目結構,如圖: