Vert.x IM聊天系統設計實現

sweetmain發表於2017-12-13

HttpVerticle.java

package com.xiaoniu.im.rest;

import com.xiaoniu.im.utils.Runner;
import com.xiaoniu.im.utils.Utils;
import io.netty.util.internal.StringUtil;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.core.shareddata.LocalMap;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import org.mapdb.DB;
import org.mapdb.DBMaker;
import org.mapdb.HTreeMap;
import org.mapdb.Serializer;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * http 服務處理除聊天以外的業務
 * Created by sweet on 2017/9/26.
 */
public class HttpVerticle extends AbstractVerticle {

  private static final Logger log = LoggerFactory.getLogger(HttpVerticle.class);

  public static void main(String[] args) {
    Runner.runExample(HttpVerticle.class);
  }
  private static final String filePath = "C:\\xiaoniu_doc\\vertx\\sweet-im\\";
  private static final Integer port = 8080;

  private DB db;
  private HTreeMap<String, String> userMap; // 儲存註冊使用者資訊
  private LocalMap<String, Object> userMapCache; // 儲存註冊使用者資訊(記憶體共享版)

  private HTreeMap<String, String> userNameAndIdMap; // 使用者id - 使用者名稱 方便快速查詢
  private LocalMap<String, Object> userNameAndIdMapCache; // 使用者id - 使用者名稱 方便快速查詢 (記憶體版)

  private HTreeMap<String, String> friendMap; // 儲存好友關係
  private LocalMap<String, Object> friendMapCache; // 儲存好友關係(記憶體共享版)

  private HTreeMap<String, String> onlineMap; // 當前登入使用者
  private LocalMap<String, Object> onlineMapCache; // 當前登入使用者 (記憶體共享版)

  private LocalMap<String, Object> messageMapCache;  // 儲存使用者私密聊天記錄 (記憶體共享版)
  private LocalMap<String, Object> messageGroupMapCache;  // 儲存群組聊天記錄 (記憶體共享版)

  private HTreeMap<String, String> groupMap; // 群組關係
  private LocalMap<String, Object> groupMapCache; // 群組關係 (記憶體共享版)

  private EventBus eventBus;

  @Override
  public void start(Future<Void> future) throws Exception {
    eventBus = vertx.eventBus();
    initDB().setHandler(init -> {
      if (init.succeeded() && init.result()) {
        Router router = Router.router(vertx);
        router.route().handler(BodyHandler.create());

        // 使用者註冊
        router.post("/register").consumes("application/json").handler(this::register);

        // 使用者登入
        router.post("/login").consumes("application/json").handler(this::login);

        // 使用者退出 (暫定)
        router.get("/logout").handler(this::logout);

        // 新增好友
        router.post("/api/friend").consumes("application/json").handler(this::addFriend);

        // 查詢 使用者的好友列表
        router.get("/api/friends").handler(this::queryFriends);

        // 查詢 私密使用者聊天記錄
        router.get("/api/message").handler(this::queryMessage);

        // 查詢當前登入使用者數
        router.get("/api/online").handler(this::queryUsersCount);

        // 查詢所有註冊使用者
        router.get("/api/users").handler(this::queryUsers);

        // 新建群組
        router.post("/api/group").consumes("application/json").handler(this::createGroup);

        // 查詢所有 Map (測試方便觀察資料)
        router.get("/maps").handler(this::queryAll);

        vertx.createHttpServer().requestHandler(router::accept).listen(port, server -> {
          if (server.succeeded()){
            log.info("Http服務啟動成功");
            future.complete();
          } else {
            server.cause().printStackTrace();
            log.error("Http服務啟動失敗," + server.cause().getMessage());
            future.fail(server.cause());
          }
        });

        vertx.exceptionHandler(ex -> {
          ex.printStackTrace();
          log.error("異常處理器: " + ex.getMessage());
        });

      } else {
        future.fail(init.cause());
      }
    });
  }

