Dubbo Admin整合Nacos、Dubbo

1.服务提供者

1.依赖

版本号为2.2.7.REALEASE

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
        </dependency>

2.配置文件

网上大多数配置都是用的zookeeper,笔者之前在的公司也是用的zookeeper做的注册中心,这里改用了Nacos

dubbo:
  metadata-report:
    address: nacos://192.168.0.188:8848
    parameters[namespace]: ac835974-259f-4669-a511-552521e8df0a
  registry:
    address: nacos://192.168.0.188:8848
    ##dubbo nacos 命名空间 默认public
    parameters[namespace]: ac835974-259f-4669-a511-552521e8df0a
    group: dubbo
  protocol:
    port: 20881
    name: dubbo

3.服务接口

1.服务接口定义

public interface AccountClient {
    R saveAccount(Account account);
    R test(String id);
}

2.服务实现类

@DubboService
public class AccountClientImpl implements AccountClient {

    @Autowired
    IAccountService accountService;

    @Override
    public R saveAccount(Account account) {
        accountService.save(account);
        return R.ok();
    }

    @Override
    public R test(String id) {
        return R.ok(id);
    }
}

3.启动类

需要标注@EnableDubbo

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

4.启动服务

可以看到如下,说明AccountClient 这个服务注册成功了
在这里插入图片描述

2.Dubbo-admin

网上的大多数的解决方案都是用的zookeeper,然后在zookeeper的节点中加一个文件,如下

1.Dubbo-admin配置修改

在这里插入图片描述
但是这是以zookeeper作为注册中心的呀,而我这次用的是nacos做注册中心
我就像这模仿着zookeeper来做内容修改:将dubbo-admin的部分配置改为

admin.registry.address=nacos://192.168.0.188:8848?group=dubbo&namespace=ac835974-259f-4669-a511-552521e8df0a
admin.config-center=nacos://192.168.0.188:8848?group=dubbo
admin.metadata-report.address=nacos://192.168.0.188:8848?group=dubbo&namespace=ac835974-259f-4669-a511-552521e8df0a

2.Dubbo-Admin整合nacos后,没有元数据信息

结合dubbo-admin的的前后端源码,本质上这里展示的元数据在dubbo服务注册到nacos时,已经存在nacos上了,现在要做的是把这个配置文件拿下来
如我注册了一个服务到nacos如下

在这里插入图片描述
说明:group、namespace等需要和你nacos中所注册服务的一致

在这里插入图片描述
此时服务已经出现,但是仍有问题
点击详情,出现以下提示
在这里插入图片描述

3.问题分析

在这个时候,其实我也是毫无头绪的,于是在nacos的配置中心中来回翻看配置文件,终于有了一丝头绪
进入到配置列表,发现配置中心多了一个以我的dubbo服务为dataId的配置文件
在这里插入图片描述
点击详情
在这里插入图片描述

看着底部这一串黑色的数据,我就在想这会不会就是元数据的信息
格式化json
在这里插入图片描述
发现这个存储的就是我们所注册服务的信息,包含接口、参数等等
反之看dubbo-admin页面
发现这个展示元数据的接口是
/api/dev/service/cn.redocloud.api.account.AccountClient
其中的cn.redocloud.api.account.AccountClient是我的服务名
在这里插入图片描述
所以我就考虑是否可以自己去拉取配置信息,然后返回给dubbo-admin的vue页面
其实这里我也看了一下源码,在dubbo-admin中他也是有获取配置信息的步骤,但是不知道什么原因返回的是空
类名为NacosMetaDataCollector
在这里插入图片描述

4.解决

由于对nacos-client提供的方法不是很了解,这里就使用resttemplate去向nacos发送请求,获取配置内容(如果哪位大佬知道上面为什么获取到的配置内容为空,或者说能解决这个问题,还请不吝赐教)

以下是我的解决方案:

1.使用RestTemplate配置

