背景

上篇文章 记录多我的项目共用一个公众号逻辑批改, 实现了多个我的项目共用一个公众号。
然而也存在几点问题,比方:

  • 两头服务器拦挡了微信的申请,尽管不便了我的项目不再须要写微信受权的代码,但如果当前须要再拓展新的微信申请,就须要再去两头服务器写api
  • 须要搭建前台,手动保护各个我的项目的地址和key。

咱们能够让两头服务器只是简略地转发申请,不再拦挡,解决第一点问题。

同时利用 服务发现与注册 解决第二个问题。

用服务注册的益处就是,咱们不须要再手动地保护我的项目信息、各个我的项目的ip,只须要让新来的我的项目向服务中心注册一下就好了。

服务发现与注册

服务注册是指向服务注册核心注册一个服务实例,服务提供者将本人的服务信息(如IP地址等)告知服务注册核心。

服务发现是指当服务消费者须要生产另外一个服务时, 服务注册核心可能告知服务消费者它所要生产服务的实例信息(如服务名、IP 地址等)。

通常状况下,一个服务既是服务提供者,也是服务消费者。

所以,如下图形式,每个我的项目都向注册核心注册,就不须要建表保护各个我的项目的ip地址了。

常见的注册核心

可能实现服务注册的组件很多,比方ZooKeeper、Eureka、Consul、Nacos等。

这里选用比拟常见的 Eureka。

Eureka

Eureka是Netflix开发的服务发现框架,SpringCloud将它集成在本人的子项目spring-cloud-netflix中, 实现SpringCloud的服务发现性能。

上图简要形容了Eureka的根本架构,由3个角色组成:

  1. Eureka Server 提供服务注册和发现
  2. Service Provider 服务提供方 将本身服务注册到Eureka,从而使服务生产方可能找到
  3. Service Consumer 服务生产方 从Eureka获取注册服务列表,从而可能生产服务

Eureka基本概念

Register——服务注册

当 Eureka Client 向 Eureka Server 注册时,Eureka Client 提供本身的元数据,比方 IP 地址、 端口、运行状况指标的 Url、主页地址等信息。

Renew——服务续约

  Eureka Client 在默认的状况下会每隔 30 秒发送一次心跳来进行服务续约。通过服务续约 来告知 Eureka Server 该 Eureka Client 依然可用,没有呈现故障。失常状况下,如果 Eureka Server 在 90 秒内没有收到 Eureka Client 的心跳,Eureka Server 会将 Eureka Client 实例从注册列表中删除。留神:官网倡议不要更改服务续约的间隔时间。

Eviction——服务剔除

在默认状况下(当然咱们能够批改) ,当 Eureka Client 间断 90 秒没有向 Eureka Server 发送服务续约(即心跳) 时,Eureka Server 会将该服务实例从服务注册列表删除,即服务剔除。

Fetch Registries——获取服务注册列表信息

   
Eureka Client 从 Eureka Server 获取服务注册表信息,并将其缓存在本地。Eureka Client 会应用服务注册列表信息查找其余服务的信息,从而进行近程调用。该注册列表信息定时(每 30 秒)更新一次,每次返回注册列表信息可能与 Eureka Client 的缓存信息不同,Eureka Client 会本人解决这些信息。如果因为某种原因导致注册列表信息不能及时匹配,Eureka Client 会从新获取整个注册表信息。

上面就来用Eureka实现服务的注册与发现

角色如下

注册核心

1.引入依赖

<properties>        <java.version>1.8</java.version>        <spring-cloud.version>2021.0.5</spring-cloud.version>    </properties>    <dependencies>        <dependency>            <groupId>org.springframework.cloud</groupId>            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>            <version>2.2.10.RELEASE</version>        </dependency><dependencyManagement>        <dependencies>            <dependency>                <groupId>org.springframework.cloud</groupId>                <artifactId>spring-cloud-dependencies</artifactId>                <version>2021.0.5</version>                <type>pom</type>                <scope>import</scope>            </dependency>        </dependencies>    </dependencyManagement>

这里有个比拟深的坑,就是spring cloud的版本肯定要和spring boot的版本绝对应.