  private void queryAll(RoutingContext ctx) {
    JsonObject j = new JsonObject();
    j.put("userMapCache", userMapCache);
    j.put("userNameAndIdMapCache", userNameAndIdMapCache);
    j.put("friendMapCache", friendMapCache);
    j.put("onlineMapCache", onlineMapCache);
    j.put("messageMapCache", messageMapCache);
    j.put("groupMapCache", groupMapCache);
    j.put("messageGroupMapCache", messageGroupMapCache);
    resp(ctx.request(), j);
  }

  private void createGroup(RoutingContext ctx) {
    JsonObject bodyAsJson = ctx.getBodyAsJson();
    String groupName = bodyAsJson.getString("groupName");
    String creater = bodyAsJson.getString("creater");
    JsonArray members = bodyAsJson.getJsonArray("members");

    if (StringUtil.isNullOrEmpty(groupName)
            || StringUtil.isNullOrEmpty(creater)
            || members == null
            || members.size() < 2) {
      respError(ctx.request(), 404, null);
      return;
    }
    Object o = userNameAndIdMapCache.get(creater);
    long count = members.stream()
            .filter(memberId -> userNameAndIdMapCache.get(memberId) != null)
            .count();
    boolean contains = members.contains(creater); // 要求成員中必須包含建立人
    if (o == null || count != members.size() || !contains) {
      respError(ctx.request(), 404, null);
      return;
    }
    // TODO 檢查群組中的人和建立人是否是好友關係
    // TODO 建立人是否線上及細節群組中的人不線上如何處理,線上如何處理

    String groupId = Utils.uuid(); // 群組ID
    JsonObject body = new JsonObject();
    body.put("id", groupId);
    body.put("groupName", groupName);
    body.put("creater", creater);
    body.put("createTime", Utils.time());
    body.put("members", members);
    groupMap.put(groupId, body.encode());
    groupMapCache.put(groupId, body);

    resp(ctx.request(), body);
  }

  private void queryMessage(RoutingContext ctx) {
    String senderName = ctx.request().getParam("sender");
    String receiverName = ctx.request().getParam("receiver");
    if (StringUtil.isNullOrEmpty(senderName) || StringUtil.isNullOrEmpty(receiverName)) {
      respError(ctx.request(), 404, null);
      return;
    }
    Object o = onlineMapCache.get(senderName);
    if (o == null) {
      respError(ctx.request(), 500, "請登入後使用");
      return;
    }
    Object o1 = userMapCache.get(receiverName);
    if (o1 == null) {
      respError(ctx.request(), 500, "不存在");
      return;
    }
    JsonObject senderJson = (JsonObject) userMapCache.get(senderName);
    JsonObject receiverJson = (JsonObject) o1;
    Object o2 = friendMapCache.get(senderJson.getString("id"));
    if (o2 == null) {
      respError(ctx.request(), 500, "不存在");
      return;
    }
    JsonArray friends = (JsonArray) o2;
    if (friends.contains(receiverJson.getString("id"))) {

      String senderId = senderJson.getString("id");
      String receiverId = receiverJson.getString("id");
      String msgMapKey = senderId.compareTo(receiverId) < 0 ? senderId+"-"+receiverId : receiverId+"-"+senderId;

      Object msgList = messageMapCache.get(msgMapKey);

      if (msgList == null) {
        resp(ctx.request(), new JsonObject().put("msg", new JsonArray()));
      } else {
        JsonArray msgArr = (JsonArray) msgList;
        List<Object> sortMsgs = msgArr.stream().sorted().collect(Collectors.toList());
        resp(ctx.request(), new JsonObject().put("msg", sortMsgs));
      }
    } else {
      respError(ctx.request(), 500, "不是好友關係");
    }
  }

  private void queryFriends(RoutingContext context) {
    String login = context.request().getParam("login");
    if (StringUtil.isNullOrEmpty(login)) {
      respError(context.request(), 404, "使用者不存在");
      return;
    }
    Object o = onlineMapCache.get(login);
    if (o == null) {
      respError(context.request(), 500, "請先登入");
      return;
    }
    JsonObject userJson = (JsonObject) userMapCache.get(login);
    Object friendsObj = friendMapCache.get(userJson.getString("id"));

    if (friendsObj == null) {
      respError(context.request(), 500, "請至少新增一個好友");
      return;
    }
    log.info("friendsObj --> " + friendsObj.getClass().getName());
    resp(context.request(), new JsonObject().put(login, friendsObj));
  }

