```xml
<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-net-stomp</artifactId>
</dependency>
```


### 1、描述

（v3.0.2 后支持）网络扩展插件。提供基础的 stomp-broker 支持。插件提供三个关键的类：



| 类                   | 说明                                                                                                     | 
| ------------ | ---------------------------------------------------------- | 
| StompBroker     | 将 WebSocket 协议，转为 Stomp 协议；并提供 Broker 服务                        | 
| StompEmitter     | Stomp 消息发射器（用于发消息）                                                           | 
| StompListener   | Stomp 监听器                                                                                       |
| | |
| `@Mapping(...)`   | Mvc 的方式，接收监听消息                                                                     | 
| `@Message`       | Mvc 请求方法限定注解，类似 `@Get`、`@Post`                                           | 
| `@To(...)`           | Mvc 消息发射器的“注解”（用于发消息，对应 StompEmitter 能力）                 | 
| | |
| ToHandlerStompListener | 将 Stomp 事件转为 Solon Handler 通用体系。从而实现控制器模式开发  |


### 2、三个发送接口


| StompEmitter 接口    |  对应的 `@To` 注解     | 说明                              | 
| ---------------- | ---------------------- | ------------------- | 
|                          | `@To("target:destination?")`  | To 注解表达式（stomp 请求时有效）       |
| | | |
| sendToSession    |  `@To(".:/...")` 或<br/> `@To(".")`                     | 发给当前客户端订阅者     | 
| sendToUser        |  `@To("user:/...")`  或<br/> `@To("user")`        | 发给特定用户订阅者        | 
| sendTo               |  `@To("*:/...")`  或<br/> `@To("*")`                   | 发给代理，再转发给所有订阅者             | 



提示：

* `@To` 没有 destination 时，即转发给请求的 destination
* 发送或转换时，不会自动增加、或去掉地址片段。
* 发送给用户（`sendToUser`）的特性，需要给连接会话命名才可用（`session.nameAs(...)`）。


### 3、规划 destination 路径

使用 solon stomp 时，一般会有三个参与角色。分别是：

* 客户端（一般是，订阅者）。可用 user 表示
* 服务端代理。可用 broker 表示
* 服务端应用处理（可以是，订阅者）。可用 app 表示

为了让消息可以自由发送，对 destination 路径做个规划（或者约定），使用会更方便、清晰：


| 路径前缀                                      | 说明                                                      | 
| --------------------------- | -------------------------------- |
| `/topic/**` 或 `/queue/**`  或别的   | 发给 broker，再转发给所有 “订阅者”      | 
| `/app/**` 或别的                           | user 直接发给 app（不经过 broker）     | 
| `/user/**` 或别的                          | app 直接发给 user（不经过 broker）     | 


### 4、使用示例

（1）注册经理人（StompBroker），并实现鉴权和异常监听。

```java
@ServerEndpoint("/chat")
public class ChatStompBroker extends StompBroker implements StompListener {
    public ChatStompBroker(){
        //可选：添加鉴权监听器（此示例，用本类实现监听）
        this.addListener(this);
        //必选
        this.setBrokerDestinationPrefixes("/topic/");
    }
    @Override
    public void onOpen(StompSession session) {
        String user = session.param("user");

        if (user == null) {
            //签权拒绝
            session.close();
        } else {
            //签权通过；并对会话命名
            session.nameAs(user); //命名后，可对 user 发消息
        }
    }

    @Override
    public void onFrame(StompSession session, Frame frame) throws Throwable {
        //可选：打印所有帧
        System.out.println(frame);
    }

    @Override
    public void onError(StompSession session, Throwable error) {
        //可选：如果出错，反馈给客户端（比如用 "/user/app/errors"）
        getEmitter().sendToSession(session,
                "/user/app/errors",
                new Message(error.getMessage()));
    }
}
```


（2）业务场景应用（用经典的 MVC 模式；与 http 请求差不多，支持 `content-type` 的序列化自动处理）

```java
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Http;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.annotation.Message;
import org.noear.solon.annotation.To;

@Controller
public class TestController {
    @Inject //@Inject("/chat") 多经理人时，指定名字
    StompEmitter stompEmitter;

    @Message
    @Mapping("/app/hello")
    @To("*:/topic/greetings")
    public Greeting greetings(@Body String msg) throws Exception {
        return new Greeting("Hello, " + msg + "!");
    }

    //有返回值，又没有 To 注解时。则用来源 destination 给 broker 发消息
    @Message
    @Mapping("/topic/chat/message")
    public Hint message(@Header("user") user, ChartMessage message) throws Exception {
        return new Hint(user + "，你发送太频繁啦!");
    }

    @Http
    @Mapping("/http/hello")
    public void greeting3(Context ctx, HelloMessage message) throws Exception {
        String payload = ctx.renderAndReturn(new Greeting("Hello, " + message.getName() + "!"));
        stompEmitter.sendTo("/topic/greetings", payload);
    }
}
```

（3）Web 客户端可例用 "stomp.js"（接口参考： https://stomp-js.github.io/api-docs/latest/classes/Client.html ）

```
npm install @stomp/stompjs@7.0.0
```

```javascript
import { Client, Versions } from '@stomp/stompjs';
const stompClient = new Client({
    webSocketFactory: ()=> new WebSocket('ws://127.0.0.1:8080/chat?user=demo'),
    onConnect: function (frame) {
        //订阅：所有主题消息（只示例）
        stompClient.subscribe('/topic/**', function (resp) {
            console.log(resp.body);
        });

        //订阅：接收 app 返回的错误
        stompClient.subscribe('/user/app/errors', function (resp) {
            console.log(resp.body);
        });
    }
stompClient.activate()

stompClient.publish({
    destination: "/app/hello",
    body: "hi"
});
```


### 5、注解使用补充说明


Mapping 说明（当有路径匹配上时）

* 没有 “方式限定”（`@Http`、`@Get`、`@Message`..）时，`@Mapping` 表示匹配所有的请求。
* 添加 `@Message` 时，表示只匹配 `ctx.method() == 'MESSAGE'` 的请求
* 添加 `@Http` 时，表示匹配 `ctx.method() == 'GET' | 'POST'..` 等 Http 的上下文
* 可以添加多个 “方式限定” 注解

Message 说明

* 跟 `@Get` 之类的一样，表过请求方式限定

To 说明

* 表示处理结果，转发给另一个目标地址
* 当没有注解，又有返回值时。则转发给来源地址


关于 Mapping 注解，更多可参考：[《@Mapping 用法说明》](/article/327)