否则就会报异样

java.lang.ClassNotFoundException: org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata

首先 pom.xml 外面是否有 dependencyManagement 版本治理的设置,因为这块是会辨认并加载所须要的依赖版本,比方我要加载 spring-cloud-starter-netflix-eureka-client ,首先确定好你的 SpringBoot 版本是否兼容依赖的 SpringCloud 版本。

其次是否设置了 spring-cloud.version ,接着确认是否设置了 dependencyManagement 上面的 spring-cloud-dependencies 依赖,最初确认好要加载的 spring-cloud-starter-netflix-eureka-client ,这样最终保障你所须要的依赖包可能争取无误的加载下来。

能够去https://spring.io/projects/spring-cloud 查看两者对应的版本

2.配置application.yml(全局配置)

spring:  application:    name: wechat-service #配置注册的名字server:  port: 8761eureka:  client:    register-with-eureka: true #是否将本人注册到eureka-server中    fetch-registry: true #是否从eureka-server中获取服务注册信息    service-url:      defaultZone: http://localhost:${server.port}/eureka/  #设置服务注册核心地址  datacenter: cloud  environment: product

3. 配置启动类

@SpringBootApplication@EnableEurekaServerpublic class WechatServiceApplication {    public static void main(String[] args) {        SpringApplication.run(WechatServiceApplication.class, args);    }}

之后关上 localhost:8761, 看到如下界面阐明注册核心启动胜利, 能够看到目前注册了一个服务 wechat-service, 即该我的项目自身。

服务注册到Eureka注册核心

另起一个spring boot我的项目作为服务。

1.引入依赖

<properties>  <java.version>1.8</java.version>  <spring-cloud.version>2020.0.4</spring-cloud.version></properties><dependencyManagement>        <dependencies>            <dependency>                <groupId>org.springframework.cloud</groupId>                <artifactId>spring-cloud-dependencies</artifactId>                <version>${spring-cloud.version}</version>                <type>pom</type>                <scope>import</scope>            </dependency>        </dependencies></dependencyManagement><dependencies>  <dependency>      <groupId>org.springframework.cloud</groupId>      <artifactId>spring-cloud-dependencies</artifactId>      <version>Hoxton.SR8</version>      <type>pom</type>      <scope>import</scope>  </dependency>  <dependency>            <groupId>org.springframework.cloud</groupId>            <version>2.2.5.RELEASE</version>            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>        </dependency></dependencies>

这里也要留神spring boot的版本和 spring cloud的对应。

2.配置 application.yml 文件

spring:  application:    name: schedule  # 注册名称eureka:  client:    serviceUrl:      defaultZone: http://localhost:8761/eureka/  #注册到 eureka  instance:    preferIpAddress: true

3.批改启动类

增加 @EnableEurekaClient 注解

