Repository: apache/dubbo (Java)
Task type: Feature

Issue:
Usage question of QoS invoke
<!-- If you need to report a security issue please visit https://github.com/apache/dubbo/security/policy -->

- [ ] I have searched the [issues](https://github.com/apache/dubbo/issues) of this repository and believe that this is not a duplicate.

## Ask your question here

`接口+分组+版本号才能唯一确定一个服务`
那QoS里invoke，的语法是什么？

`invoke 类.方法()` (里面没 分组和版本)


Discussion:
If you only pulish one service for one interface, just use `invoke className.methodName()`

If there are more than one service for one interface, use `cd serviceKey` first.
@AlbumenJ `dubbo version "3.0.5"`
假设现在`ls`出来 `group-a/org.Service:1.0.0` `group-a/org.Service:2.0.0` `group-b/org.Service:1.0.0`

```text
dubbo>cd aaaa/bbb:1.0.0
No such service
```
> @AlbumenJ `dubbo version "3.0.5"` 假设现在`ls`出来 `group-a/org.Service:1.0.0` `group-a/org.Service:2.0.0` `group-b/org.Service:1.0.0`
> 
> ```
> dubbo>cd aaaa/bbb:1.0.0
> No such service
> ```

`cd group-a/org.Service:1.0.0`
@AlbumenJ 提示`No such service `
确认了下这里确实不支持按照版本号隔离
dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/ChangeTelnet.java:59
你有兴趣完善下这一块吗
@AlbumenJ 我可以,看了下其他地方的用法,增加一个`exporter.getInvoker().getUrl().getServiceKey()` 应该就可以了

`org/apache/dubbo/qos/command/impl/CountTelnet.java:86` 也得同步修改一下
> @AlbumenJ 我可以,看了下其他地方的用法,增加一个`exporter.getInvoker().getUrl().getServiceKey()` 应该就可以了
> 
> `org/apache/dubbo/qos/command/impl/CountTelnet.java:86` 也得同步修改一下

Yes, pls go ahead

Solution patch:
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/ChangeTelnet.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/ChangeTelnet.java
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/ChangeTelnet.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/ChangeTelnet.java
@@ -58,7 +58,8 @@ public String execute(CommandContext commandContext, String[] args) {
             for (Exporter<?> exporter : dubboProtocol.getExporters()) {
                 if (message.equals(exporter.getInvoker().getInterface().getSimpleName())
                         || message.equals(exporter.getInvoker().getInterface().getName())
-                        || message.equals(exporter.getInvoker().getUrl().getPath())) {
+                        || message.equals(exporter.getInvoker().getUrl().getPath())
+                        || message.equals(exporter.getInvoker().getUrl().getServiceKey())) {
                     found = true;
                     break;
                 }
diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/CountTelnet.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/CountTelnet.java
--- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/CountTelnet.java
+++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/CountTelnet.java
@@ -85,7 +85,8 @@ public String execute(CommandContext commandContext, String[] args) {
         for (Exporter<?> exporter : dubboProtocol.getExporters()) {
             if (service.equals(exporter.getInvoker().getInterface().getSimpleName())
                     || service.equals(exporter.getInvoker().getInterface().getName())
-                    || service.equals(exporter.getInvoker().getUrl().getPath())) {
+                    || service.equals(exporter.getInvoker().getUrl().getPath())
+                    || service.equals(exporter.getInvoker().getUrl().getServiceKey())) {
                 invoker = exporter.getInvoker();
                 break;
             }


Test patch:
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/ChangeTelnetTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/ChangeTelnetTest.java
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/ChangeTelnetTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/ChangeTelnetTest.java
@@ -71,7 +71,7 @@ public void beforeEach() {
         mockChannel.attr(ChangeTelnet.SERVICE_KEY).set("org.apache.dubbo.rpc.protocol.dubbo.support.DemoService");
         given(mockCommandContext.getRemote()).willReturn(mockChannel);
         given(mockInvoker.getInterface()).willReturn(DemoService.class);
-        given(mockInvoker.getUrl()).willReturn(URL.valueOf("dubbo://127.0.0.1:20884/demo"));
+        given(mockInvoker.getUrl()).willReturn(URL.valueOf("dubbo://127.0.0.1:20884/demo?group=g&version=1.0.0"));
     }
 
     @AfterEach
@@ -110,6 +110,15 @@ void testChangePath() {
         assertEquals("Used the demo as default.\r\nYou can cancel default service by command: cd /", result);
     }
 
+    @Test
+    void testChangeServiceKey() {
+        ExtensionLoader.getExtensionLoader(Protocol.class)
+                .getExtension(DubboProtocol.NAME)
+                .export(mockInvoker);
+        String result = change.execute(mockCommandContext, new String[] {"g/demo:1.0.0"});
+        assertEquals("Used the g/demo:1.0.0 as default.\r\nYou can cancel default service by command: cd /", result);
+    }
+
     @Test
     void testChangeMessageNull() {
         String result = change.execute(mockCommandContext, null);
diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/CountTelnetTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/CountTelnetTest.java
--- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/CountTelnetTest.java
+++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/impl/CountTelnetTest.java
@@ -52,7 +52,7 @@ class CountTelnetTest {
     private CommandContext mockCommandContext;
 
     private CountDownLatch latch;
-    private final URL url = URL.valueOf("dubbo://127.0.0.1:20884/demo");
+    private final URL url = URL.valueOf("dubbo://127.0.0.1:20884/demo?group=g&version=1.0.0");
 
     @BeforeEach
     public void setUp() {
@@ -70,12 +70,14 @@ public void setUp() {
     public void tearDown() {
         FrameworkModel.destroyAll();
         mockChannel.close();
+        RpcStatus.removeStatus(url);
         reset(mockInvoker, mockCommandContext);
     }
 
     @Test
     void test() throws Exception {
         String methodName = "sayHello";
+        RpcStatus.removeStatus(url, methodName);
         String[] args = new String[] {"org.apache.dubbo.qos.legacy.service.DemoService", "sayHello", "1"};
 
         ExtensionLoader.getExtensionLoader(Protocol.class)
@@ -94,6 +96,28 @@ void test() throws Exception {
         assertThat(sb.toString(), containsString(buildTable(methodName, 10, 10, "1", "0", "0")));
     }
 
+    @Test
+    void testCountByServiceKey() throws Exception {
+        String methodName = "sayHello";
+        RpcStatus.removeStatus(url, methodName);
+        String[] args = new String[] {"g/demo:1.0.0", "sayHello", "1"};
+
+        ExtensionLoader.getExtensionLoader(Protocol.class)
+                .getExtension(DubboProtocol.NAME)
+                .export(mockInvoker);
+        RpcStatus.beginCount(url, methodName);
+        RpcStatus.endCount(url, methodName, 10L, true);
+        count.execute(mockCommandContext, args);
+        latch.await();
+
+        StringBuilder sb = new StringBuilder();
+        for (Object o : mockChannel.getReceivedObjects()) {
+            sb.append(o.toString());
+        }
+
+        assertThat(sb.toString(), containsString(buildTable(methodName, 10, 10, "1", "0", "0")));
+    }
+
     public static String buildTable(
             String methodName, long averageElapsed, long maxElapsed, String total, String failed, String active) {
         List<String> header = new LinkedList<>();