/**
 * @Author: ChenTaoTao
 * @Date: 2022/4/10 19:22
 * @Describe:
 */
@Configuration
public class RestTemplateConfig {
    @Bean
    public CloseableHttpClient httpClient(){
        HttpClientBuilder builder = HttpClients.custom();
        PoolingHttpClientConnectionManager clientConnectionManager = new PoolingHttpClientConnectionManager();
        clientConnectionManager.setMaxTotal(30);
        clientConnectionManager.setDefaultMaxPerRoute(50);
        builder.setConnectionManager(clientConnectionManager);
        builder.setRetryHandler(new DefaultHttpRequestRetryHandler(2,true));
        return builder.build();
    }

    @Bean
    public RestTemplate restTemplate(){
        RestTemplate restTemplate = new RestTemplate();
        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
        messageConverters.add(fastJsonHttpMessageConverter());
        restTemplate.setMessageConverters(messageConverters);
        return restTemplate;
    }

    @Bean
    public FastJsonHttpMessageConverter fastJsonHttpMessageConverter(){
        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteMapNullValue,
                SerializerFeature.QuoteFieldNames, SerializerFeature.DisableCircularReferenceDetect);
        fastConverter.setFastJsonConfig(fastJsonConfig);

        List<MediaType> fastMediaTypes = new ArrayList<>();
        fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8);
        fastConverter.setSupportedMediaTypes(fastMediaTypes);
        return fastConverter;
    }
}

2.自定义配置类

说明:这个配置类是我根据Nacos页面获取配置详情时的接口参数来做的

/**
 * @Author: ChenTaoTao
 * @Date: 2022/4/10 19:32
 * @Describe:
 */
@Configuration
@ConfigurationProperties(prefix = "nacos.config")
public class NacosConfig {
    private String url;
    private String group;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getGroup() {
        return group;
    }

    public void setGroup(String group) {
        this.group = group;
    }

    public String getTenant() {
        return tenant;
    }

    public void setTenant(String tenant) {
        this.tenant = tenant;
    }

    public String getNamespaceId() {
        return namespaceId;
    }