  private void queryUsers(RoutingContext ctx) {
    JsonObject data = new JsonObject();
    userMapCache.forEach((k, v) -> {
      data.put(k, v);
    });
    resp(ctx.request(), data);
  }

  private void queryUsersCount(RoutingContext ctx) {
    long count = onlineMapCache.keySet().parallelStream().count();
    resp(ctx.request(), new JsonObject().put("count", count));
  }

  private void logout(RoutingContext ctx) {
    String login = ctx.request().getParam("login");
    if (StringUtil.isNullOrEmpty(login)){
      respError(ctx.request(), 404, null);
      return;
    }
    Object o = userMapCache.get(login);
    if (o == null) {
      respError(ctx.request(), 404, null);
      return;
    }
    Object o1 = onlineMapCache.get(login);
    if (o1 == null) {
      resp(ctx.request(), new JsonObject().put("msg", "成功退出"));
      return;
    } else {
      onlineMapCache.remove(login);
      onlineMap.remove(login);
      JsonObject sendJson = new JsonObject();
      sendJson.put("loginName", login);
      eventBus.send("sweet-logout", sendJson, res -> {
        if (res.succeeded()) {
          JsonObject body = (JsonObject) res.result().body();
          String code = body.getString("code");
          if (code.equals("1")) {
            resp(ctx.request(), new JsonObject().put("msg", "成功退出"));
          } else {
            resp(ctx.request(), new JsonObject().put("msg", "沒有資料"));
          }
        } else {
          respError(ctx.request(), 500, res.cause().getMessage());
        }
      });
    }
  }

  private void login(RoutingContext ctx) {
    JsonObject bodyAsJson = ctx.getBodyAsJson();
    String login = bodyAsJson.getString("login");
    String passwd = bodyAsJson.getString("passwd");
    if (StringUtil.isNullOrEmpty(login) || StringUtil.isNullOrEmpty(passwd)) {
      respError(ctx.request(), 404, null);
      return;
    }
    Object userValue = userMapCache.get(login);
    if (userValue == null) {
      respError(ctx.request(), 404, "使用者名稱或密碼錯誤");
      return;
    }
    JsonObject userJson = (JsonObject) userValue;
    if (userJson.getString("passwd").equals(passwd)) {
      String time = Utils.time();
      onlineMap.put(login, time);
      onlineMapCache.put(login, time);
      resp(ctx.request(), new JsonObject().put("msg", "登入成功"));
    } else {
      respError(ctx.request(), 404, "使用者名稱或密碼錯誤");
    }
  }

  private void addFriend(RoutingContext ctx) {
    JsonObject bodyAsJson = ctx.getBodyAsJson();
    String login = bodyAsJson.getString("login"); // 登入使用者
    String addName = bodyAsJson.getString("add"); // 要新增的好友
    if (StringUtil.isNullOrEmpty(login) || StringUtil.isNullOrEmpty(addName)) {
      respError(ctx.request(), 404, "找不到該使用者");
      return;
    }
    Object o = userMapCache.get(login);
    Object o1 = userMapCache.get(addName);
    if (o == null || o1 == null) {
      respError(ctx.request(), 404, "找不到該使用者");
      return;
    }
    Object o2 = onlineMapCache.get(addName); // 檢查要新增的好友是否線上
    if (o2 == null) {
      respError(ctx.request(), 404, "該使用者不線上");
      return;
    }

    JsonObject loginUser = (JsonObject) o;
    JsonObject addUser = (JsonObject) o1;

    String id = loginUser.getString("id");
    String addUserId = addUser.getString("id");

    Object o3 = friendMapCache.get(id);
    if (o3 == null) {
      JsonArray arr = new JsonArray();
      arr.add(addUserId);
      friendMap.put(id, arr.encode());
      friendMapCache.put(id, arr);
    } else {
      JsonArray arr = (JsonArray) o3;
      if (arr.contains(addUserId)) {
        respError(ctx.request(), 500, "已新增過好友");
        return;
      }
      arr.add(addUserId);
      friendMap.put(id, arr.encode());
      friendMapCache.put(id, arr);
    }
    resp(ctx.request(), new JsonObject().put("msg", "新增成功"));
  }

