SpringBoot 请求复制
为什么要做请求复制?
业务场景:微信公众号限制申请个数,现在一个公众号要是 dev、int、uat、prod 环境公用的,因为微信公众号只能配置一个回调地址,当有微信公众号回调场景时,只能在让微信公众号回调到 prod 环境,再由prod 环境把请求复制后转到其他环境。
1 获取运行环境
1.1 测试代码
编写一个类,从Spring 上下文中,读取ActiveProfiles
@Component public class SpringContextHolder implements ApplicationContextAware, DisposableBean { private static ApplicationContext applicationContext = null; public static ApplicationContext getApplicationContext() { return applicationContext; } @Override public void destroy() throws Exception { applicationContext = null; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { SpringContextHolder.applicationContext = applicationContext; } /** * 获取当前环境 */ public static String getActiveProfile() { return applicationContext.getEnvironment().getActiveProfiles()[0]; } }
启动类
@SpringBootApplication public class RunApplication { public static void main(String[] args) { SpringApplication.run(RunApplication.class, args); } }
1.2 设置Active Profiles
要完成上面的业务需求,我们首先要知道当前的运行环境。提前是我们要设置 Active Profiles 参数,这样才能在 SpringContextHolder 类中的 getActiveProfile() 方法来获取到。
1.2.1 Idea 设置 Active Profiles 方式
启动 RunApplication 时,运行日志如下:
2022-05-20 12:19:33.261 INFO 7204 --- [ main] com.**.RunApplication : The following profiles are active: dev
1.2.2 JVM启动命令行方式
在使用JVM命令启动时候,可以添加如下命令来设置Active Profiles:-Dspring.profiles.active=dev
如果还是在Idea启动是测试,可以在 Environment 设置下里添加JVM命令。
启动 RunApplication 时,运行日志如下:
2022-05-20 12:19:33.261 INFO 7204 --- [ main] com.**.RunApplication : The following profiles are active: dev
1.2.3 测试用例设置 Active Profiles
如果使用SpringBootTest方式测试我们的代码,需要设置Active Profiles,可以使用 @ActiveProfiles 注解进行设置。
@SpringBootTest @ActiveProfiles(value = "dev") @RunWith(SpringRunner.class) public class SpringServiceTest { @Test public void test() { String activeProfile = SpringContextHolder.getActiveProfile(); System.out.println("activeProfile == " + activeProfile); } }
测试用例运行日志如下:
2022-05-20 17:26:14.773 INFO 936 --- [ main] com.**.test.SpringServiceTest : The following profiles are active: dev
......
activeProfile == dev
2 请求转发的拦截器
2.1 定义拦截器
我们使用AOP的方式,定义一个Intercepter,需要实现 HandlerInterceptor 接口。用来复制请求的参数,并且转发请求到其他环境的地址去。
@Component public class WeChatRequestInterceptor implements HandlerInterceptor { public static ThreadLocal
2.2 转换工具类
因为 InputStream 读取一次后就会被清除,所以我们要把InputStream 转换成 String 保存起来,用来下次使用。
String 转换成 InputStream 可以使用这行代码:InputStream inputStream = new ByteArrayInputStream(requestStr.getBytes());
public class IoUtils { /** * InputStream 转换成 String */ static public String toString(InputStream input, String encoding) { try { return (null == encoding) ? toString(new InputStreamReader(input, "UTF-8")) : toString(new InputStreamReader(input, encoding)); } catch (Exception e) { return StringUtils.EMPTY; } } static public String toString(Reader reader) throws IOException { CharArrayWriter sw = new CharArrayWriter(); copy(reader, sw); return sw.toString(); } static public long copy(Reader input, Writer output) throws IOException { char[] buffer = new char[1 << 12]; long count = 0; for (int n = 0; (n = input.read(buffer)) >= 0; ) { output.write(buffer, 0, n); count += n; } return count; } static public long copy(InputStream input, OutputStream output) throws IOException { byte[] buffer = new byte[1024]; int bytesRead; int totalBytes = 0; while ((bytesRead = input.read(buffer)) != -1) { output.write(buffer, 0, bytesRead); totalBytes += bytesRead; } return totalBytes; } }
2.3 配置拦截器
我们需要一个 SpringMVC 配置类,配置起来这个拦截器,声明要拦截的请求地址。
测试代码如下:
@Configuration public class WebMvcConfiguration implements WebMvcConfigurer { @Autowired private WeChatRequestInterceptor weChatRequestInterceptor; /** * 自定义拦截器 */ public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(weChatRequestInterceptor).addPathPatterns("/wechat/callback"); } }