# 后端架构 ### 服务器架构 * 测试服务器(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 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集群 * 单元测试 * 自动化测试 * 多数据源运维