  private void register(RoutingContext routingContext) {
    JsonObject bodyAsJson = routingContext.getBodyAsJson();
    log.info(bodyAsJson);
    String login = bodyAsJson.getString("login");
    String name = bodyAsJson.getString("name");
    String passwd = bodyAsJson.getString("passwd");
    if (StringUtil.isNullOrEmpty(login)
            || StringUtil.isNullOrEmpty(name)
            || StringUtil.isNullOrEmpty(passwd)) {
      respError(routingContext.request(), 404,null);
      return;
    }
    Object v = userMapCache.get(login);
    if (v != null) {
      respError(routingContext.request(), 405, null);
      return;
    }

    String uuid = Utils.uuid();
    JsonObject obj = new JsonObject()
            .put("id", uuid)
            .put("name", name)
            .put("login", login)
            .put("passwd", passwd)
            .put("createTime", Utils.time());
    userMap.put(login, obj.encode());
    userMapCache.put(login, obj);
    userNameAndIdMap.put(uuid, login);
    userNameAndIdMapCache.put(uuid, login);

    JsonObject copy = obj.copy();
    copy.remove("passwd");
    resp(routingContext.request(), copy);
  }

  private Future<Boolean> initDB() {
    Future<Boolean> initDBFuture = Future.future();
    try {
      db = DBMaker.fileDB(filePath +"sweet-im.db").closeOnJvmShutdown().make();

      // 儲存註冊使用者資訊
      userMap = db.hashMap("user-db", Serializer.STRING, Serializer.STRING).createOrOpen();
      userMapCache = vertx.sharedData().getLocalMap("user-db-cache");
      copyJsonObj(userMap, userMapCache); // 把檔案中的使用者資料快取到 記憶體中

      // 儲存好友關係
      friendMap = db.hashMap("friend-db", Serializer.STRING, Serializer.STRING).createOrOpen();
      friendMapCache = vertx.sharedData().getLocalMap("friend-db-cache");
      copyJsonArray(friendMap, friendMapCache);

      // 當前登入使用者
      onlineMap = db.hashMap("online-db", Serializer.STRING, Serializer.STRING).createOrOpen();
      onlineMapCache = vertx.sharedData().getLocalMap("online-db-cache");
      copyString(onlineMap, onlineMapCache);

      // 私密聊天的訊息記錄
      messageMapCache = vertx.sharedData().getLocalMap("message-db-cache");

      // 儲存群組聊天記錄
      messageGroupMapCache = vertx.sharedData().getLocalMap("message-group-db-cache");

      // 群組關係
      groupMap = db.hashMap("group-db", Serializer.STRING, Serializer.STRING).createOrOpen();
      groupMapCache = vertx.sharedData().getLocalMap("group-db-cache");
      copyJsonObj(groupMap, groupMapCache);

      // 使用者名稱 - 使用者id
      userNameAndIdMap = db.hashMap("username-id-db", Serializer.STRING, Serializer.STRING).createOrOpen();
      userNameAndIdMapCache = vertx.sharedData().getLocalMap("username-id-db-cache");
      copyString(userNameAndIdMap, userNameAndIdMapCache);

      initDBFuture.complete(true);
    } catch (Exception e) {
      e.printStackTrace();
      initDBFuture.fail(e.getCause());
    }
    return initDBFuture;
  }

  private void copyJsonObj(Map<String, String> sourceMap, LocalMap<String, Object> targetMap) {
    sourceMap.forEach((k, v) -> targetMap.put(k, new JsonObject(v)));
  }
  private void copyJsonArray(Map<String, String> sourceMap, LocalMap<String, Object> targetMap) {
    sourceMap.forEach((k, v) -> targetMap.put(k, new JsonArray(v)));
  }
  private void copyString(Map<String, String> sourceMap, LocalMap<String, Object> targetMap) {
    sourceMap.forEach((k, v) -> targetMap.put(k, v));
  }

