├── bullet-comet ├── bullet-comet-core │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── bootstrap.yml │ │ │ └── application.yml │ │ │ └── java │ │ │ └── com │ │ │ └── lyqf │ │ │ └── bullet │ │ │ └── comet │ │ │ ├── package-info.java │ │ │ ├── netty │ │ │ ├── package-info.java │ │ │ └── handler │ │ │ │ ├── MessageHandler.java │ │ │ │ └── AuthHandler.java │ │ │ ├── util │ │ │ ├── ThreadPoolExecutorService.java │ │ │ ├── SpringContextBeanUtil.java │ │ │ ├── UrlUtil.java │ │ │ └── IpUtil.java │ │ │ ├── biz │ │ │ └── PushBiz.java │ │ │ ├── constant │ │ │ ├── ExceptionCodeConstant.java │ │ │ └── CommonConstant.java │ │ │ ├── vo │ │ │ ├── LoginReq.java │ │ │ └── PushMsg.java │ │ │ ├── dto │ │ │ └── UserRoomDTO.java │ │ │ ├── exception │ │ │ ├── ParamsException.java │ │ │ └── ServiceBizException.java │ │ │ ├── CometApp.java │ │ │ ├── web │ │ │ ├── CloseUserConnectionController.java │ │ │ └── PushController.java │ │ │ ├── manager │ │ │ └── UserClearOnlineManager.java │ │ │ ├── context │ │ │ └── UserContextHolder.java │ │ │ └── BulletChatServer.java │ └── pom.xml ├── bullet-comet-facade │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── lyqf │ │ │ └── bullet │ │ │ └── comet │ │ │ └── client │ │ │ ├── PushClient.java │ │ │ ├── CloseUserConnectionClient.java │ │ │ └── vo │ │ │ └── PushReq.java │ └── pom.xml └── pom.xml ├── bullet-logic ├── bullet-logic-core │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── bootstrap.yml │ │ │ └── application.yml │ │ │ └── java │ │ │ └── com │ │ │ └── lyqf │ │ │ └── bullet │ │ │ └── logic │ │ │ ├── enums │ │ │ └── package-info.java │ │ │ ├── acl │ │ │ ├── package-info.java │ │ │ └── CloseUserConnectionFacadeAcl.java │ │ │ ├── dto │ │ │ ├── UserLiveDTO.java │ │ │ ├── UserInfoDTO.java │ │ │ ├── RoomDTO.java │ │ │ └── MsgDTO.java │ │ │ ├── constant │ │ │ ├── KafkaConstant.java │ │ │ └── RedisConstant.java │ │ │ ├── util │ │ │ └── BulletDateUtil.java │ │ │ ├── LogicApp.java │ │ │ ├── config │ │ │ ├── CustomerLoadBalancerConfig.java │ │ │ └── RedisConfig.java │ │ │ ├── web │ │ │ ├── UserAndRoomSimpleManagerController.java │ │ │ ├── UserMsgController.java │ │ │ └── UserAuthController.java │ │ │ ├── KafkaProducerConfig.java │ │ │ └── manager │ │ │ ├── KafkaManger.java │ │ │ └── UserAuthManager.java │ └── pom.xml ├── bullet-logic-facade │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── lyqf │ │ │ └── bullet │ │ │ └── logic │ │ │ └── client │ │ │ ├── package-info.java │ │ │ ├── vo │ │ │ ├── ClearOnlineReq.java │ │ │ ├── AuthUserReq.java │ │ │ ├── UserMsgResp.java │ │ │ ├── AuthUserResp.java │ │ │ └── UserMsgReq.java │ │ │ ├── UserMsgClient.java │ │ │ └── UserAuthClient.java │ └── pom.xml └── pom.xml ├── bullet-push ├── bullet-push-core │ ├── src │ │ └── main │ │ │ ├── resources │ │ │ ├── bootstrap.yml │ │ │ └── application.yml │ │ │ └── java │ │ │ └── com │ │ │ └── lyqf │ │ │ └── bullet │ │ │ └── push │ │ │ ├── acl │ │ │ └── package-info.java │ │ │ ├── consumer │ │ │ ├── SysMsgConsumer.java │ │ │ ├── CommonMsgConsumer.java │ │ │ └── ExtMsgConsumer.java │ │ │ ├── constant │ │ │ └── KafkaConstant.java │ │ │ ├── PushApp.java │ │ │ ├── web │ │ │ └── TestContoller.java │ │ │ ├── manager │ │ │ └── LiveNodeManager.java │ │ │ ├── config │ │ │ ├── CustomerLoadBalancerConfig.java │ │ │ ├── KafkaConsumerConfig.java │ │ │ └── RedisConfig.java │ │ │ └── biz │ │ │ ├── ExtMsgPushBiz.java │ │ │ └── CommonMsgPushBiz.java │ └── pom.xml ├── bullet-push-facade │ └── pom.xml └── pom.xml ├── bullet-common ├── src │ └── main │ │ └── java │ │ └── com │ │ └── lyqf │ │ └── bullet │ │ └── common │ │ ├── util │ │ └── package-info.java │ │ ├── constant │ │ ├── CommonConstant.java │ │ └── RedisConstant.java │ │ ├── enums │ │ ├── LevelEnum.java │ │ └── OperateTypeEnum.java │ │ ├── model │ │ ├── Request.java │ │ ├── Response.java │ │ └── ApiResponse.java │ │ ├── exception │ │ ├── ProviderNotFoundException.java │ │ └── BizException.java │ │ ├── holder │ │ └── RpcContextHolder.java │ │ └── loadbanlance │ │ └── CustomerLoadBalance.java └── pom.xml ├── .gitignore ├── README.md ├── bullet-client ├── src │ └── main │ │ └── java │ │ └── com │ │ └── lyqf │ │ └── bullet │ │ └── client │ │ ├── BulletChatClient.java │ │ └── handler │ │ └── WebSocketClientHandler.java └── pom.xml └── pom.xml /bullet-comet/bullet-comet-core/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bullet-common/src/main/java/com/lyqf/bullet/common/util/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @author chenlang 3 | * @date 2022/5/12 4:20 下午 4 | */ 5 | package com.lyqf.bullet.common.util; -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @author chenlang 3 | * @date 2022/5/25 3:51 下午 4 | */ 5 | package com.lyqf.bullet.comet; -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/netty/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @author chenlang 3 | * @date 2022/5/11 2:58 下午 4 | */ 5 | package com.lyqf.bullet.comet.netty; -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/enums/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @author chenlang 3 | * @date 2022/5/26 5:18 下午 4 | */ 5 | package com.lyqf.bullet.logic.enums; -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-facade/src/main/java/com/lyqf/bullet/logic/client/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @author chenlang 3 | * @date 2022/5/25 11:03 下午 4 | */ 5 | package com.lyqf.bullet.logic.client; -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/acl/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 防腐层代码(限流,功能转化,类试adapter作用) 4 | * todo 调用其他服务的入口统一放在这里 5 | * @author chenlang 6 | * @date 2022/5/30 1:25 下午 7 | */ 8 | package com.lyqf.bullet.logic.acl; -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/java/com/lyqf/bullet/push/acl/package-info.java: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 防腐层代码(限流,功能转化,类试adapter作用) 4 | * todo 调用其他服务的入口统一放在这里 5 | * 6 | * @author chenlang 7 | * @date 2022/5/30 1:25 下午 8 | */ 9 | package com.lyqf.bullet.push.acl; -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/java/com/lyqf/bullet/push/consumer/SysMsgConsumer.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.push.consumer; 2 | 3 | /** 4 | * @author chenlang 5 | * @date 2022/5/17 3:55 下午 6 | */ 7 | 8 | public class SysMsgConsumer { 9 | } 10 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/util/ThreadPoolExecutorService.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.util; 2 | 3 | /** 4 | * @author chenlang 5 | * @date 2022/5/11 2:58 下午 6 | */ 7 | public class ThreadPoolExecutorService { 8 | // todo 9 | } 10 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/biz/PushBiz.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.biz; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | /** 6 | * @author chenlang 7 | * @date 2022/5/17 4:45 下午 8 | */ 9 | @Component 10 | public class PushBiz { 11 | 12 | 13 | } 14 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: bullet-comet 4 | main: 5 | allow-bean-definition-overriding: true 6 | cloud: 7 | nacos: 8 | discovery: 9 | # server-addr: 127.0.0.1:8848 10 | server-addr: 39.105.34.3:8848 11 | 12 | server: 13 | port: 8895 -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/dto/UserLiveDTO.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author chenlang 7 | * @date 2022/5/12 4:36 下午 8 | */ 9 | 10 | @Data 11 | public class UserLiveDTO { 12 | private Long id; 13 | private Long roomId; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/constant/ExceptionCodeConstant.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.constant; 2 | 3 | /** 4 | * @author chenlang 5 | * @date 2022/5/11 4:29 下午 6 | */ 7 | public class ExceptionCodeConstant { 8 | 9 | /** 10 | * 11 | */ 12 | public static final Integer PARAMS_ERROR_CODE = 40001; 13 | } 14 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/constant/CommonConstant.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.constant; 2 | 3 | /** 4 | * @author chenlang 5 | * @date 2022/5/11 4:00 下午 6 | */ 7 | public class CommonConstant { 8 | 9 | /** 10 | * 11 | */ 12 | public static final String WEBSOCKET_URL = "ws://localhost:8099/websocket"; 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | -------------------------------------------------------------------------------- /bullet-common/src/main/java/com/lyqf/bullet/common/constant/CommonConstant.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.common.constant; 2 | 3 | /** 4 | * @author chenlang 5 | * @date 2022/5/24 4:04 下午 6 | */ 7 | public class CommonConstant { 8 | 9 | /** 10 | * 11 | */ 12 | public static final String CUSTOMER_NODE_ID = "customer_node_id"; 13 | 14 | public static final Integer NO_PROVIDER_ERROR_CODE = 4444; 15 | } 16 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/java/com/lyqf/bullet/push/constant/KafkaConstant.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.push.constant; 2 | 3 | /** 4 | * @author chenlang 5 | * @date 2022/5/17 4:06 下午 6 | */ 7 | public class KafkaConstant { 8 | 9 | public static final String SYS_MSG_TOPIC = "sys_msg"; 10 | 11 | public static final String EXT_MSG_TOPIC = "ext_msg"; 12 | 13 | public static final String COMMON_MSG_TOPIC = "common_msg"; 14 | } 15 | -------------------------------------------------------------------------------- /bullet-common/src/main/java/com/lyqf/bullet/common/enums/LevelEnum.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.common.enums; 2 | 3 | /** 4 | * @author chenlang 5 | * @date 2022/5/16 4:14 下午 6 | */ 7 | public enum LevelEnum { 8 | 9 | SYSTEM(1), EXT(2), COMMON(3), HEART(4); 10 | 11 | int code; 12 | 13 | LevelEnum(int code) { 14 | this.code = code; 15 | } 16 | 17 | public Integer getCode() { 18 | return code; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/constant/KafkaConstant.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.constant; 2 | 3 | /** 4 | * @author chenlang 5 | * @date 2022/5/16 4:28 下午 6 | */ 7 | public class KafkaConstant { 8 | 9 | public static final String SYS_MSG_TOPIC = "sys_msg"; 10 | 11 | public static final String EXT_MSG_TOPIC = "ext_msg"; 12 | 13 | public static final String COMMON_MSG_TOPIC = "common_msg"; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/vo/LoginReq.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.vo; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * @author chenlang 9 | * @date 2022/5/11 4:19 下午 10 | */ 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @Data 14 | public class LoginReq { 15 | 16 | private Long roomId; 17 | 18 | private String token; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /bullet-common/src/main/java/com/lyqf/bullet/common/constant/RedisConstant.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.common.constant; 2 | 3 | /** 4 | * @author chenlang 5 | * @date 2022/5/12 4:18 下午 6 | */ 7 | public class RedisConstant { 8 | 9 | /** 10 | * 用户在哪个节点上 11 | */ 12 | public static final String USER_NODE = "user_node:%s"; 13 | 14 | 15 | /** 16 | * 房间在哪些节点上 17 | */ 18 | public static final String ROOM_NODE = "room_nodes:%s"; 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: bullet-logic 4 | main: 5 | allow-bean-definition-overriding: true 6 | cloud: 7 | nacos: 8 | discovery: 9 | # server-addr: 127.0.0.1:8848 10 | server-addr: 192.168.22.129:8848 11 | 12 | server: 13 | port: 8086 14 | 15 | spring.redis.cluster.nodes: 82.157.68.174:7001,82.157.68.174:7002,82.157.68.174:7003 16 | spring.kafka.nodes: 192.168.22.129:9092 -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/dto/UserRoomDTO.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.Data; 6 | 7 | /** 8 | * @author chenlang 9 | * @date 2022/5/17 2:10 下午 10 | */ 11 | @Data 12 | public class UserRoomDTO implements Serializable { 13 | 14 | private static final long serialVersionUID = 169849378576190669L; 15 | 16 | private Long userId; 17 | 18 | private Long roomId; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-facade/src/main/java/com/lyqf/bullet/logic/client/vo/ClearOnlineReq.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.client.vo; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.Data; 6 | 7 | /** 8 | * @author chenlang 9 | * @date 2022/5/17 1:57 下午 10 | */ 11 | 12 | @Data 13 | public class ClearOnlineReq implements Serializable { 14 | 15 | private static final long serialVersionUID = 1760383264305020478L; 16 | 17 | private Long userId; 18 | 19 | private Long roomId; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/exception/ParamsException.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.exception; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author chenlang 7 | * @date 2022/5/11 4:26 下午 8 | */ 9 | 10 | @Data 11 | public class ParamsException extends RuntimeException { 12 | 13 | private Integer code; 14 | private String msg; 15 | 16 | public ParamsException(Integer code, String msg) { 17 | this.code = code; 18 | this.msg = msg; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/dto/UserInfoDTO.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.dto; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * @author chenlang 9 | * @date 2022/5/12 4:07 下午 10 | */ 11 | @Data 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | public class UserInfoDTO { 15 | private Long id; 16 | private String userIdCardName; 17 | private String nickName; 18 | private String headUrl; 19 | } 20 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/exception/ServiceBizException.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.exception; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author chenlang 7 | * @date 2022/5/11 4:26 下午 8 | */ 9 | 10 | @Data 11 | public class ServiceBizException extends RuntimeException { 12 | 13 | private Integer code; 14 | private String msg; 15 | 16 | public ServiceBizException(Integer code, String msg) { 17 | this.code = code; 18 | this.msg = msg; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/util/BulletDateUtil.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.util; 2 | 3 | import java.util.Date; 4 | 5 | import org.apache.commons.lang3.time.DateUtils; 6 | 7 | /** 8 | * @author chenlang 9 | * @date 2022/5/17 2:03 下午 10 | */ 11 | public class BulletDateUtil { 12 | 13 | /** 14 | * 获取当前时间的下一天这个时间 15 | * @return 16 | */ 17 | public static Date genNextDateFromCurrentDate() { 18 | return DateUtils.addDays(new Date(), 1); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /bullet-common/src/main/java/com/lyqf/bullet/common/model/Request.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.common.model; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.Data; 6 | 7 | /** 8 | * @author chenlang 9 | * @date 2022/5/11 7:00 下午 10 | */ 11 | @Data 12 | public class Request implements Serializable { 13 | 14 | private static final long serialVersionUID = 4809210224829105997L; 15 | 16 | private Long userId; 17 | 18 | private Long roomId; 19 | 20 | private Integer operation; 21 | 22 | private String data; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /bullet-common/src/main/java/com/lyqf/bullet/common/model/Response.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.common.model; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.Data; 6 | 7 | /** 8 | * @author chenlang 9 | * @date 2022/5/11 7:00 下午 10 | */ 11 | @Data 12 | public class Response implements Serializable { 13 | 14 | private static final long serialVersionUID = 4809210224829105997L; 15 | 16 | private Long userId; 17 | 18 | private Long roomId; 19 | 20 | private Integer operation; 21 | 22 | private String data; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /bullet-common/src/main/java/com/lyqf/bullet/common/exception/ProviderNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.common.exception; 2 | 3 | /** 4 | * @author chenlang 5 | * @date 2022/5/30 2:36 下午 6 | */ 7 | public class ProviderNotFoundException extends RuntimeException { 8 | private Integer code; 9 | private String ms; 10 | 11 | /** 12 | * 13 | * @param code 14 | * @param message 15 | */ 16 | public ProviderNotFoundException(Integer code, String message) { 17 | this.code = code; 18 | this.ms = message; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: bullet-agg 4 | main: 5 | allow-bean-definition-overriding: true 6 | cloud: 7 | nacos: 8 | discovery: 9 | # server-addr: 127.0.0.1:8848 10 | server-addr: 192.168.22.129:8848 11 | 12 | server: 13 | port: 8087 14 | 15 | spring.kafka.consumer.group-id: bullet-agg 16 | spring.kafka.nodes: 192.168.22.129:9092 17 | spring.kafka.consumer.auto-offset-reset: earliest 18 | 19 | spring.redis.cluster.nodes: 82.157.68.174:7001,82.157.68.174:7002,82.157.68.174:7003 20 | 21 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/constant/RedisConstant.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.constant; 2 | 3 | /** 4 | * @author chenlang 5 | * @date 2022/5/12 4:18 下午 6 | */ 7 | public class RedisConstant { 8 | 9 | public static final String TOKEN_KEY = "user_token:%s"; 10 | 11 | public static final String USER_INFO_KEY = "user_info:%s"; 12 | 13 | public static final String ROOM_KEY = "room:%s"; 14 | 15 | /** 16 | * roomId 17 | */ 18 | public static final String ANONYMOUS_USER_ID_START_KEY = "user_anonymous_start"; 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-facade/src/main/java/com/lyqf/bullet/logic/client/vo/AuthUserReq.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.client.vo; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.Data; 6 | 7 | /** 8 | * @author chenlang 9 | * @date 2022/5/12 3:48 下午 10 | */ 11 | 12 | @Data 13 | public class AuthUserReq implements Serializable { 14 | 15 | private static final long serialVersionUID = 7090117243937573161L; 16 | 17 | private String token; 18 | 19 | private String userName; 20 | 21 | private Long roomId; 22 | 23 | private String node; 24 | 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /bullet-common/src/main/java/com/lyqf/bullet/common/exception/BizException.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.common.exception; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author chenlang 7 | * @date 2022/5/23 11:40 上午 8 | */ 9 | 10 | @Data 11 | public class BizException extends RuntimeException { 12 | 13 | private Integer code; 14 | private String ms; 15 | 16 | 17 | /** 18 | * 19 | * @param code 20 | * @param message 21 | */ 22 | public BizException(Integer code, String message) { 23 | this.code = code; 24 | this.ms = message; 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /bullet-common/src/main/java/com/lyqf/bullet/common/enums/OperateTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.common.enums; 2 | 3 | /** 4 | * @author chenlang 5 | * @date 2022/5/23 11:22 上午 6 | */ 7 | public enum OperateTypeEnum { 8 | 9 | /** 10 | * 11 | */ 12 | TXT_MSG(1), IMAG_MSG(2), LIKE_MSG(3), ONLINE_EXT_MSG(11), LOTTERY__SYS_MSG(12), RED_ENVELOPE_SYS_MSG(13), 13 | PRODUCT_SYS_MSG(14), HERART_PING(100); 14 | 15 | int code; 16 | 17 | OperateTypeEnum(int code) { 18 | this.code = code; 19 | } 20 | 21 | public Integer getCode() { 22 | return code; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/vo/PushMsg.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.vo; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author chenlang 7 | * @date 2022/5/17 5:17 下午 8 | */ 9 | @Data 10 | public class PushMsg { 11 | 12 | /** 13 | * 操作类型 1 文本聊天 2 图片聊天 3 点赞 10以上为扩展功能 (抽奖,发红包,推产品,禁言,踢人等) 14 | */ 15 | private Integer operateType; 16 | 17 | /** 18 | * 具体内容 19 | */ 20 | private String content; 21 | 22 | /** 23 | * 24 | */ 25 | private Long userId; 26 | 27 | /** 28 | * 29 | */ 30 | private String userNickName; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-facade/src/main/java/com/lyqf/bullet/logic/client/vo/UserMsgResp.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.client.vo; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.Data; 6 | 7 | /** 8 | * @author chenlang 9 | * @date 2022/5/16 2:49 下午 10 | */ 11 | 12 | @Data 13 | public class UserMsgResp implements Serializable { 14 | 15 | private static final long serialVersionUID = 2869741037236201052L; 16 | 17 | private String reqId; 18 | 19 | /** 20 | * ture 成功 21 | */ 22 | private boolean result; 23 | 24 | /** 25 | * 返回其他内容信息 26 | */ 27 | private String data; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/dto/RoomDTO.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.dto; 2 | 3 | import java.util.Date; 4 | 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | 7 | import lombok.AllArgsConstructor; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | 11 | /** 12 | * @author chenlang 13 | * @date 2022/5/12 4:07 下午 14 | */ 15 | @Data 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | public class RoomDTO { 19 | private Long id; 20 | private String name; 21 | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") 22 | private Date startTime; 23 | private String imgUrl; 24 | private String desc; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bullet-chat 2 | # 直播弹幕系统 3 | 该弹幕系统主要四大组件组成:

4 | comet 接入层:该模块主要负责与client端(web浏览器)建立websocket长链接并维持长链接稳定性;

5 | logic 逻辑处理层:该模块主要提供:消息转发(mq)、登录逻辑处理、记录client与comet实例之间的关系、为client端提供可用的comet实例、为外部 6 | 提供http直接发送消息的能力;

7 | push 消息路由推送层:用户发送消到到logic,logic将消息发送到Mq中,push从Mq获取消息进行处理,然后通过logic获取消息的目的地(具体的comet实例), 8 | 并获取对应实例(rpc路由)并消息推送至comet,再由comet长链接推送到client端;

9 | client 客户端:与comet进行建立链接,接收消息和发送消息;

10 | 11 | 该项目只提供了基本的收发消息能力,比如像消息的敏感词、消息过滤及限流、client询址(server)等能力并没有提供。启动该项目还需要依赖 12 | kafka,redis,nacos 这几个中间件。本项目原则上支持水平扩展,但这个到了一定量级后要考虑的是db的路由链接等能力。 13 | 14 | ## 架构图: 15 | 图片 16 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-facade/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bullet-push 7 | com.lyqf 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | bullet-push-facade 13 | 14 | 15 | 8 16 | 8 17 | 18 | 19 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-facade/src/main/java/com/lyqf/bullet/logic/client/vo/AuthUserResp.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.client.vo; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.Data; 6 | 7 | /** 8 | * @author chenlang 9 | * @date 2022/5/12 4:39 下午 10 | */ 11 | @Data 12 | public class AuthUserResp implements Serializable { 13 | private static final long serialVersionUID = -1185223096517555110L; 14 | 15 | /** 16 | * 是否是重复登录 true是 17 | */ 18 | private Boolean repeatLogin = false; 19 | 20 | /** 21 | * 登录结果 true 为成功 22 | */ 23 | private Boolean loginResult = true; 24 | 25 | /** 26 | * 27 | */ 28 | private String msg; 29 | 30 | private Long userId; 31 | 32 | private Long roomId; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /bullet-common/src/main/java/com/lyqf/bullet/common/holder/RpcContextHolder.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.common.holder; 2 | 3 | import com.alibaba.ttl.TransmittableThreadLocal; 4 | 5 | /** 6 | * @author chenlang 7 | * @date 2022/5/30 11:13 上午 8 | */ 9 | public class RpcContextHolder { 10 | 11 | private static TransmittableThreadLocal context = new TransmittableThreadLocal<>(); 12 | 13 | /** 14 | * 15 | * @return 16 | */ 17 | public static String getStringValue() { 18 | return context.get(); 19 | } 20 | 21 | public static void setStringParameter(String data) { 22 | context.set(data); 23 | } 24 | 25 | /** 26 | * 27 | */ 28 | public static void remove() { 29 | context.remove(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-facade/src/main/java/com/lyqf/bullet/comet/client/PushClient.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.client; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.RequestBody; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | 8 | import com.lyqf.bullet.comet.client.vo.PushReq; 9 | import com.lyqf.bullet.common.model.ApiResponse; 10 | 11 | /** 12 | * @author chenlang 13 | * @date 2022/5/17 4:31 下午 14 | */ 15 | 16 | @FeignClient("bullet-comet") 17 | public interface PushClient { 18 | 19 | /** 20 | * 21 | * @param pushReq 22 | * @return 23 | */ 24 | @RequestMapping(value = "/push", method = RequestMethod.POST) 25 | ApiResponse push(@RequestBody PushReq pushReq); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /bullet-push/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bullet-chat-new 7 | com.lyqf 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | bullet-push 13 | pom 14 | 15 | bullet-push-core 16 | bullet-push-facade 17 | 18 | 19 | 20 | 8 21 | 8 22 | 23 | 24 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-facade/src/main/java/com/lyqf/bullet/logic/client/UserMsgClient.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.client; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.RequestBody; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | 8 | import com.lyqf.bullet.logic.client.vo.UserMsgReq; 9 | import com.lyqf.bullet.logic.client.vo.UserMsgResp; 10 | 11 | /** 12 | * @author chenlang 13 | * @date 2022/5/16 Re6 下午 14 | */ 15 | @FeignClient("bullet-logic") 16 | public interface UserMsgClient { 17 | 18 | /** 19 | * 用户发送消息 20 | * 21 | * @param userMsgReq 22 | * @return 23 | */ 24 | @RequestMapping(value = "/send-msg", method = RequestMethod.POST) 25 | UserMsgResp sendMsg(@RequestBody UserMsgReq userMsgReq); 26 | } 27 | -------------------------------------------------------------------------------- /bullet-logic/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bullet-chat-new 7 | com.lyqf 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | bullet-logic 13 | pom 14 | 15 | bullet-logic-core 16 | bullet-logic-facade 17 | 18 | 19 | 20 | 8 21 | 8 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-facade/src/main/java/com/lyqf/bullet/comet/client/CloseUserConnectionClient.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.client; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RequestMethod; 6 | 7 | import com.lyqf.bullet.common.model.ApiResponse; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | 10 | /** 11 | * @author chenlang 12 | * @date 2022/5/23 11:33 上午 13 | */ 14 | @FeignClient("bullet-comet") 15 | public interface CloseUserConnectionClient { 16 | 17 | /** 18 | * 断开用户老连接 19 | * 20 | * @param userId 21 | * @param roomId 22 | * @return 23 | */ 24 | @RequestMapping(value = "/close", method = RequestMethod.GET) 25 | ApiResponse close(@RequestParam Long userId, @RequestParam Long roomId); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-facade/src/main/java/com/lyqf/bullet/comet/client/vo/PushReq.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.client.vo; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * @author chenlang 9 | * @date 2022/5/17 4:31 下午 10 | */ 11 | 12 | @Data 13 | public class PushReq implements Serializable { 14 | 15 | private static final long serialVersionUID = -1043305543639856086L; 16 | 17 | /** 18 | * 系统用户id 19 | */ 20 | private Long sysUserId; 21 | 22 | private String sysUserName; 23 | 24 | private Long userId; 25 | 26 | private String userNickName; 27 | 28 | private Long roomId; 29 | 30 | /** 31 | * 单聊的userId,该值为null代表群聊 32 | */ 33 | private Long toUserId; 34 | 35 | /** 36 | * 37 | */ 38 | private Integer operateType; 39 | 40 | /** 41 | * 具体内容 42 | */ 43 | private String content; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/java/com/lyqf/bullet/push/PushApp.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.push; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | import org.springframework.cloud.openfeign.EnableFeignClients; 7 | 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | /** 11 | * @author chenlang 12 | * @date 2022/5/17 9:01 上午 13 | */ 14 | 15 | @Slf4j 16 | @EnableFeignClients(basePackages = {"com.lyqf.bullet.*"}) 17 | @EnableDiscoveryClient 18 | @SpringBootApplication 19 | public class PushApp { 20 | 21 | public static void main(String[] args) { 22 | try { 23 | SpringApplication app = new SpringApplication(PushApp.class); 24 | app.run(args); 25 | } catch (Exception e) { 26 | log.warn("main方法启动失败,", e); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /bullet-comet/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bullet-chat-new 7 | com.lyqf 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | bullet-comet 13 | pom 14 | 15 | bullet-comet-core 16 | bullet-comet-facade 17 | 18 | 19 | 20 | 21 | org.springframework.cloud 22 | spring-cloud-starter-openfeign 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/java/com/lyqf/bullet/push/consumer/CommonMsgConsumer.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.push.consumer; 2 | 3 | import org.apache.kafka.clients.consumer.ConsumerRecord; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.kafka.annotation.KafkaListener; 6 | import org.springframework.stereotype.Component; 7 | 8 | import com.lyqf.bullet.push.biz.CommonMsgPushBiz; 9 | import com.lyqf.bullet.push.constant.KafkaConstant; 10 | 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | /** 14 | * @author chenlang 15 | * @date 2022/5/17 3:55 下午 16 | */ 17 | @Slf4j 18 | @Component 19 | public class CommonMsgConsumer { 20 | 21 | @Autowired 22 | private CommonMsgPushBiz commonMsgPushBiz; 23 | 24 | @KafkaListener(topics = KafkaConstant.COMMON_MSG_TOPIC) 25 | public void listen(ConsumerRecord record) { 26 | String data = record.value().toString(); 27 | log.info("receive data :{}", data); 28 | commonMsgPushBiz.pushCommonMsg(data); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/util/SpringContextBeanUtil.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.util; 2 | 3 | import java.util.Locale; 4 | 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.context.ApplicationContextAware; 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * @author chenlang 12 | * @date 2022/5/17 11:01 上午 13 | */ 14 | @Component 15 | public class SpringContextBeanUtil implements ApplicationContextAware { 16 | 17 | private static ApplicationContext context; 18 | 19 | @Override 20 | public void setApplicationContext(ApplicationContext context) throws BeansException { 21 | SpringContextBeanUtil.context = context; 22 | } 23 | 24 | public static T getBean(Class beanClass) { 25 | return context.getBean(beanClass); 26 | } 27 | 28 | public static String getMessage(String key) { 29 | return context.getMessage(key, null, Locale.getDefault()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/acl/CloseUserConnectionFacadeAcl.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.acl; 2 | 3 | import com.lyqf.bullet.comet.client.CloseUserConnectionClient; 4 | import com.lyqf.bullet.common.exception.ProviderNotFoundException; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Component; 8 | 9 | /** 10 | * @author chenlang 11 | * @date 2022/5/30 2:41 下午 12 | */ 13 | 14 | @Slf4j 15 | @Component 16 | public class CloseUserConnectionFacadeAcl { 17 | 18 | @Autowired 19 | private CloseUserConnectionClient closeUserConnectionClient; 20 | 21 | /** 22 | * 23 | * @param userId 24 | * @param roomId 25 | * @return 26 | */ 27 | public boolean close(Long userId, Long roomId) { 28 | try { 29 | closeUserConnectionClient.close(userId, roomId); 30 | } catch (ProviderNotFoundException e) { 31 | log.info("e", e); 32 | } 33 | return true; 34 | } 35 | 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-facade/src/main/java/com/lyqf/bullet/logic/client/vo/UserMsgReq.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.client.vo; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * @author chenlang 9 | * @date 2022/5/16 2:49 下午 10 | */ 11 | 12 | @Data 13 | public class UserMsgReq implements Serializable { 14 | 15 | private static final long serialVersionUID = 2869741037236201052L; 16 | 17 | /** 18 | * 唯一id 19 | */ 20 | private String reqId; 21 | 22 | private Long userId; 23 | 24 | /** 25 | * 用户昵称 26 | */ 27 | private String userNickName; 28 | 29 | /** 30 | * 用户身证证姓名 31 | */ 32 | private String userIdCardName; 33 | 34 | /** 35 | * 头像 36 | */ 37 | private String headUrl; 38 | 39 | /** 40 | * 41 | */ 42 | private Long roomId; 43 | 44 | /** 45 | * 单聊的userId,该值为null代表群聊 46 | */ 47 | private Long toUserId; 48 | 49 | /** 50 | * 51 | */ 52 | private Integer operateType; 53 | 54 | /** 55 | * 具体内容 56 | */ 57 | private String content; 58 | } 59 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/CometApp.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | import org.springframework.cloud.openfeign.EnableFeignClients; 7 | 8 | import lombok.extern.slf4j.Slf4j; 9 | 10 | /** 11 | * 前端使用:https://github.com/joewalnes/reconnecting-websocket 12 | * @author chenlang 13 | * @date 2022/5/10 5:32 下午 14 | */ 15 | 16 | @Slf4j 17 | @EnableDiscoveryClient 18 | @EnableFeignClients(basePackages = {"com.lyqf.bullet.*"}) 19 | @SpringBootApplication 20 | public class CometApp { 21 | 22 | public static void main(String[] args) { 23 | try { 24 | SpringApplication app = new SpringApplication(CometApp.class); 25 | app.run(args); 26 | 27 | BulletChatServer server = new BulletChatServer(8889); 28 | server.start(); 29 | } catch (Exception e) { 30 | log.warn("main方法启动失败,", e); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/java/com/lyqf/bullet/push/consumer/ExtMsgConsumer.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.push.consumer; 2 | 3 | import org.apache.kafka.clients.consumer.ConsumerRecord; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.kafka.annotation.KafkaListener; 6 | import org.springframework.stereotype.Component; 7 | 8 | import com.lyqf.bullet.push.biz.ExtMsgPushBiz; 9 | import com.lyqf.bullet.push.constant.KafkaConstant; 10 | 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | /** 14 | * @author chenlang 15 | * @date 2022/5/17 3:55 下午 16 | */ 17 | 18 | @Slf4j 19 | @Component 20 | public class ExtMsgConsumer { 21 | 22 | @Autowired 23 | private ExtMsgPushBiz extMsgPushBiz; 24 | 25 | 26 | /** 27 | * 28 | * @param record 29 | */ 30 | @KafkaListener(topics = KafkaConstant.EXT_MSG_TOPIC) 31 | public void listen(ConsumerRecord record) { 32 | String data = record.value().toString(); 33 | log.info("ExtMsgConsumer receive data :{}", data); 34 | extMsgPushBiz.pushExtMsg(data); 35 | } 36 | 37 | 38 | 39 | } 40 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-facade/src/main/java/com/lyqf/bullet/logic/client/UserAuthClient.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.client; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.RequestBody; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | 8 | import com.lyqf.bullet.common.model.ApiResponse; 9 | import com.lyqf.bullet.logic.client.vo.AuthUserReq; 10 | import com.lyqf.bullet.logic.client.vo.AuthUserResp; 11 | import com.lyqf.bullet.logic.client.vo.ClearOnlineReq; 12 | 13 | /** 14 | * @author chenlang 15 | * @date 2022/5/12 3:10 下午 16 | */ 17 | 18 | @FeignClient("bullet-logic") 19 | public interface UserAuthClient { 20 | 21 | @RequestMapping(value = "/auth", method = RequestMethod.POST) 22 | ApiResponse authUser(@RequestBody AuthUserReq req); 23 | 24 | /** 25 | * 26 | * @return 27 | */ 28 | @RequestMapping(value = "/clear-online", method = RequestMethod.POST) 29 | ApiResponse clearOnlineByUserId(@RequestBody ClearOnlineReq req); 30 | } 31 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/web/CloseUserConnectionController.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.web; 2 | 3 | import com.lyqf.bullet.comet.context.UserContextHolder; 4 | import com.lyqf.bullet.common.model.ApiResponse; 5 | import io.netty.channel.Channel; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | /** 12 | * @author chenlang 13 | * @date 2022/5/24 1:39 下午 14 | */ 15 | 16 | @Slf4j 17 | @RestController 18 | public class CloseUserConnectionController { 19 | 20 | @GetMapping("/close") 21 | public ApiResponse close(@RequestParam Long userId, @RequestParam Long roomId) { 22 | Channel channel = UserContextHolder.userWithChannelMap.get(userId); 23 | UserContextHolder.clear(channel, userId, roomId); 24 | 25 | if (channel != null) { 26 | channel.close(); 27 | } 28 | log.info("重复登录,clear上一条链接信息,userId:{},roomId:{}", userId, roomId); 29 | return ApiResponse.genSuccessData(true); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/LogicApp.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic; 2 | 3 | import com.lyqf.bullet.logic.config.CustomerLoadBalancerConfig; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 8 | import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; 9 | import org.springframework.cloud.openfeign.EnableFeignClients; 10 | 11 | /** 12 | * @author chenlang 13 | * @date 2022/5/17 9:01 上午 14 | */ 15 | 16 | @Slf4j 17 | @EnableFeignClients(basePackages = {"com.lyqf.bullet.comet.client"}) 18 | @EnableDiscoveryClient 19 | @SpringBootApplication 20 | @LoadBalancerClient(name = "myServer", configuration = CustomerLoadBalancerConfig.class) 21 | public class LogicApp { 22 | 23 | public static void main(String[] args) { 24 | try { 25 | SpringApplication app = new SpringApplication(LogicApp.class); 26 | app.run(args); 27 | } catch (Exception e) { 28 | log.warn("main方法启动失败,", e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-facade/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bullet-comet 7 | com.lyqf 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | bullet-comet-facade 13 | 14 | 15 | 8 16 | 8 17 | 18 | 19 | 20 | 21 | com.lyqf 22 | bullet-common 23 | 1.0-SNAPSHOT 24 | 25 | 26 | 27 | org.projectlombok 28 | lombok 29 | true 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/dto/MsgDTO.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.dto; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * @author chenlang 7 | * @date 2022/5/16 3:02 下午 8 | */ 9 | @Data 10 | public class MsgDTO { 11 | 12 | /** 13 | * 14 | */ 15 | private Long sysUserId; 16 | 17 | private Long userId; 18 | 19 | /** 20 | * 用户昵称 21 | */ 22 | private String userNickName; 23 | 24 | /** 25 | * 用户身证证姓名 26 | */ 27 | private String userIdCardName; 28 | 29 | /** 30 | * 头像 31 | */ 32 | private String headUrl; 33 | 34 | private Long roomId; 35 | 36 | /** 37 | * 消息优先级: 1 系统级别(12 抽奖 ,13 发红包,14 推产品,15 禁言,16 踢人 ) 2.扩展自定义消息 (20 用户上线) 3.普通消息 (1 文本聊天 2 图片聊天 3 点赞) 4.心跳消息 38 | */ 39 | private Integer level; 40 | 41 | /** 42 | * 单聊的userId,该值为null代表群聊 43 | */ 44 | private Long toUserId; 45 | 46 | /** 47 | * 操作类型 1 文本聊天 2 图片聊天 3 点赞 20 以上为扩展功能 11 用户上线,12 抽奖 ,13 发红包,14 推产品, 48 | */ 49 | private Integer operateType; 50 | 51 | /** 52 | * 具体内容 53 | */ 54 | private String content; 55 | 56 | /** 57 | * 扩展字段 58 | */ 59 | private String ext; 60 | } 61 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/java/com/lyqf/bullet/push/web/TestContoller.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.push.web; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestParam; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import com.alibaba.fastjson.JSON; 8 | import com.lyqf.bullet.comet.client.PushClient; 9 | import com.lyqf.bullet.comet.client.vo.PushReq; 10 | import com.lyqf.bullet.common.model.ApiResponse; 11 | 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | import javax.annotation.Resource; 15 | 16 | /** 17 | * @author chenlang 18 | * @date 2022/5/19 7:45 下午 19 | */ 20 | 21 | @Slf4j 22 | @RestController 23 | public class TestContoller { 24 | 25 | @Resource 26 | private PushClient pushClient; 27 | 28 | @GetMapping("/a") 29 | public ApiResponse pushCommonMsg(@RequestParam String data) { 30 | // todo check biz logic and rate limit 31 | PushReq pushReq = JSON.parseObject(data, PushReq.class); 32 | // todo find roomid对应的的node id 33 | 34 | pushClient.push(pushReq); 35 | log.info("push success:{}", data); 36 | 37 | return ApiResponse.genSuccessData(true); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/java/com/lyqf/bullet/push/manager/LiveNodeManager.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.push.manager; 2 | 3 | import com.lyqf.bullet.common.constant.RedisConstant; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.data.redis.core.RedisTemplate; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.Set; 9 | 10 | /** 11 | * @author chenlang 12 | * @date 2022/5/24 5:28 下午 13 | */ 14 | 15 | @Component 16 | public class LiveNodeManager { 17 | 18 | @Autowired 19 | private RedisTemplate redisTemplate; 20 | 21 | /** 22 | * todo 后面改走rpc调用logic服务获取节点信息(redis集群做业务分离) 23 | * 24 | * @param roomId 25 | * @return 26 | */ 27 | public Set getNodeIdsByRoomId(Long roomId) { 28 | String roomNodesKey = String.format(RedisConstant.ROOM_NODE, roomId); 29 | return redisTemplate.opsForSet().members(roomNodesKey); 30 | } 31 | 32 | /** 33 | * todo 后面改走rpc调用logic服务获取节点信息(redis集群做业务分离) 34 | * 35 | * @param userId 36 | * @return 37 | */ 38 | public String getNodeIdsByUserId(Long userId) { 39 | String roomNodesKey = String.format(RedisConstant.USER_NODE, userId); 40 | return (String) redisTemplate.opsForValue().get(roomNodesKey); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/java/com/lyqf/bullet/push/config/CustomerLoadBalancerConfig.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.push.config; 2 | 3 | import org.springframework.cloud.client.ServiceInstance; 4 | import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; 5 | import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; 6 | import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; 7 | import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.core.env.Environment; 10 | 11 | import com.lyqf.bullet.common.loadbanlance.CustomerLoadBalance; 12 | 13 | /** 14 | * @author chenlang 15 | * @date 2022/5/28 11:50 下午 16 | */ 17 | @LoadBalancerClients(defaultConfiguration = CustomerLoadBalancerConfig.class) 18 | public class CustomerLoadBalancerConfig { 19 | 20 | /** 21 | * 22 | * @param 23 | * @return 24 | */ 25 | @Bean 26 | public ReactorLoadBalancer reactorServiceInstanceLoadBalancer(Environment environment, 27 | LoadBalancerClientFactory loadBalancerClientFactory) { 28 | String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); 29 | return new CustomerLoadBalance( 30 | loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/config/CustomerLoadBalancerConfig.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.config; 2 | 3 | import org.springframework.cloud.client.ServiceInstance; 4 | import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients; 5 | import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer; 6 | import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; 7 | import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.core.env.Environment; 10 | 11 | import com.lyqf.bullet.common.loadbanlance.CustomerLoadBalance; 12 | 13 | /** 14 | * @author chenlang 15 | * @date 2022/5/28 11:50 下午 16 | */ 17 | @LoadBalancerClients(defaultConfiguration = CustomerLoadBalancerConfig.class) 18 | public class CustomerLoadBalancerConfig { 19 | 20 | /** 21 | * 22 | * @param 23 | * @return 24 | */ 25 | @Bean 26 | public ReactorLoadBalancer reactorServiceInstanceLoadBalancer(Environment environment, 27 | LoadBalancerClientFactory loadBalancerClientFactory) { 28 | String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); 29 | return new CustomerLoadBalance( 30 | loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-facade/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bullet-logic 7 | com.lyqf 8 | 1.0-SNAPSHOT 9 | 10 | 11 | 4.0.0 12 | bullet-logic-facade 13 | 14 | 15 | 8 16 | 8 17 | 18 | 19 | 20 | 21 | com.lyqf 22 | bullet-common 23 | 1.0-SNAPSHOT 24 | compile 25 | 26 | 27 | 28 | org.projectlombok 29 | lombok 30 | true 31 | 32 | 33 | 34 | org.springframework.cloud 35 | spring-cloud-starter-openfeign 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/web/UserAndRoomSimpleManagerController.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.web; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.data.redis.core.RedisTemplate; 5 | import org.springframework.web.bind.annotation.PostMapping; 6 | import org.springframework.web.bind.annotation.RequestBody; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import com.lyqf.bullet.logic.constant.RedisConstant; 11 | import com.lyqf.bullet.logic.dto.RoomDTO; 12 | import com.lyqf.bullet.logic.dto.UserInfoDTO; 13 | 14 | /** 15 | * @author chenlang 16 | * @date 2022/5/19 1:21 下午 17 | */ 18 | @RestController 19 | @RequestMapping 20 | public class UserAndRoomSimpleManagerController { 21 | 22 | @Autowired 23 | private RedisTemplate redisTemplate; 24 | 25 | 26 | 27 | @PostMapping("/room/add") 28 | public String addRoom(@RequestBody RoomDTO roomDTO) { 29 | String roomKey = String.format(RedisConstant.ROOM_KEY, roomDTO.getId()); 30 | redisTemplate.opsForValue().set(roomKey, roomDTO); 31 | return "success"; 32 | } 33 | 34 | 35 | 36 | /** 37 | * 38 | * @param userInfoDTO 39 | * @return 40 | */ 41 | @PostMapping("/user/add") 42 | public String addUser(@RequestBody UserInfoDTO userInfoDTO) { 43 | String tokenKey = String.format(RedisConstant.TOKEN_KEY, userInfoDTO.getId()); 44 | redisTemplate.opsForValue().set(tokenKey, userInfoDTO); 45 | return "success"; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/util/UrlUtil.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.util; 2 | 3 | import java.util.Map; 4 | 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import com.lyqf.bullet.comet.constant.ExceptionCodeConstant; 8 | import com.lyqf.bullet.comet.exception.ParamsException; 9 | import com.lyqf.bullet.comet.vo.LoginReq; 10 | 11 | import io.github.ljwlgl.util.UrlParamsUtil; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | /** 15 | * @author chenlang 16 | * @date 2022/5/11 4:15 下午 17 | */ 18 | @Slf4j 19 | public class UrlUtil { 20 | 21 | /** 22 | * 23 | * @param url 24 | * @return 25 | */ 26 | public static LoginReq getLoginInfo(String url) { 27 | log.info("getUrlParams url:{}", url); 28 | Map data = UrlParamsUtil.split(url); 29 | 30 | LoginReq loginReq = new LoginReq(); 31 | String roomId = data.get("roomId"); 32 | if (StringUtils.isBlank(roomId)) { 33 | throw new ParamsException(ExceptionCodeConstant.PARAMS_ERROR_CODE, "房间号不能为空"); 34 | } 35 | 36 | String token = data.get("token"); 37 | if (StringUtils.isBlank(token)) { 38 | throw new ParamsException(ExceptionCodeConstant.PARAMS_ERROR_CODE, "没有认证信息!"); 39 | } 40 | 41 | try { 42 | loginReq.setRoomId(Long.parseLong(roomId)); 43 | loginReq.setToken(token); 44 | } catch (Exception e) { 45 | log.warn("e", e); 46 | throw new ParamsException(ExceptionCodeConstant.PARAMS_ERROR_CODE, "请求参数不正确"); 47 | } 48 | 49 | return loginReq; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/manager/UserClearOnlineManager.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.manager; 2 | 3 | import com.lyqf.bullet.comet.context.UserContextHolder; 4 | import com.lyqf.bullet.comet.dto.UserRoomDTO; 5 | import com.lyqf.bullet.comet.util.SpringContextBeanUtil; 6 | import com.lyqf.bullet.logic.client.UserAuthClient; 7 | import com.lyqf.bullet.logic.client.vo.ClearOnlineReq; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | /** 12 | * @author chenlang 13 | * @date 2022/5/26 4:30 下午 14 | */ 15 | 16 | @Slf4j 17 | public class UserClearOnlineManager { 18 | 19 | private static UserAuthClient userAuthClient = SpringContextBeanUtil.getBean(UserAuthClient.class); 20 | 21 | 22 | /** 23 | * 24 | * @param ctx 25 | * @param cause 26 | */ 27 | public static void clearOnlineInfo(ChannelHandlerContext ctx, Throwable cause) { 28 | UserRoomDTO userRoomDTO = UserContextHolder.channelWithUserMap.get(ctx.channel()); 29 | log.error(" authHandler error and close the channel,userId:{}", 30 | userRoomDTO == null ? -1 : userRoomDTO.getUserId(), cause); 31 | 32 | if (userRoomDTO == null) { 33 | ctx.channel().close(); 34 | return; 35 | } 36 | 37 | ClearOnlineReq req = new ClearOnlineReq(); 38 | req.setUserId(userRoomDTO.getUserId()); 39 | req.setRoomId(userRoomDTO.getRoomId()); 40 | userAuthClient.clearOnlineByUserId(req); 41 | 42 | UserContextHolder.clear(ctx.channel(), userRoomDTO.getUserId(), userRoomDTO.getRoomId()); 43 | ctx.channel().close(); 44 | 45 | log.info("clear userId online status finish,userId:{}", userRoomDTO.getUserId()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /bullet-common/src/main/java/com/lyqf/bullet/common/model/ApiResponse.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.common.model; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.AllArgsConstructor; 6 | import lombok.Builder; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | /** 11 | * @author chenlang 12 | * @date 2022/5/12 3:13 下午 13 | */ 14 | 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | @Builder 19 | @SuppressWarnings("all") 20 | public class ApiResponse implements Serializable { 21 | 22 | private static final long serialVersionUID = 7090117243937573164L; 23 | 24 | 25 | public static final String ERROR_MES = "error"; 26 | public static final String SUCCESS_MES = "success"; 27 | 28 | public static final Integer SUCCESS_CODE = 200; 29 | public static final Integer ERROR_CODE = 400; 30 | 31 | private Integer code; 32 | private String msg; 33 | private T data; 34 | 35 | /** 36 | * 37 | * @return 38 | */ 39 | public boolean isSuccess() { 40 | return SUCCESS_CODE.equals(code); 41 | } 42 | 43 | /** 44 | * 45 | */ 46 | public static ApiResponse genSuccessData(Object data) { 47 | return ApiResponse.builder().code(SUCCESS_CODE).msg(SUCCESS_MES).data(data).build(); 48 | } 49 | 50 | public static ApiResponse genDefaultErrorData() { 51 | return ApiResponse.builder().code(ERROR_CODE).msg(ERROR_MES).build(); 52 | } 53 | 54 | public static ApiResponse genErrorData(Integer code, String msg) { 55 | return ApiResponse.builder().code(code).msg(msg).build(); 56 | } 57 | 58 | public static ApiResponse genErrorData(Integer code, String msg, Object data) { 59 | return ApiResponse.builder().code(code).msg(msg).data(data).build(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /bullet-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bullet-chat-new 7 | com.lyqf 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | bullet-common 13 | 14 | 15 | 8 16 | 8 17 | 18 | 19 | 20 | 21 | org.projectlombok 22 | lombok 23 | true 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | true 30 | 31 | 32 | 33 | org.springframework.cloud 34 | spring-cloud-loadbalancer 35 | true 36 | 37 | 38 | 39 | org.apache.commons 40 | commons-lang3 41 | true 42 | 43 | 44 | 45 | com.alibaba 46 | transmittable-thread-local 47 | 48 | 49 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/KafkaProducerConfig.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.apache.kafka.clients.producer.ProducerConfig; 7 | import org.apache.kafka.common.serialization.StringSerializer; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.kafka.core.DefaultKafkaProducerFactory; 12 | import org.springframework.kafka.core.KafkaTemplate; 13 | import org.springframework.kafka.core.ProducerFactory; 14 | 15 | /** 16 | * @author chenlang 17 | * @date 2022/5/17 3:21 下午 18 | */ 19 | @Configuration 20 | public class KafkaProducerConfig { 21 | 22 | @Value("${spring.kafka.nodes}") 23 | private String kafkaServer; 24 | 25 | @Bean 26 | public ProducerFactory producerFactory() { 27 | return new DefaultKafkaProducerFactory<>(producerConfigs()); 28 | } 29 | 30 | @Bean 31 | public Map producerConfigs() { 32 | Map props = new HashMap<>(); 33 | props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaServer); 34 | props.put(ProducerConfig.RETRIES_CONFIG, 0); 35 | props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); 36 | props.put(ProducerConfig.LINGER_MS_CONFIG, 1); 37 | props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); 38 | props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); 39 | props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); 40 | return props; 41 | } 42 | 43 | @Bean 44 | public KafkaTemplate kafkaTemplate() { 45 | return new KafkaTemplate(producerFactory()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/manager/KafkaManger.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.manager; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.kafka.core.KafkaTemplate; 5 | import org.springframework.kafka.support.SendResult; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.util.concurrent.ListenableFuture; 8 | import org.springframework.util.concurrent.ListenableFutureCallback; 9 | 10 | import com.alibaba.fastjson.JSON; 11 | import com.lyqf.bullet.common.enums.LevelEnum; 12 | import com.lyqf.bullet.logic.constant.KafkaConstant; 13 | import com.lyqf.bullet.logic.dto.MsgDTO; 14 | 15 | import lombok.extern.slf4j.Slf4j; 16 | 17 | /** 18 | * @author chenlang 19 | * @date 2022/5/16 4:16 下午 20 | */ 21 | @Component 22 | @Slf4j 23 | public class KafkaManger { 24 | 25 | @Autowired 26 | KafkaTemplate kafkaTemplate; 27 | 28 | /** 29 | * todo 同一用户发到同一个partition中 30 | * 相同hashcode一样的roomId走同一个topic 31 | */ 32 | public void sendMsg(MsgDTO msgDTO) { 33 | ListenableFuture> future = null; 34 | if (LevelEnum.SYSTEM.getCode().equals(msgDTO.getLevel())) { 35 | future = kafkaTemplate.send(KafkaConstant.SYS_MSG_TOPIC, JSON.toJSONString(msgDTO)); 36 | } else if (LevelEnum.EXT.getCode().equals(msgDTO.getLevel())) { 37 | future = kafkaTemplate.send(KafkaConstant.EXT_MSG_TOPIC, JSON.toJSONString(msgDTO)); 38 | } else { 39 | future = kafkaTemplate.send(KafkaConstant.COMMON_MSG_TOPIC, JSON.toJSONString(msgDTO)); 40 | } 41 | 42 | future.addCallback(new ListenableFutureCallback>() { 43 | @Override 44 | public void onSuccess(SendResult result) { 45 | log.info("kafka 发送消息成功,result :{}", result.toString()); 46 | } 47 | 48 | @Override 49 | public void onFailure(Throwable ex) { 50 | log.warn("kafka 发送消息失败", ex); 51 | } 52 | }); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/java/com/lyqf/bullet/push/biz/ExtMsgPushBiz.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.push.biz; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.lyqf.bullet.comet.client.PushClient; 5 | import com.lyqf.bullet.comet.client.vo.PushReq; 6 | import com.lyqf.bullet.push.manager.LiveNodeManager; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.commons.lang3.ObjectUtils; 9 | import org.apache.commons.lang3.StringUtils; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Component; 12 | 13 | import javax.annotation.Resource; 14 | import java.util.Set; 15 | 16 | /** 17 | * @author chenlang 18 | * @date 2022/5/18 4:25 下午 19 | */ 20 | @Slf4j 21 | @Component 22 | public class ExtMsgPushBiz { 23 | 24 | @Resource 25 | private PushClient pushClient; 26 | 27 | @Autowired 28 | private LiveNodeManager liveNodeManager; 29 | 30 | /** 31 | * 32 | * @param data 33 | */ 34 | public void pushExtMsg(String data) { 35 | // todo check biz logic and rate limit 36 | PushReq pushReq = JSON.parseObject(data, PushReq.class); 37 | // todo find roomid对应的的node id 38 | 39 | if (pushReq == null) { 40 | return; 41 | } 42 | 43 | if (pushReq.getToUserId() != null) { 44 | String nodeId = liveNodeManager.getNodeIdsByUserId(pushReq.getToUserId()); 45 | if (StringUtils.isNotBlank(nodeId)) { 46 | pushClient.push(pushReq); 47 | } else { 48 | log.info("单聊消息,没有找到用户:{}的连接信息", pushReq.getToUserId()); 49 | } 50 | 51 | } else { // broadcast 52 | Set nodeIds = liveNodeManager.getNodeIdsByRoomId(pushReq.getRoomId()); 53 | if (!ObjectUtils.isEmpty(nodeIds)) { 54 | nodeIds.forEach(k -> { 55 | pushClient.push(pushReq); 56 | }); 57 | } else { 58 | log.info("群聊消息,没有找到room:{}的连接信息", pushReq.getRoomId()); 59 | } 60 | 61 | } 62 | 63 | log.info("pushExtMsg push success:{}", data); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/java/com/lyqf/bullet/push/config/KafkaConsumerConfig.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.push.config; 2 | 3 | import org.apache.kafka.clients.consumer.ConsumerConfig; 4 | import org.apache.kafka.common.serialization.StringDeserializer; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; 9 | import org.springframework.kafka.core.ConsumerFactory; 10 | import org.springframework.kafka.core.DefaultKafkaConsumerFactory; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | /** 16 | * @author chenlang 17 | * @date 2022/5/18 5:13 下午 18 | */ 19 | 20 | @Configuration 21 | public class KafkaConsumerConfig { 22 | 23 | @Value("${spring.kafka.nodes}") 24 | private String nodes; 25 | 26 | @Value("${spring.kafka.consumer.group-id}") 27 | private String groupId; 28 | 29 | 30 | @Bean 31 | ConcurrentKafkaListenerContainerFactory 32 | kafkaListenerContainerFactory() { 33 | ConcurrentKafkaListenerContainerFactory factory = 34 | new ConcurrentKafkaListenerContainerFactory<>(); 35 | factory.setConsumerFactory(consumerFactory()); 36 | return factory; 37 | } 38 | 39 | 40 | @Bean 41 | public ConsumerFactory consumerFactory() { 42 | return new DefaultKafkaConsumerFactory<>(consumerProps()); 43 | } 44 | 45 | @Bean 46 | public Map consumerProps() { 47 | Map props = new HashMap<>(); 48 | props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, nodes); 49 | props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); 50 | props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true); 51 | props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "100"); 52 | props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "15000"); 53 | props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); 54 | props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); 55 | return props; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/context/UserContextHolder.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.context; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | 8 | import org.apache.commons.lang3.ObjectUtils; 9 | 10 | import com.lyqf.bullet.comet.dto.UserRoomDTO; 11 | 12 | import io.netty.channel.Channel; 13 | 14 | /** 15 | * @author chenlang 16 | * @date 2022/5/11 6:41 下午 17 | */ 18 | 19 | @SuppressWarnings("all") 20 | public class UserContextHolder { 21 | 22 | /** 23 | * 用户 -> 连接 map 24 | */ 25 | public static Map userWithChannelMap = new ConcurrentHashMap<>(); 26 | 27 | /** 28 | * 连接 -> 用户 map 29 | */ 30 | public static Map channelWithUserMap = new ConcurrentHashMap<>(); 31 | 32 | /** 33 | * 一个房间记录了哪些用户链接 34 | */ 35 | public static Map> roomWithChannelsMap = new ConcurrentHashMap<>(); 36 | 37 | /** 38 | * 39 | * @param channel 40 | * @param userRoomDTO 41 | */ 42 | public static void add(Channel channel, UserRoomDTO userRoomDTO) { 43 | userWithChannelMap.put(userRoomDTO.getUserId(), channel); 44 | channelWithUserMap.put(channel, userRoomDTO); 45 | List channels = roomWithChannelsMap.get(userRoomDTO.getRoomId()); 46 | if (ObjectUtils.isEmpty(channels)) { 47 | channels = new ArrayList(); 48 | } 49 | channels.add(channel); 50 | 51 | roomWithChannelsMap.put(userRoomDTO.getRoomId(), channels); 52 | } 53 | 54 | /** 55 | * 56 | * @param channel 57 | * @param userId 58 | */ 59 | public static void clear(Channel channel, Long userId, Long roomId) { 60 | if (channel != null) { 61 | channelWithUserMap.remove(channel); 62 | } 63 | 64 | if (userId != null) { 65 | userWithChannelMap.remove(userId); 66 | } 67 | 68 | if (roomId == null) { 69 | return; 70 | } 71 | 72 | List channels = roomWithChannelsMap.get(roomId); 73 | if (!ObjectUtils.isEmpty(channels) && channels.contains(channels)) { 74 | channels.remove(channel); 75 | } 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/java/com/lyqf/bullet/push/biz/CommonMsgPushBiz.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.push.biz; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.lyqf.bullet.comet.client.PushClient; 5 | import com.lyqf.bullet.comet.client.vo.PushReq; 6 | import com.lyqf.bullet.common.holder.RpcContextHolder; 7 | import com.lyqf.bullet.push.manager.LiveNodeManager; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.apache.commons.lang3.ObjectUtils; 10 | import org.apache.commons.lang3.StringUtils; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Component; 13 | 14 | import javax.annotation.Resource; 15 | import java.util.Set; 16 | 17 | /** 18 | * @author chenlang 19 | * @date 2022/5/18 4:25 下午 20 | */ 21 | @Slf4j 22 | @Component 23 | public class CommonMsgPushBiz { 24 | 25 | @Resource 26 | private PushClient pushClient; 27 | 28 | @Autowired 29 | private LiveNodeManager liveNodeManager; 30 | 31 | /** 32 | * 33 | * @param data 34 | */ 35 | public void pushCommonMsg(String data) { 36 | // todo check biz logic and rate limit 37 | PushReq pushReq = JSON.parseObject(data, PushReq.class); 38 | // todo find roomid对应的的node id 39 | 40 | if (pushReq == null) { 41 | return; 42 | } 43 | 44 | if (pushReq.getToUserId() != null) { 45 | String nodeId = liveNodeManager.getNodeIdsByUserId(pushReq.getToUserId()); 46 | if (StringUtils.isNotBlank(nodeId)) { 47 | RpcContextHolder.setStringParameter(nodeId); 48 | pushClient.push(pushReq); 49 | } else { 50 | log.info("单聊消息,没有找到用户:{}的连接信息", pushReq.getToUserId()); 51 | } 52 | 53 | } else { // broadcast 54 | Set nodeIds = liveNodeManager.getNodeIdsByRoomId(pushReq.getRoomId()); 55 | if (!ObjectUtils.isEmpty(nodeIds)) { 56 | nodeIds.forEach(k -> { 57 | RpcContextHolder.setStringParameter(k); 58 | pushClient.push(pushReq); 59 | }); 60 | } else { 61 | log.info("群聊消息,没有找到room:{}的连接信息", pushReq.getRoomId()); 62 | } 63 | } 64 | 65 | log.info("push success:{}", data); 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/netty/handler/MessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.netty.handler; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.lyqf.bullet.comet.context.UserContextHolder; 5 | import com.lyqf.bullet.comet.dto.UserRoomDTO; 6 | import com.lyqf.bullet.comet.manager.UserClearOnlineManager; 7 | import com.lyqf.bullet.comet.util.SpringContextBeanUtil; 8 | import com.lyqf.bullet.common.enums.OperateTypeEnum; 9 | import com.lyqf.bullet.common.exception.BizException; 10 | import com.lyqf.bullet.logic.client.UserMsgClient; 11 | import com.lyqf.bullet.logic.client.vo.UserMsgReq; 12 | import io.netty.channel.ChannelHandlerContext; 13 | import io.netty.channel.SimpleChannelInboundHandler; 14 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.apache.commons.lang3.StringUtils; 17 | 18 | /** 19 | * @author chenlang 20 | * @date 2022/5/11 3:48 下午 21 | */ 22 | 23 | @Slf4j 24 | public class MessageHandler extends SimpleChannelInboundHandler { 25 | 26 | private static final String PING = "PING@!2#4%6&8"; 27 | 28 | private static final String PONG = "PONG@!2#4%6&8"; 29 | 30 | private UserMsgClient userMsgClient = SpringContextBeanUtil.getBean(UserMsgClient.class); 31 | 32 | @Override 33 | protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { 34 | UserRoomDTO userRoomDTO = UserContextHolder.channelWithUserMap.get(ctx.channel()); 35 | if (userRoomDTO == null) { 36 | log.error("没找到用户数据。。。。"); 37 | throw new BizException(4000, "用户数据没找到"); 38 | } 39 | 40 | String msgData = msg.text(); 41 | log.info("receive userId msg ,userId:{},msg:{}", userRoomDTO.getUserId(), msgData); 42 | if (StringUtils.isBlank(msgData)) { 43 | return; 44 | } 45 | 46 | UserMsgReq userMsgReq; 47 | //heart msg 48 | if (msgData.toUpperCase().contains(PING)) { 49 | userMsgReq = new UserMsgReq(); 50 | userMsgReq.setUserId(userRoomDTO.getUserId()); 51 | userMsgReq.setToUserId(userRoomDTO.getUserId()); 52 | userMsgReq.setOperateType(OperateTypeEnum.HERART_PING.getCode()); 53 | userMsgReq.setContent(PONG); 54 | } else { 55 | userMsgReq = JSONObject.parseObject(msgData, UserMsgReq.class); 56 | userMsgReq.setUserId(userRoomDTO.getUserId()); 57 | } 58 | 59 | userMsgClient.sendMsg(userMsgReq); 60 | } 61 | 62 | @Override 63 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 64 | UserClearOnlineManager.clearOnlineInfo(ctx, cause); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/web/PushController.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.web; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.lyqf.bullet.comet.context.UserContextHolder; 5 | import com.lyqf.bullet.comet.dto.UserRoomDTO; 6 | import com.lyqf.bullet.comet.client.vo.PushReq; 7 | import com.lyqf.bullet.comet.vo.PushMsg; 8 | import com.lyqf.bullet.common.model.ApiResponse; 9 | import io.netty.channel.Channel; 10 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.apache.commons.lang3.ObjectUtils; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | import java.util.List; 18 | 19 | /** 20 | * @author chenlang 21 | * @date 2022/5/17 4:27 下午 22 | */ 23 | @Slf4j 24 | @RestController 25 | public class PushController { 26 | 27 | @PostMapping("/push") 28 | public ApiResponse push(@RequestBody PushReq pushReq) { 29 | log.info("接到收消息:{}", pushReq); 30 | 31 | PushMsg msg = new PushMsg(); 32 | msg.setContent(pushReq.getContent()); 33 | msg.setOperateType(pushReq.getOperateType()); 34 | msg.setUserNickName(pushReq.getUserNickName()); 35 | msg.setUserId(pushReq.getUserId()); 36 | String outMsg = JSONObject.toJSONString(msg); 37 | 38 | // s to s 39 | if (pushReq.getToUserId() != null) { 40 | log.info("to user msg,to userId:{}", pushReq.getToUserId()); 41 | Channel channel = UserContextHolder.userWithChannelMap.get(pushReq.getToUserId()); 42 | if (channel != null) { 43 | channel.writeAndFlush(new TextWebSocketFrame(outMsg)); 44 | } 45 | } else { // broadcast 46 | List channelList = UserContextHolder.roomWithChannelsMap.get(pushReq.getRoomId()); 47 | if (ObjectUtils.isNotEmpty(channelList)) { 48 | channelList.forEach(k -> { 49 | UserRoomDTO userRoomDTO = UserContextHolder.channelWithUserMap.get(k); 50 | if (userRoomDTO != null) { 51 | Long channelUserId = userRoomDTO.getUserId(); 52 | // 不能自己发消息给自己 53 | if (!pushReq.getUserId().equals(channelUserId)) { 54 | k.writeAndFlush(new TextWebSocketFrame(outMsg)); 55 | log.info("向userid 为:{} 推送消息成功.", userRoomDTO.getUserId()); 56 | } 57 | } 58 | }); 59 | } 60 | } 61 | 62 | return ApiResponse.genSuccessData(true); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /bullet-common/src/main/java/com/lyqf/bullet/common/loadbanlance/CustomerLoadBalance.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.common.loadbanlance; 2 | 3 | import com.lyqf.bullet.common.constant.CommonConstant; 4 | import com.lyqf.bullet.common.exception.ProviderNotFoundException; 5 | import com.lyqf.bullet.common.holder.RpcContextHolder; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.springframework.beans.factory.ObjectProvider; 9 | import org.springframework.cloud.client.ServiceInstance; 10 | import org.springframework.cloud.client.loadbalancer.DefaultResponse; 11 | import org.springframework.cloud.client.loadbalancer.EmptyResponse; 12 | import org.springframework.cloud.client.loadbalancer.Request; 13 | import org.springframework.cloud.client.loadbalancer.Response; 14 | import org.springframework.cloud.loadbalancer.core.ReactorServiceInstanceLoadBalancer; 15 | import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier; 16 | import reactor.core.publisher.Mono; 17 | 18 | import java.util.List; 19 | import java.util.Random; 20 | 21 | /** 22 | * @author chenlang 23 | * @date 2022/5/19 2:40 下午 24 | */ 25 | @SuppressWarnings("all") 26 | @Slf4j 27 | public class CustomerLoadBalance implements ReactorServiceInstanceLoadBalancer { 28 | 29 | // 服务列表 30 | private ObjectProvider serviceInstanceListSupplierProvider; 31 | 32 | public CustomerLoadBalance(ObjectProvider serviceInstanceListSupplierProvider, 33 | String name) { 34 | this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider; 35 | } 36 | 37 | @Override 38 | public Mono> choose(Request request) { 39 | try { 40 | String customerNodeId = RpcContextHolder.getStringValue(); 41 | log.info("choose customerNodeId:{}", customerNodeId); 42 | ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable(); 43 | return supplier.get().next().map(serviceInstances -> chooseService(serviceInstances, customerNodeId)); 44 | } finally { 45 | RpcContextHolder.remove(); 46 | } 47 | } 48 | 49 | /** 50 | * 使用随机数获取服务 51 | * 52 | * @param instances 53 | * @return 54 | */ 55 | private Response chooseService(List instances, String customerNodeId) { 56 | if (instances.isEmpty()) { 57 | if (log.isWarnEnabled()) { 58 | log.warn("customer loadbance. No servers available for service........."); 59 | } 60 | 61 | return new EmptyResponse(); 62 | } 63 | 64 | if (StringUtils.isNotBlank(customerNodeId)) { 65 | for (ServiceInstance instance : instances) { 66 | String host = instance.getHost(); 67 | if (customerNodeId.equalsIgnoreCase(host)) { 68 | log.info("load banlance choose host:{} node", host); 69 | return new DefaultResponse(instance); 70 | } 71 | } 72 | throw new ProviderNotFoundException(CommonConstant.NO_PROVIDER_ERROR_CODE,"can't find host:" + customerNodeId + " provider"); 73 | } 74 | 75 | // 随机算法 76 | int size = instances.size(); 77 | Random random = new Random(); 78 | ServiceInstance instance = instances.get(random.nextInt(size)); 79 | return new DefaultResponse(instance); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/manager/UserAuthManager.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.manager; 2 | 3 | import com.lyqf.bullet.comet.client.CloseUserConnectionClient; 4 | import com.lyqf.bullet.common.exception.BizException; 5 | import com.lyqf.bullet.common.holder.RpcContextHolder; 6 | import com.lyqf.bullet.logic.acl.CloseUserConnectionFacadeAcl; 7 | import com.lyqf.bullet.logic.client.vo.AuthUserReq; 8 | import com.lyqf.bullet.logic.constant.RedisConstant; 9 | import com.lyqf.bullet.logic.dto.RoomDTO; 10 | import com.lyqf.bullet.logic.dto.UserInfoDTO; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.apache.commons.lang3.StringUtils; 13 | import org.springframework.data.redis.core.RedisTemplate; 14 | import org.springframework.stereotype.Component; 15 | 16 | import javax.annotation.Resource; 17 | import javax.servlet.http.HttpServletRequest; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | /** 21 | * @author chenlang 22 | * @date 2022/5/23 11:37 上午 23 | */ 24 | 25 | @Slf4j 26 | @SuppressWarnings("all") 27 | @Component 28 | public class UserAuthManager { 29 | 30 | @Resource 31 | private RedisTemplate redisTemplate; 32 | 33 | 34 | @Resource 35 | private CloseUserConnectionClient closeUserConnectionFacade; 36 | 37 | @Resource 38 | private CloseUserConnectionFacadeAcl closeUserConnectionFacadeAcl; 39 | 40 | /** 41 | * 42 | */ 43 | public UserInfoDTO checkUserInfo(AuthUserReq req, HttpServletRequest request) { 44 | String tokenKey = String.format(RedisConstant.TOKEN_KEY, req.getToken()); 45 | 46 | UserInfoDTO userInfoDTO = (UserInfoDTO)redisTemplate.opsForValue().get(tokenKey); 47 | if (userInfoDTO == null) { 48 | return genAnonymousAccount(); 49 | } 50 | 51 | String roomKey = String.format(RedisConstant.ROOM_KEY, req.getRoomId()); 52 | RoomDTO room = (RoomDTO)redisTemplate.opsForValue().get(roomKey); 53 | if (room == null) { 54 | throw new BizException(4002, "房间号信息不正确"); 55 | } 56 | 57 | checkUserHasBanLog(userInfoDTO.getId()); 58 | 59 | String userNodeKey = 60 | String.format(com.lyqf.bullet.common.constant.RedisConstant.USER_NODE, userInfoDTO.getId()); 61 | String userNodeInfo = (String)redisTemplate.opsForValue().get(userNodeKey); 62 | if (StringUtils.isNotBlank(userNodeInfo)) { 63 | RpcContextHolder.setStringParameter(req.getNode()); 64 | closeUserConnectionFacadeAcl.close(userInfoDTO.getId(), req.getRoomId()); 65 | log.info("用户重复链接,剔除老连接,userId:{}", userInfoDTO.getId()); 66 | } 67 | return userInfoDTO; 68 | } 69 | 70 | /** 71 | * 72 | * @return 73 | */ 74 | private UserInfoDTO genAnonymousAccount() { 75 | String startIdKey = String.format(RedisConstant.ANONYMOUS_USER_ID_START_KEY); 76 | UserInfoDTO userInfoDTO = new UserInfoDTO(); 77 | userInfoDTO.setId(redisTemplate.opsForValue().decrement(startIdKey)); 78 | userInfoDTO.setNickName("匿名用户"); 79 | 80 | String userInfoKey = String.format(RedisConstant.USER_INFO_KEY, userInfoDTO.getId()); 81 | redisTemplate.opsForValue().set(userInfoKey, userInfoDTO, 3, TimeUnit.HOURS); 82 | log.info("create AnonymousAccount,userId:{}", userInfoDTO.getId()); 83 | return userInfoDTO; 84 | } 85 | 86 | /** 87 | * 禁止登录 88 | */ 89 | private void checkUserHasBanLog(Long userId) { 90 | log.info("该用户id没有被禁言"); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /bullet-client/src/main/java/com/lyqf/bullet/client/BulletChatClient.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.client; 2 | 3 | import com.lyqf.bullet.client.handler.WebSocketClientHandler; 4 | import io.netty.bootstrap.Bootstrap; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.ChannelPipeline; 7 | import io.netty.channel.EventLoopGroup; 8 | import io.netty.channel.nio.NioEventLoopGroup; 9 | import io.netty.channel.socket.SocketChannel; 10 | import io.netty.channel.socket.nio.NioSocketChannel; 11 | import io.netty.handler.codec.http.HttpClientCodec; 12 | import io.netty.handler.codec.http.HttpHeaders; 13 | import io.netty.handler.codec.http.HttpObjectAggregator; 14 | import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; 15 | import io.netty.handler.codec.http.websocketx.WebSocketVersion; 16 | import lombok.extern.slf4j.Slf4j; 17 | 18 | import java.net.URI; 19 | import java.util.concurrent.ExecutorService; 20 | import java.util.concurrent.Executors; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | 23 | /** 24 | * @author 25 | */ 26 | 27 | @Slf4j 28 | @SuppressWarnings("all") 29 | public class BulletChatClient { 30 | 31 | private static final EventLoopGroup group = new NioEventLoopGroup(); 32 | 33 | private static ExecutorService executorService = Executors.newFixedThreadPool(50); 34 | 35 | private static AtomicInteger atomicInteger = new AtomicInteger(1); 36 | 37 | public static void main(String[] args) throws Exception { 38 | for (int i = 0; i < 10000; i++) { 39 | executorService.submit(() -> { 40 | final String url = "ws://127.0.0.1:8889?roomId=1&token=" + atomicInteger.incrementAndGet(); 41 | final BulletChatClient client = new BulletChatClient(); 42 | URI uri = URI.create(url); 43 | try { 44 | client.open(uri); 45 | Thread.sleep(100); 46 | } catch (Exception e) { 47 | e.printStackTrace(); 48 | } 49 | }); 50 | } 51 | } 52 | 53 | public static void open(URI uri) throws Exception { 54 | Bootstrap b = new Bootstrap(); 55 | String protocol = uri.getScheme(); 56 | if (!"ws".equals(protocol)) { 57 | throw new IllegalArgumentException("Unsupported protocol: " + protocol); 58 | } 59 | 60 | // Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00. 61 | // If you change it to V00, ping is not supported and remember to change 62 | // HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline. 63 | final WebSocketClientHandler handler = new WebSocketClientHandler(WebSocketClientHandshakerFactory 64 | .newHandshaker(uri, WebSocketVersion.V13, null, false, HttpHeaders.EMPTY_HEADERS, 1280000)); 65 | 66 | b.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer() { 67 | @Override 68 | public void initChannel(SocketChannel ch) throws Exception { 69 | ChannelPipeline pipeline = ch.pipeline(); 70 | pipeline.addLast("http-codec", new HttpClientCodec()); 71 | pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); 72 | pipeline.addLast("ws-handler", handler); 73 | } 74 | }); 75 | 76 | // System.out.println("WebSocket Client connecting"); 77 | b.connect(uri.getHost(), uri.getPort()).sync().channel(); 78 | log.info("conncet success"); 79 | handler.handshakeFuture().sync(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /bullet-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bullet-chat-new 7 | com.lyqf 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | bullet-client 13 | 14 | 15 | 8 16 | 8 17 | 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-web 23 | 24 | 25 | 26 | com.alibaba.cloud 27 | spring-cloud-starter-alibaba-nacos-discovery 28 | 29 | 30 | springframework.cloud 31 | spring-cloud-starter-netflix-ribbon 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.cloud 38 | spring-cloud-loadbalancer 39 | 40 | 41 | 42 | org.springframework.cloud 43 | spring-cloud-starter-openfeign 44 | 45 | 46 | 47 | io.netty 48 | netty-all 49 | 50 | 51 | 52 | io.github.ljwlgl 53 | common-util 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-actuator 59 | 60 | 61 | 62 | 63 | org.projectlombok 64 | lombok 65 | 66 | 67 | 68 | com.lyqf 69 | bullet-logic-facade 70 | 1.0-SNAPSHOT 71 | 72 | 73 | 74 | com.lyqf 75 | bullet-common 76 | 1.0-SNAPSHOT 77 | 78 | 79 | 80 | com.lyqf 81 | bullet-comet-facade 82 | 1.0-SNAPSHOT 83 | 84 | 85 | 86 | 87 | com.alibaba 88 | fastjson 89 | 90 | 91 | 92 | org.projectreactor 93 | reactor-spring 94 | 95 | 96 | 97 | org.apache.commons 98 | commons-lang3 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /bullet-client/src/main/java/com/lyqf/bullet/client/handler/WebSocketClientHandler.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.client.handler; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelPromise; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import io.netty.handler.codec.http.FullHttpResponse; 9 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 10 | import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; 11 | import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; 12 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 13 | import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; 14 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 15 | import io.netty.util.CharsetUtil; 16 | import lombok.extern.slf4j.Slf4j; 17 | 18 | /** 19 | * @author chenlang 20 | * @date 2022/6/6 5:56 下午 21 | */ 22 | 23 | @SuppressWarnings("all") 24 | @Slf4j 25 | public class WebSocketClientHandler extends SimpleChannelInboundHandler { 26 | 27 | private final WebSocketClientHandshaker handshaker; 28 | private ChannelPromise handshakeFuture; 29 | 30 | public WebSocketClientHandler(final WebSocketClientHandshaker handshaker) { 31 | this.handshaker = handshaker; 32 | } 33 | 34 | public ChannelFuture handshakeFuture() { 35 | return handshakeFuture; 36 | } 37 | 38 | @Override 39 | public void handlerAdded(final ChannelHandlerContext ctx) throws Exception { 40 | handshakeFuture = ctx.newPromise(); 41 | } 42 | 43 | @Override 44 | public void channelActive(final ChannelHandlerContext ctx) throws Exception { 45 | handshaker.handshake(ctx.channel()); 46 | } 47 | 48 | @Override 49 | public void channelInactive(final ChannelHandlerContext ctx) throws Exception { 50 | // System.out.println("WebSocket Client disconnected!"); 51 | } 52 | 53 | @Override 54 | protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 55 | final Channel ch = ctx.channel(); 56 | if (!handshaker.isHandshakeComplete()) { 57 | // web socket client connected 58 | handshaker.finishHandshake(ch, (FullHttpResponse)msg); 59 | handshakeFuture.setSuccess(); 60 | return; 61 | } 62 | 63 | if (msg instanceof FullHttpResponse) { 64 | final FullHttpResponse response = (FullHttpResponse)msg; 65 | throw new Exception("Unexpected FullHttpResponse (getStatus=" + response.getStatus() + ", content=" 66 | + response.content().toString(CharsetUtil.UTF_8) + ')'); 67 | } 68 | 69 | final WebSocketFrame frame = (WebSocketFrame)msg; 70 | if (frame instanceof TextWebSocketFrame) { 71 | final TextWebSocketFrame textFrame = (TextWebSocketFrame)frame; 72 | // uncomment to print request 73 | // logger.info(textFrame.text()); 74 | } else if (frame instanceof PongWebSocketFrame) { 75 | } else if (frame instanceof CloseWebSocketFrame) 76 | ch.close(); 77 | else if (frame instanceof BinaryWebSocketFrame) { 78 | // uncomment to print request 79 | // logger.info(frame.content().toString()); 80 | } 81 | 82 | } 83 | 84 | @Override 85 | public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable cause) throws Exception { 86 | cause.printStackTrace(); 87 | 88 | if (!handshakeFuture.isDone()) { 89 | handshakeFuture.setFailure(cause); 90 | } 91 | 92 | ctx.close(); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/util/IpUtil.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.util; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.http.FullHttpRequest; 6 | import io.netty.handler.codec.http.HttpHeaders; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.apache.commons.lang3.StringUtils; 9 | 10 | import java.net.InetAddress; 11 | import java.net.NetworkInterface; 12 | import java.net.SocketAddress; 13 | import java.util.Enumeration; 14 | 15 | /** 16 | * @author chenlang 17 | * @date 2022/5/11 3:55 下午 18 | */ 19 | 20 | @Slf4j 21 | public class IpUtil { 22 | 23 | /** 24 | * 25 | * @param ctx 26 | * @param request 27 | * @return 28 | */ 29 | public static String getIpInfo(ChannelHandlerContext ctx, FullHttpRequest request) { 30 | HttpHeaders headers = request.headers(); 31 | // todo x-forwarded-for 可能有多个,第一个是最原始的 32 | String ip = headers.get("x-forwarded-for"); 33 | if (StringUtils.isNotBlank(ip)) { 34 | return ip; 35 | } 36 | 37 | return getIpFromChannel(ctx.channel()); 38 | } 39 | 40 | /** 41 | * 42 | * @param channel 43 | * @return 44 | */ 45 | private static String getIpFromChannel(Channel channel) { 46 | if (null == channel) { 47 | return ""; 48 | } 49 | SocketAddress remote = channel.remoteAddress(); 50 | final String addr = remote != null ? remote.toString() : ""; 51 | 52 | if (addr.length() > 0) { 53 | int index = addr.lastIndexOf("/"); 54 | if (index >= 0) { 55 | return addr.substring(index + 1); 56 | } 57 | return addr; 58 | } 59 | 60 | return ""; 61 | } 62 | 63 | /** 64 | * 获取本机ip 65 | * 66 | * @return 67 | */ 68 | public static String getLocalIp() { 69 | InetAddress localHost = getLocalHostExactAddress(); 70 | if (localHost == null) { 71 | log.warn("cant find ip,return default ip 127.0.0.1"); 72 | return "127.0.0.1"; 73 | } 74 | return localHost.getHostAddress(); 75 | } 76 | 77 | private static InetAddress getLocalHostExactAddress() { 78 | try { 79 | InetAddress candidateAddress = null; 80 | 81 | Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); 82 | while (networkInterfaces.hasMoreElements()) { 83 | NetworkInterface iface = networkInterfaces.nextElement(); 84 | // 该网卡接口下的ip会有多个,也需要一个个的遍历,找到自己所需要的 85 | for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements();) { 86 | InetAddress inetAddr = inetAddrs.nextElement(); 87 | // 排除loopback回环类型地址(不管是IPv4还是IPv6 只要是回环地址都会返回true) 88 | if (!inetAddr.isLoopbackAddress()) { 89 | if (inetAddr.isSiteLocalAddress()) { 90 | // 如果是site-local地址,就是它了 就是我们要找的 ,绝大部分情况下都会在此处返回你的ip地址值 91 | return inetAddr; 92 | } 93 | 94 | // 若不是site-local地址 那就记录下该地址当作候选 95 | if (candidateAddress == null) { 96 | candidateAddress = inetAddr; 97 | } 98 | 99 | } 100 | } 101 | } 102 | 103 | // 如果出去loopback回环地之外无其它地址了,那就回退到原始方案吧 104 | return candidateAddress == null ? InetAddress.getLocalHost() : candidateAddress; 105 | } catch (Exception e) { 106 | log.warn("e", e); 107 | } 108 | return null; 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/web/UserMsgController.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.web; 2 | 3 | import org.springframework.beans.BeanUtils; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.data.redis.core.RedisTemplate; 6 | import org.springframework.web.bind.annotation.RequestBody; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestMethod; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import com.lyqf.bullet.comet.client.PushClient; 12 | import com.lyqf.bullet.common.enums.LevelEnum; 13 | import com.lyqf.bullet.logic.client.vo.UserMsgReq; 14 | import com.lyqf.bullet.logic.client.vo.UserMsgResp; 15 | import com.lyqf.bullet.logic.constant.RedisConstant; 16 | import com.lyqf.bullet.logic.dto.MsgDTO; 17 | import com.lyqf.bullet.logic.dto.UserInfoDTO; 18 | import com.lyqf.bullet.logic.manager.KafkaManger; 19 | 20 | import lombok.extern.slf4j.Slf4j; 21 | 22 | /** 23 | * @author chenlang 24 | * @date 2022/5/12 4:02 下午 25 | */ 26 | 27 | @Slf4j 28 | @SuppressWarnings("all") 29 | @RestController 30 | @RequestMapping 31 | public class UserMsgController { 32 | 33 | @Autowired 34 | private KafkaManger kafkaManger; 35 | 36 | @Autowired 37 | private PushClient pushClient; 38 | 39 | @Autowired 40 | private RedisTemplate redisTemplate; 41 | 42 | /** 43 | * 44 | * @param userMsgReq 45 | * @return 46 | */ 47 | @RequestMapping(value = "/send-msg", method = RequestMethod.POST) 48 | public UserMsgResp sendMsg(@RequestBody UserMsgReq userMsgReq) { 49 | log.info("receive sendMsg:{}", userMsgReq); 50 | 51 | // todo check rate limit 52 | UserMsgResp resp = new UserMsgResp(); 53 | resp.setReqId(userMsgReq.getReqId()); 54 | 55 | boolean result = check(userMsgReq); 56 | if (!result) { 57 | resp.setResult(false); 58 | return resp; 59 | } 60 | 61 | MsgDTO msgDTO = new MsgDTO(); 62 | BeanUtils.copyProperties(userMsgReq, msgDTO); 63 | UserInfoDTO userInfoDTO = getUserInfoById(userMsgReq.getUserId()); 64 | // todo 走heart 65 | msgDTO.setLevel(LevelEnum.COMMON.getCode()); 66 | msgDTO.setUserNickName(userInfoDTO.getNickName()); 67 | kafkaManger.sendMsg(msgDTO); 68 | resp.setResult(true); 69 | return resp; 70 | } 71 | 72 | /** 73 | * 74 | * @return 75 | */ 76 | private UserInfoDTO getUserInfoById(Long userId) { 77 | String tokenKey = String.format(RedisConstant.USER_INFO_KEY, userId); 78 | UserInfoDTO userInfoDTO = (UserInfoDTO)redisTemplate.opsForValue().get(tokenKey); 79 | if (userInfoDTO == null) { 80 | userInfoDTO = new UserInfoDTO(); 81 | userInfoDTO.setNickName("zhangsan"); 82 | userInfoDTO.setUserIdCardName("张三"); 83 | userInfoDTO.setHeadUrl("url"); 84 | } 85 | return userInfoDTO; 86 | } 87 | 88 | /** 89 | * 90 | * @param userMsgReq 91 | * @return 92 | */ 93 | private boolean check(UserMsgReq userMsgReq) { 94 | return checkRateLimit(userMsgReq); 95 | } 96 | 97 | /** 98 | * todo 放在consumer端 检测文本是否有违禁词 99 | * 100 | * @return 101 | */ 102 | private boolean checkTxt(UserMsgReq userMsgReq) { 103 | return true; 104 | } 105 | 106 | /** 107 | * todo 放在consumer端 108 | * 109 | * @return 110 | */ 111 | private boolean checkImag(UserMsgReq userMsgReq) { 112 | return true; 113 | } 114 | 115 | /** 116 | * 用户限流及全局限流 117 | * 118 | * @return 119 | */ 120 | private boolean checkRateLimit(UserMsgReq userMsgReq) { 121 | return true; 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/web/UserAuthController.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.web; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.data.redis.core.RedisTemplate; 5 | import org.springframework.web.bind.annotation.RequestBody; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.bind.annotation.RequestMethod; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import com.lyqf.bullet.common.constant.RedisConstant; 11 | import com.lyqf.bullet.common.enums.LevelEnum; 12 | import com.lyqf.bullet.common.enums.OperateTypeEnum; 13 | import com.lyqf.bullet.common.model.ApiResponse; 14 | import com.lyqf.bullet.logic.client.vo.AuthUserReq; 15 | import com.lyqf.bullet.logic.client.vo.AuthUserResp; 16 | import com.lyqf.bullet.logic.client.vo.ClearOnlineReq; 17 | import com.lyqf.bullet.logic.dto.MsgDTO; 18 | import com.lyqf.bullet.logic.dto.UserInfoDTO; 19 | import com.lyqf.bullet.logic.manager.KafkaManger; 20 | import com.lyqf.bullet.logic.manager.UserAuthManager; 21 | import com.lyqf.bullet.logic.util.BulletDateUtil; 22 | 23 | import lombok.extern.slf4j.Slf4j; 24 | 25 | import javax.servlet.http.HttpServletRequest; 26 | 27 | /** 28 | * @author chenlang 29 | * @date 2022/5/12 4:02 下午 30 | */ 31 | 32 | @Slf4j 33 | @SuppressWarnings("all") 34 | @RestController 35 | @RequestMapping 36 | public class UserAuthController { 37 | 38 | @Autowired 39 | private HttpServletRequest httpServletRequest; 40 | 41 | @Autowired 42 | private RedisTemplate redisTemplate; 43 | 44 | @Autowired 45 | private KafkaManger kafkaManger; 46 | 47 | @Autowired 48 | private UserAuthManager userAuthManager; 49 | 50 | 51 | @RequestMapping(value = "/auth", method = RequestMethod.POST) 52 | public ApiResponse authUser(@RequestBody AuthUserReq req) { 53 | AuthUserResp resp = new AuthUserResp(); 54 | UserInfoDTO userInfoDTO = userAuthManager.checkUserInfo(req,httpServletRequest); 55 | resp.setUserId(userInfoDTO.getId()); 56 | resp.setRoomId(req.getRoomId()); 57 | saveUserWithNode(req, userInfoDTO.getId()); 58 | sendMsgToMq(req, userInfoDTO); 59 | return ApiResponse.genSuccessData(resp); 60 | } 61 | 62 | private void sendMsgToMq(AuthUserReq authUserReq, UserInfoDTO userInfoDTO) { 63 | MsgDTO msgDTO = new MsgDTO(); 64 | msgDTO.setLevel(LevelEnum.EXT.getCode()); 65 | msgDTO.setRoomId(authUserReq.getRoomId()); 66 | msgDTO.setUserId(userInfoDTO.getId()); 67 | msgDTO.setOperateType(OperateTypeEnum.ONLINE_EXT_MSG.getCode()); 68 | msgDTO.setUserNickName(userInfoDTO.getNickName()); 69 | msgDTO.setHeadUrl(userInfoDTO.getHeadUrl()); 70 | kafkaManger.sendMsg(msgDTO); 71 | } 72 | 73 | private void saveUserWithNode(AuthUserReq req, Long userId) { 74 | String roomNodesKey = String.format(RedisConstant.ROOM_NODE, req.getRoomId()); 75 | String userNodeKey = String.format(RedisConstant.USER_NODE, userId); 76 | 77 | // 用户在哪个节点上 78 | redisTemplate.opsForValue().set(userNodeKey, req.getNode()); 79 | log.info("saveUserWithNode userId:{} 分配在node :{}", userId, req.getNode()); 80 | 81 | // room分布在哪些节点上 82 | redisTemplate.opsForSet().add(roomNodesKey, req.getNode()); 83 | log.info("saveUserWithNode room:{} 分配在nodes :{}", req.getRoomId(), req.getNode()); 84 | 85 | redisTemplate.expireAt(roomNodesKey, BulletDateUtil.genNextDateFromCurrentDate()); 86 | redisTemplate.expireAt(userNodeKey, BulletDateUtil.genNextDateFromCurrentDate()); 87 | } 88 | 89 | 90 | /** 91 | * 92 | * @param req 93 | * @return 94 | */ 95 | @RequestMapping(value = "/clear-online", method = RequestMethod.POST) 96 | public ApiResponse clearOnlineByUserId(@RequestBody ClearOnlineReq req) { 97 | String userNodeKey = String.format(RedisConstant.USER_NODE, req.getUserId()); 98 | redisTemplate.delete(userNodeKey); 99 | log.info("clear online status ,userId:{}", req.getUserId()); 100 | return ApiResponse.genSuccessData(true); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bullet-comet 7 | com.lyqf 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | bullet-comet-core 13 | 14 | 15 | 16 | 8 17 | 8 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | 27 | com.alibaba.cloud 28 | spring-cloud-starter-alibaba-nacos-discovery 29 | 30 | 31 | springframework.cloud 32 | spring-cloud-starter-netflix-ribbon 33 | 34 | 35 | 36 | 37 | 38 | org.springframework.cloud 39 | spring-cloud-loadbalancer 40 | 41 | 42 | 43 | org.springframework.cloud 44 | spring-cloud-starter-openfeign 45 | 46 | 47 | 48 | io.netty 49 | netty-all 50 | 51 | 52 | 53 | io.github.ljwlgl 54 | common-util 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-actuator 60 | 61 | 62 | 63 | 64 | org.projectlombok 65 | lombok 66 | 67 | 68 | 69 | com.lyqf 70 | bullet-logic-facade 71 | 1.0-SNAPSHOT 72 | 73 | 74 | 75 | com.lyqf 76 | bullet-common 77 | 1.0-SNAPSHOT 78 | 79 | 80 | 81 | com.lyqf 82 | bullet-comet-facade 83 | 1.0-SNAPSHOT 84 | 85 | 86 | 87 | 88 | com.alibaba 89 | fastjson 90 | 91 | 92 | 93 | org.projectreactor 94 | reactor-spring 95 | 96 | 97 | 98 | org.apache.commons 99 | commons-lang3 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | org.springframework.boot 108 | spring-boot-maven-plugin 109 | 2.3.12.RELEASE 110 | 111 | 112 | com.lyqf.bullet.comet.CometApp 113 | ZIP 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/BulletChatServer.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet; 2 | 3 | import com.lyqf.bullet.comet.netty.handler.AuthHandler; 4 | import com.lyqf.bullet.comet.netty.handler.MessageHandler; 5 | import io.netty.bootstrap.ServerBootstrap; 6 | import io.netty.channel.ChannelFuture; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.ChannelOption; 9 | import io.netty.channel.DefaultEventLoopGroup; 10 | import io.netty.channel.nio.NioEventLoopGroup; 11 | import io.netty.channel.socket.SocketChannel; 12 | import io.netty.channel.socket.nio.NioServerSocketChannel; 13 | import io.netty.handler.codec.http.HttpObjectAggregator; 14 | import io.netty.handler.codec.http.HttpServerCodec; 15 | import io.netty.handler.stream.ChunkedWriteHandler; 16 | import io.netty.handler.timeout.IdleStateHandler; 17 | import lombok.extern.slf4j.Slf4j; 18 | 19 | import java.net.InetSocketAddress; 20 | import java.util.concurrent.ThreadFactory; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | 23 | /** 24 | * @author 25 | */ 26 | 27 | @Slf4j 28 | public class BulletChatServer { 29 | 30 | private final Integer port; 31 | private DefaultEventLoopGroup defLoopGroup = null; 32 | private NioEventLoopGroup bossGroup; 33 | private NioEventLoopGroup workGroup; 34 | private ServerBootstrap serverBootstrap; 35 | protected ChannelFuture channelFuture; 36 | 37 | /** 38 | * 39 | * @param port 40 | */ 41 | public BulletChatServer(int port) { 42 | this.port = port; 43 | } 44 | 45 | /** 46 | * 47 | */ 48 | private void init() { 49 | defLoopGroup = new DefaultEventLoopGroup(8, new ThreadFactory() { 50 | private AtomicInteger index = new AtomicInteger(0); 51 | 52 | @Override 53 | public Thread newThread(Runnable r) { 54 | return new Thread(r, "netty-server-defLoop_" + index.incrementAndGet()); 55 | } 56 | }); 57 | 58 | bossGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { 59 | private AtomicInteger index = new AtomicInteger(0); 60 | 61 | @Override 62 | public Thread newThread(Runnable r) { 63 | return new Thread(r, "netty-server-boss_" + index.incrementAndGet()); 64 | } 65 | }); 66 | 67 | workGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 10, new ThreadFactory() { 68 | private AtomicInteger index = new AtomicInteger(0); 69 | 70 | @Override 71 | public Thread newThread(Runnable r) { 72 | return new Thread(r, "netty-server-work_" + index.incrementAndGet()); 73 | } 74 | }); 75 | 76 | serverBootstrap = new ServerBootstrap(); 77 | } 78 | 79 | /** 80 | * 81 | */ 82 | public void start() { 83 | this.init(); 84 | 85 | serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class) 86 | .option(ChannelOption.SO_KEEPALIVE, true).option(ChannelOption.TCP_NODELAY, true) 87 | .option(ChannelOption.SO_BACKLOG, 1024).localAddress(new InetSocketAddress(port)) 88 | .childHandler(new ChannelInitializer() { 89 | 90 | @Override 91 | protected void initChannel(SocketChannel ch) { 92 | ch.pipeline().addLast(defLoopGroup, new HttpServerCodec(), // 请求解码器 93 | new HttpObjectAggregator(65536), // 将多个消息转换成单一的消息对象 94 | new ChunkedWriteHandler(), // 支持异步发送大的码流,一般用于发送文件流 95 | new IdleStateHandler(360, 0, 0), // 检测链路是否读空闲 96 | new AuthHandler(), // 处理握手和认证 97 | new MessageHandler() // 处理消息的发送 98 | ); 99 | } 100 | }); 101 | 102 | try { 103 | channelFuture = serverBootstrap.bind().sync(); 104 | InetSocketAddress addr = (InetSocketAddress) channelFuture.channel().localAddress(); 105 | log.info("WebSocketServer start success, port is:{}", addr.getPort()); 106 | 107 | // 定时扫描所有的Channel,关闭失效的Channel todo 108 | // executorService.scheduleAtFixedRate(new Runnable() { 109 | // @Override 110 | // public void run() { 111 | // log.info("scanNotActiveChannel --------"); 112 | // UserInfoManager.scanNotActiveChannel(); 113 | // } 114 | // }, 3, 60, TimeUnit.SECONDS); 115 | } catch (InterruptedException e) { 116 | log.error("WebSocketServer start fail,", e); 117 | } 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.lyqf 8 | bullet-chat-new 9 | pom 10 | 1.0-SNAPSHOT 11 | 12 | bullet-comet 13 | bullet-logic 14 | bullet-common 15 | bullet-push 16 | bullet-client 17 | 18 | 19 | 20 | 8 21 | 8 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-parent 27 | 2.4.2 28 | 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-actuator 36 | 2.3.12.RELEASE 37 | 38 | 39 | 40 | org.springframework.kafka 41 | spring-kafka 42 | 2.5.2.RELEASE 43 | 44 | 45 | 46 | io.github.ljwlgl 47 | common-util 48 | 2.1.0 49 | 50 | 51 | 52 | io.netty 53 | netty-all 54 | 4.1.31.Final 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-aop 60 | 61 | 62 | 63 | org.projectlombok 64 | lombok 65 | 1.18.8 66 | 67 | 68 | 69 | 70 | 71 | com.alibaba 72 | fastjson 73 | 1.2.7 74 | 75 | 76 | 77 | 78 | org.projectreactor 79 | reactor-spring 80 | 1.0.1.RELEASE 81 | 82 | 83 | 84 | org.apache.commons 85 | commons-lang3 86 | 3.10 87 | 88 | 89 | 90 | 91 | com.alibaba.cloud 92 | spring-cloud-alibaba-dependencies 93 | 2021.1 94 | pom 95 | import 96 | 97 | 98 | 99 | org.springframework.cloud 100 | spring-cloud-dependencies 101 | 2020.0.3 102 | pom 103 | import 104 | 105 | 106 | 107 | com.alibaba 108 | transmittable-thread-local 109 | 2.12.1 110 | 111 | 112 | 113 | org.redisson 114 | redisson 115 | 3.7.3 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | rdc-releases 126 | https://packages.aliyun.com/maven/repository/2136681-release-8jTOTL/ 127 | 128 | true 129 | always 130 | 131 | 132 | false 133 | 134 | 135 | 136 | 137 | rdc-snapshots 138 | https://packages.aliyun.com/maven/repository/2136681-snapshot-Aa6tYj/ 139 | 140 | false 141 | 142 | 143 | true 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/src/main/java/com/lyqf/bullet/push/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.push.config; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.data.redis.connection.RedisClusterConfiguration; 10 | import org.springframework.data.redis.connection.RedisConnectionFactory; 11 | import org.springframework.data.redis.connection.RedisNode; 12 | import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; 13 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 14 | import org.springframework.data.redis.core.RedisTemplate; 15 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 16 | import org.springframework.data.redis.serializer.StringRedisSerializer; 17 | 18 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 19 | import com.fasterxml.jackson.annotation.PropertyAccessor; 20 | import com.fasterxml.jackson.databind.ObjectMapper; 21 | 22 | import redis.clients.jedis.JedisPoolConfig; 23 | 24 | /** 25 | * @author chenlang 26 | * @date 2022/4/21 2:38 下午 27 | */ 28 | 29 | @Configuration 30 | public class RedisConfig { 31 | 32 | @Value("#{'${spring.redis.cluster.nodes}'.split(',')}") 33 | private List redisCluster; 34 | 35 | @Bean 36 | public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { 37 | RedisTemplate redisTemplate = new RedisTemplate(); 38 | redisTemplate.setConnectionFactory(redisConnectionFactory); 39 | 40 | // 使用Jackson2JsonRedisSerialize 替换默认序列化 41 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = 42 | new Jackson2JsonRedisSerializer(Object.class); 43 | ObjectMapper objectMapper = new ObjectMapper(); 44 | objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 45 | objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 46 | 47 | jackson2JsonRedisSerializer.setObjectMapper(objectMapper); 48 | 49 | // 设置value的序列化规则和 key的序列化规则 50 | redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); 51 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 52 | redisTemplate.setHashKeySerializer(new StringRedisSerializer()); 53 | redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); // 很多地方存了实体类,无法转换成序列化字符串 54 | redisTemplate.afterPropertiesSet(); 55 | return redisTemplate; 56 | } 57 | 58 | @Bean 59 | public JedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPool) { 60 | //single 61 | // RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); 62 | // configuration.setPassword("yasnbangweb4"); 63 | // configuration.setHostName("47.93.61.147"); 64 | // configuration.setPort(6379); 65 | 66 | //cluster 67 | List redisNodeList = new ArrayList<>(); 68 | redisCluster.forEach(k -> { 69 | String[] temp = k.split(":"); 70 | redisNodeList.add(new RedisNode(temp[0], Integer.parseInt(temp[1]))); 71 | }); 72 | 73 | RedisClusterConfiguration configuration = new RedisClusterConfiguration(); 74 | configuration.setClusterNodes(redisNodeList); 75 | configuration.setPassword("chenlanglang"); 76 | 77 | JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcb = 78 | JedisClientConfiguration.builder().usePooling(); 79 | jpcb.poolConfig(jedisPool); 80 | JedisClientConfiguration jedisClientConfiguration = jpcb.build(); 81 | return new JedisConnectionFactory(configuration, jedisClientConfiguration); 82 | } 83 | 84 | 85 | @Bean 86 | public JedisPoolConfig jedisPoolConfig() { 87 | // 创建连接池对象 88 | JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); 89 | // 最大空闲数 90 | jedisPoolConfig.setMaxIdle(50); 91 | // 连接池最大连接数数据库数量 92 | jedisPoolConfig.setMaxTotal(50); 93 | // 连接最大等待时间 94 | jedisPoolConfig.setMaxWaitMillis(1000); 95 | // 逐出连接最小空空闲时间 96 | jedisPoolConfig.setMinEvictableIdleTimeMillis(2000); 97 | // 每次逐出的最大数量 98 | jedisPoolConfig.setNumTestsPerEvictionRun(1); 99 | // 逐出扫描时间的间隔 100 | jedisPoolConfig.setTimeBetweenEvictionRunsMillis(40000); 101 | // 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个 102 | jedisPoolConfig.setTestOnBorrow(false); 103 | // 空闲时检查有效性 104 | jedisPoolConfig.setTestWhileIdle(false); 105 | return jedisPoolConfig; 106 | } 107 | 108 | 109 | // @Bean 110 | // public RedissonClient getRedissonClient() throws Exception { 111 | // Config config = new Config(); 112 | // ClusterServersConfig clusterServersConfig = config.useClusterServers(); 113 | // for (int i = 0; i < redisCluster.size(); i++) { 114 | // clusterServersConfig.addNodeAddress("redis://" + redisCluster.get(i)); 115 | // } 116 | // 117 | // clusterServersConfig.setPassword("chenlanglang"); 118 | // Codec codec = (Codec) ClassUtils 119 | // .forName("org.redisson.codec.JsonJacksonCodec", ClassUtils.getDefaultClassLoader()).newInstance(); 120 | // config.setCodec(codec); 121 | // config.setEventLoopGroup(new NioEventLoopGroup()); 122 | // return Redisson.create(config); 123 | // } 124 | 125 | 126 | } 127 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/src/main/java/com/lyqf/bullet/logic/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.logic.config; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import org.redisson.Redisson; 7 | import org.redisson.api.RedissonClient; 8 | import org.redisson.config.ClusterServersConfig; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.data.redis.connection.RedisClusterConfiguration; 13 | import org.springframework.data.redis.connection.RedisConnectionFactory; 14 | import org.springframework.data.redis.connection.RedisNode; 15 | import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; 16 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; 17 | import org.springframework.data.redis.core.RedisTemplate; 18 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 19 | import org.springframework.data.redis.serializer.StringRedisSerializer; 20 | import org.springframework.util.ClassUtils; 21 | 22 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 23 | import com.fasterxml.jackson.annotation.PropertyAccessor; 24 | import com.fasterxml.jackson.databind.ObjectMapper; 25 | 26 | import io.netty.channel.nio.NioEventLoopGroup; 27 | import redis.clients.jedis.JedisPoolConfig; 28 | 29 | /** 30 | * @author chenlang 31 | * @date 2022/4/21 2:38 下午 32 | */ 33 | 34 | @Configuration 35 | public class RedisConfig { 36 | 37 | @Value("#{'${spring.redis.cluster.nodes}'.split(',')}") 38 | private List redisCluster; 39 | 40 | @Bean 41 | public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { 42 | RedisTemplate redisTemplate = new RedisTemplate(); 43 | redisTemplate.setConnectionFactory(redisConnectionFactory); 44 | 45 | // 使用Jackson2JsonRedisSerialize 替换默认序列化 46 | Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = 47 | new Jackson2JsonRedisSerializer(Object.class); 48 | ObjectMapper objectMapper = new ObjectMapper(); 49 | objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 50 | objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 51 | 52 | jackson2JsonRedisSerializer.setObjectMapper(objectMapper); 53 | 54 | // 设置value的序列化规则和 key的序列化规则 55 | redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); 56 | redisTemplate.setKeySerializer(new StringRedisSerializer()); 57 | redisTemplate.setHashKeySerializer(new StringRedisSerializer()); 58 | redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); // 很多地方存了实体类,无法转换成序列化字符串 59 | redisTemplate.afterPropertiesSet(); 60 | return redisTemplate; 61 | } 62 | 63 | @Bean 64 | public JedisConnectionFactory redisConnectionFactory(JedisPoolConfig jedisPool) { 65 | //single 66 | // RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration(); 67 | // configuration.setPassword("yasnbangweb4"); 68 | // configuration.setHostName("47.93.61.147"); 69 | // configuration.setPort(6379); 70 | 71 | //cluster 72 | List redisNodeList = new ArrayList<>(); 73 | redisCluster.forEach(k -> { 74 | String[] temp = k.split(":"); 75 | redisNodeList.add(new RedisNode(temp[0], Integer.parseInt(temp[1]))); 76 | }); 77 | 78 | RedisClusterConfiguration configuration = new RedisClusterConfiguration(); 79 | configuration.setClusterNodes(redisNodeList); 80 | configuration.setPassword("chenlanglang"); 81 | 82 | JedisClientConfiguration.JedisPoolingClientConfigurationBuilder jpcb = 83 | JedisClientConfiguration.builder().usePooling(); 84 | jpcb.poolConfig(jedisPool); 85 | JedisClientConfiguration jedisClientConfiguration = jpcb.build(); 86 | return new JedisConnectionFactory(configuration, jedisClientConfiguration); 87 | } 88 | 89 | 90 | @Bean 91 | public JedisPoolConfig jedisPoolConfig() { 92 | // 创建连接池对象 93 | JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); 94 | // 最大空闲数 95 | jedisPoolConfig.setMaxIdle(50); 96 | // 连接池最大连接数数据库数量 97 | jedisPoolConfig.setMaxTotal(50); 98 | // 连接最大等待时间 99 | jedisPoolConfig.setMaxWaitMillis(1000); 100 | // 逐出连接最小空空闲时间 101 | jedisPoolConfig.setMinEvictableIdleTimeMillis(2000); 102 | // 每次逐出的最大数量 103 | jedisPoolConfig.setNumTestsPerEvictionRun(1); 104 | // 逐出扫描时间的间隔 105 | jedisPoolConfig.setTimeBetweenEvictionRunsMillis(40000); 106 | // 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个 107 | jedisPoolConfig.setTestOnBorrow(false); 108 | // 空闲时检查有效性 109 | jedisPoolConfig.setTestWhileIdle(false); 110 | return jedisPoolConfig; 111 | } 112 | 113 | // @Bean 114 | // public RedissonClient getRedissonClient() throws Exception { 115 | // Config config = new Config(); 116 | // ClusterServersConfig clusterServersConfig = config.useClusterServers(); 117 | // for (int i = 0; i < redisCluster.size(); i++) { 118 | // clusterServersConfig.addNodeAddress("redis://" + redisCluster.get(i)); 119 | // } 120 | // 121 | // clusterServersConfig.setPassword("chenlanglang"); 122 | // Codec codec = (Codec)ClassUtils 123 | // .forName("org.redisson.codec.JsonJacksonCodec", ClassUtils.getDefaultClassLoader()).newInstance(); 124 | // config.setCodec(codec); 125 | // config.setEventLoopGroup(new NioEventLoopGroup()); 126 | // return Redisson.create(config); 127 | // } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /bullet-push/bullet-push-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bullet-push 7 | com.lyqf 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | bullet-push-core 13 | 14 | 15 | 8 16 | 8 17 | 18 | 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-web 25 | 26 | 27 | 28 | com.alibaba.cloud 29 | spring-cloud-starter-alibaba-nacos-discovery 30 | 31 | 32 | springframework.cloud 33 | spring-cloud-starter-netflix-ribbon 34 | 35 | 36 | 37 | 38 | 39 | org.springframework.cloud 40 | spring-cloud-loadbalancer 41 | 42 | 43 | 44 | org.springframework.cloud 45 | spring-cloud-starter-openfeign 46 | 47 | 48 | 49 | 50 | io.github.ljwlgl 51 | common-util 52 | 53 | 54 | 55 | io.netty 56 | netty-all 57 | 58 | 59 | 60 | org.springframework.boot 61 | spring-boot-starter-actuator 62 | 63 | 64 | 65 | 66 | org.projectlombok 67 | lombok 68 | 69 | 70 | 71 | com.lyqf 72 | bullet-logic-facade 73 | 1.0-SNAPSHOT 74 | 75 | 76 | 77 | com.lyqf 78 | bullet-common 79 | 1.0-SNAPSHOT 80 | 81 | 82 | 83 | com.lyqf 84 | bullet-comet-facade 85 | 1.0-SNAPSHOT 86 | 87 | 88 | 89 | com.alibaba 90 | fastjson 91 | 92 | 93 | 94 | 95 | org.projectreactor 96 | reactor-spring 97 | 98 | 99 | 100 | org.apache.commons 101 | commons-lang3 102 | 103 | 104 | 105 | org.springframework.boot 106 | spring-boot-starter-data-redis 107 | 108 | 109 | io.lettuce 110 | lettuce-core 111 | 112 | 113 | 114 | 115 | 116 | redis.clients 117 | jedis 118 | 119 | 120 | 121 | 122 | org.springframework.kafka 123 | spring-kafka 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | org.springframework.boot 134 | spring-boot-maven-plugin 135 | 2.3.12.RELEASE 136 | 137 | 138 | com.lyqf.bullet.push.PushApp 139 | ZIP 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /bullet-logic/bullet-logic-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | bullet-logic 7 | com.lyqf 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | bullet-logic-core 13 | 14 | 15 | 8 16 | 8 17 | 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | 27 | com.alibaba.cloud 28 | spring-cloud-starter-alibaba-nacos-discovery 29 | 30 | 31 | springframework.cloud 32 | spring-cloud-starter-netflix-ribbon 33 | 34 | 35 | 36 | 37 | 38 | org.springframework.cloud 39 | spring-cloud-loadbalancer 40 | 41 | 42 | 43 | 44 | org.springframework.cloud 45 | spring-cloud-starter-openfeign 46 | 47 | 48 | 49 | io.github.ljwlgl 50 | common-util 51 | 52 | 53 | 54 | io.netty 55 | netty-all 56 | 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-actuator 61 | 62 | 63 | 64 | org.projectlombok 65 | lombok 66 | 67 | 68 | 69 | com.lyqf 70 | bullet-logic-facade 71 | 1.0-SNAPSHOT 72 | 73 | 74 | 75 | com.lyqf 76 | bullet-common 77 | 1.0-SNAPSHOT 78 | 79 | 80 | 81 | com.lyqf 82 | bullet-comet-facade 83 | 1.0-SNAPSHOT 84 | 85 | 86 | 87 | com.alibaba 88 | fastjson 89 | 90 | 91 | 92 | 93 | org.projectreactor 94 | reactor-spring 95 | 96 | 97 | 98 | org.apache.commons 99 | commons-lang3 100 | 101 | 102 | 103 | org.springframework.boot 104 | spring-boot-starter-data-redis 105 | 106 | 107 | io.lettuce 108 | lettuce-core 109 | 110 | 111 | 112 | 113 | 114 | redis.clients 115 | jedis 116 | 117 | 118 | 119 | org.springframework.kafka 120 | spring-kafka 121 | 122 | 123 | 124 | org.redisson 125 | redisson 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | org.springframework.boot 135 | spring-boot-maven-plugin 136 | 2.3.12.RELEASE 137 | 138 | 139 | com.lyqf.bullet.logic.LogicApp 140 | ZIP 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /bullet-comet/bullet-comet-core/src/main/java/com/lyqf/bullet/comet/netty/handler/AuthHandler.java: -------------------------------------------------------------------------------- 1 | package com.lyqf.bullet.comet.netty.handler; 2 | 3 | import com.lyqf.bullet.comet.context.UserContextHolder; 4 | import com.lyqf.bullet.comet.dto.UserRoomDTO; 5 | import com.lyqf.bullet.comet.manager.UserClearOnlineManager; 6 | import com.lyqf.bullet.comet.util.IpUtil; 7 | import com.lyqf.bullet.comet.util.SpringContextBeanUtil; 8 | import com.lyqf.bullet.comet.util.UrlUtil; 9 | import com.lyqf.bullet.comet.vo.LoginReq; 10 | import com.lyqf.bullet.common.model.ApiResponse; 11 | import com.lyqf.bullet.logic.client.UserAuthClient; 12 | import com.lyqf.bullet.logic.client.vo.AuthUserResp; 13 | import com.lyqf.bullet.logic.client.vo.ClearOnlineReq; 14 | 15 | import io.netty.channel.ChannelHandlerContext; 16 | import io.netty.channel.SimpleChannelInboundHandler; 17 | import io.netty.handler.codec.http.FullHttpRequest; 18 | import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; 19 | import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; 20 | import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; 21 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 22 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 23 | import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; 24 | import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; 25 | import io.netty.handler.timeout.IdleState; 26 | import io.netty.handler.timeout.IdleStateEvent; 27 | import lombok.extern.slf4j.Slf4j; 28 | 29 | import java.util.concurrent.atomic.LongAdder; 30 | 31 | /** 32 | * @author chenlang 33 | * @date 2022/5/11 3:05 下午 34 | */ 35 | @Slf4j 36 | @SuppressWarnings("all") 37 | public class AuthHandler extends SimpleChannelInboundHandler { 38 | 39 | private WebSocketServerHandshaker webSocketServerHandshaker; 40 | 41 | private UserAuthClient userAuthClient = SpringContextBeanUtil.getBean(UserAuthClient.class); 42 | 43 | private static LongAdder adder = new LongAdder(); 44 | 45 | @Override 46 | protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 47 | // websocket第一步是通过http建立连接,第二步才是真正的websocket数据 48 | if (msg instanceof FullHttpRequest) { 49 | handleHttpRequest(ctx, (FullHttpRequest)msg); 50 | } else if (msg instanceof WebSocketFrame) { 51 | handleWebSocketInfo(ctx, (WebSocketFrame)msg); 52 | } 53 | } 54 | 55 | @Override 56 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 57 | if (evt instanceof IdleStateEvent) { 58 | IdleStateEvent evnet = (IdleStateEvent)evt; 59 | log.info("收到连接信息长时间没有消息事件。。。"); 60 | // 判断Channel是否读空闲, 读空闲时移除Channel 61 | if (evnet.state().equals(IdleState.READER_IDLE)) { 62 | UserRoomDTO userRoomDTO = UserContextHolder.channelWithUserMap.get(ctx.channel()); 63 | if (userRoomDTO == null) { 64 | return; 65 | } 66 | 67 | log.info("该连接信息长时间没有消息,userid:{} 开始断开连接。。", userRoomDTO.getUserId()); 68 | ClearOnlineReq req = new ClearOnlineReq(); 69 | req.setRoomId(userRoomDTO.getRoomId()); 70 | req.setUserId(userRoomDTO.getUserId()); 71 | userAuthClient.clearOnlineByUserId(req); 72 | 73 | UserContextHolder.clear(ctx.channel(), userRoomDTO.getUserId(), userRoomDTO.getRoomId()); 74 | ctx.channel().close(); 75 | log.info("该连接信息长时间没有消息,userid:{} 断开连接完成", userRoomDTO.getUserId()); 76 | } 77 | } 78 | ctx.fireUserEventTriggered(evt); 79 | } 80 | 81 | /** 82 | * 建立连接时 依赖http,但是header会添加 Upgrade 作为判断 83 | * 84 | * @param ctx 85 | * @param request 86 | */ 87 | private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) { 88 | if (!request.decoderResult().isSuccess() || !"websocket".equals(request.headers().get("Upgrade"))) { 89 | log.warn("protobuf don't support websocket"); 90 | ctx.channel().close(); 91 | return; 92 | } 93 | 94 | String realAddress = IpUtil.getIpInfo(ctx, request); 95 | log.info("auth realAddress:{}", realAddress); 96 | 97 | WebSocketServerHandshakerFactory handshakerFactory = new WebSocketServerHandshakerFactory("", null, true); 98 | 99 | webSocketServerHandshaker = handshakerFactory.newHandshaker(request); 100 | 101 | if (webSocketServerHandshaker == null) { 102 | WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); 103 | } else { 104 | // 动态加入websocket的编解码处理 105 | webSocketServerHandshaker.handshake(ctx.channel(), request); 106 | String uri = request.uri(); 107 | LoginReq loginReq = UrlUtil.getLoginInfo(uri); 108 | 109 | // AuthUserReq req = new AuthUserReq(); 110 | // req.setNode(IpUtil.getLocalIp()); 111 | // req.setRoomId(loginReq.getRoomId()); 112 | // req.setToken(loginReq.getToken()); 113 | // ApiResponse authUserResult = userAuthClient.authUser(req); 114 | // Long userId = handleAuthResult(authUserResult, ctx); 115 | // Long userId = 12L; 116 | // log.info("auth finish,userId:{}", userId); 117 | log.info("auth finish,token:{}", loginReq.getToken()); 118 | adder.increment(); 119 | log.info("当前连接数:{}",adder.longValue()); 120 | } 121 | } 122 | 123 | /** 124 | * 125 | * @param authUserResult 126 | */ 127 | private Long handleAuthResult(ApiResponse authUserResult, ChannelHandlerContext ctx) { 128 | if (!authUserResult.isSuccess()) { 129 | ctx.channel().close(); 130 | return -1L; 131 | } 132 | 133 | AuthUserResp resp = authUserResult.getData(); 134 | if (resp == null || !resp.getLoginResult()) { 135 | ctx.channel().close(); 136 | return -1L; 137 | } 138 | 139 | UserRoomDTO userRoomDTO = new UserRoomDTO(); 140 | userRoomDTO.setRoomId(resp.getRoomId()); 141 | userRoomDTO.setUserId(resp.getUserId()); 142 | UserContextHolder.add(ctx.channel(), userRoomDTO); 143 | 144 | return resp.getUserId() == null ? -1 : resp.getUserId(); 145 | } 146 | 147 | /** 148 | * websocket消息体 149 | * 150 | * @param ctx 151 | * @param frame 152 | */ 153 | @SuppressWarnings("all") 154 | private void handleWebSocketInfo(ChannelHandlerContext ctx, WebSocketFrame frame) { 155 | // 判断是否关闭链路命令 156 | if (frame instanceof CloseWebSocketFrame) { 157 | log.info("收到客服端主动断开链接请求"); 158 | UserClearOnlineManager.clearOnlineInfo(ctx,null); 159 | return; 160 | } 161 | 162 | // 判断是否Ping消息 163 | if (frame instanceof PingWebSocketFrame) { 164 | log.info("ping message:{}", frame.content().retain()); 165 | ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain())); 166 | return; 167 | } 168 | 169 | // 判断是否Pong消息 170 | if (frame instanceof PongWebSocketFrame) { 171 | log.info("pong message:{}", frame.content().retain()); 172 | ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain())); 173 | return; 174 | } 175 | 176 | if (!(frame instanceof TextWebSocketFrame)) { 177 | throw new UnsupportedOperationException(frame.getClass().getName() + " frame type not supported"); 178 | } 179 | 180 | // 后续消息交给MessageHandler处理 181 | ctx.fireChannelRead(frame.retain()); 182 | } 183 | 184 | @Override 185 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 186 | UserClearOnlineManager.clearOnlineInfo(ctx,cause); 187 | } 188 | 189 | } 190 | --------------------------------------------------------------------------------