@SpringBootApplication@EnableEurekaClientpublic class ScheduleApplication {  public static void main(String[] args) {    SpringApplication.run(ScheduleApplication.class, args);  }

之后启动该我的项目, 而后去注册核心查看,能够看到 schedule 我的项目胜利注册。

那么注册胜利之后,咱们怎么获取呢?

获取服务

获取所有服务:

如果注册中心想获取所有服务能够应用 eurekaClient 提供的 getRegisteredApplications 办法获取所有服务。

@Controller@RequestMapping("/eurekacenter")public class EuServiceController {  @Autowired  private EurekaClient eurekaClient;  @GetMapping("/services")  @ResponseBody  public List<Application> getServices() {    return eurekaClient.getApplications().getRegisteredApplications();  }}

获取指定服务:

一般来说,咱们更罕用的是获取某一个服务。能够应用 discoveryClient.getInstances("服务的name"),来获取一个服务。

@Autowiredprivate DiscoveryClient discoveryClient;// 依据服务名称获取服务List<ServiceInstance> serviceInstances = discoveryClient.getInstances("wechat-service");if (CollectionUtils.isEmpty(serviceInstances)) {  return null;}ServiceInstance si = serviceInstances.get(0);String requestUrl = "http://" + si.getHost() + ":" + si.getPort() + "/request/getQrCode";

转发申请性能实现

当初咱们晓得了,能够依据服务的name来获取一个服务,那么作为一个申请转发核心,怎么晓得一个申请要转发给哪个服务呢?

上一篇文章实现第三方登陆:微信扫码登录 (spring boot) 理解到,申请微信服务器须要生成一个场景值, 并且微信返回的申请 也会返回这个场景值。

所以,咱们只有简略地在场景值后面加一个服务的关键字就能够了.逻辑如下

注册核心转发申请逻辑

接管微信申请后,转发申请的逻辑如下:

  1. 获取申请源客户端
  2. 设置转发申请的参数,转发给客户端
  3. 获取客户端响应数据
  4. 返回响应数据给微信

代码如下:

/**   * 当设置完微信公众号的接口后,微信会把用户发送的音讯,扫码事件等推送过去   *   * @param signature 微信加密签名,signature联合了开发者填写的 token 参数和申请中的 timestamp 参数、nonce参数。   * @param encType 加密类型(暂未启用加密音讯)   * @param msgSignature 加密的音讯   * @param timestamp 工夫戳   * @param nonce 随机数   * @throws IOException   */  @PostMapping(produces = "text/xml; charset=UTF-8")  public void api(HttpServletRequest httpServletRequest,                  HttpServletResponse httpServletResponse,                  @RequestParam("signature") String signature,                  @RequestParam(name = "encrypt_type", required = false) String encType,                  @RequestParam(name = "msg_signature", required = false) String msgSignature,                  @RequestParam("timestamp") String timestamp,                  @RequestParam("nonce") String nonce) throws IOException {    if (!this.weChatMpService.checkSignature(timestamp, nonce, signature)) {      this.logger.warn("接管到了未通过校验的微信音讯,这可能是token配置错了,或是接管了非微信官网的申请");      return;    }    // 获取客户端url    String targetUrl = this.wechatService.selectClientUrl(httpServletRequest);    UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(targetUrl + RequestUrl)        .queryParam("signature", signature)        .queryParam("timestamp", timestamp)        .queryParam("nonce", nonce);    if (msgSignature != null) {      builder.queryParam("msg_signature", msgSignature);    }    if (encType != null) {      builder.queryParam("encrypt_type", encType);    }    URI uri = builder.build().encode().toUri();    // 设置转发申请的参数    HttpURLConnection connection = (HttpURLConnection) uri.toURL().openConnection();    connection.setRequestMethod("POST");    connection.setRequestProperty("Content-Type", "text/xml; charset=UTF-8");    connection.setDoOutput(true);    connection.setDoInput(true);    if (msgSignature != null) {      connection.setRequestProperty("msg_signature", msgSignature);    }    if (encType != null) {      connection.setRequestProperty("encrypt_type", encType);    }    // 将 httpServletRequest 中的数据写入 转发申请中    OutputStream outputStream = connection.getOutputStream();    String requestBody =  new RequestWrapper(httpServletRequest).getBodyString();    outputStream.write(requestBody.getBytes(StandardCharsets.UTF_8));    outputStream.flush();    outputStream.close();    // 获取client 的响应数据    InputStream inputStream = connection.getInputStream();    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();    byte[] buffer = new byte[1024];    int bytesRead;    while ((bytesRead = inputStream.read(buffer)) != -1) {      byteArrayOutputStream.write(buffer, 0, bytesRead);    }    byte[] responseBytes = byteArrayOutputStream.toByteArray();    inputStream.close();    // 将响应数据返回给微信    httpServletResponse.setContentType("text/xml; charset=UTF-8");    httpServletResponse.setContentLength(responseBytes.length);    httpServletResponse.getOutputStream().write(responseBytes);    httpServletResponse.getOutputStream().flush();    httpServletResponse.getOutputStream().close();  }

效果图和以前一样:

源代码:https://github.com/weiweiyi189/weChatServiceCenter

参考:

https://juejin.cn/post/6910031138048180238
https://blog.51cto.com/u_10401840/5179710
https://juejin.cn/post/6910031138048180238#heading-8