  private static void resp(HttpServerRequest request, JsonObject ret) {
    request.response()
            .putHeader("content-type", "application/json;charset=utf-8")
            .putHeader("Access-Control-Allow-Origin", "*")
            .putHeader("Access-Control-Allow-Credentials", "true")
            .putHeader("Content-Disposition", "attachment")
            .end(Json.encodePrettily(ret));
  }

  private static void respError(HttpServerRequest request, int code, String error) {
    request.response()
            .putHeader("content-type", "application/json;charset=utf-8")
            .putHeader("Access-Control-Allow-Origin", "*")
            .putHeader("Access-Control-Allow-Credentials", "true")
            .putHeader("Content-Disposition", "attachment")
            .setStatusCode(code)
            .end(Json.encodePrettily(new JsonObject().put("error", error)));
  }

  @Override
  public void stop(Future<Void> stopFuture) throws Exception {
    userMap.close();
    friendMap.close();
    onlineMap.close();
    groupMap.close();
    userNameAndIdMap.close();

    if (!db.isClosed()) {
      db.commit();
      db.close();
    }
    stopFuture.complete();
  }
}
複製程式碼

SocketVerticle.java

package com.xiaoniu.im.socket;

import com.xiaoniu.im.utils.Utils;
import io.netty.util.internal.StringUtil;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.http.ServerWebSocket;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;
import io.vertx.core.shareddata.LocalMap;
import org.mapdb.DB;
import org.mapdb.DBMaker;
import org.mapdb.HTreeMap;
import org.mapdb.Serializer;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * socket 通訊模組 主要處理聊天
 * Created by sweet on 2017/9/26.
 */
public class SocketVerticle extends AbstractVerticle {
  private static final Logger log = LoggerFactory.getLogger(SocketVerticle.class);

  private static final String filePath = "C:\\xiaoniu_doc\\vertx\\sweet-im\\";
  private static final Integer port = 8081;

  private DB db;

  private HTreeMap<String, String> messageMap; // 儲存使用者私密聊天記錄
  private LocalMap<String, Object> messageMapCache;  // 儲存使用者私密聊天記錄 (記憶體共享版)

  private HTreeMap<String, String> messageGroupMap; // 儲存群組聊天記錄
  private LocalMap<String, Object> messageGroupMapCache;  // 儲存群組聊天記錄 (記憶體共享版)

  private LocalMap<String, Object> userMapCache; // 儲存註冊使用者資訊(記憶體共享版)

  private LocalMap<String, Object> friendMapCache; // 儲存好友關係(記憶體共享版)

  private LocalMap<String, Object> onlineMapCache; // 當前登入使用者 (記憶體共享版)

  private LocalMap<String, Object> userNameAndIdMapCache; // 使用者id - 使用者名稱 方便快速查詢 (記憶體版)

  private LocalMap<String, Object> groupMapCache; // 群組關係 (記憶體共享版)

  private Map<String, ServerWebSocket> socketMap; // 每個使用者對應一個 socket連線

  private EventBus eventBus; // 處理使用者退出,關閉並刪除 socket 引用

