From 0d9a0c9af6573967f588735c2d88d185e93123f0 Mon Sep 17 00:00:00 2001 From: zhangye <654600784@qq.com> Date: Sat, 28 Dec 2019 10:25:58 +0800 Subject: [PATCH] 1228 --- .../cloudutil/feign/TallFeignClient.java | 29 ++ .../java/com/ccsens/game/GameApplication.java | 14 +- .../com/ccsens/game/api/ScreenController.java | 2 + .../com/ccsens/game/bean/dto/ScreenDto.java | 2 + .../game/bean/dto/message/AckMessageDto.java | 32 ++ .../game/bean/dto/message/AuthMessageDto.java | 34 ++ .../game/bean/dto/message/BaseMessageDto.java | 35 ++ .../bean/dto/message/ChromeMessageDto.java | 32 ++ .../bean/dto/message/GameMessageCountIn.java | 5 + .../bean/dto/message/GameMessageCountOut.java | 31 ++ .../dto/message/GameMessageWithGetUrlDto.java | 32 ++ .../bean/dto/message/HeartMessageDto.java | 23 ++ .../bean/dto/message/PPTCtlMessageDto.java | 35 ++ .../com/ccsens/game/bean/vo/ScreenVo.java | 4 +- .../com/ccsens/game/config/SpringConfig.java | 1 - .../com/ccsens/game/netty/ChannelManager.java | 362 ++++++++++++++++++ .../game/netty/GameMessageConstant.java | 217 +++++++++++ .../ccsens/game/netty/WrapperedChannel.java | 200 ++++++++++ .../game/netty/wsserver/NettyWsServer.java | 70 ++++ .../game/netty/wsserver/WebSocketDecoder.java | 33 ++ .../game/netty/wsserver/WebSocketEncoder.java | 25 ++ .../game/netty/wsserver/WebSocketHandler.java | 135 +++++++ .../game/persist/dao/GameUserJoinDao.java | 4 +- .../ccsens/game/service/ClientService.java | 35 +- .../ccsens/game/service/IClientService.java | 6 + .../ccsens/game/service/IMessageService.java | 18 + .../ccsens/game/service/IScreenService.java | 2 +- .../ccsens/game/service/MessageService.java | 67 ++++ .../ccsens/game/service/ScreenService.java | 65 +++- mt/src/main/resources/application-test.yml | 2 +- mt/src/main/resources/application.yml | 4 +- mt/src/main/resources/druid-test.yml | 4 +- pom.xml | 7 + .../bean/dto/message/ChromeMessageDto.java | 32 ++ .../bean/dto/message/PPTCtlMessageDto.java | 35 ++ .../dto/message/SyncMessageWithStartDto.java | 4 +- .../com/ccsens/tall/config/SpringConfig.java | 1 + .../tall/persist/dao/ProTaskMemberDao.java | 8 + .../ccsens/tall/service/IMessageService.java | 2 +- .../tall/service/IProMemberService.java | 2 + .../tall/service/ITaskMemberService.java | 3 + .../ccsens/tall/service/MessageService.java | 4 +- .../ccsens/tall/service/ProMemberService.java | 17 + .../tall/service/TaskSubTimeService.java | 21 +- .../com/ccsens/tall/web/UserController.java | 10 + .../main/java/com/ccsens/util/CodeEnum.java | 4 +- .../java/com/ccsens/util/WebConstant.java | 34 +- .../bean/message/common/MessageConstant.java | 34 ++ 48 files changed, 1738 insertions(+), 40 deletions(-) create mode 100644 game/src/main/java/com/ccsens/game/bean/dto/message/AckMessageDto.java create mode 100644 game/src/main/java/com/ccsens/game/bean/dto/message/AuthMessageDto.java create mode 100644 game/src/main/java/com/ccsens/game/bean/dto/message/BaseMessageDto.java create mode 100644 game/src/main/java/com/ccsens/game/bean/dto/message/ChromeMessageDto.java create mode 100644 game/src/main/java/com/ccsens/game/bean/dto/message/GameMessageCountIn.java create mode 100644 game/src/main/java/com/ccsens/game/bean/dto/message/GameMessageCountOut.java create mode 100644 game/src/main/java/com/ccsens/game/bean/dto/message/GameMessageWithGetUrlDto.java create mode 100644 game/src/main/java/com/ccsens/game/bean/dto/message/HeartMessageDto.java create mode 100644 game/src/main/java/com/ccsens/game/bean/dto/message/PPTCtlMessageDto.java create mode 100644 game/src/main/java/com/ccsens/game/netty/ChannelManager.java create mode 100644 game/src/main/java/com/ccsens/game/netty/GameMessageConstant.java create mode 100644 game/src/main/java/com/ccsens/game/netty/WrapperedChannel.java create mode 100644 game/src/main/java/com/ccsens/game/netty/wsserver/NettyWsServer.java create mode 100644 game/src/main/java/com/ccsens/game/netty/wsserver/WebSocketDecoder.java create mode 100644 game/src/main/java/com/ccsens/game/netty/wsserver/WebSocketEncoder.java create mode 100644 game/src/main/java/com/ccsens/game/netty/wsserver/WebSocketHandler.java create mode 100644 game/src/main/java/com/ccsens/game/service/IMessageService.java create mode 100644 game/src/main/java/com/ccsens/game/service/MessageService.java create mode 100644 tall/src/main/java/com/ccsens/tall/bean/dto/message/ChromeMessageDto.java create mode 100644 tall/src/main/java/com/ccsens/tall/bean/dto/message/PPTCtlMessageDto.java create mode 100644 tall/src/main/java/com/ccsens/tall/persist/dao/ProTaskMemberDao.java diff --git a/cloudutil/src/main/java/com/ccsens/cloudutil/feign/TallFeignClient.java b/cloudutil/src/main/java/com/ccsens/cloudutil/feign/TallFeignClient.java index c06d0e17..11eaa6ca 100644 --- a/cloudutil/src/main/java/com/ccsens/cloudutil/feign/TallFeignClient.java +++ b/cloudutil/src/main/java/com/ccsens/cloudutil/feign/TallFeignClient.java @@ -18,6 +18,7 @@ import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.List; import java.util.Map; /** @@ -90,6 +91,24 @@ public interface TallFeignClient { */ @GetMapping("/users/member") JsonResponse getMemberByUserId(@RequestParam( name = "userId") Long userId,@RequestParam( name = "projectId") Long projectId); + + + /** + * 获取项目下的所有成员id + * @param projectId + * @return + */ + @GetMapping("/users/allMemberAll") + List getMemberIdListByProject(@RequestParam( name = "projectId")Long projectId); + + /** + * 通过任务id获得项目id(消息系统用) + * @param token + * @return + */ + @GetMapping("/users/claims") + String getUserId(@RequestParam( name = "token")String token); + } @Slf4j @@ -142,6 +161,16 @@ class TallFeignClientFallBack implements FallbackFactory { public JsonResponse getMemberByUserId(Long userId,Long projectId) { return JsonResponse.newInstance().fail(); } + + @Override + public List getMemberIdListByProject(Long projectId) { + return null; + } + + @Override + public String getUserId(String token) { + return null; + } }; } diff --git a/game/src/main/java/com/ccsens/game/GameApplication.java b/game/src/main/java/com/ccsens/game/GameApplication.java index 54ee7de2..ae24fe24 100644 --- a/game/src/main/java/com/ccsens/game/GameApplication.java +++ b/game/src/main/java/com/ccsens/game/GameApplication.java @@ -1,6 +1,9 @@ package com.ccsens.game; +import com.ccsens.game.netty.wsserver.NettyWsServer; import org.mybatis.spring.annotation.MapperScan; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.web.servlet.ServletComponentScan; @@ -15,10 +18,17 @@ import org.springframework.scheduling.annotation.EnableAsync; @EnableCircuitBreaker @EnableFeignClients(basePackages = "com.ccsens.cloudutil.feign") @SpringBootApplication(scanBasePackages = "com.ccsens") -public class GameApplication { +public class GameApplication implements CommandLineRunner { + @Autowired + private NettyWsServer nettyWsServer; public static void main(String[] args) { - SpringApplication.run(GameApplication.class, args); + SpringApplication.run(GameApplication.class,args); } + @Override + public void run(String... args) throws Exception { + nettyWsServer.start(); + + } } diff --git a/game/src/main/java/com/ccsens/game/api/ScreenController.java b/game/src/main/java/com/ccsens/game/api/ScreenController.java index fd83d544..a16faaff 100644 --- a/game/src/main/java/com/ccsens/game/api/ScreenController.java +++ b/game/src/main/java/com/ccsens/game/api/ScreenController.java @@ -44,6 +44,7 @@ public class ScreenController { return JsonResponse.newInstance().ok(gameInfoVo); } + @Login @ApiOperation(value = "获取游戏的状态", notes = "") @ApiImplicitParams({ }) @@ -64,6 +65,7 @@ public class ScreenController { return JsonResponse.newInstance().ok(); } + @Login @ApiOperation(value = "再玩一次", notes = "") @ApiImplicitParams({ }) diff --git a/game/src/main/java/com/ccsens/game/bean/dto/ScreenDto.java b/game/src/main/java/com/ccsens/game/bean/dto/ScreenDto.java index 04254e43..03c91fe2 100644 --- a/game/src/main/java/com/ccsens/game/bean/dto/ScreenDto.java +++ b/game/src/main/java/com/ccsens/game/bean/dto/ScreenDto.java @@ -9,6 +9,8 @@ public class ScreenDto { @Data @ApiModel public static class MemberGame{ + @ApiModelProperty("项目id") + private Long projectId; @ApiModelProperty("要创建的小游戏的类型 例如:SQ 代表数钱小游戏") private String gameType; } diff --git a/game/src/main/java/com/ccsens/game/bean/dto/message/AckMessageDto.java b/game/src/main/java/com/ccsens/game/bean/dto/message/AckMessageDto.java new file mode 100644 index 00000000..a17ca984 --- /dev/null +++ b/game/src/main/java/com/ccsens/game/bean/dto/message/AckMessageDto.java @@ -0,0 +1,32 @@ +package com.ccsens.game.bean.dto.message; + +import com.ccsens.util.WebConstant; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +@Data +public class AckMessageDto extends BaseMessageDto { + @Setter + @Getter + public static class Data { + Long msgId; + } + + private Data data; + + public AckMessageDto(){ + setType(WebConstant.Message_Type.Ack.phase); + setEvent(WebConstant.Message_Ack_Event.Ack.phase); + setTime(System.currentTimeMillis()); + } + + public AckMessageDto(Long projectId, Long msgId){ + this(); + setProjectId(projectId); + + Data d = new Data(); + d.setMsgId(msgId); + setData(d); + } +} diff --git a/game/src/main/java/com/ccsens/game/bean/dto/message/AuthMessageDto.java b/game/src/main/java/com/ccsens/game/bean/dto/message/AuthMessageDto.java new file mode 100644 index 00000000..f0794349 --- /dev/null +++ b/game/src/main/java/com/ccsens/game/bean/dto/message/AuthMessageDto.java @@ -0,0 +1,34 @@ +package com.ccsens.game.bean.dto.message; + +import com.ccsens.util.WebConstant; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +@Data +public class AuthMessageDto extends BaseMessageDto { + @Setter + @Getter + public static class Data{ + private Long id; + private String userId; + private String channelId; + private String token; + } + + private Data data; + + public AuthMessageDto(){ + setType(WebConstant.Message_Type.Auth.phase); + setEvent(WebConstant.Message_Auth_Event.Auth.phase); + setTime(System.currentTimeMillis()); + } + + public AuthMessageDto(String userId, String channelId){ + this(); + Data d = new Data(); + d.setUserId(userId); + d.setChannelId(channelId); + setData(d); + } +} diff --git a/game/src/main/java/com/ccsens/game/bean/dto/message/BaseMessageDto.java b/game/src/main/java/com/ccsens/game/bean/dto/message/BaseMessageDto.java new file mode 100644 index 00000000..c67ac7d7 --- /dev/null +++ b/game/src/main/java/com/ccsens/game/bean/dto/message/BaseMessageDto.java @@ -0,0 +1,35 @@ +package com.ccsens.game.bean.dto.message; + +import lombok.Data; + +import java.util.List; + +@Data +public class BaseMessageDto { + @Data + public static class MessageUser { + private Long id; + private Long userId; //本质上是authId //20190507 本质上是userId + private String nickname; + private String avatarUrl; + private boolean hasRead; + public MessageUser(){ + hasRead = false; + } + public MessageUser(Long id,Long userId,String nickname,String avatarUrl){ + this(); + this.id = id; + this.userId = userId; + this.nickname = nickname; + this.avatarUrl = avatarUrl; + } + } + + private Long time; + private String type; + private String event; + private Long projectId; + private MessageUser sender; + private List receivers; +// private Object data; +} diff --git a/game/src/main/java/com/ccsens/game/bean/dto/message/ChromeMessageDto.java b/game/src/main/java/com/ccsens/game/bean/dto/message/ChromeMessageDto.java new file mode 100644 index 00000000..ba88013e --- /dev/null +++ b/game/src/main/java/com/ccsens/game/bean/dto/message/ChromeMessageDto.java @@ -0,0 +1,32 @@ +package com.ccsens.game.bean.dto.message; + +import com.ccsens.util.WebConstant; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +/** + * @author __zHangSan + */ +@Data +public class ChromeMessageDto extends BaseMessageDto{ + @Setter + @Getter + public static class Data{ + private String url; + } + private Data data; + + public ChromeMessageDto(){ + setType(WebConstant.Message_Type.Chrome.phase); + setTime(System.currentTimeMillis()); + } + + public ChromeMessageDto(String url){ + this(); + if(data == null){ + data = new Data(); + } + data.setUrl(url); + } +} diff --git a/game/src/main/java/com/ccsens/game/bean/dto/message/GameMessageCountIn.java b/game/src/main/java/com/ccsens/game/bean/dto/message/GameMessageCountIn.java new file mode 100644 index 00000000..3ed968f7 --- /dev/null +++ b/game/src/main/java/com/ccsens/game/bean/dto/message/GameMessageCountIn.java @@ -0,0 +1,5 @@ +package com.ccsens.game.bean.dto.message; + +public class GameMessageCountIn extends BaseMessageDto{ + +} diff --git a/game/src/main/java/com/ccsens/game/bean/dto/message/GameMessageCountOut.java b/game/src/main/java/com/ccsens/game/bean/dto/message/GameMessageCountOut.java new file mode 100644 index 00000000..03398438 --- /dev/null +++ b/game/src/main/java/com/ccsens/game/bean/dto/message/GameMessageCountOut.java @@ -0,0 +1,31 @@ +package com.ccsens.game.bean.dto.message; + +import com.ccsens.util.WebConstant; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; +@Data +public class GameMessageCountOut extends BaseMessageDto { + @Setter + @Getter + public static class Data{ + private int totalTimes; + private int totalScore; + } + + private Data data; + + public GameMessageCountOut(){ + setType(WebConstant.Message_Type.Game.phase); + setEvent(WebConstant.Message_Url_Event.Url.phase); + setTime(System.currentTimeMillis()); + } + + public GameMessageCountOut(int totalTimes, int totalScore){ + this(); + Data d = new Data(); + d.setTotalTimes(totalTimes); + d.setTotalScore(totalScore); + setData(d); + } +} diff --git a/game/src/main/java/com/ccsens/game/bean/dto/message/GameMessageWithGetUrlDto.java b/game/src/main/java/com/ccsens/game/bean/dto/message/GameMessageWithGetUrlDto.java new file mode 100644 index 00000000..6688cd46 --- /dev/null +++ b/game/src/main/java/com/ccsens/game/bean/dto/message/GameMessageWithGetUrlDto.java @@ -0,0 +1,32 @@ +package com.ccsens.game.bean.dto.message; + +import com.ccsens.util.WebConstant; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +@Data +public class GameMessageWithGetUrlDto extends BaseMessageDto { + @Setter + @Getter + public static class Data{ + private Long recordId; + private String url; + } + + private Data data; + + public GameMessageWithGetUrlDto(){ + setType(WebConstant.Message_Type.Game.phase); + setEvent(WebConstant.Message_Url_Event.Url.phase); + setTime(System.currentTimeMillis()); + } + + public GameMessageWithGetUrlDto(Long recordId, String url){ + this(); + Data d = new Data(); + d.setRecordId(recordId); + d.setUrl(url); + setData(d); + } +} diff --git a/game/src/main/java/com/ccsens/game/bean/dto/message/HeartMessageDto.java b/game/src/main/java/com/ccsens/game/bean/dto/message/HeartMessageDto.java new file mode 100644 index 00000000..bd19b18a --- /dev/null +++ b/game/src/main/java/com/ccsens/game/bean/dto/message/HeartMessageDto.java @@ -0,0 +1,23 @@ +package com.ccsens.game.bean.dto.message; + +import com.ccsens.util.WebConstant; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +@Data +public class HeartMessageDto extends BaseMessageDto{ + @Setter + @Getter + public static class Data{ + private int major; + private int minor; + } + private Data data; + + public HeartMessageDto(){ + setType(WebConstant.Message_Type.Heart.phase); + setEvent(WebConstant.Message_Heart_Event.Heart.phase); + setTime(System.currentTimeMillis()); + } +} diff --git a/game/src/main/java/com/ccsens/game/bean/dto/message/PPTCtlMessageDto.java b/game/src/main/java/com/ccsens/game/bean/dto/message/PPTCtlMessageDto.java new file mode 100644 index 00000000..3ba14032 --- /dev/null +++ b/game/src/main/java/com/ccsens/game/bean/dto/message/PPTCtlMessageDto.java @@ -0,0 +1,35 @@ +package com.ccsens.game.bean.dto.message; + +import com.ccsens.util.WebConstant; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +/** + * @author __zHangSan + */ +@Data +public class PPTCtlMessageDto extends BaseMessageDto{ + @Setter + @Getter + public static class Data{ + /** + * Supported Operation: up,down,begin,end + */ + private String oper; + } + private Data data; + + public PPTCtlMessageDto(){ + setType(WebConstant.Message_Type.PPTCtl.phase); + setTime(System.currentTimeMillis()); + } + + public PPTCtlMessageDto(String oper){ + this(); + if(data == null){ + data = new Data(); + } + data.setOper(oper); + } +} diff --git a/game/src/main/java/com/ccsens/game/bean/vo/ScreenVo.java b/game/src/main/java/com/ccsens/game/bean/vo/ScreenVo.java index 946c9418..98e3e938 100644 --- a/game/src/main/java/com/ccsens/game/bean/vo/ScreenVo.java +++ b/game/src/main/java/com/ccsens/game/bean/vo/ScreenVo.java @@ -12,9 +12,11 @@ public class ScreenVo { @Data @ApiModel public static class UrlVo{ + @ApiModelProperty("大屏的路径") + private Long id; @ApiModelProperty("大屏的路径") private String url; - @ApiModelProperty("游戏路径") + @ApiModelProperty("游戏规则") private List ruleList; } diff --git a/game/src/main/java/com/ccsens/game/config/SpringConfig.java b/game/src/main/java/com/ccsens/game/config/SpringConfig.java index 55fa3c48..efa90773 100644 --- a/game/src/main/java/com/ccsens/game/config/SpringConfig.java +++ b/game/src/main/java/com/ccsens/game/config/SpringConfig.java @@ -76,7 +76,6 @@ public class SpringConfig implements WebMvcConfigurer { configurer.favorPathExtension(false); } - @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS"); diff --git a/game/src/main/java/com/ccsens/game/netty/ChannelManager.java b/game/src/main/java/com/ccsens/game/netty/ChannelManager.java new file mode 100644 index 00000000..0bf8ffba --- /dev/null +++ b/game/src/main/java/com/ccsens/game/netty/ChannelManager.java @@ -0,0 +1,362 @@ +package com.ccsens.game.netty; + +import cn.hutool.core.collection.CollectionUtil; +import io.netty.channel.Channel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author wei + */ +public class ChannelManager { + private static Logger logger = LoggerFactory.getLogger(ChannelManager.class); + private static ThreadLocal threadLocal = new ThreadLocal<>(); + + /** + * UserId,WrappedChannel authed channels; + */ + private static Map> authedChannels; + /** + * Channel,WrapperedChannel + */ + private static Map rawChannels; + + static { + authedChannels = new ConcurrentHashMap<>(); + rawChannels = new ConcurrentHashMap<>(); + } + + /** + * 私有构造,不允许生成该类对象 + */ + private ChannelManager(){} + + /** + * 设置当前正在错误的channel + * @param channel netty ws/tcp连接 + */ + public static void setCurrentChannel(Channel channel){ + threadLocal.set(channel); + } + + /** + * 获取当前线程的channel对象 + * @return channel + */ + public static Channel getCurrentChannel(){ + return threadLocal.get(); + } + + /** + * 删除当前线程的channel对象 + */ + public static void removeCurrentChannel(){ + threadLocal.remove(); + } + + /** + * 添加一个新的连接(Channel) + * @param channel 新的连接 + * @param serverType 服务器类型 + */ + public static synchronized void addChannel(Channel channel,String serverType){ + logger.debug("Invoke addChannel({},{})",channel,serverType); + if(null != channel) { + rawChannels.put(channel, new WrapperedChannel(channel, serverType)); + logger.debug("Add a new channel: {},{}",channel.id().asLongText(),serverType); + }else{ + logger.error("channel is null"); + } + } + + /** + * 用户认证 + * @param channel 连接 + * @param userId 用户ID + * @param major 客户端主版本号 + * @param minor 客户端次版本号 + */ + public static synchronized void authChannel(Channel channel,String userId,Integer major,Integer minor){ + logger.debug("Invoke authedChannels({},{},{},{})",channel.id().asLongText(),userId,major,minor); + major = major != null ? major : 0; + minor = minor != null ? minor : 0; + + WrapperedChannel wrapperedChannel = rawChannels.get(channel); + if(wrapperedChannel != null){ + wrapperedChannel.whenAuthed(userId,major,minor); + Set authedWchannelSet = authedChannels.computeIfAbsent(userId, k -> new HashSet<>()); + authedWchannelSet.add(wrapperedChannel); + logger.debug("Authed channel {} with user {}", channel.id().asLongText(), userId); + }else{ + logger.error("Authed channel,but wrappedChannel is null."); + } + } + + /** + * 移除一个连接(Channel) + * @param channel 要移除的连接 + */ + public static synchronized void removeChannel(Channel channel){ + logger.debug("Invoke removeChannel({})",channel.id().asLongText()); + WrapperedChannel wrapperedChannel = rawChannels.get(channel); + if(wrapperedChannel != null){ + //从rawChannels集合中删除 + rawChannels.remove(channel); + logger.debug("Remove a channel from rawChannels: {}",channel.id().asLongText()); + if(wrapperedChannel.isAuthed()){ + Set authedChannelSet = authedChannels.get(wrapperedChannel.getUserId()); + //从authedChannel集合的value(set)中删除 + if(CollectionUtil.isNotEmpty(authedChannelSet)){ + authedChannelSet.remove(wrapperedChannel); + logger.debug("Remove a channel from authedChannelSet: {}, {}",wrapperedChannel.getUserId(),channel.id().asLongText()); + } + //从authedChannel中删除,此处不用else,因为if中语句执行完毕之后,authedChannelSet也可能变成空集合 + if(CollectionUtil.isEmpty(authedChannelSet)){ + authedChannels.remove(wrapperedChannel.getUserId()); + logger.debug("Remove a user from authedChannels: {}",wrapperedChannel.getUserId()); + } + } + }else{ + logger.error("Remove channel,but wrappedChannel is null."); + } + + if(channel.isOpen() || channel.isActive()){ + channel.close(); + } + } + + /** + * 移除一个用户 + * @param userId 要移除的用户 + */ + public static synchronized void removeUser(String userId){ + logger.debug("Invoke remove user : {}",userId); + Set wChannelSet = authedChannels.get(userId); + if(CollectionUtil.isNotEmpty(wChannelSet)){ + for(WrapperedChannel wChannel : wChannelSet){ + //从rawChannel中依次删除 + rawChannels.remove(wChannel.getChannel()); + logger.debug("Remove a channel from rawChannels: {}",wChannel.getChannel().id().asLongText()); + } + } + //从authedChannel中删除 + authedChannels.remove(userId); + logger.debug("Remove a user from authedChannels: {}",userId); + } + + /** + * 添加版本号 + * 只能给已认证的请求添加版本号 + * @param channel 连接 + * @param major 主版本号 + * @param minor 次版本号 + */ + public static synchronized void versionChannel(Channel channel,int major,int minor){ + logger.debug("Invoke Version channel({},{},{}))",channel.id().asLongText(),major,minor); + WrapperedChannel wChannel = rawChannels.get(channel); + if(wChannel != null){ + wChannel.setVersion(major,minor); + logger.debug("Version Channel: {},{},{}",channel.id().asLongText(),major,minor); + }else{ + logger.error("Remove channel,but wrappedChannel is null."); + } + } + + /** + * 发送广播消息给所有已认证Channel + * @param message 消息 + */ + public static synchronized void broadCastAuthed(Object message) { + logger.debug("Invoke broadCastAuthed({})",message); + for (Map.Entry entry : rawChannels.entrySet()) { + WrapperedChannel wChannel = entry.getValue(); + if(wChannel.isAuthed()) { + wChannel.writeAndFlush(message); + logger.debug("Send Message {} to {},{}",message,wChannel.getUserId(),wChannel.getId()); + } + } + } + + /** + * 发送广播消息 + * @param message 消息 + */ + public static synchronized void broadCast(Object message) { + logger.debug("Invoke broadCast({})",message); + for (Map.Entry entry : rawChannels.entrySet()) { + entry.getValue().writeAndFlush(message); + logger.debug("Send Message {} to {}",message,entry.getValue().getId()); + } + } + + /** + * 发送消息给某个连接 + * @param channel 连接 + * @param message 消息 + */ + public static synchronized void sendTo(Channel channel,Object message){ + logger.debug("Invoke sendTo({},{})",channel.id().asLongText(),message); + WrapperedChannel wrapperedChannel = rawChannels.get(channel); + if(wrapperedChannel != null) { + wrapperedChannel.writeAndFlush(message); + logger.debug("Write message {} to Channel {}",message,channel); + }else{ + logger.error("can't find channel from rawChannels"); + } + } + + /** + * 发送消息给某个用户 + * @param userId 用户ID + * @param message 消息 + */ + public static synchronized boolean sendTo(String userId,Object message){ + logger.debug("Invoke sendTo({},{})",userId,message); + Set wChannelSet = authedChannels.get(userId); + if(CollectionUtil.isNotEmpty(wChannelSet)){ + for(WrapperedChannel wChannel:wChannelSet){ + wChannel.writeAndFlush(message); + logger.debug("Send message {} to channel {}",message,userId); + } + return true; + }else{ + return false; + } + } + + /** + * 刷新最后一次接收数据时间 每次接收数据需要调用该函数 + * @param channel 接收数据的连接 + */ + public static synchronized void flushReceiveTimestamp(Channel channel){ + logger.debug("Invoke flushReceiveTimestamp({})",channel.id().asLongText()); + WrapperedChannel wrapperedChannel = rawChannels.get(channel); + if(wrapperedChannel != null){ + wrapperedChannel.whenReceivedData(); + }else{ + logger.error("can find channel from rawChannels"); + } + } + + /** + * 关闭所有未认证的连接 + * @param unAuthedChannelsMaxAliveTimeInSeconds 未认证的最大时长(s) + * @throws Exception + * @deprecated 已过期,该方法在多线程并发下可能出现问题,建议使用messageService中的同名方法 + */ + @Deprecated + public static synchronized void closeUnAuthedChannels(Long unAuthedChannelsMaxAliveTimeInSeconds) throws Exception { + logger.debug("Inovke closeUnAuthedChannels({})",unAuthedChannelsMaxAliveTimeInSeconds); + Iterator> it = rawChannels.entrySet().iterator(); + while(it.hasNext()){ + Map.Entry entry = it.next(); + WrapperedChannel wrapperedChannel = entry.getValue(); + if((!wrapperedChannel.isAuthed()) && (wrapperedChannel.getConnectedSeconds() > unAuthedChannelsMaxAliveTimeInSeconds)) { + it.remove(); + //关闭连接 + wrapperedChannel.getChannel().close(); + logger.debug("Remove a unAuthed Channel {}, which has connected {}s,maxOutTime is {}s", wrapperedChannel.getId(),wrapperedChannel.getConnectedSeconds() , + unAuthedChannelsMaxAliveTimeInSeconds ); + } + } + } + + /** + * 判断Channel是否存在 + * @param channel 连接 + * @return 是否存在 + */ + public static synchronized boolean channelExist(Channel channel){ + return rawChannels.containsKey(channel); + } + + /** + * 判断Channel是否已经认证 + * @param channel 连接 + * @return 是否认证 + */ + public static synchronized boolean channelAuthed(Channel channel){ + return rawChannels.containsKey(channel) && rawChannels.get(channel).isAuthed(); + } + + /** + * 判断用户是否在线 + * @param userId 用户ID + * @return 是否认证 + */ + public static synchronized boolean isUserOnline(String userId){ + return authedChannels.containsKey(userId); + } + + /** + * 根据Channel获取对应的用户ID + * @param channel 连接 + * @return 用户ID + */ + public static synchronized String getUserIdByChannel(Channel channel) { + return rawChannels.containsKey(channel) ? rawChannels.get(channel).getUserId() : null; + } + + /** + * 获取所有在线用户 + * @return 所有用户的集合列表 + */ + public static synchronized Set getOnlineUsers(){ + return authedChannels.keySet(); + } + + /** + * 获取某种类型的所有在线用户的连接 + * @param type 客户端类型(ws/tcp modebus/tcp text) + * @return 所有在西安channel的集合列表 + */ + public static synchronized Set getOnlineChannels(String type){ + Set onLineChannels = CollectionUtil.newHashSet(); + for(Map.Entry entry: rawChannels.entrySet()){ + WrapperedChannel wrapperedChannel = entry.getValue(); + if(wrapperedChannel.isAuthed() && wrapperedChannel.getType().equals(type)){ + onLineChannels.add(entry.getKey()); + } + } + return onLineChannels; + } + + /** + * 获取一个rawChannel的副本 + * @return 副本 + */ + public static Map getCopyOfAllChannels() { + Map copyMap = new HashMap<>(rawChannels.size()); + copyMap.putAll(rawChannels); + return copyMap; + } + + /** + * 根据Channel获取WrapperedChannel + * @param channel channel + * @return 对应的wrapperedChannel + */ + public static WrapperedChannel getWrapperedChannelByChannel(Channel channel) { + return rawChannels.get(channel); + } + + public static synchronized void showAuthedChannels(){ + for(Map.Entry> entry : authedChannels.entrySet()){ + for (WrapperedChannel channel:entry.getValue()){ + logger.debug("{}-->{}",entry.getKey(),channel.toString()); + } + } + } + public static synchronized void showAllChannels(){ + for(Map.Entry entry : rawChannels.entrySet()){ + logger.debug(entry.getValue().toString()); + } + } + + public static Set getAllOnlineUsers() { + return authedChannels.keySet(); + } +} diff --git a/game/src/main/java/com/ccsens/game/netty/GameMessageConstant.java b/game/src/main/java/com/ccsens/game/netty/GameMessageConstant.java new file mode 100644 index 00000000..a144b049 --- /dev/null +++ b/game/src/main/java/com/ccsens/game/netty/GameMessageConstant.java @@ -0,0 +1,217 @@ +package com.ccsens.game.netty; + +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * @author wei + * 消息相关常量 + */ +public class GameMessageConstant { + public enum ClientMessageType{ + //客户端心跳 + Ping(0x00), + //客户端认证 + Auth(0x01), + //客户端收到消息ACK + Ack(0x02), + //客户端收到消息ACK + HasRead(0x03), + //客户端请求连接状态 + GetChannelStatus(0x04), + + //不可预期的错误 + UnExceptedError(0x21), + ClientIdleClosed(0x22), + ClientAuthTimeOut(0x23), + + //设置消息状态消息(disable撤销消息,适用于,消息撤回),针对消息本身(eg:消息撤回) + SetMsgSuccess(0x52), + SetMsgReverted(0x53), + SetMsgDeleted(0x54); + + public int value; + + ClientMessageType(int value){ + this.value = value; + } + + /** + * 从int到enum的转换函数 + * @param value 枚举int值 + * @return 对应的枚举,找不到则返回null + */ + public static ClientMessageType valueOf(int value) { + for(ClientMessageType type : values()){ + if(type.value == value){ + return type; + } + } + return null; + } + } + + public enum ServerMessageType{ + //客户端心跳 + Pong(0x00), + //客户端收到消息ACK + Ack(0x02), + //客户端请求连接状态 + ChannelStatus(0x03); + //撤销某个消息 + //DelMessage(0x04), + //客户端请求连接状态 + //QueueStatus(0x05); + + public int value; + + ServerMessageType(int value){ + this.value = value; + } + + /** + * 从int到enum的转换函数 + * @param value 枚举int值 + * @return 对应的枚举,找不到则返回null + */ + public static ServerMessageType valueOf(int value) { + for(ServerMessageType type : values()){ + if(type.value == value){ + return type; + } + } + return null; + } + } + + /** + * JsonFormat是Json Serialize相关配置 + */ + @JsonFormat(shape = JsonFormat.Shape.OBJECT) + public enum Error{ + //Ok + Ok(200,"Ok"), + + /** + * 通用消息错误 + */ + //消息无接收者错误 + MessageNoReceiversError(1001,"[用户]消息至少应该有一个接收者"), + //消息无数据错误 + MessageNoDataError(1002,"不允许消息内容为空"), + + /** + * server消息错误 + */ + //server消息无type错误 + MessageNoTypeError(1003,"发送至Server域的消息必须有type字段"), + //Ack参数错误,没有找到对应的msgId + AckParameterError(1103,"Ack参数错误,没有找到对应的msgId"), + //SetSuccess参数错误,没有找到对应的msgId + SetSuccessParameterError(1104,"SetSuccess参数错误,没有找到对应的msgId"), + //SetStatusParameterError + SetStatusParameterError(1105,"SetStatus参数错误,没有找到对应的msgId"), + //HasRead参数错误,没有找到对应的msgId + HasReadParameterError(1106,"HasRead参数错误,没有找到对应的msgId"), + + /** + * 身份认证错误 + */ + //无权限 + UnAuthed(1021, "未认证的用户"), + + /** + * 业务错误 + */ + //认证失败 + AuthFailed(1301,"认证失败"), + //空闲断开连接(连续Ns没有收到数据) + ChannelIdle(1302,"连接断开:连续N秒没有从收到客户端收到任何数据"), + //认证超时断开连接 + ChannelAuthTimeOut(1303,"连接断开:认证超时"), + //不可预期错误 + UnExpectedError(1304,"不可预期异常"); + + public int code; + public String text; + public String extra; + + Error(int code,String text) { + this.code = code; + this.text = text; + } + Error(int code,String text,String extra) { + this.code = code; + this.text = text; + this.extra = extra; + } + public Error joinExtra(String extra){ + this.extra = extra; + return this; + } + } + + + /** + * 域(User、Queue、Rest、Server) + */ + public enum DomainType{ + //Netty Client + User(1), + //Queue Client + Queue(2), + //Rest Client + Rest(3), + //系统 + Server(4); + + public int value; + + DomainType(int value){ + this.value = value; + } + + public static DomainType valueOf(int value) { + for(DomainType domainType : values()){ + if(domainType.value == value){ + return domainType; + } + } + return null; + } + } + + public enum Status{ + //未决状态(未完成) + Pending(0), + //发送成功(投递成功) + Succeed(1), + //发送失败(投递失败) + Failed(2), + //消息过期 + Expired(3), + //消息被撤回 + Reverted(4), + //消息被删除 + Deleted(5), + //消息达到重试次数上限 + SendTimesUpLimit(6), + + //临时完成状态,当ackIsSuccess==0时,一旦ack设置消息为此状态 + TempSucceed(10); + + public int value; + + Status(int value){ + this.value = value; + } + + public static Status valueOf(int value){ + for(Status status : values()){ + if(status.value == value){ + return status; + } + } + return null; + } + } +} diff --git a/game/src/main/java/com/ccsens/game/netty/WrapperedChannel.java b/game/src/main/java/com/ccsens/game/netty/WrapperedChannel.java new file mode 100644 index 00000000..3bd137b4 --- /dev/null +++ b/game/src/main/java/com/ccsens/game/netty/WrapperedChannel.java @@ -0,0 +1,200 @@ +package com.ccsens.game.netty; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.ObjectUtil; +import io.netty.channel.Channel; +import lombok.Getter; +import lombok.Setter; + +import java.net.InetSocketAddress; + +/** + * @author wei + */ +@Getter +@Setter +public class WrapperedChannel { + @Getter + @Setter + private static class ClientVersion{ + int major; + int minor; + public ClientVersion(int major,int minor){ + this.major = major; + this.minor = minor; + } + } + + /** + * Netty's Channel + */ + private Channel channel; + /** + * userId + */ + private String userId; + /** + * 客户端版本号 + */ + private ClientVersion version; + + /** + * 客户端类型(哪种服务器) + */ + private String type; + /** + * 是否认证 + */ + private boolean authed; + /** + * 连接建立时间 + */ + private Long createdAtInSeconds; + /** + * 认证时间(s) + */ + private Long authedAtInSeconds; + /** + * 最后一次接收有效数据时间(包括心跳包) + */ + private Long lastDataReceivedInSeconds; + /** + * 最后一次发送有效数据时间(包括心跳包) + */ + private Long lastDataSendInSeconds; + /** + * 接收到数据条数(每接收到一条协议加1,包括心跳) + */ + private long dataReceivedCount; + /** + * 发送数据条数(每发送到一条协议加1,包括心跳) + */ + private long dataSendCount; + + public WrapperedChannel(Channel channel){ + this.channel = channel; + this.createdAtInSeconds = DateUtil.currentSeconds(); + } + + public WrapperedChannel(Channel channel,String type){ + this(channel); + this.type = type; + } + + public WrapperedChannel(Channel channel,String type,int major,int minor){ + this(channel,type); + this.version = new ClientVersion(major,minor); + } + + public String getVersion(){ + if(ObjectUtil.isNotNull(version)){ + return "v" + version.getMajor() + "." + version.getMinor(); + } + return null; + } + + public void setVersion(int major,int minor){ + if (ObjectUtil.isNull(version)) { + version = new ClientVersion(major, minor); + } else { + version.setMajor(major); + version.setMinor(minor); + } + } + + public String getRemoteAddr(){ + if(ObjectUtil.isNotNull(channel)){ + InetSocketAddress insocket = (InetSocketAddress) channel.remoteAddress(); + if(ObjectUtil.isNotNull(insocket)) { + return insocket.getAddress().getHostAddress(); + } + } + return null; + } + public Integer getRemotePort(){ + if(ObjectUtil.isNotNull(channel)){ + InetSocketAddress insocket = (InetSocketAddress) channel.remoteAddress(); + if(ObjectUtil.isNotNull(insocket)) { + return insocket.getPort(); + } + } + return null; + } + + public String getId(){ + if(ObjectUtil.isNotNull(channel)){ + return channel.id().asLongText(); + } + return null; + } + + public void whenReceivedData(){ + lastDataReceivedInSeconds = DateUtil.currentSeconds(); + dataReceivedCount++; + } + + public void whenSendData(){ + lastDataSendInSeconds = DateUtil.currentSeconds(); + dataSendCount++; + } + + public void whenAuthed(){ + authed = true; + authedAtInSeconds = DateUtil.currentSeconds(); + } + + public void whenAuthed(String userId,int major,int minor){ + whenAuthed(); + setVersion(major,minor); + this.userId = userId; + } + + public void writeAndFlush(Object message){ + if(channel != null && channel.isActive()){ + channel.writeAndFlush(message); + whenSendData(); + } + } + + public Long getConnectedSeconds(){ + return createdAtInSeconds == null ? null : DateUtil.currentSeconds() - createdAtInSeconds; + } + + public Long getOnlineSeconds(){ + return authedAtInSeconds == null ? null : DateUtil.currentSeconds() - authedAtInSeconds ; + } + + @Override + public boolean equals(Object obj) { + if(ObjectUtil.isNull(obj)) { + return false; + } + if(this == obj) { + return true; + } + if(ObjectUtil.isNull(channel)) { + return false; + } + if(obj.getClass() == this.getClass()){ + WrapperedChannel other = (WrapperedChannel)obj; + return channel.equals(other.channel); + }else if(obj.getClass() == channel.getClass()){ + Channel other = (Channel)obj; + return channel.equals(other); + } + return false; + } + + @Override + public int hashCode() { + if(ObjectUtil.isNotNull(channel)){ + return channel.hashCode(); + } + return 0; + } + + @Override + public String toString() { + return String.format("id: %s, type: %s, authed: %b, userId: %s, version: %s",getId(),type,authed,userId,getVersion()); + } +} diff --git a/game/src/main/java/com/ccsens/game/netty/wsserver/NettyWsServer.java b/game/src/main/java/com/ccsens/game/netty/wsserver/NettyWsServer.java new file mode 100644 index 00000000..69632d3d --- /dev/null +++ b/game/src/main/java/com/ccsens/game/netty/wsserver/NettyWsServer.java @@ -0,0 +1,70 @@ +package com.ccsens.game.netty.wsserver; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; +import io.netty.handler.logging.LogLevel; +import io.netty.handler.logging.LoggingHandler; +import io.netty.handler.stream.ChunkedWriteHandler; +import io.netty.handler.timeout.IdleStateHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +/** + * @author wei + */ +@Component +public class NettyWsServer { + private static final String URI = "/game/v3.0/ws"; + private static final short PORT = 7070; + + @Autowired + private WebSocketHandler webSocketHandler; + + @Async + public void start() { + // Configure the server. + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_BACKLOG, 100) + .handler(new LoggingHandler(LogLevel.INFO)) + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline p = ch.pipeline(); + p.addLast(new IdleStateHandler(40, + 0, 0, TimeUnit.SECONDS)); + p.addLast(new HttpServerCodec()); + p.addLast(new HttpObjectAggregator(64 * 1024)); + p.addLast(new ChunkedWriteHandler()); + p.addLast(new WebSocketServerProtocolHandler(URI)); + p.addLast(new WebSocketDecoder()); + p.addLast(new WebSocketEncoder()); + p.addLast(webSocketHandler); + } + }); + // Start the server. + ChannelFuture f = b.bind(PORT).sync(); + // Wait until the server socket is closed. + f.channel().closeFuture().sync(); + } catch(Exception e){ + e.printStackTrace(); + }finally { + // Shut down all event loops to terminate all threads. + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} diff --git a/game/src/main/java/com/ccsens/game/netty/wsserver/WebSocketDecoder.java b/game/src/main/java/com/ccsens/game/netty/wsserver/WebSocketDecoder.java new file mode 100644 index 00000000..998048e5 --- /dev/null +++ b/game/src/main/java/com/ccsens/game/netty/wsserver/WebSocketDecoder.java @@ -0,0 +1,33 @@ +package com.ccsens.game.netty.wsserver; + +import com.ccsens.game.bean.dto.message.BaseMessageDto; +import com.ccsens.util.JacksonUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageDecoder; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.List; + +/** + * @author wei + */ +public class WebSocketDecoder extends MessageToMessageDecoder { + private static Logger logger = LoggerFactory.getLogger(WebSocketDecoder.class); + + @Override + protected void decode(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame msg, List out) throws Exception { + String text = msg.text(); + logger.info("Websocket received: {}",text); + + try { + out.add(JacksonUtil.jsonToBean(text, BaseMessageDto.class)); + }catch(IOException e){ + e.printStackTrace(); + logger.error("Websocket Read Error: {}",text); + throw e; + } + } +} diff --git a/game/src/main/java/com/ccsens/game/netty/wsserver/WebSocketEncoder.java b/game/src/main/java/com/ccsens/game/netty/wsserver/WebSocketEncoder.java new file mode 100644 index 00000000..ddd5e14d --- /dev/null +++ b/game/src/main/java/com/ccsens/game/netty/wsserver/WebSocketEncoder.java @@ -0,0 +1,25 @@ +package com.ccsens.game.netty.wsserver; + +import com.ccsens.game.bean.dto.message.BaseMessageDto; +import com.ccsens.util.JacksonUtil; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author wei + */ +public class WebSocketEncoder extends MessageToByteEncoder { + private static Logger logger = LoggerFactory.getLogger(WebSocketEncoder.class); + + @Override + protected void encode(ChannelHandlerContext ctx, BaseMessageDto outMessageSet, ByteBuf out) throws Exception { + String msg = JacksonUtil.beanToJson(outMessageSet); + ctx.writeAndFlush(new TextWebSocketFrame(msg)); + + logger.info("Websocket send: {}",msg); + } +} diff --git a/game/src/main/java/com/ccsens/game/netty/wsserver/WebSocketHandler.java b/game/src/main/java/com/ccsens/game/netty/wsserver/WebSocketHandler.java new file mode 100644 index 00000000..8dd65dee --- /dev/null +++ b/game/src/main/java/com/ccsens/game/netty/wsserver/WebSocketHandler.java @@ -0,0 +1,135 @@ +package com.ccsens.game.netty.wsserver; + + +import com.ccsens.game.bean.dto.message.AckMessageDto; +import com.ccsens.game.bean.dto.message.AuthMessageDto; +import com.ccsens.game.bean.dto.message.BaseMessageDto; +import com.ccsens.game.bean.dto.message.HeartMessageDto; +import com.ccsens.game.netty.ChannelManager; +import com.ccsens.game.service.IClientService; +import com.ccsens.game.service.IMessageService; +import com.ccsens.util.WebConstant; +import com.ccsens.util.bean.message.common.MessageConstant; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.timeout.IdleState; +import io.netty.handler.timeout.IdleStateEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * @author wei + */ +@ChannelHandler.Sharable +@Component +public class WebSocketHandler extends SimpleChannelInboundHandler { + private static final String TYPE = "netty_ws"; + + private static Logger logger = LoggerFactory.getLogger(WebSocketHandler.class); + + @Autowired + private IClientService clientService; + @Autowired + private IMessageService messageService; + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + ChannelManager.addChannel(ctx.channel(), TYPE); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + ChannelManager.removeChannel(ctx.channel()); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + + cause.printStackTrace(); + ctx.close(); + logger.error("Channel closed. Ws get a exception: {}", cause.getMessage()); + } + + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof IdleStateEvent) { + IdleStateEvent idleStateEvent = (IdleStateEvent) evt; + if (idleStateEvent.state() == IdleState.READER_IDLE) { + ctx.channel().close(); + logger.error("Ws channel idle,closed."); + } + } else { + super.userEventTriggered(ctx, evt); + } + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, BaseMessageDto baseMessage) throws Exception { + MessageConstant.GameClientMessageType gameClientMessageType = MessageConstant.GameClientMessageType.valueOf(baseMessage.getType()); + + System.out.println(baseMessage); + try { + switch (gameClientMessageType) { + case Heart: { + HeartMessageDto message = (HeartMessageDto) baseMessage; + message.setTime(System.currentTimeMillis()); + if (message.getData() != null) { + ChannelManager.versionChannel( + ctx.channel(), message.getData().getMajor(), message.getData().getMinor()); + } + ChannelManager.sendTo(ctx.channel(), message); + break; + } + case Ack: { + AckMessageDto message = (AckMessageDto) baseMessage; + String userId = ChannelManager.getUserIdByChannel(ctx.channel()); + messageService.doAckMessageWithAck(userId, message); + break; + } + case Auth: + AuthMessageDto theMessage = (AuthMessageDto) baseMessage; + theMessage.getData().setChannelId(ctx.channel().id().asLongText()); + doAuthMessage(ctx, theMessage); + break; + case Count: + String userId = ChannelManager.getUserIdByChannel(ctx.channel()); + clientService.clientAddTimes(userId); + break; + case ChangeStatus: + break; + default: + break; + } + } catch (Exception e) { + e.printStackTrace(); + logger.error("Websocket Process Message Failed: {},{}", e.getMessage(), baseMessage); + throw e; + } finally { + ChannelManager.removeCurrentChannel(); + } + } + + private void doAuthMessage(ChannelHandlerContext ctx, AuthMessageDto message) throws Exception { + WebConstant.Message_Auth_Event event = WebConstant.Message_Auth_Event.phaseOf(message.getEvent()); + switch (event) { + case Auth: + messageService.doAuthMessageWithAuth(ctx, message); + break; + default: + break; + } + } +} + diff --git a/game/src/main/java/com/ccsens/game/persist/dao/GameUserJoinDao.java b/game/src/main/java/com/ccsens/game/persist/dao/GameUserJoinDao.java index 60ca0fde..1cec130e 100644 --- a/game/src/main/java/com/ccsens/game/persist/dao/GameUserJoinDao.java +++ b/game/src/main/java/com/ccsens/game/persist/dao/GameUserJoinDao.java @@ -5,6 +5,8 @@ import com.ccsens.game.persist.mapper.GameUserJoinMapper; import org.apache.ibatis.annotations.Param; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface GameUserJoinDao extends GameUserJoinMapper { @@ -16,6 +18,6 @@ public interface GameUserJoinDao extends GameUserJoinMapper { */ int getRanking(@Param("userId") Long userId, @Param("recordId") Long recordId); - ClientVo.MemberInfo selectByRecordId(@Param("recordId") Long recordId); + List selectByRecordId(@Param("recordId") Long recordId); } diff --git a/game/src/main/java/com/ccsens/game/service/ClientService.java b/game/src/main/java/com/ccsens/game/service/ClientService.java index bd5a8d9c..a0469bf2 100644 --- a/game/src/main/java/com/ccsens/game/service/ClientService.java +++ b/game/src/main/java/com/ccsens/game/service/ClientService.java @@ -2,10 +2,12 @@ package com.ccsens.game.service; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.Snowflake; +import cn.hutool.core.util.ObjectUtil; import com.alibaba.fastjson.JSON; import com.ccsens.cloudutil.bean.tall.vo.MemberVo; import com.ccsens.cloudutil.feign.TallFeignClient; import com.ccsens.game.bean.dto.ClientDto; +import com.ccsens.game.bean.dto.message.BaseMessageDto; import com.ccsens.game.bean.po.GameRecord; import com.ccsens.game.bean.po.GameUserJoin; import com.ccsens.game.bean.po.GameUserJoinExample; @@ -171,20 +173,45 @@ public class ClientService implements IClientService { joinVo.setCountMembers(count); return joinVo; } + + /** + * 查询总排名表 + * @param params + * @return + */ @Override public ClientVo.RankingAll getRankingAll(QueryDto params) { ClientDto.GatRanking gatRanking = params.getParam(); + ClientVo.RankingAll rankingAll = new ClientVo.RankingAll(); GameUserJoinExample userJoinExample = new GameUserJoinExample(); userJoinExample.createCriteria().andRecordIdEqualTo(gatRanking.getRecordId()); List userJoinList = gameUserJoinDao.selectByExample(userJoinExample); - + if(CollectionUtil.isNotEmpty(userJoinList)){ + rankingAll.setTotalMembers(userJoinList.size()); + }else { + rankingAll.setTotalMembers(0); + } PageHelper.startPage(gatRanking.getPageNum(), gatRanking.getPageSize()); - ClientVo.MemberInfo memberInfo = gameUserJoinDao.selectByRecordId(gatRanking.getRecordId()); + List memberInfo = gameUserJoinDao.selectByRecordId(gatRanking.getRecordId()); log.info("查询成员信息"); - PageInfo pageInfo =new PageInfo<>(); + PageInfo pageInfo =new PageInfo<>(memberInfo); + + rankingAll.setMemberInfoList(memberInfo); + rankingAll.setFirstPage(pageInfo.isIsFirstPage()); + rankingAll.setLastPage(pageInfo.isIsLastPage()); - return null; + return rankingAll; + } + + @Override + public void clientAddTimes(String userId) { + if(ObjectUtil.isNotNull(userId)){ + GameUserJoinExample gameUserJoinExample = new GameUserJoinExample(); + gameUserJoinExample.createCriteria().andUserIdEqualTo(Long.valueOf(userId)); + List userJoinList = gameUserJoinDao.selectByExample(gameUserJoinExample); + + } } } diff --git a/game/src/main/java/com/ccsens/game/service/IClientService.java b/game/src/main/java/com/ccsens/game/service/IClientService.java index 6a10060a..9169e9c7 100644 --- a/game/src/main/java/com/ccsens/game/service/IClientService.java +++ b/game/src/main/java/com/ccsens/game/service/IClientService.java @@ -1,6 +1,7 @@ package com.ccsens.game.service; import com.ccsens.game.bean.dto.ClientDto; +import com.ccsens.game.bean.dto.message.BaseMessageDto; import com.ccsens.game.bean.vo.ClientVo; import com.ccsens.util.bean.dto.QueryDto; @@ -20,4 +21,9 @@ public interface IClientService { ClientVo.Join join(ClientDto.Join join, Long userId); ClientVo.RankingAll getRankingAll(QueryDto params); + + /** + * 滑动时增加次数 + */ + void clientAddTimes(String userId); } diff --git a/game/src/main/java/com/ccsens/game/service/IMessageService.java b/game/src/main/java/com/ccsens/game/service/IMessageService.java new file mode 100644 index 00000000..87f8c817 --- /dev/null +++ b/game/src/main/java/com/ccsens/game/service/IMessageService.java @@ -0,0 +1,18 @@ +package com.ccsens.game.service; + +import com.ccsens.game.bean.dto.message.AckMessageDto; +import com.ccsens.game.bean.dto.message.AuthMessageDto; +import com.ccsens.game.bean.dto.message.ChromeMessageDto; +import com.ccsens.game.bean.dto.message.GameMessageWithGetUrlDto; +import com.fasterxml.jackson.core.JsonProcessingException; +import io.netty.channel.ChannelHandlerContext; + +public interface IMessageService { + //获取路径后给每个人发送游戏路径消息 + void sendGameMessageWithGetUrl(ChromeMessageDto message) throws Exception; + + void doAckMessageWithAck(String userId, AckMessageDto message); + + void doAuthMessageWithAuth(ChannelHandlerContext ctx, AuthMessageDto message); +} + diff --git a/game/src/main/java/com/ccsens/game/service/IScreenService.java b/game/src/main/java/com/ccsens/game/service/IScreenService.java index 111ee53b..42e0d2ae 100644 --- a/game/src/main/java/com/ccsens/game/service/IScreenService.java +++ b/game/src/main/java/com/ccsens/game/service/IScreenService.java @@ -5,7 +5,7 @@ import com.ccsens.game.bean.vo.ScreenVo; import com.ccsens.util.bean.dto.QueryDto; public interface IScreenService { - ScreenVo.UrlVo getScreenUrl(QueryDto params); + ScreenVo.UrlVo getScreenUrl(QueryDto params) throws Exception; ScreenVo.GameInfoVo getGameInformation(QueryDto params); diff --git a/game/src/main/java/com/ccsens/game/service/MessageService.java b/game/src/main/java/com/ccsens/game/service/MessageService.java new file mode 100644 index 00000000..3c200a6e --- /dev/null +++ b/game/src/main/java/com/ccsens/game/service/MessageService.java @@ -0,0 +1,67 @@ +package com.ccsens.game.service; + +import com.ccsens.cloudutil.feign.TallFeignClient; +import com.ccsens.game.bean.dto.message.AckMessageDto; +import com.ccsens.game.bean.dto.message.AuthMessageDto; +import com.ccsens.game.bean.dto.message.ChromeMessageDto; +import com.ccsens.game.netty.ChannelManager; +import com.ccsens.util.JacksonUtil; +import com.ccsens.util.config.RabbitMQConfig; +import io.netty.channel.ChannelHandlerContext; +import org.springframework.amqp.core.AmqpTemplate; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class MessageService implements IMessageService{ + @Autowired + private AmqpTemplate rabbitTemplate; + @Autowired + private TallFeignClient tallFeignClient; + + @Override + public void sendGameMessageWithGetUrl(ChromeMessageDto message) throws Exception { + System.out.println(JacksonUtil.beanToJson(message)); + rabbitTemplate.convertAndSend(RabbitMQConfig.RabbitMQ_QUEUE_NAME, + JacksonUtil.beanToJson(message)); + } + + @Override + public void doAckMessageWithAck(String userId, AckMessageDto message) { +// System.out.println(message); +// //1.存储到MongoDB(ack消息不需要存储,修改响应消息的发送成功标志即可) +//// Long userId = message.getSender().getUserId(); +// Long msgId = message.getData().getMsgId(); +// messageLogDao.updateMessageLogSendSuccess(userId,msgId,1); + } + + @Override + public void doAuthMessageWithAuth(ChannelHandlerContext ctx, AuthMessageDto message) { + System.out.println(message); + //ws连接认证 + String userId = message.getData().getUserId(); + if(userId == null){ + String token = message.getData().getToken(); + userId = tallFeignClient.getUserId(token); + } + ChannelManager.authChannel(ctx.channel(),userId,1,1); +// if(userId == null){ +//// logger.warn(String.format("Auth failed: %s",userId,message.getData().getToken())); +//// ChannelManager.removeChannel(ctx.channel()); +// AuthMessageWithAnswerDto messageWithAnswerDto = new AuthMessageWithAnswerDto(false,"未找到对应的用户信息"); +// sendTo(ctx.channel(),messageWithAnswerDto); +// ctx.channel().close(); +// return; +// } +// String channelId = message.getData().getChannelId(); +// if(ChannelManager.authChannel(ctx.channel(),userId)){ //认证成功 + //查找所有该用户相关未收到的消息和未读消息依次发送给该用户 +// sendAllUnSendSuccessedMessagesByUserId(userId,0L); +// AuthMessageWithAnswerDto messageWithAnswerDto = new AuthMessageWithAnswerDto(true,"ok"); +// sendTo(ctx.channel(),messageWithAnswerDto); +// }else { +//// AuthMessageWithAnswerDto messageWithAnswerDto = new AuthMessageWithAnswerDto(false,"认证失败"); +//// sendTo(ctx.channel(),messageWithAnswerDto); +// } + } +} diff --git a/game/src/main/java/com/ccsens/game/service/ScreenService.java b/game/src/main/java/com/ccsens/game/service/ScreenService.java index 2a613e02..52c7e9d0 100644 --- a/game/src/main/java/com/ccsens/game/service/ScreenService.java +++ b/game/src/main/java/com/ccsens/game/service/ScreenService.java @@ -1,10 +1,13 @@ package com.ccsens.game.service; -import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.Snowflake; import cn.hutool.core.util.ObjectUtil; +import com.ccsens.cloudutil.feign.TallFeignClient; import com.ccsens.game.bean.dto.ScreenDto; +import com.ccsens.game.bean.dto.message.BaseMessageDto; +import com.ccsens.game.bean.dto.message.ChromeMessageDto; +import com.ccsens.game.bean.dto.message.GameMessageWithGetUrlDto; import com.ccsens.game.bean.po.*; import com.ccsens.game.bean.vo.ScreenVo; import com.ccsens.game.persist.dao.*; @@ -12,7 +15,6 @@ import com.ccsens.util.CodeEnum; import com.ccsens.util.WebConstant; import com.ccsens.util.bean.dto.QueryDto; import com.ccsens.util.exception.BaseException; -import feign.Param; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -41,7 +43,10 @@ public class ScreenService implements IScreenService{ private GamePrizeInstructionsDao prizeInstructionsDao; @Autowired private GameUserJoinDao gameMemberJoinDao; - + @Autowired + private TallFeignClient tallFeignClient; + @Autowired + private IMessageService messageService; @Autowired private Snowflake snowflake; @@ -50,7 +55,7 @@ public class ScreenService implements IScreenService{ * @return */ @Override - public ScreenVo.UrlVo getScreenUrl(QueryDto params) { + public ScreenVo.UrlVo getScreenUrl(QueryDto params) throws Exception { ScreenDto.MemberGame memberGame = params.getParam(); //查找游戏 GameType gameType = null; @@ -86,15 +91,33 @@ public class ScreenService implements IScreenService{ GameRecord gameRecord = new GameRecord(); gameRecord.setId(snowflake.nextId()); gameRecord.setUserPayId(gameUserPay.getId()); - gameRecord.setUrl(WebConstant.TEST_URL_GAME + gameRecord.getId() + File.separator + gameType.getScreenUrl()); + gameRecord.setUrl(WebConstant.TEST_URL_GAME + gameType.getScreenUrl() + "?id="+gameRecord.getId()); gameRecord.setQrCodeUrl(WebConstant.TEST_URL_GAME + gameRecord.getId() + File.separator + gameType.getClientUrl()); gameRecordDao.insertSelective(gameRecord); //5、查询该游戏的规则 List ruleList = getGameActivityRule(gameType.getId()); //6、返回 ScreenVo.UrlVo urlVo = new ScreenVo.UrlVo(); + urlVo.setId(gameRecord.getId()); urlVo.setUrl(gameRecord.getUrl()); urlVo.setRuleList(ruleList); + + //给所有人发送消息发送消息 + ChromeMessageDto chromeMessageDto = new ChromeMessageDto(gameRecord.getUrl()); + BaseMessageDto.MessageUser messageUser = null; + List messageUserList = new ArrayList<>(); + //获取项目下所有成员 + List memberIdList = tallFeignClient.getMemberIdListByProject(memberGame.getProjectId()); + if(CollectionUtil.isNotEmpty(memberIdList)){ + for(Long memberId:memberIdList){ + messageUser = new BaseMessageDto.MessageUser(); + messageUser.setUserId(memberId); + messageUserList.add(messageUser); + } + } + chromeMessageDto.setReceivers(messageUserList); + messageService.sendGameMessageWithGetUrl(chromeMessageDto); + return urlVo; } @@ -243,6 +266,7 @@ public class ScreenService implements IScreenService{ @Override public String startAgain(QueryDto params) { ScreenDto.MemberRecord memberRecord = params.getParam(); + GameRecord gameRecordNew = null; GameRecord gameRecord = gameRecordDao.selectByPrimaryKey(memberRecord.getMemberRecord()); if(ObjectUtil.isNull(gameRecord)){ throw new BaseException(CodeEnum.NOT_GAME_RECORD); @@ -251,21 +275,28 @@ public class ScreenService implements IScreenService{ if(ObjectUtil.isNull(gameUserPay)){ throw new BaseException(CodeEnum.NOT_GAME_TYPE); } - GameType gameType = gameTypeDao.selectByPrimaryKey(gameUserPay.getId()); + GameType gameType = gameTypeDao.selectByPrimaryKey(gameUserPay.getGameTypeId()); if(ObjectUtil.isNull(gameType)){ throw new BaseException(CodeEnum.NOT_GAME_TYPE); } - //添加一场新的游戏记录 - GameRecord gameRecordNew = new GameRecord(); - gameRecordNew.setId(snowflake.nextId()); - gameRecordNew.setUserPayId(gameUserPay.getId()); - gameRecordNew.setUrl(WebConstant.TEST_URL_GAME + gameRecordNew.getId() + File.separator + gameType.getScreenUrl()); - gameRecordNew.setQrCodeUrl(WebConstant.TEST_URL_GAME + gameRecordNew.getId() + File.separator + gameType.getClientUrl()); - gameRecordDao.insertSelective(gameRecordNew); - //修改购买的游戏的使用次数 - gameUserPay.setUsedCount(gameUserPay.getUsedCount() + 1); - typeMemberDao.updateByPrimaryKeySelective(gameUserPay); + if(gameUserPay.getUsedCount() >= gameUserPay.getTotalCount()){ + throw new BaseException(CodeEnum. GAME_NOT_TIMES); + } + if(gameRecord.getGameStatus() == 3){ + //添加一场新的游戏记录 + gameRecordNew = new GameRecord(); + gameRecordNew.setId(snowflake.nextId()); + gameRecordNew.setUserPayId(gameUserPay.getId()); + gameRecordNew.setUrl(WebConstant.TEST_URL_GAME + gameType.getScreenUrl() + "?id="+gameRecord.getId()); + gameRecordNew.setQrCodeUrl(WebConstant.TEST_URL_GAME + gameRecordNew.getId() + File.separator + gameType.getClientUrl()); + gameRecordDao.insertSelective(gameRecordNew); + //修改购买的游戏的使用次数 + gameUserPay.setUsedCount(gameUserPay.getUsedCount() + 1); + typeMemberDao.updateByPrimaryKeySelective(gameUserPay); + }else { + throw new BaseException(CodeEnum.GAME_NO_END); + } return gameRecordNew.getUrl(); } @@ -343,4 +374,6 @@ public class ScreenService implements IScreenService{ completedData.setOver(over); return completedData; } + + } diff --git a/mt/src/main/resources/application-test.yml b/mt/src/main/resources/application-test.yml index f2448086..c1697488 100644 --- a/mt/src/main/resources/application-test.yml +++ b/mt/src/main/resources/application-test.yml @@ -28,4 +28,4 @@ swagger: enable: true eureka: instance: - ip-address: 49.233.89.188 \ No newline at end of file + ip-address: 119.28.76.62 \ No newline at end of file diff --git a/mt/src/main/resources/application.yml b/mt/src/main/resources/application.yml index 5c2cd5c4..5889ff7f 100644 --- a/mt/src/main/resources/application.yml +++ b/mt/src/main/resources/application.yml @@ -1,4 +1,4 @@ spring: profiles: - active: dev - include: common, util-dev \ No newline at end of file + active: test + include: common, util-test \ No newline at end of file diff --git a/mt/src/main/resources/druid-test.yml b/mt/src/main/resources/druid-test.yml index 56cd9c56..1a0aa389 100644 --- a/mt/src/main/resources/druid-test.yml +++ b/mt/src/main/resources/druid-test.yml @@ -15,7 +15,7 @@ spring: maxWait: 60000 minEvictableIdleTimeMillis: 300000 minIdle: 5 - password: + password: 37080c1f223685592316b02dad8816c019290a476e54ebb638f9aa3ba8b6bdb9 poolPreparedStatements: true servletLogSlowSql: true servletLoginPassword: 111111 @@ -27,7 +27,7 @@ spring: testOnReturn: false testWhileIdle: true timeBetweenEvictionRunsMillis: 60000 - url: jdbc:mysql://127.0.0.1/mt?useUnicode=true&characterEncoding=UTF-8 + url: jdbc:mysql://49.233.89.188/mt?useUnicode=true&characterEncoding=UTF-8 username: root validationQuery: SELECT 1 FROM DUAL env: CCSENS_GAME \ No newline at end of file diff --git a/pom.xml b/pom.xml index c9d15fa3..d24d0af3 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,13 @@ + + + io.netty + netty-all + 4.1.32.Final + + org.springframework.boot spring-boot-starter-data-redis diff --git a/tall/src/main/java/com/ccsens/tall/bean/dto/message/ChromeMessageDto.java b/tall/src/main/java/com/ccsens/tall/bean/dto/message/ChromeMessageDto.java new file mode 100644 index 00000000..52c3d278 --- /dev/null +++ b/tall/src/main/java/com/ccsens/tall/bean/dto/message/ChromeMessageDto.java @@ -0,0 +1,32 @@ +package com.ccsens.tall.bean.dto.message; + +import com.ccsens.util.WebConstant; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +/** + * @author __zHangSan + */ +@Data +public class ChromeMessageDto extends BaseMessageDto{ + @Setter + @Getter + public static class Data{ + private String url; + } + private Data data; + + public ChromeMessageDto(){ + setType(WebConstant.Message_Type.Chrome.phase); + setTime(System.currentTimeMillis()); + } + + public ChromeMessageDto(String url){ + this(); + if(data == null){ + data = new Data(); + } + data.setUrl(url); + } +} diff --git a/tall/src/main/java/com/ccsens/tall/bean/dto/message/PPTCtlMessageDto.java b/tall/src/main/java/com/ccsens/tall/bean/dto/message/PPTCtlMessageDto.java new file mode 100644 index 00000000..28161fbd --- /dev/null +++ b/tall/src/main/java/com/ccsens/tall/bean/dto/message/PPTCtlMessageDto.java @@ -0,0 +1,35 @@ +package com.ccsens.tall.bean.dto.message; + +import com.ccsens.util.WebConstant; +import lombok.Data; +import lombok.Getter; +import lombok.Setter; + +/** + * @author __zHangSan + */ +@Data +public class PPTCtlMessageDto extends BaseMessageDto{ + @Setter + @Getter + public static class Data{ + /** + * Supported Operation: up,down,begin,end + */ + private String oper; + } + private Data data; + + public PPTCtlMessageDto(){ + setType(WebConstant.Message_Type.PPTCtl.phase); + setTime(System.currentTimeMillis()); + } + + public PPTCtlMessageDto(String oper){ + this(); + if(data == null){ + data = new Data(); + } + data.setOper(oper); + } +} diff --git a/tall/src/main/java/com/ccsens/tall/bean/dto/message/SyncMessageWithStartDto.java b/tall/src/main/java/com/ccsens/tall/bean/dto/message/SyncMessageWithStartDto.java index 39d07e30..3ac80c1f 100644 --- a/tall/src/main/java/com/ccsens/tall/bean/dto/message/SyncMessageWithStartDto.java +++ b/tall/src/main/java/com/ccsens/tall/bean/dto/message/SyncMessageWithStartDto.java @@ -17,6 +17,7 @@ public class SyncMessageWithStartDto extends BaseMessageDto { Long endTaskId; Long time; Long duration; //获取当前节点时长,为倒计时做准备 + String player; //参赛选手名字 } private Data data; @@ -28,7 +29,7 @@ public class SyncMessageWithStartDto extends BaseMessageDto { } public SyncMessageWithStartDto(Long projectId, MessageUser sender, List receivers, Long roleId, Long beginTaskId, - Long endTaskId, Long time, Long duration){ + Long endTaskId, Long time, Long duration,String player){ this(); setProjectId(projectId); setSender(sender); @@ -40,6 +41,7 @@ public class SyncMessageWithStartDto extends BaseMessageDto { d.setEndTaskId(endTaskId); d.setTime(time); d.setDuration(duration); + d.setPlayer(player); setData(d); } } diff --git a/tall/src/main/java/com/ccsens/tall/config/SpringConfig.java b/tall/src/main/java/com/ccsens/tall/config/SpringConfig.java index 72055bd7..ab3d2532 100644 --- a/tall/src/main/java/com/ccsens/tall/config/SpringConfig.java +++ b/tall/src/main/java/com/ccsens/tall/config/SpringConfig.java @@ -135,6 +135,7 @@ public class SpringConfig implements WebMvcConfigurer { .excludePathPatterns("/users/token") .excludePathPatterns("/users/claims") .excludePathPatterns("/users/member") + .excludePathPatterns("/users/allMemberAll") .addPathPatterns("/plugins/**") .addPathPatterns("/delivers/**") .addPathPatterns("/tasks/**") diff --git a/tall/src/main/java/com/ccsens/tall/persist/dao/ProTaskMemberDao.java b/tall/src/main/java/com/ccsens/tall/persist/dao/ProTaskMemberDao.java new file mode 100644 index 00000000..509b4e89 --- /dev/null +++ b/tall/src/main/java/com/ccsens/tall/persist/dao/ProTaskMemberDao.java @@ -0,0 +1,8 @@ +package com.ccsens.tall.persist.dao; + +import com.ccsens.tall.persist.mapper.ProTaskMemberMapper; +import org.springframework.stereotype.Repository; + +@Repository +public interface ProTaskMemberDao extends ProTaskMemberMapper{ +} diff --git a/tall/src/main/java/com/ccsens/tall/service/IMessageService.java b/tall/src/main/java/com/ccsens/tall/service/IMessageService.java index 78adaba9..9cba185e 100644 --- a/tall/src/main/java/com/ccsens/tall/service/IMessageService.java +++ b/tall/src/main/java/com/ccsens/tall/service/IMessageService.java @@ -8,7 +8,7 @@ import com.ccsens.util.bean.message.common.InMessage; public interface IMessageService { void sendDeliverMessageWithUpload(InMessage inMessage) throws Exception; //同步消息——Start - void sendSyncMessageWithStart(Long currentUserId, Long projectId, Long roleId, Long taskId, Long time, Long duration) throws Exception; + void sendSyncMessageWithStart(Long currentUserId, Long projectId, Long roleId, Long taskId, Long time, Long duration,String player) throws Exception; void sendDeliverMessageWithChecker(InMessage inMessage)throws Exception; diff --git a/tall/src/main/java/com/ccsens/tall/service/IProMemberService.java b/tall/src/main/java/com/ccsens/tall/service/IProMemberService.java index 749b8e77..acfac85f 100644 --- a/tall/src/main/java/com/ccsens/tall/service/IProMemberService.java +++ b/tall/src/main/java/com/ccsens/tall/service/IProMemberService.java @@ -25,4 +25,6 @@ public interface IProMemberService { List selectByRole(Long roleId)throws Exception; MemberVo.MemberInfo getMemberByUserIdAndProjectId(Long userId, Long projectId); + + List getMemberIdByProjectId(Long projectId); } diff --git a/tall/src/main/java/com/ccsens/tall/service/ITaskMemberService.java b/tall/src/main/java/com/ccsens/tall/service/ITaskMemberService.java index c4ede195..ef3c828f 100644 --- a/tall/src/main/java/com/ccsens/tall/service/ITaskMemberService.java +++ b/tall/src/main/java/com/ccsens/tall/service/ITaskMemberService.java @@ -1,7 +1,10 @@ package com.ccsens.tall.service; import com.ccsens.tall.bean.po.ProTaskMember; +import org.springframework.beans.factory.annotation.Autowired; public interface ITaskMemberService { + + void saveTaskMember(ProTaskMember proTaskMember); } diff --git a/tall/src/main/java/com/ccsens/tall/service/MessageService.java b/tall/src/main/java/com/ccsens/tall/service/MessageService.java index 51b04aa2..52233b12 100644 --- a/tall/src/main/java/com/ccsens/tall/service/MessageService.java +++ b/tall/src/main/java/com/ccsens/tall/service/MessageService.java @@ -47,7 +47,7 @@ public class MessageService implements IMessageService{ return messageUsers; } @Override - public void sendSyncMessageWithStart(Long currentUserId, Long projectId, Long roleId, Long taskId,Long time, Long duration) throws Exception { + public void sendSyncMessageWithStart(Long currentUserId, Long projectId, Long roleId, Long taskId,Long time, Long duration,String player) throws Exception { MemberVo.MemberInfo currentMemberVo = memberService.getProMemberByProjectIdAndUserId(projectId,currentUserId); if(ObjectUtil.isNotNull(currentMemberVo)) { //生成userMessage @@ -58,7 +58,7 @@ public class MessageService implements IMessageService{ List memberList = memberService.getAuthedMemberByProjectId(projectId); receivers = constructMessageUsers(memberList); - SyncMessageWithStartDto message = new SyncMessageWithStartDto(projectId, sender, receivers, roleId, taskId, null, time, duration); + SyncMessageWithStartDto message = new SyncMessageWithStartDto(projectId, sender, receivers, roleId, taskId, null, time, duration,player); //FixMe 发送到消息队列 System.out.println(message); rabbitTemplate.convertAndSend(RabbitMQConfig.RabbitMQ_QUEUE_NAME, diff --git a/tall/src/main/java/com/ccsens/tall/service/ProMemberService.java b/tall/src/main/java/com/ccsens/tall/service/ProMemberService.java index 6b79dea7..60e41b1b 100644 --- a/tall/src/main/java/com/ccsens/tall/service/ProMemberService.java +++ b/tall/src/main/java/com/ccsens/tall/service/ProMemberService.java @@ -208,4 +208,21 @@ public class ProMemberService implements IProMemberService { } return memberInfo; } + + @Override + public List getMemberIdByProjectId(Long projectId) { + List memberIdList = null; + ProMemberExample memberExample = new ProMemberExample(); + memberExample.createCriteria().andProjectIdEqualTo(projectId); + List proMemberList = proMemberDao.selectByExample(memberExample); + if(CollectionUtil.isNotEmpty(proMemberList)){ + memberIdList = new ArrayList<>(); + for(ProMember member : proMemberList){ + if(ObjectUtil.isNotNull(member.getUserId())){ + memberIdList.add(member.getUserId()); + } + } + } + return memberIdList; + } } diff --git a/tall/src/main/java/com/ccsens/tall/service/TaskSubTimeService.java b/tall/src/main/java/com/ccsens/tall/service/TaskSubTimeService.java index 5e05b334..242fd229 100644 --- a/tall/src/main/java/com/ccsens/tall/service/TaskSubTimeService.java +++ b/tall/src/main/java/com/ccsens/tall/service/TaskSubTimeService.java @@ -39,12 +39,16 @@ public class TaskSubTimeService implements ITaskSubTimeService { @Autowired private ProSubTimeMemberDao proSubTimeMemberDao; @Autowired + private TaskMemberDao taskMemberDao; + @Autowired private ProTaskDeliverPostLogDao proTaskDeliverPostLogDao; @Autowired private TaskDetailDao taskDetailDao; @Autowired private ProRoleDao proRoleDao; @Autowired + private ProMemberDao proMemberDao; + @Autowired private IProMemberService proMemberService; @Autowired private IProTaskDetailService taskDetailService; @@ -185,12 +189,27 @@ public class TaskSubTimeService implements ITaskSubTimeService { taskSubTime.setComplatedStatus(1); taskSubTimeDao.updateByPrimaryKeySelective(taskSubTime); + //查找任务的负责人名 + String player = null; + ProTaskDetail taskDetail = taskDetailDao.selectByPrimaryKey(taskSubTime.getTaskDetailId()); + if(ObjectUtil.isNotNull(taskDetail)){ + if(taskDetail.getAllMember() == 0){ + ProTaskMemberExample taskMemberExample = new ProTaskMemberExample(); + taskMemberExample.createCriteria().andTaskDetailIdEqualTo(taskDetail.getId()); + List taskMemberList = taskMemberDao.selectByExample(taskMemberExample); + if(CollectionUtil.isNotEmpty(taskMemberList)){ + ProMember member = proMemberDao.selectByPrimaryKey(taskMemberList.get(0).getMemberId()); + player = member.getNickname(); + } + } + } + //发送同步消息 SysProject project = sysProjectDao.selectByPrimaryKey(startTaskDto.getProjectId()); //已发布的项目才同步 if (ObjectUtil.isNotNull(project.getPublished()) && project.getPublished() == 1) { messageService.sendSyncMessageWithStart(currentUserId, startTaskDto.getProjectId(), startTaskDto.getRoleId(), taskSubTime.getTaskDetailId(), now, - taskSubTime.getEndTime() - taskSubTime.getBeginTime()); + taskSubTime.getEndTime() - taskSubTime.getBeginTime(),player); } //3.添加记录 proLogService.addNewProLog(now, taskSubTime.getId(), diff --git a/tall/src/main/java/com/ccsens/tall/web/UserController.java b/tall/src/main/java/com/ccsens/tall/web/UserController.java index 526f80ab..e3cc18b9 100644 --- a/tall/src/main/java/com/ccsens/tall/web/UserController.java +++ b/tall/src/main/java/com/ccsens/tall/web/UserController.java @@ -272,6 +272,16 @@ public class UserController { MemberVo.MemberInfo memberInfo = proMemberService.getMemberByUserIdAndProjectId(userId,projectId); return JsonResponse.newInstance().ok(memberInfo); } + + /** + * 获取项目下的所有成员ID + */ + @RequestMapping(value = "allMemberAll", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"}) + public List getMemberIdByProjectId(Long projectId) throws Exception { + + List memberIdInfo = proMemberService.getMemberIdByProjectId(projectId); + return memberIdInfo; + } } diff --git a/util/src/main/java/com/ccsens/util/CodeEnum.java b/util/src/main/java/com/ccsens/util/CodeEnum.java index 9d978696..dcb2610b 100644 --- a/util/src/main/java/com/ccsens/util/CodeEnum.java +++ b/util/src/main/java/com/ccsens/util/CodeEnum.java @@ -76,7 +76,9 @@ public enum CodeEnum { SCORE_REPEAT(60,"您已经评分,请勿重复提交",true), NOT_MEMBER(61,"对不起,找不到对应的成员信息",true), NOT_GAME_RECORD(62,"对不起,找不到对应的游戏场次",true), - NOT_JOIN_GAME(63,"您还未加入游戏,请参加游戏后再试",true) + NOT_JOIN_GAME(63,"您还未加入游戏,请参加游戏后再试",true), + GAME_NO_END(64,"您的上一场游戏尚未结束,请勿重复开启",true), + GAME_NOT_TIMES(65,"游戏可玩次数不足,请充值后重试",true) ; public CodeEnum addMsg(String msg){ diff --git a/util/src/main/java/com/ccsens/util/WebConstant.java b/util/src/main/java/com/ccsens/util/WebConstant.java index b98c27cf..7dd8030a 100644 --- a/util/src/main/java/com/ccsens/util/WebConstant.java +++ b/util/src/main/java/com/ccsens/util/WebConstant.java @@ -72,7 +72,7 @@ public class WebConstant { public static final String UPLOAD_PROJECT_WBS = UPLOAD_PATH_BASE + File.separator + "project"; public static final String URL_BASE = "https://api.ccsens.com/ptpro/uploads/"; public static final String TEST_URL = "https://test.tall.wiki/"; - public static final String TEST_URL_GAME = TEST_URL + "game/"; + public static final String TEST_URL_GAME = TEST_URL + "game-dev/"; public static final String TEST_URL_BASE = TEST_URL + "gateway/tall/v1.0/uploads/"; public static final Integer Expired_Verify_Code_In_Seconds = 120; @@ -358,7 +358,10 @@ public class WebConstant { ,BatchSetting(0x11,"BatchSetting") ,Admin(0x12,"Admin") ,Ring(0x13,"Ring") - ,Deliver(0x14,"Deliver"); + ,Deliver(0x14,"Deliver") + ,Game(0x15,"Game") + ,Chrome(0x15,"Chrome") + ,PPTCtl(0x15,"PPTCtl"); public int value; public String phase; Message_Type(int value,String thePhase){ @@ -380,6 +383,7 @@ public class WebConstant { case 0x12: return Admin; case 0x13: return Ring; case 0x14: return Deliver; + case 0x15: return Game; default: return null; } } @@ -423,7 +427,10 @@ public class WebConstant { } if(phase.equalsIgnoreCase("Deliver")) { return Deliver; - } else { + } + if(phase.equalsIgnoreCase("Game")) { + return Game; + }else { return null; } } @@ -797,6 +804,27 @@ public class WebConstant { } } + public enum Message_Url_Event{ + Url(0,"Url"); + public int value; + public String phase; + Message_Url_Event(int value,String thePhase){ + this.value = value; + this.phase = thePhase; + } + public static Message_Url_Event valueOf(int value) { // 手写的从int到enum的转换函数 + switch (value) { + case 0: return Url; + default: return null; + } + } + public static Message_Url_Event phaseOf(String phase) { // 手写的从String到enum的转换函数 + if(phase.equalsIgnoreCase("Url")) { + return Url; + } + return null; + } + } //wbs表时间类型==================================================================/ } diff --git a/util/src/main/java/com/ccsens/util/bean/message/common/MessageConstant.java b/util/src/main/java/com/ccsens/util/bean/message/common/MessageConstant.java index 06be4b6b..74de022a 100644 --- a/util/src/main/java/com/ccsens/util/bean/message/common/MessageConstant.java +++ b/util/src/main/java/com/ccsens/util/bean/message/common/MessageConstant.java @@ -214,4 +214,38 @@ public class MessageConstant { return null; } } + + public enum GameClientMessageType{ + //客户端心跳 + Heart(0x00), + //客户端认证 + Auth(0x01), + //客户端收到消息ACK + Ack(0x02), + //滑动 + Count(0x03), + //状态改变 + ChangeStatus(0x04) + ; + + public int value; + + GameClientMessageType(int value){ + this.value = value; + } + + /** + * 从int到enum的转换函数 + * @param value 枚举int值 + * @return 对应的枚举,找不到则返回null + */ + public static GameClientMessageType valueOf(int value) { + for(GameClientMessageType type : values()){ + if(type.value == value){ + return type; + } + } + return null; + } + } }