# 后端架构

### 服务器架构

* 测试服务器（172.16.0.223）
  * 内网地址：172.16.0.223
  * 公网地址：139.159.229.250
  * ssh端口：22
  * 安装的服务：
    * 测试应用
    * 测试数据库
    * redis
    * rocketmq
* 生产服务器
  * 生产应用服务器（172.16.0.246）
    * 内网地址：172.16.0.246
    * 公网地址：139.159.210.228
    * ssh端口：7711
    * 安装的服务
      * 生产应用
      * redis
      * rocketmq
      * nginx（为前端应用提供web服务）
  * 生产数据库服务器（172.16.0.159）
    * 内网地址：172.16.0.159
    * 公网地址：无
    * 安装的服务：
      * 生产数据库
* 共通服务器
  * 堡垒机（以后只有堡垒机才有公网访问权限，提供跳板机服务已经nginx转发服务）
    * 内网地址：172.16.0.99
    * 公网地址：139.9.56.49
    * ssh端口：22
    * 安装的服务：
      * Jumpserver（提供跳板机服务）
      * nginx（域名转发）
  * DevOps服务器
    * 内网地址：172.16.0.61
    * 公网地址：139.159.196.162
    * ssh端口：22
    * 安装的服务
      * 在线文档服务
      * gogs（提供git服务）
      * YApi（提供接口文档服务）
      * Jenkins（提供发版服务）
      * Nexus（提供 maven 私服服务）
      * 代码生成器

### 网络架构

* 绘服务小程序
  * 域名：m.huifuwu.cn
  * 转发地址：http://172.16.0.246:8171
* 绘管家小程序
  * 域名：m.huiguanjia.cn
  * 转发地址：http://172.16.0.246:8171
* 绘服务Web端
  * 域名：pm.huiguanjia.cn
  * 转发：http://172.16.0.246:8171
* 绘管家Web端
  * 域名：wy.huiguanjia.cn
  * 转发：http://172.16.0.246:3000
* Java后端接口
  * http://172.16.0.246:8161

### 应用架构

* 公共包
  * wisdom-common（核心包+各个微服务rpc共用的pojo）
  * wisdom-ds（动态数据源）
  * wisdom-generator（代码生成器）
  * wisdom-mq（mq接入封装）
  * wisdom-parent（maven parent 统一管理第三方包版本）
* 监控
  * wisdom-monitor（微服务健康状况监控）
  * wisdom-track（微服务请求链追踪）

* 网关
  * wisdom-gateway
* 业务
  * swagger-ui（springfox-swagger2）
  * 动态数据源（dynamic-datasource-spring-boot-starter）
  * MyBatis 增强 （tk.mybatis、pagehelper）
  * Lombok
  * Feign
  * OkHttp
  * Druid

### WEB端访问要完成的事情

* 访问 https://wy.huiguanjia.cn

* 堡垒机域名解析

  ```nginx
  server {
      listen 80;
      server_name wy.huiguanjia.cn;
      return 301 https://wy.huiguanjia.cn$request_uri;
  }
  server {
      listen 443;
      server_name wy.huiguanjia.cn;
   
      root   html;
      index  index.html index.htm index.php;
  
      ssl on;
      ssl_certificate cert/wy.huiguanjia.cn.pem;
      ssl_certificate_key cert/wy.huiguanjia.cn.key;
      ssl_session_timeout 5m;
      ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
      ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
      ssl_prefer_server_ciphers on;

      location / {
          proxy_pass  http://172.16.0.246:3000/;
   
          #Proxy Settings
          proxy_redirect     off;
          proxy_set_header   Host             $host;
          proxy_set_header   X-Real-IP        $remote_addr;
          proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
          proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
          proxy_max_temp_file_size 0;
          proxy_connect_timeout      300;
          proxy_send_timeout         300;
          proxy_read_timeout         300;
          proxy_buffer_size          4k;
          proxy_buffers              4 32k;
          proxy_busy_buffers_size    64k;
          proxy_temp_file_write_size 64k;
     }
  
  }
  ```

  

* 转发请求到应用服务器的前端服务

  ```nginx
  server {
      listen 3000;
      server_name 127.0.0.1;
   
      root   html;
      index  index.html index.htm index.php;

      location / {
          root /data/bin/hgj2/web_publish/admin/public/;
  	index index.html;
  	try_files $uri $uri/ /index.html;
  
          #Proxy Settings
          proxy_redirect     off;
          proxy_set_header   Host             $host;
          proxy_set_header   X-Real-IP        $remote_addr;
          proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
          proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
          proxy_max_temp_file_size 0;
          proxy_connect_timeout      300;
          proxy_send_timeout         300;
          proxy_read_timeout         300;
          proxy_buffer_size          4k;
          proxy_buffers              4 32k;
          proxy_busy_buffers_size    64k;
          proxy_temp_file_write_size 64k;
     }
     
     location /9ad134a361f8d778/ {
  	proxy_pass http://127.0.0.1:8161/;
  	proxy_read_timeout 600;
     }
  
     location /ureport/res/ {
          proxy_pass http://127.0.0.1:8161/ureport/res/;
     }
  
     location /ureport/preview/ {
          proxy_pass http://127.0.0.1:8161/ureport/preview/;
     }
  }
  ```

  