  @Override
  public void start(Future<Void> future) throws Exception {
    eventBus = vertx.eventBus();
    initDB().setHandler(res -> {
      vertx.createHttpServer().websocketHandler(serverWebSocket -> {

        String path = serverWebSocket.path();
        String query = serverWebSocket.query();
        log.info("path: " + path + ", socket id: " + serverWebSocket.textHandlerID());
        log.info("query: " + query);

        if (!"/socket".equals(path) || StringUtil.isNullOrEmpty(query) || !query.startsWith("id=")) {
          serverWebSocket.reject();
          serverWebSocket.close();
          return;
        }
        String userId = query.substring(3, query.length()); // 當前登入使用者的ID
        if (StringUtil.isNullOrEmpty(userId)) {
          serverWebSocket.reject();
          serverWebSocket.close();
          return;
        }

        Object o = userNameAndIdMapCache.get(userId); // 判斷使用者是否是註冊使用者 o = 使用者name
        if (o == null || StringUtil.isNullOrEmpty(o.toString())) {
          serverWebSocket.reject();
          serverWebSocket.close();
          return;
        }

        Object o1 = onlineMapCache.get(o.toString()); // 判斷使用者是否線上
        if (o1 == null) {
          serverWebSocket.reject();
          serverWebSocket.close();
          return;
        }
        // TODO 使用者重新整理如何處理 Socket連線 ?
        put(socketMap, userId, serverWebSocket);
        serverWebSocket.handler(buffer -> {
          System.out.println("-------------Message------------");
          System.out.println("收到的buffer : " + buffer);
          System.out.println("-------------Message------------");
          JsonObject jsonObject = buffer.toJsonObject();
          // 好友之間聊天 所需欄位
          String to = jsonObject.getString("to"); // 接受人的名字
          String from = jsonObject.getString("from"); // 傳送人的名字
          String msg = jsonObject.getString("msg");

          // 群組之間聊天 所需欄位
          String groupId = jsonObject.getString("groupId");
          String fromForGroup = jsonObject.getString("from");

          if (StringUtil.isNullOrEmpty(from) || !onlineMapCache.containsKey(from) || !from.equals(o)) {
            serverWebSocket.writeTextMessage("欄位不能為空"); // 缺少欄位 和 傳送人沒有登入
            return;
          }

          // 好友之間聊天 start ===========================================
          // 好友之間聊天 TODO 全部暫定只能傳送線上使用者
          if (!StringUtil.isNullOrEmpty(to) && msg != null && onlineMapCache.containsKey(to)) {
            Object o4 = friendMapCache.get(userId);
            if (o4 == null) {
              serverWebSocket.writeTextMessage("你還沒有好友"); // 缺少欄位 和 傳送人沒有登入
              return;
            }
            JsonArray fromFriends = (JsonArray) o4; // 傳送人的好友

            Object o2 = userMapCache.get(to);
            JsonObject toUserJson = (JsonObject) o2;

            Object o5 = friendMapCache.get(toUserJson.getString("id"));
            if (o5 == null) {
              serverWebSocket.writeTextMessage("你倆不是好友關係"); // 缺少欄位 和 傳送人沒有登入
              return;
            }
            JsonArray toFriends = (JsonArray) o5;
            if (fromFriends.contains(toUserJson.getString("id")) && toFriends.contains(userId)) { // 確定雙方好友關係
              String toUserId = toUserJson.getString("id"); // 接收人ID
              ServerWebSocket toUserServerWebSocket = socketMap.get(toUserId); // TODO 暫時不做判斷 是否線上,不線上如何處理
              String msgMapKey = toUserId.compareTo(userId) < 0 ? toUserId+"-"+userId : userId+"-"+toUserId;
              String msgValue = Utils.time()+"-"+from+"-"+msg;
              if (messageMapCache.containsKey(msgMapKey)) {
                Object o3 = messageMapCache.get(msgMapKey);
                JsonArray msgArr = (JsonArray) o3;
                msgArr.add(msgValue);
                messageMap.put(msgMapKey, msgArr.encode());
                messageMapCache.put(msgMapKey, msgArr);
              } else {
                JsonArray jsonArray = new JsonArray();
                jsonArray.add(msgValue);
                messageMap.put(msgMapKey, jsonArray.encode());
                messageMapCache.put(msgMapKey, jsonArray);
              }
              toUserServerWebSocket.writeTextMessage(msg);
              return;
            } else {
              serverWebSocket.writeTextMessage("你倆不是好友關係2"); // 缺少欄位 和 傳送人沒有登入
              return;
            }// 好友之間聊天 end ===========================================
            // 群組之間聊天 start ===========================================
          } else if (!StringUtil.isNullOrEmpty(groupId)
                  && !StringUtil.isNullOrEmpty(fromForGroup)
                  && msg != null && groupMapCache.get(groupId) != null) {
            log.info("==================群組聊天==================");
            Object o2 = groupMapCache.get(groupId);
            JsonObject groupJsonObj = (JsonObject) o2;
            if (onlineMapCache.containsKey(fromForGroup)) {
              JsonArray members = groupJsonObj.getJsonArray("members"); // 群組成員
              JsonObject fromUserJson = (JsonObject) userMapCache.get(fromForGroup);
              if (members.contains(fromUserJson.getString("id"))) {
                sendGroupMessage(groupId, fromUserJson.getString("login"),
                        fromUserJson.getString("id"), members, msg); // 給全部群組成員傳送訊息
              } else {
                serverWebSocket.writeTextMessage("你不是群組成員");
              }
              return;
            } else {
              serverWebSocket.writeTextMessage("請先登入");
              return;
            } // 群組之間聊天 end ===========================================
          } else {
            // 其他不符合情況
            serverWebSocket.writeTextMessage("欄位不能為空!!!");
            return;
          }
        });

        // 異常處理
        serverWebSocket.exceptionHandler(ex -> {
          ex.printStackTrace();
          log.error(ex.getMessage());
        });

      }).listen(port, server -> {
        if (server.succeeded()) {
          log.info("socket server 啟動成功 埠8081");
          future.complete();
        } else {
          log.error(server.cause().getMessage());
          server.cause().printStackTrace();
          future.fail(server.cause());
        }
      });

      vertx.setPeriodic(10000, timer -> {
        System.out.println("------------Socket Map Start---------");
        socketMap.forEach((k, v) -> System.out.println("k: " + k + ", v: " + v));
        System.out.println("------------Socket Map End  ---------");
      });
////
//      vertx.setPeriodic(10000, timer -> {
//        System.out.println("----------Message Map Start-----------");
//        messageMapCache.forEach((k, v) -> System.out.println("k: " + k + ", v: " + v));
//        System.out.println("----------Message Map End  -----------");
//      });

      // 接受使用者退出訊息,然後處理 SocketMap中的引用
      eventBus.consumer("sweet-logout", msg -> {
        JsonObject body = (JsonObject) msg.body();
        String loginName = body.getString("loginName");
        ServerWebSocket serverWebSocket = socketMap.get(loginName);
        JsonObject replyMsg = new JsonObject();
        if (serverWebSocket != null) {
          serverWebSocket.close();
          socketMap.remove(loginName);
          replyMsg.put("code", "1"); // code 1 退出成功
          msg.reply(replyMsg);
        } else {
          replyMsg.put("code", "0"); // code 0 沒有資料
          msg.reply(replyMsg);
        }
      });
    });
  }

