文章目录
- 1.服务提供者
- 1.依赖
- 2.配置文件
- 3.服务接口
- 1.服务接口定义
- 2.服务实现类
- 3.启动类
- 4.启动服务
- 2.Dubbo-admin
- 1.Dubbo-admin配置修改
- 2.Dubbo-Admin整合nacos后,没有元数据信息
- 3.问题分析
- 4.解决
- 1.使用RestTemplate配置
- 2.自定义配置类
- 3.Nacos请求发起工具类
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");
("开始获取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启动定时任务