* 跨域处理，是接口请求同源

  ```nginx
  location /9ad134a361f8d778/ {
  	proxy_pass http://127.0.0.1:8161/;
  	proxy_read_timeout 600;
  }
  ```

  

* 后端gateway网关拦截器拦截所有请求

  ```java
  // wisdom-gateway
  // CustomGatewayFilter.java
  public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
  	....
  }
  ```

  

* gateway网关从注册中心获取所有服务的访问地址

  ```properties
  # eureka
  eureka.instance.status-page-url-path=/actuator/info
  eureka.instance.health-check-url-path=/actuator/health
  eureka.instance.home-page-url-path=/
  eureka.client.service-url.defaultZone=http://${EUREKA_HOST:localhost}:${EUREKA_PORT:8162}/eureka/
  ## 表示eureka client间隔多久去拉取服务注册信息，默认为30秒，对于api-gateway，如果要迅速获取服务注册状态，可以缩小该值
  eureka.client.registry-fetch-interval-seconds=5
  ## 表示eureka client发送心跳给server端的频率。如果在leaseExpirationDurationInSeconds后，server端没有收到client的心跳，则将摘除该instance
  eureka.instance.lease-renewal-interval-in-seconds=30
  ```

  

* gateway网关根据接口正则前缀将请求路由到各个服务

  ```properties
  ## platform
  spring.cloud.gateway.routes[0].id=wisdom-platform
  spring.cloud.gateway.routes[0].uri=lb://wisdom-platform
  spring.cloud.gateway.routes[0].predicates[0]=Path=/platform/**
  spring.cloud.gateway.routes[0].filters[0]=GatewaySwaggerHeaderFilter
  spring.cloud.gateway.routes[0].filters[1]=StripPrefix=1
  ## asset
  spring.cloud.gateway.routes[1].id=wisdom-asset
  spring.cloud.gateway.routes[1].uri=lb://wisdom-asset
  spring.cloud.gateway.routes[1].predicates[0]=Path=/asset/**
  spring.cloud.gateway.routes[1].filters[0]=GatewaySwaggerHeaderFilter
  spring.cloud.gateway.routes[1].filters[1]=StripPrefix=1
  ## auth
  spring.cloud.gateway.routes[2].id=wisdom-auth
  spring.cloud.gateway.routes[2].uri=lb://wisdom-auth
  spring.cloud.gateway.routes[2].predicates[0]=Path=/auth/**
  spring.cloud.gateway.routes[2].filters[0]=GatewaySwaggerHeaderFilter
  spring.cloud.gateway.routes[2].filters[1]=StripPrefix=1
  ## payment
  spring.cloud.gateway.routes[3].id=wisdom-payment
  spring.cloud.gateway.routes[3].uri=lb://wisdom-payment
  spring.cloud.gateway.routes[3].predicates[0]=Path=/payment/**
  spring.cloud.gateway.routes[3].filters[0]=GatewaySwaggerHeaderFilter
  spring.cloud.gateway.routes[3].filters[1]=StripPrefix=1
  ## serve
  spring.cloud.gateway.routes[4].id=wisdom-serve
  spring.cloud.gateway.routes[4].uri=lb://wisdom-serve
  spring.cloud.gateway.routes[4].predicates[0]=Path=/serve/**
  spring.cloud.gateway.routes[4].filters[0]=GatewaySwaggerHeaderFilter
  spring.cloud.gateway.routes[4].filters[1]=StripPrefix=1
  ## file
  spring.cloud.gateway.routes[5].id=wisdom-file
  spring.cloud.gateway.routes[5].uri=lb://wisdom-file
  spring.cloud.gateway.routes[5].predicates[0]=Path=/file/**
  spring.cloud.gateway.routes[5].filters[0]=GatewaySwaggerHeaderFilter
  spring.cloud.gateway.routes[5].filters[1]=StripPrefix=1
  ## bill
  spring.cloud.gateway.routes[6].id=wisdom-bill
  spring.cloud.gateway.routes[6].uri=lb://wisdom-bill
  spring.cloud.gateway.routes[6].predicates[0]=Path=/bill/**
  spring.cloud.gateway.routes[6].filters[0]=GatewaySwaggerHeaderFilter
  spring.cloud.gateway.routes[6].filters[1]=StripPrefix=1
  ## report
  spring.cloud.gateway.routes[7].id=wisdom-report
  spring.cloud.gateway.routes[7].uri=lb://wisdom-report
  spring.cloud.gateway.routes[7].predicates[0]=Path=/report/**
  spring.cloud.gateway.routes[7].filters[0]=GatewaySwaggerHeaderFilter
  spring.cloud.gateway.routes[7].filters[1]=StripPrefix=1
  ## ureport
  spring.cloud.gateway.routes[8].id=wisdom-report
  spring.cloud.gateway.routes[8].uri=lb://wisdom-report
  spring.cloud.gateway.routes[8].predicates[0]=Path=/ureport/**
  spring.cloud.gateway.routes[8].filters[0]=GatewaySwaggerHeaderFilter
  ## log
  spring.cloud.gateway.routes[9].id=wisdom-log
  spring.cloud.gateway.routes[9].uri=lb://wisdom-log
  spring.cloud.gateway.routes[9].predicates[0]=Path=/log/**
  spring.cloud.gateway.routes[9].filters[0]=GatewaySwaggerHeaderFilter
  spring.cloud.gateway.routes[9].filters[1]=StripPrefix=1
  ## task
  spring.cloud.gateway.routes[10].id=wisdom-task
  spring.cloud.gateway.routes[10].uri=lb://wisdom-task
  spring.cloud.gateway.routes[10].predicates[0]=Path=/task/**
  spring.cloud.gateway.routes[10].filters[0]=GatewaySwaggerHeaderFilter
  spring.cloud.gateway.routes[10].filters[1]=StripPrefix=1
  ## hardware
  spring.cloud.gateway.routes[11].id=wisdom-hardware
  spring.cloud.gateway.routes[11].uri=lb://wisdom-hardware
  spring.cloud.gateway.routes[11].predicates[0]=Path=/hardware/**
  spring.cloud.gateway.routes[11].filters[0]=GatewaySwaggerHeaderFilter
  spring.cloud.gateway.routes[11].filters[1]=StripPrefix=1
  ## hris
  spring.cloud.gateway.routes[12].id=wisdom-hris
  spring.cloud.gateway.routes[12].uri=lb://wisdom-hris
  spring.cloud.gateway.routes[12].predicates[0]=Path=/hris/**
  spring.cloud.gateway.routes[12].filters[0]=GatewaySwaggerHeaderFilter
  spring.cloud.gateway.routes[12].filters[1]=StripPrefix=1
  ## app
  spring.cloud.gateway.routes[13].id=wisdom-app
  spring.cloud.gateway.routes[13].uri=lb://wisdom-app
  spring.cloud.gateway.routes[13].predicates[0]=Path=/app/**
  spring.cloud.gateway.routes[13].filters[0]=GatewaySwaggerHeaderFilter
  spring.cloud.gateway.routes[13].filters[1]=StripPrefix=1
  ```

  