  /**
   * @param groupId 群組id
   * @param fromUserLogin 傳送人login
   * @param fromUserId 傳送人Id
   * @param members 群組成員 id
   * @param msg 訊息
   */
  private void sendGroupMessage(String groupId, String fromUserLogin, String fromUserId,
                                JsonArray members, String msg) {
    String msgValue = Utils.time()+"-"+fromUserLogin+"-"+msg; // 要儲存的聊天記錄
    JsonArray copy = members.copy();
    copy.remove(fromUserId);
    if (messageGroupMapCache.containsKey(groupId)) {
      Object o = messageGroupMapCache.get(groupId);
      JsonArray messageGroupArr = (JsonArray) o; // 群組聊天記錄
      messageGroupArr.add(msgValue);
      messageGroupMap.put(groupId, messageGroupArr.encode());
      messageGroupMapCache.put(groupId, messageGroupArr);
    } else {
      JsonArray msgArr = new JsonArray();
      msgArr.add(msgValue);
      messageGroupMap.put(groupId, msgArr.encode());
      messageGroupMapCache.put(groupId, msgArr);
    }

    copy.forEach(memberId -> {
      System.out.println("群發訊息 " + Utils.time());
      ServerWebSocket serverWebSocket = socketMap.get(memberId); // TODO 沒有處理不線上的情況,方便測試預設都線上
      serverWebSocket.writeTextMessage(msg);
    });
  }