    public void setNamespaceId(String namespaceId) {
        this.namespaceId = namespaceId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public String getLoginUrl() {
        return loginUrl;
    }

    public void setLoginUrl(String loginUrl) {
        this.loginUrl = loginUrl;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    private String tenant;
    private String namespaceId;
    private String username;
    private String password;

    private String loginUrl;
}

3.Nacos请求发起工具类

/**
 * @Author: ChenTaoTao
 * @Date: 2022/4/10 19:19
 * @Describe:
 */
@Component
public class NacosUtil {
  @Autowired
    private RestTemplate restTemplate;

  private static final Logger logger = LoggerFactory.getLogger(NacosUtil.class);

  @Autowired
  NacosConfig nacosConfig;

  public String getDataId(String service,String application){
      service = service+":::provider:"+application;
      return service;
  }

  public JSONObject getConfig(String dataId){
      String api = nacosConfig.getUrl()+"&group="+nacosConfig.getGroup()+"&tenant="+nacosConfig.getTenant()+"&namespaceId="+nacosConfig.getNamespaceId()+"&dataId="+dataId;
      api = api+"&accessToken="+execLogin();
      System.out.println(api);

      ResponseEntity<JSONObject> exchange = restTemplate.getForEntity(api,JSONObject.class);
      return exchange.getBody().getJSONObject("content");
  }

  public String execLogin(){
      String url = nacosConfig.getLoginUrl()+"?username="+nacosConfig.getUsername()+"&password="+nacosConfig.getPassword();

      ResponseEntity<JSONObject> exchange = restTemplate.exchange(url, HttpMethod.POST,null,JSONObject.class);

      if(exchange.getStatusCode().equals(HttpStatus.OK)){
         return (String)exchange.getBody().get("accessToken");
      }
      return "response";
  }

修改ServiceController中的serviceDetail方法
在这里插入图片描述
我修改后的完整代码如下:

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.dubbo.admin.controller;

import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;

import com.google.gson.JsonParseException;
import org.apache.dubbo.admin.annotation.Authority;
import org.apache.dubbo.admin.common.exception.VersionValidationException;
import org.apache.dubbo.admin.common.util.Constants;
import org.apache.dubbo.admin.common.util.Tool;
import org.apache.dubbo.admin.model.domain.Consumer;
import org.apache.dubbo.admin.model.domain.Provider;
import org.apache.dubbo.admin.model.dto.ServiceDTO;
import org.apache.dubbo.admin.model.dto.ServiceDetailDTO;
import org.apache.dubbo.admin.service.ConsumerService;
import org.apache.dubbo.admin.service.ProviderService;
import org.apache.dubbo.admin.utils.NacosUtil;
import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
import org.apache.dubbo.metadata.report.identifier.MetadataIdentifier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Authority(needLogin = true)
@RestController
@RequestMapping("/api/{env}")
public class ServiceController {

    private final ProviderService providerService;
    private final ConsumerService consumerService;
    private final Gson gson;

    @Autowired
    NacosUtil nacosUtil;

    @Autowired
    public ServiceController(ProviderService providerService, ConsumerService consumerService) {
        this.providerService = providerService;
        this.consumerService = consumerService;
        this.gson = new Gson();
    }

    @RequestMapping(value = "/service", method = RequestMethod.GET)
    public Page<ServiceDTO> searchService(@RequestParam String pattern,
                                          @RequestParam String filter,
                                          @PathVariable String env,
                                          Pageable pageable) {
        final Set<ServiceDTO> serviceDTOS = providerService.getServiceDTOS(pattern, filter, env);

        final int total = serviceDTOS.size();
        final List<ServiceDTO> content =
                serviceDTOS.stream()
                        .skip(pageable.getOffset())
                        .limit(pageable.getPageSize())
                        .collect(Collectors.toList());

        final Page<ServiceDTO> page = new PageImpl<>(content, pageable, total);
        return page;
    }

    @RequestMapping(value = "/service/{service}", method = RequestMethod.GET)
    public ServiceDetailDTO serviceDetail(@PathVariable String service, @PathVariable String env) {
        String dataId = ""+service;
        service = service.replace(Constants.ANY_VALUE, Constants.PATH_SEPARATOR);
        String group = Tool.getGroup(service);
        String version = Tool.getVersion(service);
        String interfaze = Tool.getInterface(service);
        List<Provider> providers = providerService.findByService(service);

        List<Consumer> consumers = consumerService.findByService(service);

        String application = null;
        if (providers != null && providers.size() > 0) {
            application = providers.get(0).getApplication();
        }
        MetadataIdentifier identifier = new MetadataIdentifier(interfaze, version, group, Constants.PROVIDER_SIDE, application);
        String metadata = providerService.getProviderMetaData(identifier);
        System.out.println("这是nacos获取到的配置"+metadata);
        ServiceDetailDTO serviceDetailDTO = new ServiceDetailDTO();
        serviceDetailDTO.setConsumers(consumers);
        serviceDetailDTO.setProviders(providers);
        if (metadata != null) {
            try {
                // for dubbo version under 2.7, this metadata will represent as IP address, like 10.0.0.1.
                // So the json conversion will fail.
                String release = providerService.findVersionInApplication(application);
                // serialization compatible 2.x version
                if (release.startsWith("2")) {
                    org.apache.dubbo.admin.model.domain.FullServiceDefinition serviceDefinition = gson.fromJson(metadata, org.apache.dubbo.admin.model.domain.FullServiceDefinition.class);
                    serviceDetailDTO.setMetadata(serviceDefinition);
                } else {
                    FullServiceDefinition serviceDefinition = gson.fromJson(metadata, FullServiceDefinition.class);
                    serviceDetailDTO.setMetadata(serviceDefinition);
                }
            } catch (JsonParseException e) {
                throw new VersionValidationException("dubbo 2.6 does not support metadata");
            }
        }
        serviceDetailDTO.setConsumers(consumers);
        serviceDetailDTO.setProviders(providers);
        serviceDetailDTO.setService(service);
        serviceDetailDTO.setApplication(application);

       dataId = dataId+":::provider:"+serviceDetailDTO.getApplication();

        System.out.println("dataId"+dataId);
       JSONObject metaData = nacosUtil.getConfig(dataId);
       serviceDetailDTO.setMetadata(metaData);
        return serviceDetailDTO;
    }

    @RequestMapping(value = "/services", method = RequestMethod.GET)
    public Set<String> allServices(@PathVariable String env) {
        return new HashSet<>(providerService.findServices());
    }

    @RequestMapping(value = "/applications/instance", method = RequestMethod.GET)
    public Set<String> allInstanceServices(@PathVariable String env) {
        return new HashSet<>(providerService.findInstanceApplications());
    }

    @RequestMapping(value = "/applications", method = RequestMethod.GET)
    public Set<String> allApplications(@PathVariable String env) {
        return providerService.findApplications();
    }

    @RequestMapping(value = "/consumers", method = RequestMethod.GET)
    public Set<String> allConsumers(@PathVariable String env) {
        List<Consumer> consumers = consumerService.findAll();
        return consumers.stream().map(Consumer::getApplication).collect(Collectors.toSet());
    }
}

修改完成后启动项目
在这里插入图片描述
点击详情
在这里插入图片描述
此时已有元数据
针对上面的工具类中登录的步骤,做如下优化
新建一个map,当做一个缓存的,nacos的token过期时间为5小时还是多久,这里的话我就4小时执行一次

package org.apache.dubbo.admin.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.dubbo.admin.config.ConfigCenter;
import org.apache.dubbo.admin.config.NacosConfig;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * @Author: ChenTaoTao
 * @Date: 2022/4/10 19:19
 * @Describe:
 */
@Component
public class NacosUtil {
  @Autowired
    private RestTemplate restTemplate;

  private static final Logger logger = LoggerFactory.getLogger(NacosUtil.class);

  private Map<String,String> params = new ConcurrentHashMap<>();

  @Autowired
  NacosConfig nacosConfig;

  public String getDataId(String service,String application){
      service = service+":::provider:"+application;
      return service;
  }

  public JSONObject getConfig(String dataId){
      String api = nacosConfig.getUrl()+"&group="+nacosConfig.getGroup()+"&tenant="+nacosConfig.getTenant()+"&namespaceId="+nacosConfig.getNamespaceId()+"&dataId="+dataId;
      api = api+"&accessToken="+getToken();
      System.out.println(api);

      ResponseEntity<JSONObject> exchange = restTemplate.getForEntity(api,JSONObject.class);
      return exchange.getBody().getJSONObject("content");
  }

  public String execLogin(){
      String url = nacosConfig.getLoginUrl()+"?username="+nacosConfig.getUsername()+"&password="+nacosConfig.getPassword();

      ResponseEntity<JSONObject> exchange = restTemplate.exchange(url, HttpMethod.POST,null,JSONObject.class);

      if(exchange.getStatusCode().equals(HttpStatus.OK)){
          String accessToken = (String)exchange.getBody().get("accessToken");
         return accessToken;
      }
      return "response";
  }

  @Scheduled(cron = "* * */4 * * ?")
  public String getToken(){
      System.out.println("获取token");
      logger.info("开始获取token");
      if(params.get("accessToken")==null){
         String accessToken = execLogin();
         params.put("accessToken",accessToken);
         return accessToken;
      }else{
          return params.get("accessToken");
      }
  }
}

将cron表达式改为0 0 0/4 * * ? * 四小时执行一次
完后,在启动类上添加@EnableScheduling启动定时任务

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SinceThenLater

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值