SpringSecurity实战笔记之RESTful(包含过滤器、拦截器、统一异常处理等)
=================================RESTful========================================
一、JsonPath
1、github:https://github.com/json-path/JsonPath
二、@JsonView使用步骤(用于解决同一个对象在不同的接口返回的字段不同的场景)
1、使用接口来声明多个视图
2、在值对象的get方法上指定视图
3、在Controller方法上指定视图
package com.imooc.dto; import com.fasterxml.jackson.annotation.JsonView; public class User { //1、使用接口来声明多个视图 public interface UserSimpleView{}; public interface UserDetailView extends UserSimpleView{}; private String username; private String password; public User() {} public User(String username, String password) { this.username = username; this.password = password; }
//2、在值对象的get方法上指定视图
@JsonView(UserSimpleView.class) public String getUsername() { return username; } public void setUsername(String username) { this.username = username; }
//2、在值对象的get方法上指定视图
@JsonView(UserDetailView.class) public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
3、在Controller方法上指定视图
@JsonView(User.UserSimpleView.class)
返回对象为User或User的集合才有效
三、@RequestBody和@RequestParam区别
1、@RequestParam 用来处理Content-Type: 为 application/x-www-form-urlencoded编码的内容。
2、@RequestBody 处理HttpEntity传递过来的数据,一般用来处理非Content-Type: application/x-www-form-urlencoded编码格式的数据。
四、日期类型参数处理
1、以时间戳的形式保存并传输,具体显示格式由前台来控制
五、校验
1、类属性上使用@NotNull之类注解,NotBlank比NotEmpty更严格
2、controller方法上添加@Valid注解
3、参数中添加 BindingResult,请求就可以进行方法体中,错误信息会保存在BindingResult对象中,可以从中取出做日志记录
@PostMapping public User create(@Valid @RequestBody User user, BindingResult errors){ if(errors.hasErrors()){ errors.getAllErrors().stream().forEach(error -> System.out.println(error.getDefaultMessage())); } System.out.println(ReflectionToStringBuilder.toString(user,ToStringStyle.MULTI_LINE_STYLE)); user.setId("1"); return user; }
4、常用的验证注解Hibernate Validator
@NotNull //值不能为空
@Null //值必须为空
@Pattern(regex=) //字符串必须匹配正则表达式
@Size(min=,max=) //集合的元素数量必须在min与max之间
@CreditCarNumber(ignoreNinDigitCharacters=) //字符串必须是信用卡号(近美国的标准验证)
@Email //字符串必须是Email
@Length(min=,max=) //检查字符串的长度
@NotBlank //字符串去空格后,必须有字符
@NotEmpty //字符串不能为空,集合有元素
@Range(min=,max=) //数字必须大于等于min,小于等于max
@SafeHtml //字符串是安全的html
@URL //字符串是合法的URL
@AssertFalse //boolean必须是false
@AssertTrue //boolean必须是true
@DecimalMax(value=,inclusive=) //值必须小于等于(inclusive=true)/小于(inclusive=false)value属性指定的值。可以注解在字符串类型的属性上。
@DecimalMim(value=,inclusive=) //值必须大于等于(inclusive=true)/大于(inclusive=false)value属性指定的值。可以注解在字符串类型的属性上。
@Max(value=) //值必须小于等于value属性指定的值。只能注解在数字类型的属性上。
@Mim(value=) //值必须大于等于value属性指定的值。只能注解在数字类型的属性上。
@Digits(integer=,fraction=) //数字格式检查。integer指定整数部分的最大长度。fraction指定小数部分最大长度。
@Future //值必须是未来的日期
@Past //值必须是过去的日期
5、自定义验证注解
5.1、注解类
package com.imooc.validator; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD,ElementType.FIELD}) //定义注解可以标注在方法及字段上面 @Retention(RetentionPolicy.RUNTIME) //运行时注解 @Constraint(validatedBy = MyConstraintValidator.class) //指定使用的类进行逻辑判断 public @interface MyConstraint { String message() ; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
5.2、实现类
package com.imooc.validator; //import com.imooc.Service.HelloService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class MyConstraintValidator implements ConstraintValidator<MyConstraint,Object>{ //可以注入spring容器中的bean //@Autowired //private HelloService helloService; @Override public void initialize(MyConstraint myConstraint) { System.out.println("my validator init"); } @Override public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { //helloService.greeting("anquing"); System.out.println("value:"+value); return false; } }
六、异常处理机制
1、spring boot 默认处理机制(BasicErrorController):网页中返回html,接口中返回json
<html> <body> <h1>Whitelabel Error Page</h1> <p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p> <div id='created'>Wed Nov 08 18:18:37 CST 2017</div> <div>There was an unexpected error (type=Not Found, status=404).</div> <div>No message available</div> </body> </html> { "timestamp": 1510135862443, "status": 404, "error": "Not Found", "message": "No message available", "path": "/userdd" }
2、自定义html异常页面
在resources目录下创建resources/error两个文件夹,在error文件夹下创建404.html({status}.html)文件,当产生相应的异常时就会显示对应的页面。
3、自定义接口异常
3.1、创建异常类,继承RunTimeException
package com.imooc.exception; import java.io.Serializable; public class UserNotExistException extends RuntimeException{ private static final long serialVersionUID = -6112780192479692859L; private String id; public String getId() { return id; } public void setId(String id) { this.id = id; } public UserNotExistException(String id){ super("user not exist"); this.id = id; } }
3.2、创建ControllerExceptionHandler类
package com.imooc.web.controller; import com.imooc.exception.UserNotExistException; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import java.util.HashMap; import java.util.Map; @ControllerAdvice public class ControllerExceptionHandler { @ExceptionHandler(UserNotExistException.class) @ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public Map<String,Object> handlerUserNotExistException(UserNotExistException ex){ Map<String,Object> result = new HashMap<>(); result.put("id",ex.getId()); result.put("message",ex.getMessage()); return result; } }
七、拦截(过滤器>拦截器>切片>controller>切片>ControllerAdvice(有异常再会经过)>拦截器>过滤器)
1、过滤器(Filter)优点:能拿到http请求request,http响应response;缺点:无法拿到被过滤的类、方法信息
1.1、自定义(可以直接添加注解 @Component)
package com.imooc.web.controller.filter; import org.springframework.stereotype.Component; import javax.servlet.*; import java.io.IOException; import java.util.Date; @Component //将filter加入容器中,自动会添加到过滤器链上(对所有的请求起作用) public class TimeFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("time filter init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("time filter start"); Long start = new Date().getTime(); filterChain.doFilter(servletRequest,servletResponse); Long finish = new Date().getTime(); System.out.println("time filter:"+(finish - start)); System.out.println("time filter finish"); } @Override public void destroy() { System.out.println("time filter destroy"); } }
1.2、通过Configuration将第三方没有@Component的filter加入到过滤器链上
package com.imooc.web.config; import com.imooc.web.filter.TimeFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.ArrayList; import java.util.List; @Configuration public class WebConfig { @Bean public FilterRegistrationBean timeFilter(){ FilterRegistrationBean registrationBean = new FilterRegistrationBean(); TimeFilter timeFilter = new TimeFilter(); registrationBean.setFilter(timeFilter); List<String> urls = new ArrayList<>(); urls.add("/*");//添加需要经过过滤器的url(自定义) registrationBean.setUrlPatterns(urls); return registrationBean; } }
2、拦截器(Interceptor)
spring有提供,如果发生异常,处理的优先级为@ControllerAdvice到afterCompletion再到spring boot 默认处理机制(BasicErrorController)
优点:能拿到http请求request,http响应response,被过滤的类、方法信息;缺点:无法拿到方法参数的值
2.1、定义拦截器
package com.imooc.web.interceptor; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Date; @Component public class TimeInterceptor implements HandlerInterceptor { /** * 控制器方法调用之前,这个方法被调用 * @param httpServletRequest * @param httpServletResponse * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception { System.out.println("time interceptor preHandle"); //将类名与方法名打印 System.out.println("className:"+((HandlerMethod)handler).getBean().getClass().getName()); System.out.println("methodName:"+((HandlerMethod)handler).getMethod().getName()); //保存开始时间 httpServletRequest.setAttribute("startTime",new Date().getTime()); //如果返回false,就不会调用Controller中的方法 return true; } /** * 控制器方法顺利执行之后,这个方法被调用 * @param httpServletRequest * @param httpServletResponse * @param handler * @param modelAndView * @throws Exception */ @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("time interceptor postHandle"); //总耗时 Long startTime = (Long)httpServletRequest.getAttribute("startTime"); System.out.println("time interceptor 耗时:"+(new Date().getTime()-startTime)); } /** * 控制器方法执行(包括抛出异常)之后,这个方法被调用 * @param httpServletRequest * @param httpServletResponse * @param handler * @param e * @throws Exception */ @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, Exception e) throws Exception { System.out.println("time interceptor afterCompletion"); //总耗时 Long startTime = (Long)httpServletRequest.getAttribute("startTime"); System.out.println("time interceptor 耗时:"+(new Date().getTime()-startTime)); System.out.println("time interceptor ex is:"+e); } }
2.2、通过Configuration注册
package com.imooc.web.config; import com.imooc.web.interceptor.TimeInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class WebConfig extends WebMvcConfigurerAdapter{ @Autowired private TimeInterceptor timeInterceptor; /** * 同步请求方法拦截器注册 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { // 多个拦截器组成一个拦截器链 // addPathPatterns 用于添加拦截规则 // excludePathPatterns 用户排除拦截 registry.addInterceptor(timeInterceptor).addPathPatterns("/**"); } //以下是扩展内容 @Autowired private TimeDeferredResultProcessingInterceptor timeDeferredResultProcessingInterceptor; /** * 异步请求方法拦截器注册 * @param configurer */ @Override public void configureAsyncSupport(AsyncSupportConfigurer configurer) { configurer.registerDeferredResultInterceptors(timeDeferredResultProcessingInterceptor); } }
3、切片(Aspect)
优点:可以拿到方法参数的值,但无法拿到http请求request,http响应response
3.1、execution表达式:https://docs.spring.io/spring/docs/4.3.12.RELEASE/spring-framework-reference/htmlsingle/#aop-pointcuts-examples
3.2、代码实现
package com.imooc.web.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Date; /** * 切片类(类下有切入点,增强) */ @Aspect @Component public class TimeAspect { //切入点(注解):1、在哪些方法上起作用; @Pointcut("execution(* com.imooc.web.controller.UserController.*(..))") public void pointcut(){} // 2、在什么时候起作用 // @Before("pointcut()")//执行方法之前,也可以不需要定义pointcut,直接将execution表达式写在括号中@Before("execution(* com.imooc.web.controller.UserController.*(..))") // @After("pointcut()")//成功执行方法之后 // @AfterThrowing("pointcut()")//执行方法抛出异常之后 @Around("pointcut()")//包含了以上方法 public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {//增强(方法):起作用时执行的业务逻辑 System.out.println("time aspect start"); //取得方法中的参数 Object[] args = pjp.getArgs(); for (Object arg : args){ System.out.println("arg is : "+arg); } Long start = new Date().getTime(); //执行被拦截了的方法,与过滤器 filterChain.doFilter(servletRequest,servletResponse);作用相似 Object object = pjp.proceed();//object就是方法的返回值 Long finish = new Date().getTime(); System.out.println("time aspect 耗时:"+(finish - start)); System.out.println("time aspect end"); return object; } }
八、文件上传与下载
1、文件上传流程(单个上传)
-前端上传文件到temp文件夹,服务端返回文件名给前端;
-前端确认保存时,将文件名提交上来,服务端根据文件名,将temp文件转移到正式文件夹;
-将文件名以","隔开,保存在数据库中;
2、代码实现
package com.imooc.web.controller; import com.imooc.dto.FileInfo; import org.apache.commons.io.IOUtils; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.util.Date; /** * 文件上传下载用例 */ @RestController @RequestMapping("/file") public class FileController { String folder = "D:\\document\\ideagit\\imooc-security\\imooc-security-demo\\src\\main\\java\\com\\imooc\\web\\controller"; /** * 文件上传 * @param file * @return * @throws IOException */ @PostMapping public FileInfo upload(MultipartFile file)throws IOException{ System.out.println("name:"+file.getName()); System.out.println("OriginalFilename:"+file.getOriginalFilename()); System.out.println("Size:"+file.getSize()); String originalFilename = file.getOriginalFilename(); String suffix = originalFilename.substring(originalFilename.lastIndexOf(".")); File localFile = new File(folder,new Date().getTime()+suffix); file.transferTo(localFile); return new FileInfo(localFile.getAbsolutePath()); } /** * 文件下载 * @param id 文件名,不包括后缀 * @param response * @param request */ @GetMapping("/{id}") public void download(@PathVariable(value = "id") String id, HttpServletResponse response, HttpServletRequest request){ System.out.println(id); //将流的操作放在try()中,使用完后会自动关闭流 try(InputStream inputStream = new FileInputStream(new File(folder, id+".txt")); OutputStream outputStream = response.getOutputStream();) { response.setContentType("application/x-download"); response.addHeader("Content-Disposition","attachment;filename=test.txt"); IOUtils.copy(inputStream,outputStream); outputStream.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
九、异步处理Rest服务(好处:主线程会立马释放,提高服务的吞吐量)
1、使用Runnable异步处理Rest服务(不足:副线程必须由主线唤起的,并写在主线程代码当中,无法满足复杂的场景)
/** * 异步的处理方式 * @return * @throws InterruptedException */ @GetMapping("/asynch") public Callable<String> orderAsynch() throws InterruptedException { logger.info("主线程开始"); Callable<String> result = new Callable<String>(){ @Override public String call() throws Exception { logger.info("副线程开始"); Thread.sleep(3000); //睡眠了3秒,但主线程不受影响 logger.info("副线程返回"); return "success"; } }; logger.info("主线程返回"); return result; }
打印结果:
2017-11-09 14:56:32.660 INFO 3088 --- [nio-8080-exec-3] com.imooc.web.async.AsyncController : 主线程开始
2017-11-09 14:56:32.661 INFO 3088 --- [nio-8080-exec-3] com.imooc.web.async.AsyncController : 主线程返回
2017-11-09 14:56:32.666 INFO 3088 --- [ MvcAsync1] com.imooc.web.async.AsyncController : 副线程开始
2017-11-09 14:56:35.667 INFO 3088 --- [ MvcAsync1] com.imooc.web.async.AsyncController : 副线程返回
2、使用DeferredResult异步处理Rest服务
2.1、下单场景描述(有两个应用分别为A(与客户端通信)、B(处理订单业务),它们之间通过消息队列进行通讯):
Http通过线程1请求应用 A,A将业务放入消息队列,线程1释放;B监听到消息队列有新业务要处理,就进行处理(线程3),处理完成后将处理结果放入消息队列;应用A中的线程2监听到有结果了,就将结果返回结客户端。
2.2、DeferredResult的作用可以使线程2与线程1能够交互,从而正确的返回给客户端
2.3、模拟消息队列类
package com.imooc.web.async; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * 模拟消息队列类(线程3) */ @Component public class MockQueue { private Logger logger = LoggerFactory.getLogger(MockQueue.class); /** * 下单消息 */ private String placeOrder; /** * 下单完成消息 */ private String completeOrder; public String getPlaceOrder() { return placeOrder; } public void setPlaceOrder(String placeOrder){ //这里是由于要模拟是另外一个应用在处理,所以为单独线程 new Thread(()->{ logger.info("接到下单请求,"+placeOrder); try { Thread.sleep(3000); //假设要3秒时间 } catch (InterruptedException e) { e.printStackTrace(); } this.completeOrder = placeOrder; logger.info("下单请求处理完毕,"+placeOrder); }).start(); } public String getCompleteOrder() { return completeOrder; } public void setCompleteOrder(String completeOrder) { this.completeOrder = completeOrder; } }
2.4、DeferredResultHolder类
package com.imooc.web.async; import org.springframework.stereotype.Component; import org.springframework.web.context.request.async.DeferredResult; import java.util.HashMap; import java.util.Map; @Component public class DeferredResultHolder { private Map<String,DeferredResult<String>> map = new HashMap<>(); public Map<String, DeferredResult<String>> getMap() { return map; } public void setMap(Map<String, DeferredResult<String>> map) { this.map = map; } }
2.5、队列的监听器
package com.imooc.web.async; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; /** * 队列监听器(线程3) */ @Component public class QueueListener implements ApplicationListener<ContextRefreshedEvent>{ private Logger logger = LoggerFactory.getLogger(QueueListener.class); @Autowired private MockQueue mockQueue; @Autowired private DeferredResultHolder deferredResultHolder; /** * 容器启动事件 * @param contextRefreshedEvent */ @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { //当容器启动后,一直监听mockQueue中completeOrder是否有值,要使用单独线程来轮询 new Thread(()->{ while(true){ if(StringUtils.isNotBlank(mockQueue.getCompleteOrder())){ String orderNumber = mockQueue.getCompleteOrder(); logger.info("返回订单处理结果:"+orderNumber); deferredResultHolder.getMap().get(orderNumber).setResult("place order success"); mockQueue.setCompleteOrder(null); }else{ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
2.6、controller方法
package com.imooc.web.async; import org.apache.commons.lang.RandomStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Callable; @RestController @RequestMapping("/order") public class AsyncController { private Logger logger = LoggerFactory.getLogger(AsyncController.class); @Autowired private MockQueue mockQueue; @Autowired private DeferredResultHolder deferredResultHolder; /** * DeferredResult异步处理方式(线程1) * @return * @throws InterruptedException */ @GetMapping("/deferredResultAsynch") public DeferredResult<String> deferredResultAsynch() throws InterruptedException { logger.info("主线程开始"); String orderNumber = RandomStringUtils.randomNumeric(8); mockQueue.setPlaceOrder(orderNumber); //由于setPlaceOrder方法是启用新的线程进行处理,所以不用等待处理完成就可以往下执行 DeferredResult<String> result = new DeferredResult<>(); deferredResultHolder.getMap().put(orderNumber,result); logger.info("主线程返回"); return result; } }
2.7、执行结果
2017-11-09 16:06:49.135 INFO 1836 --- [nio-8080-exec-5] com.imooc.web.async.AsyncController : 主线程开始(线程1) 2017-11-09 16:06:49.135 INFO 1836 --- [nio-8080-exec-5] com.imooc.web.async.AsyncController : 主线程返回(线程1) 2017-11-09 16:06:49.135 INFO 1836 --- [ Thread-30] com.imooc.web.async.MockQueue : 接到下单请求,25860084(线程3) 2017-11-09 16:06:52.136 INFO 1836 --- [ Thread-30] com.imooc.web.async.MockQueue : 下单请求处理完毕,25860084(线程3) 2017-11-09 16:06:52.214 INFO 1836 --- [ Thread-17] com.imooc.web.async.QueueListener : 返回订单处理结果:25860084(线程2)
十、使用Swagger自动生成html文档
1、依赖
<!-- 扫描程序生成文档数据 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.7.0</version> </dependency> <!-- 将文档数据生成最终可视化的界面 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.7.0</version> </dependency>
2、再启动类加注解@EnableSwagger2
3、访问页面:http://localhost:8080/swagger-ui.htm
4、常用注解
4.1、针对方法:@ApiOperation(value="用户查询服务")//方法描述
4.2、针对参数:
4.2.1、方法参数封装在对象中的,在对象中对应的属性上添加
@ApiModelProperty(value="用户名")//参数描述
4.2.2、方法参数直接在方法中的,在参数前添加
@ApiParam("用户id")
十一、使用WireMock快速伪造RESTful服务
1、下载启动
官网:http://wiremock.org/
在http://wiremock.org/docs/running-standalone/页面下载downloaded the standalone JAR
启动:$ java -jar wiremock-standalone-2.10.1.jar --port=8082
2、与spring boot 整合
2.1、依赖
<!-- 使用WireMock快速伪造RESTful服务 --> <dependency> <groupId>com.github.tomakehurst</groupId> <artifactId>wiremock</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> </dependency>
2.2、客户端类
package com.imooc.wiremock; import com.github.tomakehurst.wiremock.client.WireMock; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.springframework.core.io.ClassPathResource; import java.io.IOException; public class MockServer { public static void main(String[] args) throws IOException { WireMock.configureFor("localhost",8082); WireMock.removeAllMappings();//每次启动时,将原来的配置清除 mock("/order/1","01"); mock("/order/2","02"); } private static void mock(String url,String fileName)throws IOException{ //将返回内容写在txt文档中 String filePath = "mock/response/"+fileName+".txt"; ClassPathResource resource = new ClassPathResource(filePath); String content = StringUtils.join(FileUtils.readLines(resource.getFile(), "UTF-8").toArray(), "\n"); WireMock.stubFor( WireMock.get(WireMock.urlPathEqualTo(url)) .willReturn(WireMock.aResponse().withBody(content).withStatus(200)) ); } }
2.3、接口地址 http://localhost:8082/order/1