sunkean 3 éve
szülő
commit
920e8282e6

+ 16 - 0
src/main/java/com/izouma/meta/config/Constants.java

@@ -13,6 +13,22 @@ public interface Constants {
 
     String MESSAGE_NORMAL = "META_MESSAGE_NORMAL";
 
+    String META_WEBSOCKET_NOTICE_EMAIL = "META_WEBSOCKET_NOTICE_EMAIL";
+
+    String META_WEBSOCKET_NOTICE_EMAIL_READ = "META_WEBSOCKET_NOTICE_EMAIL_READ";
+
+    String META_WEBSOCKET_NOTICE_EMAIL_DEL = "META_WEBSOCKET_NOTICE_EMAIL_DEL";
+
+    String META_WEBSOCKET_NOTICE_ZOU_MA_LIGHT = "META_WEBSOCKET_NOTICE_ZOU_MA_LIGHT";
+
+    String META_WEBSOCKET_NOTICE_FIRE_OPEN = "META_WEBSOCKET_NOTICE_FIRE_OPEN";
+
+    String META_WEBSOCKET_NOTICE_FIRE_CLOSE = "META_WEBSOCKET_NOTICE_FIRE_CLOSE";
+
+    String META_WEBSOCKET_NOTICE_LIVE_OPEN = "META_WEBSOCKET_NOTICE_LIVE_OPEN";
+
+    String META_WEBSOCKET_NOTICE_LIVE_CLOSE = "META_WEBSOCKET_NOTICE_LIVE_CLOSE";
+
     interface MetaRestCode {
 
         int success = 200;

+ 40 - 0
src/main/java/com/izouma/meta/domain/MetaEmail.java

@@ -0,0 +1,40 @@
+package com.izouma.meta.domain;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+import javax.persistence.Transient;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@ApiModel("元宇宙邮件配置")
+public class MetaEmail extends BaseEntity {
+
+    @ApiModelProperty("邮件标题")
+    @ExcelProperty("邮件标题")
+    private String title;
+
+    @ApiModelProperty("邮件作者")
+    @ExcelProperty("邮件作者")
+    private String author;
+
+    @ApiModelProperty("邮件内容")
+    @ExcelProperty("邮件内容")
+    private String description;
+
+    @ApiModelProperty("是否发布")
+    @ExcelProperty("是否发布")
+    private boolean publish;
+
+    @Transient
+    @ApiModelProperty("是否已读")
+    @ExcelProperty("是否已读")
+    private boolean read;
+}

+ 35 - 0
src/main/java/com/izouma/meta/domain/MetaEmailRecord.java

@@ -0,0 +1,35 @@
+package com.izouma.meta.domain;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@ApiModel("元宇宙题库记录")
+public class MetaEmailRecord extends BaseEntity {
+
+    @ApiModelProperty("用户ID")
+    @ExcelProperty("用户ID")
+    private Long userId;
+
+    @ApiModelProperty("邮件id")
+    @ExcelProperty("邮件id‘")
+    private Long emailId;
+
+    @ApiModelProperty("是否删除")
+    @ExcelProperty("是否删除")
+    private boolean emailDel;
+
+    @ApiModelProperty("是否已读")
+    @ExcelProperty("是否已读")
+    private boolean emailRead;
+
+}

+ 30 - 0
src/main/java/com/izouma/meta/domain/MetaSwitch.java

@@ -0,0 +1,30 @@
+package com.izouma.meta.domain;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@ApiModel("元宇宙空间码头列表")
+public class MetaSwitch extends BaseEntity {
+
+    @ApiModelProperty("开关名称")
+    @ExcelProperty("开关名称")
+    private String name;
+
+    @ApiModelProperty("描述")
+    @ExcelProperty("描述")
+    private String description;
+
+    @ApiModelProperty("开关状态")
+    @ExcelProperty("开关状态")
+    private boolean status;
+}

+ 26 - 0
src/main/java/com/izouma/meta/domain/MetaZouMaLight.java

@@ -0,0 +1,26 @@
+package com.izouma.meta.domain;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import javax.persistence.Entity;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Entity
+@ApiModel("元宇宙走马灯配置")
+public class MetaZouMaLight extends BaseEntity {
+
+    @ApiModelProperty("是否发布")
+    @ExcelProperty("是否发布")
+    private boolean publish;
+
+    @ApiModelProperty("走马灯详情")
+    @ExcelProperty("走马灯详情")
+    private String description;
+}

+ 16 - 0
src/main/java/com/izouma/meta/repo/MetaEmailRecordRepo.java

@@ -0,0 +1,16 @@
+package com.izouma.meta.repo;
+
+import com.izouma.meta.domain.MetaEmailRecord;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+import java.util.List;
+
+public interface MetaEmailRecordRepo extends JpaRepository<MetaEmailRecord, Long>, JpaSpecificationExecutor<MetaEmailRecord> {
+
+    List<Long> findIdByUserIdAndEmailDel(Long userId, boolean emailDel);
+
+    List<Long> findIdByUserIdAndEmailRead(Long userId, boolean emailRead);
+
+    MetaEmailRecord findByUserIdAndEmailId(Long userId, Long emailId);
+}

+ 12 - 0
src/main/java/com/izouma/meta/repo/MetaEmailRepo.java

@@ -0,0 +1,12 @@
+package com.izouma.meta.repo;
+
+import com.izouma.meta.domain.MetaEmail;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+import java.util.List;
+
+public interface MetaEmailRepo extends JpaRepository<MetaEmail, Long>, JpaSpecificationExecutor<MetaEmail> {
+
+    List<MetaEmail> findAllByIdNotIn(List<Long> id);
+}

+ 10 - 0
src/main/java/com/izouma/meta/repo/MetaZouMaLightRepo.java

@@ -0,0 +1,10 @@
+package com.izouma.meta.repo;
+
+import com.izouma.meta.domain.MetaZouMaLight;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+
+public interface MetaZouMaLightRepo extends JpaRepository<MetaZouMaLight, Long>, JpaSpecificationExecutor<MetaZouMaLight> {
+
+    String findDescriptionByPublishAndDel(boolean publish, boolean del);
+}

+ 3 - 3
src/main/java/com/izouma/meta/web/MetaCacheable.java

@@ -1,7 +1,7 @@
 package com.izouma.meta.web;
 
 import com.izouma.meta.dto.MetaRestResult;
-import com.izouma.meta.websocket.WebSocket;
+import com.izouma.meta.websocket.MMOWebSocket;
 import lombok.AllArgsConstructor;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
@@ -12,10 +12,10 @@ import org.springframework.web.bind.annotation.RestController;
 @AllArgsConstructor
 public class MetaCacheable {
 
-    private WebSocket webSocket;
+    private MMOWebSocket mmoWebSocket;
 
     @PostMapping("/clearMMO")
     public MetaRestResult clearMMO(String userId, String regionId, String cityId) {
-        return webSocket.clearMMO(userId, regionId, cityId);
+        return mmoWebSocket.clearMMO(userId, regionId, cityId);
     }
 }

+ 421 - 0
src/main/java/com/izouma/meta/websocket/MMOWebSocket.java

@@ -0,0 +1,421 @@
+package com.izouma.meta.websocket;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.izouma.meta.config.Constants;
+import com.izouma.meta.domain.MetaMMOLoginInfo;
+import com.izouma.meta.dto.MMOMessage;
+import com.izouma.meta.dto.MMOSingleMessage;
+import com.izouma.meta.dto.MetaRestResult;
+import com.izouma.meta.dto.MetaServiceResult;
+import com.izouma.meta.enums.MoveType;
+import com.izouma.meta.repo.MetaMMOLoginInfoRepo;
+import com.izouma.meta.utils.ApplicationContextUtil;
+import com.izouma.meta.utils.ObjUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import javax.websocket.*;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+@Service
+@ServerEndpoint(value = "/websocket/mmo/{nickName}/{userId}")
+@Slf4j
+public class MMOWebSocket {
+
+    /**
+     * 当前在线的客户端map
+     */
+    private static final Map<String, Session> clients = new ConcurrentHashMap();
+
+    private final String REDIS_PREFIX = "meta:";
+
+    private RedisTemplate redisTemplate;
+
+    private MetaMMOLoginInfoRepo metaMMOLoginInfoRepo;
+
+    private WebsocketCommon websocketCommon;
+
+    private void init() {
+        if (Objects.isNull(redisTemplate)) {
+            redisTemplate = (RedisTemplate) ApplicationContextUtil.getBean("redisTemplate");
+        }
+        if (Objects.isNull(metaMMOLoginInfoRepo)) {
+            metaMMOLoginInfoRepo = (MetaMMOLoginInfoRepo) ApplicationContextUtil.getBean("metaMMOLoginInfoRepo");
+        }
+        if (Objects.isNull(websocketCommon)) {
+            websocketCommon = (WebsocketCommon) ApplicationContextUtil.getBean("websocketCommon");
+        }
+    }
+
+    @OnOpen
+    public void onOpen(@PathParam("nickName") String nickName, @PathParam("userId") String userId, Session session) {
+        init();
+        // 判断当前玩家是否在其他地方登陆
+        if (clients.containsKey(REDIS_PREFIX.concat(userId))) {
+            log.info(String.format("当前玩家[%S]已经在别处登陆,sessionId为[%S]", userId, session.getId()));
+            // 关闭连接
+            MMOSingleMessage mmoSingleMessage = new MMOSingleMessage();
+            mmoSingleMessage.setMessageType(6);
+            mmoSingleMessage.setMessage("您已在其他地方登录,已为您关闭上次登陆信息");
+            websocketCommon.sendMessageTo(clients, JSON.toJSONString(mmoSingleMessage), REDIS_PREFIX.concat(userId));
+            try {
+                log.info("关闭上次登陆的session连接");
+                clients.get(REDIS_PREFIX.concat(userId)).close();
+            } catch (Exception e) {
+                log.error("session close throw exception:", e);
+            }
+        }
+        log.info("现在来连接的sessionId:" + session.getId() + "玩家id:" + userId + "玩家昵称" + nickName);
+        clients.put(REDIS_PREFIX.concat(userId), session);
+        // 获取上次登录的信息
+        MetaMMOLoginInfo dbMetaMMOLoginInfo = metaMMOLoginInfoRepo.findLastByUserId(Long.parseLong(userId));
+        // 玩家登陆信息入库
+        MetaMMOLoginInfo metaMMOLoginInfo = MetaMMOLoginInfo.initMetaMMOLoginInfo(dbMetaMMOLoginInfo);
+        metaMMOLoginInfo.setNickname(nickName);
+        metaMMOLoginInfo.setUserId(Long.parseLong(userId));
+        metaMMOLoginInfo.setOnLineTime(LocalDateTime.now());
+        metaMMOLoginInfo.setSessionId(session.getId());
+        MetaMMOLoginInfo save = metaMMOLoginInfoRepo.save(metaMMOLoginInfo);
+        MMOMessage mmoMessage = new MMOMessage();
+        mmoMessage.setMessageType(5);
+        mmoMessage.setMessage(save);
+        log.info(String.format("通知玩家[%S],本次登陆信息[%S]", userId, JSON.toJSONString(mmoMessage)));
+        websocketCommon.sendMessageTo(clients, JSON.toJSONString(mmoMessage), REDIS_PREFIX.concat(userId));
+    }
+
+    @OnError
+    public void onError(Session session, Throwable error) {
+        // 异常处理
+        log.error(String.format("sessionId[%S]的服务端发生了错误:[%S]", session.getId(), error.getMessage()));
+    }
+
+    @OnClose
+    public void onClose(@PathParam("userId") String userId) {
+        init();
+        // 查询地图中玩家信息
+        MetaMMOLoginInfo metaMMOLoginInfo = (MetaMMOLoginInfo) redisTemplate.opsForValue().get(REDIS_PREFIX.concat(userId));
+        if (Objects.isNull(metaMMOLoginInfo)) {
+            // 如果缓存中玩家信息为空,根据userId和sessionId查询数据库
+            MetaMMOLoginInfo dbMetaMMOLoginInfo = metaMMOLoginInfoRepo.findByUserIdAndSessionIdAndDel(Long.parseLong(userId), clients.get(REDIS_PREFIX.concat(userId)).getId(), false);
+            dbMetaMMOLoginInfo.setOffLineTime(LocalDateTime.now());
+            // 更新离线时间
+            metaMMOLoginInfoRepo.save(dbMetaMMOLoginInfo);
+            clients.remove(REDIS_PREFIX.concat(userId));
+            return;
+        }
+        String key = String.valueOf(metaMMOLoginInfo.getCityId()).concat(":").concat(String.valueOf(metaMMOLoginInfo.getRegionId()));
+        List<String> otherUserIds = remove(key, userId);
+        if (CollectionUtils.isNotEmpty(otherUserIds)) {
+            // 分发下线消息给区域内其他玩家
+            buildMessageForSendingToAllOther(otherUserIds, 3, metaMMOLoginInfo);
+        }
+        MetaMMOLoginInfo dbMetaMMOLoginInfo = metaMMOLoginInfoRepo.findById(metaMMOLoginInfo.getId()).orElse(null);
+        if (Objects.isNull(dbMetaMMOLoginInfo)) {
+            log.error(String.format("数据库中不存在id[%S]的记录", metaMMOLoginInfo.getId()));
+            return;
+        }
+        ObjUtils.merge(dbMetaMMOLoginInfo, metaMMOLoginInfo);
+        dbMetaMMOLoginInfo.setOffLineTime(LocalDateTime.now());
+        metaMMOLoginInfoRepo.save(dbMetaMMOLoginInfo);
+        clients.remove(REDIS_PREFIX.concat(userId));
+        // 删除redis中自己的信息
+        redisTemplate.delete(REDIS_PREFIX.concat(userId));
+        // 移除地图中自己的信息
+        redisTemplate.opsForList().remove(REDIS_PREFIX.concat(key), 0, REDIS_PREFIX.concat(userId));
+    }
+
+    @OnMessage
+    public void onMessage(@PathParam("nickName") String nickName, @PathParam("userId") String userId, String message, Session session) {
+        init();
+        if (StringUtils.isBlank(message)) {
+            log.error("Illegal parameter : message can not be null");
+            return;
+        }
+        if (Constants.HEART_RECEIVE.equals(message)) {
+            log.info(String.format("sessionId:[%S] userId:[%S] 连接正常", session.getId(), userId));
+            websocketCommon.sendMessageTo(clients, Constants.HEART_RETURN, REDIS_PREFIX.concat(userId));
+            return;
+        }
+        JSONObject jsonObject = JSON.parseObject(message);
+        MetaServiceResult result = checkParams(jsonObject);
+        if (!result.isSuccess()) {
+            log.error(result.getMessage());
+            return;
+        }
+        log.info("来自客户端消息:" + message + "客户端的id是:" + session.getId());
+        int type = Integer.parseInt(jsonObject.getString("type"));
+        String cityId = jsonObject.getString("cityId");
+        String regionId = jsonObject.getString("regionId");
+        String key = cityId.concat(":").concat(regionId);
+        List<String> otherUserIds = remove(key, userId);
+        MetaMMOLoginInfo metaMMOLoginInfo;
+        List<MetaMMOLoginInfo> metaMMOLoginInfos = redisTemplate.opsForValue().multiGet(otherUserIds);
+        switch (type) {
+            case 1:
+                log.info("当前操作类型为1 -> 玩家进入地图");
+                metaMMOLoginInfo = buildMetaMMOLoginInfo(jsonObject, Long.parseLong(cityId), Long.parseLong(regionId), nickName, userId);
+                if (CollectionUtils.isNotEmpty(otherUserIds)) {
+                    if (CollectionUtils.isNotEmpty(metaMMOLoginInfos)) {
+                        // 分发区域内其他玩家信息给自己
+                        buildMessageForSendingToUser(REDIS_PREFIX.concat(userId), 1, metaMMOLoginInfos);
+                        // 分发自己信息给区域内其他玩家
+                        buildMessageForSendingToAllOther(otherUserIds, 4, metaMMOLoginInfo);
+                    }
+                }
+                // 将自己信息存到redis中
+                redisTemplate.opsForValue().set(REDIS_PREFIX.concat(userId), metaMMOLoginInfo);
+                redisTemplate.opsForList().leftPush(REDIS_PREFIX.concat(key), REDIS_PREFIX.concat(userId));
+                break;
+            case 2:
+                log.info(String.format("当前操作类型为[%S] -> 玩家切换区域", type));
+                metaMMOLoginInfo = buildMetaMMOLoginInfo(jsonObject, Long.parseLong(cityId), Long.parseLong(regionId), nickName, userId);
+                if (CollectionUtils.isNotEmpty(otherUserIds)) {
+                    // 分发自己信息给区域内其他玩家
+                    buildMessageForSendingToAllOther(otherUserIds, 4, metaMMOLoginInfo);
+                    // 分发区域内其他玩家信息给自己
+                    buildMessageForSendingToUser(REDIS_PREFIX.concat(userId), 1, metaMMOLoginInfos);
+                }
+                MetaMMOLoginInfo oldMetaMMOLoginInfo = (MetaMMOLoginInfo) redisTemplate.opsForValue().get(REDIS_PREFIX.concat(userId));
+                if (Objects.isNull(oldMetaMMOLoginInfo)) {
+                    log.error("缺失玩家上次区域的地图缓存信息");
+                    break;
+                }
+                String oldKey = String.valueOf(oldMetaMMOLoginInfo.getCityId()).concat(":").concat(String.valueOf(oldMetaMMOLoginInfo.getRegionId()));
+                List<String> oldUserIds = redisTemplate.opsForList().range(REDIS_PREFIX.concat(oldKey), 0, -1);
+                if (CollectionUtils.isEmpty(oldUserIds)) {
+                    log.error("查询不到上次所进入的区域的玩家信息");
+                    break;
+                }
+                // 分发消息给之前区域的玩家
+                buildMessageForSendingToAllOther(oldUserIds, 3, oldMetaMMOLoginInfo);
+                // 清除玩家上次缓存的地图信息
+                redisTemplate.opsForList().remove(REDIS_PREFIX.concat(oldKey), 0, REDIS_PREFIX.concat(userId));
+                // 缓存玩家新的地图信息
+                redisTemplate.opsForValue().set(REDIS_PREFIX.concat(userId), metaMMOLoginInfo);
+                redisTemplate.opsForList().leftPush(REDIS_PREFIX.concat(key), REDIS_PREFIX.concat(userId));
+                break;
+            case 3:
+                log.info(String.format("当前操作类型为[%S] -> 玩家在地图内移动", type));
+                metaMMOLoginInfo = (MetaMMOLoginInfo) redisTemplate.opsForValue().get(REDIS_PREFIX.concat(userId));
+                if (Objects.isNull(metaMMOLoginInfo)) {
+                    log.error("缓存中不存在本玩家信息");
+                    break;
+                }
+                // 更新玩家位置信息
+                buildCommonProperty(metaMMOLoginInfo, jsonObject);
+                // 分发玩家信息给区域内其他玩家
+                buildMessageForSendingToAllOther(otherUserIds, 2, metaMMOLoginInfo);
+                redisTemplate.opsForValue().set(REDIS_PREFIX.concat(userId), metaMMOLoginInfo);
+                break;
+            case 4:
+                log.info(String.format("当前操作类型为[%S] -> 玩家进入大厅", type));
+                metaMMOLoginInfo = (MetaMMOLoginInfo) redisTemplate.opsForValue().get(REDIS_PREFIX.concat(userId));
+                if (Objects.isNull(metaMMOLoginInfo)) {
+                    log.error("缓存中不存在本玩家信息");
+                    break;
+                }
+                // 分发玩家信息给区域内其他玩家
+                buildMessageForSendingToAllOther(otherUserIds, 3, metaMMOLoginInfo);
+                // 更新库中玩家位置信息
+                MetaMMOLoginInfo dbMetaMMOLoginInfo = metaMMOLoginInfoRepo.findById(metaMMOLoginInfo.getId()).orElse(null);
+                if (Objects.isNull(dbMetaMMOLoginInfo)) {
+                    log.error(String.format("数据库不存在id[%S]的记录", metaMMOLoginInfo.getId()));
+                    break;
+                }
+                dbMetaMMOLoginInfo.setAxisX(metaMMOLoginInfo.getAxisX());
+                dbMetaMMOLoginInfo.setAxisY(metaMMOLoginInfo.getAxisY());
+                dbMetaMMOLoginInfo.setAxisZ(metaMMOLoginInfo.getAxisZ());
+                dbMetaMMOLoginInfo.setEulerX(metaMMOLoginInfo.getEulerX());
+                dbMetaMMOLoginInfo.setEulerY(metaMMOLoginInfo.getEulerY());
+                dbMetaMMOLoginInfo.setEulerZ(metaMMOLoginInfo.getEulerZ());
+                dbMetaMMOLoginInfo.setCityId(metaMMOLoginInfo.getCityId());
+                dbMetaMMOLoginInfo.setRegionId(metaMMOLoginInfo.getRegionId());
+                dbMetaMMOLoginInfo.setTop(metaMMOLoginInfo.getTop());
+                dbMetaMMOLoginInfo.setHat(metaMMOLoginInfo.getHat());
+                dbMetaMMOLoginInfo.setShoes(metaMMOLoginInfo.getShoes());
+                dbMetaMMOLoginInfo.setDown(metaMMOLoginInfo.getDown());
+                dbMetaMMOLoginInfo.setEmoji(metaMMOLoginInfo.getEmoji());
+                dbMetaMMOLoginInfo.setAnim(metaMMOLoginInfo.getAnim());
+                dbMetaMMOLoginInfo.setRole(metaMMOLoginInfo.getRole());
+                dbMetaMMOLoginInfo.setMoveType(metaMMOLoginInfo.getMoveType());
+                metaMMOLoginInfoRepo.save(dbMetaMMOLoginInfo);
+                break;
+            default:
+                log.error(String.format("不存在的操作类型[%S]", type));
+        }
+
+    }
+
+    /**
+     * 分发玩家信息给区域内其他玩家
+     *
+     * @param userIds          玩家id集合
+     * @param messageType      消息类型
+     * @param metaMMOLoginInfo 消息体
+     */
+    private void buildMessageForSendingToAllOther(List<String> userIds, int messageType, MetaMMOLoginInfo metaMMOLoginInfo) {
+        MMOMessage mmoMessage = new MMOMessage();
+        mmoMessage.setMessageType(messageType);
+        mmoMessage.setMessage(metaMMOLoginInfo);
+        if (!clients.containsKey(REDIS_PREFIX.concat(String.valueOf(metaMMOLoginInfo.getUserId())))) {
+            log.error("session信息不存在");
+            return;
+        }
+        userIds.forEach(id -> {
+            try {
+                log.info(String.format("服务器给所有当前区域内在线用户发送消息,当前在线人员为[%S]。消息:[%S]", id, JSON.toJSONString(mmoMessage)));
+                clients.get(id).getBasicRemote().sendText(JSON.toJSONString(mmoMessage));
+            } catch (Exception e) {
+                log.error(String.format("send message [%S] to [%S] throw exception [%S]:", JSON.toJSONString(mmoMessage), id, e));
+            }
+        });
+    }
+
+    /**
+     * 分发区域内其他玩家信息给自己
+     *
+     * @param userId            玩家id
+     * @param messageType       消息类型
+     * @param metaMMOLoginInfos 消息体(其他玩家信息)
+     */
+    private void buildMessageForSendingToUser(String userId, int messageType, List<MetaMMOLoginInfo> metaMMOLoginInfos) {
+        MMOMessage mmoMessage = new MMOMessage();
+        mmoMessage.setMessageType(messageType);
+        mmoMessage.setMap(metaMMOLoginInfos);
+        websocketCommon.sendMessageTo(clients, JSON.toJSONString(mmoMessage), userId);
+    }
+
+    /**
+     * 构建玩家登陆信息
+     *
+     * @param jsonObject 玩家位置等信息json对象
+     * @param cityId     城市id
+     * @param regionId   区域id
+     * @return 玩家位置信息
+     */
+    private MetaMMOLoginInfo buildMetaMMOLoginInfo(JSONObject jsonObject, Long cityId, Long regionId, String nickName, String userId) {
+        // 获取到进入地图时自己的信息
+        MetaMMOLoginInfo metaMMOLoginInfo = new MetaMMOLoginInfo();
+        buildCommonProperty(metaMMOLoginInfo, jsonObject);
+        metaMMOLoginInfo.setCityId(cityId);
+        metaMMOLoginInfo.setRegionId(regionId);
+        metaMMOLoginInfo.setUserId(Long.parseLong(userId));
+        metaMMOLoginInfo.setNickname(nickName);
+        if (Objects.nonNull(jsonObject.getString("role"))) {
+            metaMMOLoginInfo.setRole(jsonObject.getString("role"));
+        }
+        if (Objects.nonNull(jsonObject.getString("id"))) {
+            metaMMOLoginInfo.setId(Long.parseLong(jsonObject.getString("id")));
+        }
+        metaMMOLoginInfo.setCityId(cityId);
+        return metaMMOLoginInfo;
+    }
+
+    /**
+     * 根据jsonObject构建玩家位置信息
+     *
+     * @param metaMMOLoginInfo 玩家登陆信息
+     * @param jsonObject       玩家位置信息json对象
+     */
+    private void buildCommonProperty(MetaMMOLoginInfo metaMMOLoginInfo, JSONObject jsonObject) {
+        if (Objects.nonNull(jsonObject.getString("axisX"))) {
+            metaMMOLoginInfo.setAxisX(Float.parseFloat(jsonObject.getString("axisX")));
+        }
+        if (Objects.nonNull(jsonObject.getString("axisY"))) {
+            metaMMOLoginInfo.setAxisY(Float.parseFloat(jsonObject.getString("axisY")));
+        }
+        if (Objects.nonNull(jsonObject.getString("axisZ"))) {
+            metaMMOLoginInfo.setAxisZ(Float.parseFloat(jsonObject.getString("axisZ")));
+        }
+        if (Objects.nonNull(jsonObject.getString("eulerX"))) {
+            metaMMOLoginInfo.setEulerX(Float.parseFloat(jsonObject.getString("eulerX")));
+        }
+        if (Objects.nonNull(jsonObject.getString("eulerY"))) {
+            metaMMOLoginInfo.setEulerY(Float.parseFloat(jsonObject.getString("eulerY")));
+        }
+        if (Objects.nonNull(jsonObject.getString("eulerZ"))) {
+            metaMMOLoginInfo.setEulerZ(Float.parseFloat(jsonObject.getString("eulerZ")));
+        }
+        if (Objects.nonNull(jsonObject.getString("top"))) {
+            metaMMOLoginInfo.setTop(Integer.parseInt(jsonObject.getString("top")));
+        }
+        if (Objects.nonNull(jsonObject.getString("hat"))) {
+            metaMMOLoginInfo.setHat(Integer.parseInt(jsonObject.getString("hat")));
+        }
+        if (Objects.nonNull(jsonObject.getString("down"))) {
+            metaMMOLoginInfo.setDown(Integer.parseInt(jsonObject.getString("down")));
+        }
+        if (Objects.nonNull(jsonObject.getString("shoes"))) {
+            metaMMOLoginInfo.setShoes(Integer.parseInt(jsonObject.getString("shoes")));
+        }
+        if (Objects.nonNull(jsonObject.getString("anim"))) {
+            metaMMOLoginInfo.setAnim(Integer.parseInt(jsonObject.getString("anim")));
+        }
+        if (Objects.nonNull(jsonObject.getString("emoji"))) {
+            metaMMOLoginInfo.setEmoji(Integer.parseInt(jsonObject.getString("emoji")));
+        }
+        if (Objects.nonNull(jsonObject.getString("moveType"))) {
+            metaMMOLoginInfo.setMoveType(MoveType.valueOf(jsonObject.getString("moveType")));
+        }
+    }
+
+    /**
+     * 校验参数
+     *
+     * @param jsonObject 参数
+     * @return 校验结果
+     */
+    private MetaServiceResult checkParams(JSONObject jsonObject) {
+        if (Objects.isNull(jsonObject)) {
+            return MetaServiceResult.returnError("Illegal parameter : jsonObject can not be null");
+        }
+        if (Objects.isNull(jsonObject.getString("type"))) {
+            return MetaServiceResult.returnError("Illegal parameter : type can not be null");
+        }
+        if (Objects.isNull(jsonObject.getString("cityId"))) {
+            return MetaServiceResult.returnError("Illegal parameter : cityId can not be null");
+        }
+        if (Objects.isNull(jsonObject.getString("regionId"))) {
+            return MetaServiceResult.returnError("Illegal parameter : regionId can not be null");
+        }
+        if (Objects.isNull(jsonObject.getString("id"))) {
+            return MetaServiceResult.returnError("Illegal parameter : id can not be null");
+        }
+        return MetaServiceResult.returnSuccess("check success");
+    }
+
+    private List<String> remove(String key, String userId) {
+        List<String> userIds = redisTemplate.opsForList().range(REDIS_PREFIX.concat(key), 0, -1);
+        //  去除当前玩家id
+        List<String> otherUserIds = new ArrayList<>();
+        if (CollectionUtils.isNotEmpty(userIds)) {
+            otherUserIds = userIds.stream().filter(id -> !Objects.equals(id, REDIS_PREFIX.concat(userId))).collect(Collectors.toList());
+        }
+        return otherUserIds;
+    }
+
+    public MetaRestResult clearMMO(String userId, String regionId, String cityId) {
+        init();
+        if (clients.containsKey(REDIS_PREFIX.concat(userId))) {
+            String errMsg = String.format("userId[%S]用户mmo连接正常,无法清除缓存!", userId);
+            log.info(errMsg);
+            return MetaRestResult.returnError(errMsg);
+        }
+        String key = cityId.concat(":").concat(regionId);
+        redisTemplate.delete(REDIS_PREFIX.concat(userId));
+        redisTemplate.opsForList().remove(REDIS_PREFIX.concat(key), 0, REDIS_PREFIX.concat(userId));
+        return MetaRestResult.returnSuccess("清除成功");
+    }
+}

+ 208 - 0
src/main/java/com/izouma/meta/websocket/Websocket.java

@@ -0,0 +1,208 @@
+package com.izouma.meta.websocket;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.izouma.meta.config.Constants;
+import com.izouma.meta.domain.MetaEmail;
+import com.izouma.meta.domain.MetaEmailRecord;
+import com.izouma.meta.dto.PublicScreenChatExceptionMsg;
+import com.izouma.meta.repo.MetaEmailRecordRepo;
+import com.izouma.meta.repo.MetaEmailRepo;
+import com.izouma.meta.repo.MetaZouMaLightRepo;
+import com.izouma.meta.utils.ApplicationContextUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.StringUtils;
+import org.springframework.stereotype.Service;
+
+import javax.websocket.*;
+import javax.websocket.server.PathParam;
+import javax.websocket.server.ServerEndpoint;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+@ServerEndpoint(value = "/websocket/{userId}")
+@Slf4j
+public class Websocket {
+
+    /**
+     * 当前在线的客户端map
+     */
+    private static final Map<String, Session> clients = new ConcurrentHashMap();
+
+    private WebsocketCommon websocketCommon;
+
+    private MetaEmailRepo metaEmailRepo;
+
+    private MetaEmailRecordRepo metaEmailRecordRepo;
+
+    private MetaZouMaLightRepo metaZouMaLightRepo;
+
+    private void init() {
+        if (Objects.isNull(websocketCommon)) {
+            websocketCommon = (WebsocketCommon) ApplicationContextUtil.getBean("websocketCommon");
+        }
+        if (Objects.isNull(metaEmailRepo)) {
+            metaEmailRepo = (MetaEmailRepo) ApplicationContextUtil.getBean("metaEmailRepo");
+        }
+        if (Objects.isNull(metaEmailRecordRepo)) {
+            metaEmailRecordRepo = (MetaEmailRecordRepo) ApplicationContextUtil.getBean("metaEmailRecordRepo");
+        }
+        if (Objects.isNull(metaZouMaLightRepo)) {
+            metaZouMaLightRepo = (MetaZouMaLightRepo) ApplicationContextUtil.getBean("metaZouMaLightRepo");
+        }
+    }
+
+    @OnOpen
+    public void onOpen(@PathParam("userId") String userId, Session session) {
+        init();
+        // 判断当前玩家是否在其他地方登陆
+        if (clients.containsKey(userId)) {
+            String msg = String.format("已在别处登陆,sessionId为[%S],正在为您关闭本连接", session.getId());
+            exceptionHandle(userId, new PublicScreenChatExceptionMsg(1, msg));
+            try {
+                log.info("关闭session连接");
+                clients.get(userId).close();
+            } catch (Exception e) {
+                exceptionHandle(userId, new PublicScreenChatExceptionMsg(1, String.format("session close throw exception[%S]", e)));
+                return;
+            }
+        }
+        log.info("现在来连接的sessionId:" + session.getId() + "玩家id:" + userId);
+        clients.put(userId, session);
+    }
+
+    @OnError
+    public void onError(Session session, Throwable error) {
+        // 异常处理
+        error.printStackTrace();
+        log.error(String.format("sessionId[%S]的服务端发生了错误:[%S]", session.getId(), error));
+    }
+
+    @OnClose
+    public void onClose(@PathParam("userId") String userId, Session session) {
+        log.info(String.format("关闭session[%S]连接", session.getId()));
+        clients.remove(userId);
+    }
+
+    @OnMessage
+    public void onMessage(@PathParam("userId") String userId, String message, Session session) {
+        init();
+        if (StringUtils.isBlank(message)) {
+            log.error("Illegal parameter : message can not be null");
+            return;
+        }
+        if (Constants.HEART_RECEIVE.equals(message)) {
+            log.info(String.format("sessionId:[%S] userId:[%S] 连接正常", session.getId(), userId));
+            websocketCommon.sendMessageTo(clients, Constants.HEART_RETURN, userId);
+            return;
+        }
+        JSONObject jsonObject = JSON.parseObject(message);
+        String type = jsonObject.getString("type");
+        log.info("来自客户端消息:" + message + "客户端的id是:" + session.getId());
+        switch (type) {
+            case Constants.META_WEBSOCKET_NOTICE_EMAIL:
+                log.info("查询邮件");
+                List<MetaEmail> metaEmails = queryEmail(Long.parseLong(userId));
+                if (CollectionUtils.isNotEmpty(metaEmails)) {
+                    websocketCommon.sendMessageTo(clients, JSON.toJSONString(metaEmails), userId);
+                }
+                break;
+            case Constants.META_WEBSOCKET_NOTICE_EMAIL_READ:
+                log.info("读取邮件");
+                try {
+                    Long emailId = Long.parseLong(jsonObject.getString("value"));
+                    metaEmailRecordRepo.save(new MetaEmailRecord(Long.parseLong(userId), emailId, false, true));
+                } catch (Exception e) {
+                    log.error(String.format("读取邮件失败,错误信息[%S]", e.getMessage()));
+                }
+                break;
+            case Constants.META_WEBSOCKET_NOTICE_EMAIL_DEL:
+                log.info("删除邮件");
+                try {
+                    Long emailId = Long.parseLong(jsonObject.getString("value"));
+                    delEmail(Long.parseLong(userId), emailId);
+                } catch (Exception e) {
+                    log.error(String.format("读取邮件失败,错误信息[%S]", e.getMessage()));
+                }
+                break;
+            case Constants.META_WEBSOCKET_NOTICE_ZOU_MA_LIGHT:
+                log.info("查询走马灯");
+                String description = metaZouMaLightRepo.findDescriptionByPublishAndDel(true, false);
+                if (StringUtils.isNotBlank(description)) {
+                    websocketCommon.sendMessageToOther(clients, description, userId);
+                }
+                break;
+            case Constants.META_WEBSOCKET_NOTICE_FIRE_OPEN:
+                log.info("烟花开启");
+                websocketCommon.sendMessageToOther(clients, Constants.META_WEBSOCKET_NOTICE_FIRE_OPEN, userId);
+                break;
+            case Constants.META_WEBSOCKET_NOTICE_FIRE_CLOSE:
+                log.info("烟花关闭");
+                websocketCommon.sendMessageToOther(clients, Constants.META_WEBSOCKET_NOTICE_FIRE_CLOSE, userId);
+                break;
+            case Constants.META_WEBSOCKET_NOTICE_LIVE_OPEN:
+                log.info("直播开启");
+                websocketCommon.sendMessageToOther(clients, Constants.META_WEBSOCKET_NOTICE_LIVE_OPEN, userId);
+                break;
+            case Constants.META_WEBSOCKET_NOTICE_LIVE_CLOSE:
+                log.info("直播关闭");
+                websocketCommon.sendMessageToOther(clients, Constants.META_WEBSOCKET_NOTICE_LIVE_CLOSE, userId);
+                break;
+            default:
+                log.error(String.format("不支持的类型[%S]", type));
+        }
+    }
+
+    /**
+     * 查询未删除邮件
+     *
+     * @param userId 用户id
+     * @return 未删除邮件
+     */
+    private List<MetaEmail> queryEmail(Long userId) {
+        init();
+        List<Long> delIds = metaEmailRecordRepo.findIdByUserIdAndEmailDel(userId, true);
+        List<MetaEmail> metaEmails;
+        metaEmails = CollectionUtils.isEmpty(delIds) ? metaEmailRepo.findAll() : metaEmailRepo.findAllByIdNotIn(delIds);
+        if (CollectionUtils.isEmpty(metaEmails)) {
+            return metaEmails;
+        }
+        List<Long> readIds = metaEmailRecordRepo.findIdByUserIdAndEmailRead(userId, true);
+        if (CollectionUtils.isEmpty(readIds)) {
+            return metaEmails;
+        }
+        metaEmails.forEach(metaEmail -> {
+            if (readIds.contains(metaEmail.getId())) {
+                metaEmail.setRead(true);
+            }
+        });
+        return metaEmails;
+    }
+
+    /**
+     * 删除邮件
+     *
+     * @param userId  用户id
+     * @param emailId 邮件id
+     */
+    private void delEmail(Long userId, Long emailId) {
+        MetaEmailRecord metaEmailRecord = metaEmailRecordRepo.findByUserIdAndEmailId(userId, emailId);
+        if (Objects.isNull(userId)) {
+            metaEmailRecordRepo.save(new MetaEmailRecord(userId, emailId, true, true));
+            return;
+        }
+        metaEmailRecord.setEmailDel(true);
+        metaEmailRecordRepo.save(metaEmailRecord);
+    }
+
+    private void exceptionHandle(String userId, PublicScreenChatExceptionMsg msg) {
+        log.error(JSON.toJSONString(msg));
+        // 推送消息给该玩家
+        websocketCommon.sendMessageTo(clients, JSON.toJSONString(msg), userId);
+    }
+
+}