【技术交流】SpringCloud项目接入华为云微服务引擎CSE(一)

举报
HuaweiCloudDeveloper 发表于 2022/01/28 09:34:40 2022/01/28
【摘要】 本项目是将单体项目改造为微服务架构,并将该微服务架构项目改造成支持华为云CSE微服务引擎项目,项目过程中,让您能够熟悉微服务开发模式以及关键组件、华为云servicestage平台,将基于SpringCloud技术栈迁移至华为云CSE,为企业开发者提供微服务改造参考。详细内容可阅读文章进行了解。

1.项目Gitee地址

https://gitee.com/caichunfu/dtse-practice-microservice

2.运行环境

JDK1.8

Maven3.6.3

本地CSE引擎

下载地址:https://support.huaweicloud.com/devg-cse/cse_devg_0036.html


3.注意事项

踩过的一些坑:

3.1  依赖导入报错

需要把Maven的中央仓库地址改为华为中央仓库地址,修改setting.xml文件

      <mirror>
          <id>huaweicloud</id>
          <mirrorOf>*,!HuaweiCloudSDK</mirrorOf>
          <url>https://repo.huaweicloud.com/repository/maven/</url>
      </mirror>

3.2  版本问题

官网地址:https://github.com/huaweicloud/spring-cloud-huawei

1)使用Hoxton分支,项目启动时ribbon的启动类会报错,建议使用master分支

com.huaweicloud.servicecomb.discovery.ribbon.ServiceCombRibbonClientConfiguration required a bean of type 'com.netflix.client.config.IClientConfig' that could not be found.

2)master分支1.8.0-2020.0.x与网关存在兼容性问题,建议使用1.6.1-2020.0.x版本

3)master分支需要使用 springcloud 2020.x 的版本,这个版本移除了Netflix相关的组件,所以需要对组件进行替换

3.3  本次测试使用的版本

   <properties>
        <spring-boot.version>2.5.3</spring-boot.version>
        <spring-cloud.version>2020.0.4</spring-cloud.version>
        <spring-cloud-huawei.version>1.6.1-2020.0.x</spring-cloud-huawei.version>
        <servicecomb.version>2.5.0</servicecomb.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

4.引入依赖

