{"id":37023619,"url":"https://github.com/wangzihaogithub/sse-server","last_synced_at":"2026-01-14T02:50:33.417Z","repository":{"id":57724007,"uuid":"462621693","full_name":"wangzihaogithub/sse-server","owner":"wangzihaogithub","description":"sse协议的后端service，前端可以不引入js依赖，直接写业务逻辑，后端可以直接根据用户ID推送消息","archived":false,"fork":false,"pushed_at":"2025-09-01T16:04:55.000Z","size":490,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-09-01T18:10:35.939Z","etag":null,"topics":["eventlisteners","eventsource","sse","visibilitychange"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wangzihaogithub.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-02-23T07:11:36.000Z","updated_at":"2025-09-01T16:02:45.000Z","dependencies_parsed_at":"2024-05-21T06:46:35.686Z","dependency_job_id":"75bdb130-5f67-4508-a85f-7387e86e7d2e","html_url":"https://github.com/wangzihaogithub/sse-server","commit_stats":null,"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"purl":"pkg:github/wangzihaogithub/sse-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wangzihaogithub%2Fsse-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wangzihaogithub%2Fsse-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wangzihaogithub%2Fsse-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wangzihaogithub%2Fsse-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wangzihaogithub","download_url":"https://codeload.github.com/wangzihaogithub/sse-server/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wangzihaogithub%2Fsse-server/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408775,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eventlisteners","eventsource","sse","visibilitychange"],"created_at":"2026-01-14T02:50:32.671Z","updated_at":"2026-01-14T02:50:33.399Z","avatar_url":"https://github.com/wangzihaogithub.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sse-server\n\n#### 介绍\nsse协议的后端API, 比websocket轻量的实时通信, 支持集群，qos，异步，监控\n\n在LocalConnectionService和sse.js中封装了业务 (浏览器tab切换逻辑, 断线重连, 根据用户ID发送, 获取在线用户, 上线通知, 离线通知)\n\n\n1. 只有用户当前能看到或正在使用的页签会保持链接. 如果用户不浏览会自动下线, 强实时在线.\n\n2. 不需要前端引入任何js代码和任何依赖, 客户端代码在后端更新, 前端立即生效. 使用的是es6的前端语法, 动态import(接口)\n\n3. 支持双向通信, 后端发送消息后会返回是否成功, 前端发送有可靠保证, 会自动重连, 成功后会自动将离线期间的请求继续发送.\n\n\n### demo地址\n\n[https://github.com/wangzihaogithub/sse-server-demo](https://github.com/wangzihaogithub/sse-server-demo \"https://github.com/wangzihaogithub/sse-server-demo\")\n\n[https://gitee.com/wangzihaogitee/sse-server-demo](https://gitee.com/wangzihaogitee/sse-server-demo \"https://gitee.com/wangzihaogitee/sse-server-demo\")\n\n\n### 简单介绍\n\n        // 1.后端给前端推数据\n        const listeners = {\n           'myHunterBell': (event) =\u003e {console.log(event.data)},\n           'xxx-xx': this.xx\n         }\n        sseEventListener('/sse/hr', listeners).then(sseConnection =\u003e {\n           this.sseConnection = sseConnection\n        })\n\n        // 2.前端给后端送数据 当前连接发json请求\n        sseConnection.send(path, body, query, headers).then(response =\u003e{\n            console.log(response)\n        })\n        // 3.前端给后端送文件 当前连接的文件上传\n        const data = new FormData()\n        data.set(name, file)\n        sseConnection.upload(path, data, query, headers).then(response =\u003e{\n            console.log(response)\n        })\n\n\n### TypeScript定义\n\n      // 与后端接口建立连接\n      sseEventListener(url:string,\n                     eventListeners:Record\u003cstring, (event: MessageEvent) =\u003e void\u003e,\n                     query?: Record\u003cstring, any\u003e) : Promise\u003cSseSocket\u003e;\n      \n\n      // 建立连接后获得的对象\n      interface SseSocket {\n         addListener(eventName: string, listener: (event: MessageEvent) =\u003e void): Promise\u003cResponse\u003e;\n      \n          addListener(eventListeners: Record\u003cstring, (event: MessageEvent) =\u003e void\u003e): Promise\u003cResponse\u003e;\n      \n          removeListener(eventName: string, listener: (event: MessageEvent) =\u003e void): Promise\u003cResponse\u003e;\n      \n          removeListener(eventListeners: Record\u003cstring, (event: MessageEvent) =\u003e void\u003e): Promise\u003cResponse\u003e;\n      \n          connect(): void;\n      \n          destroy(): void;\n      \n          switchURL(newUrl): void;\n      \n          close(reason: string): void;\n      \n          close(): void;\n      \n          isActive(): boolean;\n      \n          send(path: string, body: Record\u003cstring, any\u003e | undefined, query: Record\u003cstring, any\u003e | undefined, header: string[][] | Record\u003cstring, string\u003e | Headers | undefined): Promise\u003cResponse\u003e;\n      \n          upload(path: string, formData: FormData, query: object  undefined, header: string[][] | Record\u003cstring, string\u003e | Headers | undefined): Promise\u003cResponse\u003e;\n      \n      }\n\n\n4. 在nginx开启http2情况下, 可以和其他短链接ajax请求, 复用一个连接, 摆脱了浏览器单个域名下的最大连接数限制, 在客户网络繁忙或网卡老化的情况下有奇效, 这是websocket做不到的. \n\n\n### 项目原理\n\n1. 前端浏览器通过\n\n       import(后段接口).then(conn =\u003e {\n           实现获得sse监听对象\n       }) \n\n2. 后端服务端，直接面向前端，提供http接口\n\n通过继承 SseWebController\u003cMyAccessUser\u003e 实现提供http-sse接口\n\n        @RestController\n        @RequestMapping(\"/my-sse\")\n        public class MySseWebController extends SseWebController\u003cMyAccessUser\u003e \n\n获取sse链接方式如下\n\n        @Resource\n        private LocalConnectionService localConnectionService; // 通过这种方式获得sse链接\n   \n        注！如果一个应用需要报漏多个SseWebController服务，请手工向spring注册LocalConnectionService\n\n\n3. 集群需要用户添加依赖nacos， 开启配置\n\n        spring:\n            sse-server:\n                remote:\n                    enabled: true\n                        nacos:\n                            service-name: 'demo2-distributed'\n                            server-addr: 'xx.xx.xx.xx'\n\n4. 开启集群后，支持后端客户端（不依赖tomcat）\n\n         @Resource\n         private DistributedConnectionService distributedConnectionService; // 这种方式获得sse链接\n\n5. qos发送，保证至少发送一次\n\n         // 可以通过下面两个接口发送\n         localConnectionService.qos().sendByUserId()\n         distributedConnectionService.qos().sendByUserId()\n\n### QA问答\n\n    Q：开启集群后的如何确认消息落点并转发，怎么实现的？\n    A: sse-client（DistributedConnectionService），sse-controller(SseWebController), localConnectionService.getCluster()\n       这三个发消息入口发消息前会先看本地是否存在该链接ID，如果不存在则广播集群所有机器，\n       每个sse-server内置暴露了随机端口号的http接口，并注册到nacos上（放心有安全机制）\n\n    Q：如何保证qos？\n    A：如果用户不在线，会将消息存放在MessageRepository接口中，如果下次用户上线，会触发重新发送并删除消息，\n       目前MessageRepository的实现是MemoryMessageRepository，未来会增加其他实现\n\n\n#### 安装教程\n\n1.  添加maven依赖, 在pom.xml中加入 [![Maven Central](https://img.shields.io/maven-central/v/com.github.wangzihaogithub/sse-server.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:com.github.wangzihaogithub%20AND%20a:sse-server)\n\n\n        \u003c!-- https://mvnrepository.com/artifact/com.github.wangzihaogithub/sse-server --\u003e\n        \u003cdependency\u003e\n            \u003cgroupId\u003ecom.github.wangzihaogithub\u003c/groupId\u003e\n            \u003cartifactId\u003esse-server\u003c/artifactId\u003e\n            \u003cversion\u003e1.2.22\u003c/version\u003e\n        \u003c/dependency\u003e\n        \n2.  配置业务逻辑 （后端）\n\n\n        // 实现AccessUser 可以使用sendByUserId(). \n        // 实现AccessToken 可以使用sendByAccessToken(), (多客户端登陆系统)\n        // 实现TenantAccessUser 可以使用sendByTenantId(), (多租户系统)\n        @Data\n        public class HrAccessUser implements AccessToken, AccessUser, TenantAccessUser {\n            private String accessToken;\n            private Integer id;\n            private String name;\n            private Integer tenantId;\n        }\n\n        // 支持多系统\n        @Bean\n        public LocalConnectionService hrLocalConnectionService() {\n            // hr系统 用hrLocalConnectionService\n            return new LocalConnectionServiceImpl();\n        }\n    \n        @Bean\n        public LocalConnectionService hunterLocalConnectionService() {\n            // hunter系统 用hunterLocalConnectionService\n            return new LocalConnectionServiceImpl();\n        }\n        \n        \n        /**\n         * 消息事件推送 (分布式)\n         * \u003cp\u003e\n         * 1. 如果用nginx代理, 要加下面的配置\n         * # 长连接配置\n         * proxy_buffering off;\n         * proxy_read_timeout 7200s;\n         * proxy_pass http://172.17.83.249:9095;\n         * proxy_http_version 1.1; #nginx默认是http1.0, 改为1.1 支持长连接, 和后端保持长连接,复用,防止出现文件句柄打开数量过多的错误\n         * proxy_set_header Connection \"\"; # 去掉Connection的close字段\n         *\n         * @author hao 2021年12月7日19:29:51\n         */\n        @RestController\n        @RequestMapping(\"/sse/hr\") // 这里自定义地址, 给前端这个地址连\n        public class HrController extends SseWebController\u003cHrAccessUser\u003e {\n            @Override\n            protected HrAccessUser getAccessUser() {\n                return WebSecurityAccessFilter.getCurrentAccessUser(super.request);\n            }\n        \n            @Autowired\n            @Override\n            public void setLocalConnectionService(LocalConnectionService hrLocalConnectionService) {\n                super.setLocalConnectionService(hrLocalConnectionService);\n            }\n        \n            @Override\n            protected Object wrapOkResponse(Object result) {\n                return ResponseData.success(result);\n            }\n        }\n\n\n3.  接口示例: 实现推送信息业务逻辑（后端）\n\n\n            // 获取所有在线用户\n            List\u003cACCESS_USER\u003e userList = hrLocalConnectionService.getUsers();\n            // 上线通知\n            hrLocalConnectionService.addConnectListener(Consumer\u003cSseEmitter\u003cACCESS_USER\u003e\u003e consumer);\n            // 离线通知\n            hrLocalConnectionService.addDisConnectListener(Consumer\u003cSseEmitter\u003cACCESS_USER\u003e\u003e consumer);\n            \n            // 推送消息 (根据用户ID)\n            MyHrBellDTO bellDTO = new MyHrBellDTO();\n            bellDTO.setCount(100);\n            int successCount = hrLocalConnectionService.sendByUserId(hrUserId,\n                    SseEmitter.event()\n                            .data(bellDTO)\n                            .name(\"myHrBell\")\n            );\n            \n            // 推送消息 (根据登陆令牌)\n            int sendByAccessToken(accessToken, message)\n            \n            // 推送消息 (根据租户ID)\n            int sendByTenantId(tenantId, message)\n                        \n            // 推送消息 (根据自定义信道)\n            int sendByChannel(channel, message)\n            \n            // 推送消息 (群发)\n            int sendAll(message);\n            \n            // 默认自带http接口 (分页查询当前在线用户) 在SseWebController\n            http://localhost:8080/sse/hr/users\n            \n            // 默认自带http接口 (分页查询当前在线连接) 在SseWebController\n            http://localhost:8080/sse/hr/connections\n            \n            // 默认自带http接口 (踢掉用户) 在SseWebController\n            http://localhost:8080/sse/hr/disconnectUser\n                        \n           \n4.  编写业务逻辑 （前端） \n            \n            \n1. 强烈推荐! 原生html示例, 或Vue (不需要前端引入任何依赖sse.js, 只需要这几行代码)\n    \n    \n    \n          1. 函数声明, 在index.html或Vue的index.html里加入代码\n          \n          \u003cscript\u003e\n              function sseEventListener(url, eventListeners, query) {\n                return import(url + '?' + new URLSearchParams(query))\n                  .then(module =\u003e new module.default({url,eventListeners,query}))\n              }\n          \u003c/script\u003e\n  \n         2. 使用\n         \n            const listeners = {\n               'myHunterBell': this.onHunterBell,\n               'xxx-xx': this.xx\n             }\n            sseEventListener('/sse/hr', listeners).then(sseConnection =\u003e {\n               this.sseConnection = sseConnection\n            })\n             \n           \n2. Vue示例(方式1)：\n     \n     \n     \n            下载前端代码 https://github.com/wangzihaogithub/sse-js.git, 或复制本项目中的 /src/resources/sse.js\n\n            import Sse from '../util/sse.js'\n    \n            mounted() {\n              // 来自HR系统的服务端推送\n              this.hrSse = new Sse({\n                url : '/sse/hr',\n                eventListeners:{\n                  'myHrBell': this.onHrBell,\n                  'xxx-xx': this.xx\n                }\n              })\n              \n              // 来自猎头系统的服务端推送\n              this.hunterSse = new Sse({\n                url : '/sse/hunter',\n                eventListeners:{\n                  'myHunterBell': this.onHunterBell,\n                  'xxx-xx': this.xx\n                },\n                query: { arg1 : 123 }, // 非必填 - 连接时携带的参数 \n                clientId: '自定义设备ID', // 非必填\n                accessTimestamp: Date.now(), // 非必填 - 接入时间\n                reconnectTime: 5000, // 非必填 - 网络错误时的重连时间\n              })\n            },\n            beforeDestroy() {\n              this.hrSse.destroy()\n              this.hunterSse.destroy()\n            }\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwangzihaogithub%2Fsse-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwangzihaogithub%2Fsse-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwangzihaogithub%2Fsse-server/lists"}