  private void put(Map<String, ServerWebSocket> socketMap, String userId, ServerWebSocket serverWebSocket) {
    try {
      if (socketMap.containsKey(userId)) {
        log.error(" ********* 連線存在,清除舊連線 ********* ");
        ServerWebSocket serverWebSocket1 = socketMap.get(userId);
        serverWebSocket1.close();
        socketMap.remove(userId);
      } else {
        socketMap.put(userId, serverWebSocket);
      }
    } catch (Exception e) {
      log.error("異常捕獲, " + e.getMessage());
      socketMap.put(userId, serverWebSocket);
    }
  }

  private Future<Boolean> initDB() {
    Future<Boolean> initDBFuture = Future.future();
    try {
      // 儲存使用者私密聊天記錄
      db = DBMaker.fileDB(filePath + "sweet-msg-im.db").closeOnJvmShutdown().make();
      messageMap = db.hashMap("message-db", Serializer.STRING, Serializer.STRING).createOrOpen();
      messageMapCache = vertx.sharedData().getLocalMap("message-db-cache");
      copyJsonArray(messageMap, messageMapCache);

      // 儲存群組聊天記錄
      messageGroupMap = db.hashMap("message-group-db", Serializer.STRING, Serializer.STRING).createOrOpen();
      messageGroupMapCache = vertx.sharedData().getLocalMap("message-group-db-cache");
      copyJsonArray(messageGroupMap, messageGroupMapCache);

      // 儲存註冊使用者資訊
      userMapCache = vertx.sharedData().getLocalMap("user-db-cache");

      // 儲存好友關係
      friendMapCache = vertx.sharedData().getLocalMap("friend-db-cache");

      // 當前登入使用者
      onlineMapCache = vertx.sharedData().getLocalMap("online-db-cache");

      // 使用者名稱 - 使用者id
      userNameAndIdMapCache = vertx.sharedData().getLocalMap("username-id-db-cache");

      // 群組關係
      groupMapCache = vertx.sharedData().getLocalMap("group-db-cache");

      socketMap = new ConcurrentHashMap<>();

      initDBFuture.complete(true);
    } catch (Exception e) {
      e.printStackTrace();
      initDBFuture.fail(e.getCause());
    }
    return initDBFuture;
  }

  @Override
  public void stop(Future<Void> stopFuture) throws Exception {
    messageMap.close();
    messageGroupMap.close();
    if (!db.isClosed()) {
      db.commit();
      db.close();
    }
    stopFuture.complete();
  }

  private void copyJsonArray(Map<String, String> sourceMap, LocalMap<String, Object> targetMap) {
    sourceMap.forEach((k, v) -> targetMap.put(k, new JsonArray(v)));
  }
}
複製程式碼

BootVerticle.java

package com.xiaoniu.im;

import com.xiaoniu.im.rest.HttpVerticle;
import com.xiaoniu.im.socket.SocketVerticle;
import com.xiaoniu.im.utils.Runner;
import io.vertx.core.*;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;

/**
 * Verticle 啟動類
 * Created by sweet on 2017/9/26.
 */
public class BootVerticle extends AbstractVerticle{

  private static final Logger log = LoggerFactory.getLogger(BootVerticle.class);

  public static void main(String[] args) {
    Runner.runExample(BootVerticle.class);
  }

  @Override
  public void start(Future<Void> startFuture) throws Exception {
    Future<String> future = Future.future();
    Future<String> future1 = Future.future();

    vertx.deployVerticle(new HttpVerticle(), future1);
    future1.setHandler(res -> {
      if (res.succeeded()) {
        vertx.deployVerticle(new SocketVerticle(), future);
      } else {
        startFuture.fail(res.cause());
        return;
      }
    });

    future.setHandler(handler -> {
      if (handler.succeeded()) {
        startFuture.complete();
        log.info("全部部署 OK");
      } else {
        startFuture.fail(handler.cause());
      }
    });
  }
}
複製程式碼

使用MapDB儲存,需要修改程式碼裡的檔案儲存路徑,程式碼寫的沒那麼精緻,沒有使用配置檔案,原始碼裡有readme,寫了如何使用 原始碼地址 連結:http://pan.baidu.com/s/1o8ysUQQ 密碼:5crm

相關文章