4.1  父工程引入

 <dependencyManagement>
      <!-- configure spring cloud huawei version -->
      <dependency>
        <groupId>com.huaweicloud</groupId>
        <artifactId>spring-cloud-huawei-bom</artifactId>
        <version>${spring-cloud-huawei.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

4.2  子工程引入,并删除eureka相关依赖

<dependency>
  <groupId>com.huaweicloud</groupId>
  <artifactId>spring-cloud-starter-huawei-service-engine</artifactId>
</dependency>

5.创建bootstrap.yml文件

spring:
  application:
    name: #微服务名
  cloud:
    servicecomb:
      discovery:
        enabled: true
        address: http://127.0.0.1:30100
        appName: #应用名
        serviceName: ${spring.application.name}
        version: 0.0.1
        healthCheckInterval: 30
      config:
        serverAddr: http://127.0.0.1:30110
        serverType: kie


6.启动类添加注解

@EnableDiscoveryClient

启动本地微服引擎,访问http://127.0.0.1:30103/进入微服务引擎管理界面,查看服务是否注册成功


7.启动前遇到的问题

7.1  报错信息:

could not be registered. A bean with that name has already been defined in URL 
​
Action:
​
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
​
无法注册。URL中已经定义了一个具有该名称的bean
​
考虑重命名一个bean,或者通过设置Spring实现重写。主要的允许bean定义覆盖=true

解决方案:

yml配置文件添加配置

spring:
  main:
    allow-bean-definition-overriding: true


7.2  报错信息:

Action:
​
Correct the classpath of your application so that it contains a single, compatible version of io.micrometer.core.instrument.distribution.DistributionStatisticConfig$Builder

解决方案:

pom.xml里面的依赖包有重复,需要将重复的依赖包删除

7.3  报错信息:

此问题只会在使用Honxton版本时出现,建议使用master分支1.6.1-2020.0.x版本

Parameter 0 of method ribbonServerList in com.huaweicloud.servicecomb.discovery.ribbon.ServiceCombRibbonClientConfiguration required a bean of type 'com.netflix.client.config.IClientConfig' that could not be found.
​
Action:
​
Consider defining a bean of type 'com.netflix.client.config.IClientConfig' in your configuration.

解决方案:

IClientConfig 类,这个类定义时,不能直接 return 一个没有任何属性的 DefaultClientConfigImpl 对象, openFeign 会在源码里面使用这个对象,报空指针异常,如果要自己定义,需要初始化里面该有的属性

@Configuration
public class IClientConfig {
    @Bean
    public DefaultClientConfigImpl iClientConfig(){
    
        //网上很多的错误写法
        //return new DefaultClientConfigImp();
        
        //加上getClientConfigWithDefaultValues初始化参数
        return DefaultClientConfigImpl.getClientConfigWithDefaultValues();
    }
}


8.Feign远程调用

使用SpringCloudHuawei做远程调用时会报错,可能兼容性存在问题

为了验证问题还原了SpringCloud项目,openfeign调用不会报错

报错信息:

org.apache.servicecomb.service.center.client.exception.OperationException: get service instances list fails, statusCode = 400; message = Bad Request; content = {"errorCode":"400012","errorMessage":"Micro-service does not exist","detail":"Consumer[30b75156753bc55385a7ae74d0611c77fc5f7522][development/dtse-practice-microservice/dtse-system/0.0.1] find provider[development/dtse-practice-microservice/default/0+] failed, provider does not exist"}
​
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: default

解决---暂未解决:

已对接后端技术专家,暂未解决,解决后会更新进度


9.改为RestTemplate方式调用

前端参数为MultipartFile和JSON,请求类型为POST

    @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public String upLoadOneFile(@RequestPart("file") MultipartFile file, @RequestParam("obsParamsJson") String obsParamsJson) throws IOException {
        
        OBSStorageParams obsParams = JSON.parseObject(obsParamsJson, OBSStorageParams.class);
​
        String objURL = obsService.uploadOneFile(file.getInputStream(), obsParams);
        return objURL;
    }

RestTemplate调用代码

        //请求url
        String url = "http://xxxx:xxxx/xx/x";
​
        //构造请求头
        HttpHeaders httpHeaders = new HttpHeaders();
        HttpHeaders headers = httpHeaders;
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
​
        //FileSystemResource将文件变成流以发送
        File file = MultipartFileToFile.multipartFileToFile(multipartFile);
        FileSystemResource fileSystemResource = new FileSystemResource(file);
​
        //构造请求体,使用LinkedMultiValueMap
        MultiValueMap<String, Object> resultMap = new LinkedMultiValueMap<>();
        resultMap.add("file", fileSystemResource);
        resultMap.add("obsParamsJson", obsParamsJson);
​
        //HttpEntity封装整个请求报文
        HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<>(resultMap, headers);
​
        //postForObject发送请求体
        String objURL = restTemplate.postForObject(url, httpEntity, String.class);

MultipartFile转File

 import org.springframework.web.multipart.MultipartFile;
 
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
​
public class MultipartFileToFile {
 
    /**
     * MultipartFile 转 File
     *
     * @param file
     * @throws Exception
     */
    public static File multipartFileToFile(MultipartFile file) throws Exception {
 
        File toFile = null;
        if (file.equals("") || file.getSize() <= 0) {
            file = null;
        } else {
            InputStream ins = null;
            ins = file.getInputStream();
            toFile = new File(file.getOriginalFilename());
            inputStreamToFile(ins, toFile);
            ins.close();
        }
        return toFile;
    }
 
    //获取流文件
    private static void inputStreamToFile(InputStream ins, File file) {
        try {
            OutputStream os = new FileOutputStream(file);
            int bytesRead = 0;
            byte[] buffer = new byte[8192];
            while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            ins.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 删除本地临时文件
     * @param file
     */
    public static void delteTempFile(File file) {
    if (file != null) {
        File del = new File(file.toURI());
        del.delete();
    }
}
}


10.网关改造

官网的说明

Spring Cloud Huawei Hoxton分支只提供Spring Cloud Gateway基于Ribbon的负载均衡,及其配套的基于流量治理和灰度发布功能。

Spring Cloud Huawei master(2020.0.x版本)分支只提供Spring Cloud Gateway基于Spring Cloud LoadBalance的负载均衡, 及其配套的基于流量治理和灰度发布功能。建议Spring Cloud Gateway升级到2020.0.x版本。

由于原项目使用的网关为Zuul,需要改为Spring Cloud Gateway

10.1  删除原项目zuul的依赖

添加springcloudgateway的依赖和springcloudhuawei提供的网关依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>com.huaweicloud</groupId>
    <artifactId>spring-cloud-starter-huawei-service-engine-gateway</artifactId>
</dependency>

10.2  添加网关配置文件

spring:
  main:
    allow-bean-definition-overriding: true
  cloud:
    gateway:
    # 路由(routes:路由,它由唯一标识(ID)、目标服务地址(uri)、一组断言(predicates)和一组过滤器组成(filters)。filters 不是必需参数。)
      routes:
        #路由标识(id:标识,具有唯一性)
        - id: dtse-system-route
        # 目标服务地址(uri:地址,请求转发后的地址)
          uri: lb://dtse-system
          filters:
             args:
        # 路由条件(predicates:断言,匹配 HTTP 请求内容)
          predicates:
            - Path=/**
urifiler:
  login-uri: /login

10.3  定义全局过滤器实现鉴权

package com.huaweicloud.filter;
​
import com.huaweicloud.commons.outhUtils.JwtUtil;
import com.huaweicloud.commons.response.ResultCode;
import com.huaweicloud.config.URIFilter;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.RequestPath;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
​
@Component
@Slf4j
public class RouteConfiguration implements GlobalFilter, Ordered {
​
    @Autowired
    JwtUtil jwtUtil;
    @Autowired
    URIFilter uriFilter;
​
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
​
        ServerHttpRequest request = exchange.getRequest();
        RequestPath path = request.getPath();
​
        // 2、登陆请求放行
        if(path.value().contains(uriFilter.getLoginuri().get(0))){
            System.out.println("登陆请求路经:request.getPath() = " + path.value());
            log.info("登录");
            return chain.filter(exchange);
        }
        //3、非登陆请求用户权限校验
        String authorization = request.getHeaders().getFirst("Authorization");
        if (!StringUtils.isEmpty((authorization))) {
            System.out.println("非登陆请求路径:request.getPath() = " + path.value());
            //2、获取请求头中Token信息
            String token = authorization.replace("Bearer", "");
​
            //3、Token校验
            Claims claims = jwtUtil.parseToken(token) ;
​
            //4、获取用户id,并将用户id传送到后端
            if (claims == null) {
                try {
                    throw new Exception(String.valueOf(ResultCode.UNAUTHENTICATED));
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return  null;
            }
            String id = claims.getId();
​
            //5、添加用户请求头
            request.mutate().header("userId",id).build();
            return chain.filter(exchange);
        }
​
        return chain.filter(exchange);
    }
​
    @Override
    public int getOrder() {
        return 0;
    }
}

10.4  urifiler配置类

@ConfigurationProperties(prefix = "urifiler", ignoreUnknownFields = false)
@Data
@Component
public class URIFilter {
    private List<String> loginuri;
}


11.配置中心的使用

访问http://127.0.0.1:30103/进入微服务本地引擎管理界面

选择配置列表,创建配置项

配置中心_20220127101933.png

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。