* 服务接收到请求之后，先判断header是否指定数据源，否，则接着判断是否有会话，从会话获取当前请求需要访问的数据源名称

  ```java
  // wisdom-ds
  // DsCustomerProcessor.java
  // 从header获取数据源
  ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("entCode");
  // 从登陆会话获取数据源
  UserInfoUtil.getUserDs();
  ```

* 接口日志拦截

  ```java
  // wisdom-common
  // ControllerLoggerAspect.java
  @Pointcut("@annotation(io.swagger.annotations.ApiOperation)")
  public void controllerPointCut() {
  }
  
  @Around("controllerPointCut()")
  public Object handlerControllerMethod(ProceedingJoinPoint pjp){
      ...
  }
  ```

* 数据权限拦截

  ```java
  // wisdom-common
  // PermissionInterceptor.java
  实现小区、数据权限sql拦截
  ```

* 统一异常拦截

  ```java
  // wisdom-common
  // PromiseExceptionHandler.java
  @ExceptionHandler(PromiseException.class)
  public ResponseEntity handleBizException(PromiseException ex) {
      ...
  }
  @ExceptionHandler(MethodArgumentNotValidException.class)
  public ResponseEntity argumentNotValidException(MethodArgumentNotValidException ex) {
      ...
  }
  @ExceptionHandler(FeignException.class)
  public ResponseEntity handleException(FeignException ex){
      ...
  }
  @ExceptionHandler(BindException.class)
  public ResponseEntity handleException(BindException ex){
      ...
  }
  @ExceptionHandler(Exception.class)
  public ResponseEntity handleException(Exception ex) {
      ...
  }
  ```

  

### 需要解决的问题

* quartz定时任务多数据源问题
* 分布式事务
* Redis集群
* MQ集群
* 单元测试
* 自动化测试
* 多数据源运维



