一套簡單的web即時通信——初版

  前言

  咱們以前已經實現了 WebSocket+Java 私聊、羣聊實例,後面咱們模仿layer彈窗,封裝了一個本身的web彈窗 自定義web彈窗/層:簡易風格的msg與可拖放的dialog,生成博客園文章目錄彈窗,再後來就產生了將二者結合起來的想法,加上咱們以前實現了一套自動生成代碼的jpa究極進化版 SpringBoot系列——Spring-Data-JPA(究極進化版) 自動生成單表基礎增、刪、改、查接口,因而去網上搜索,參考即時通信系統的消息存儲如何建表,看下能不能把咱們以前的東西稍微整合一下,將以前的寫的東西應用起來學以至用,實現一套簡單的web即時通信,一版一版的升級完善。javascript

  初版功能css

  一、實現簡單的登陸/註冊html

  二、將以前的頁面改爲自定義web彈窗的形式,而且完善羣聊、私聊功能java

 

  代碼編寫

  目前建了三個表,SQL以下:mysql

  

/*
 Navicat Premium Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 50528
 Source Host           : localhost:3306
 Source Schema         : test

 Target Server Type    : MySQL
 Target Server Version : 50528
 File Encoding         : 65001

 Date: 09/05/2019 10:09:11
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for ims_friend
-- ----------------------------
DROP TABLE IF EXISTS `ims_friend`;
CREATE TABLE `ims_friend`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
  `user_id` int(11) NULL DEFAULT NULL COMMENT '用戶id',
  `friend_id` int(11) NULL DEFAULT NULL COMMENT '好友id',
  `created_time` datetime NULL DEFAULT NULL COMMENT '建立時間',
  `updata_time` datetime NULL DEFAULT NULL COMMENT '更新時間',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '好友表' ROW_FORMAT = Compact;

-- ----------------------------
-- Table structure for ims_friend_message
-- ----------------------------
DROP TABLE IF EXISTS `ims_friend_message`;
CREATE TABLE `ims_friend_message`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
  `from_user_id` int(11) NULL DEFAULT NULL COMMENT '發消息的人的id',
  `to_user_id` int(11) NULL DEFAULT NULL COMMENT '收消息的人的id',
  `content` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '消息內容',
  `is_read` int(11) NULL DEFAULT NULL COMMENT '是否已讀,1是0否',
  `is_back` int(11) NULL DEFAULT NULL COMMENT '是否撤回,1是0否',
  `created_time` datetime NULL DEFAULT NULL COMMENT '建立時間',
  `updata_time` datetime NULL DEFAULT NULL COMMENT '更新時間',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '好友消息表' ROW_FORMAT = Compact;

-- ----------------------------
-- Table structure for ims_user
-- ----------------------------
DROP TABLE IF EXISTS `ims_user`;
CREATE TABLE `ims_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
  `user_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '賬號',
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密碼',
  `nick_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '暱稱',
  `gender` int(11) NULL DEFAULT 0 COMMENT '性別:0爲男,1爲女',
  `avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '頭像',
  `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '電子郵箱',
  `phone` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手機號碼',
  `sign` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '個性簽名',
  `created_time` datetime NULL DEFAULT NULL COMMENT '建立時間',
  `updata_time` datetime NULL DEFAULT NULL COMMENT '更新時間',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用戶信息表' ROW_FORMAT = Compact;

SET FOREIGN_KEY_CHECKS = 1;
View Code

 

  自動生成代碼,運行main方法執行jquery

package cn.huanzi.ims.util;

import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 自動生成代碼
 */
public class CodeDOM {

    /**
     * 構造參數,出入表名
     */
    private CodeDOM(String tableName) {
        this.tableName = tableName;
        basePackage_ = "cn\\huanzi\\ims\\";
        package_ = basePackage_ + StringUtil.camelCaseName(tableName).toLowerCase() + "\\";
        //System.getProperty("user.dir") 獲取的是項目所在路徑
        basePath = System.getProperty("user.dir") + "\\src\\main\\java\\" + package_;
    }

    /**
     * 數據鏈接相關
     */
    private static final String URL = "jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&characterEncoding=utf-8";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "123456";
    private static final String DRIVERCLASSNAME = "com.mysql.jdbc.Driver";
    /**
     * 表名
     */
    private String tableName;

    /**
     * 基礎路徑
     */
    private String basePackage_;
    private String package_;
    private String basePath;

    /**
     * 建立pojo實體類
     */
    private void createPojo(List<TableInfo> tableInfos) {
        File file = FileUtil.createFile(basePath + "pojo\\" + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + ".java");
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(
                "package " + package_.replaceAll("\\\\", ".") + "pojo;\n" +
                        "\n" +
                        "import lombok.Data;\n" +
                        "import javax.persistence.*;\n" +
                        "import java.io.Serializable;\n" +
                        "import java.util.Date;\n" +
                        "\n" +
                        "@Entity\n" +
                        "@Table(name = \"" + tableName + "\")\n" +
                        "@Data\n" +
                        "public class " + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + " implements Serializable {\n"
        );
        //遍歷設置屬性
        for (TableInfo tableInfo : tableInfos) {
            //主鍵
            if ("PRI".equals(tableInfo.getColumnKey())) {
                stringBuffer.append("    @Id\n");
            }
            //自增
            if ("auto_increment".equals(tableInfo.getExtra())) {
                stringBuffer.append("    @GeneratedValue(strategy= GenerationType.IDENTITY)\n");
            }
            stringBuffer.append("    private " + StringUtil.typeMapping(tableInfo.getDataType()) + " " + StringUtil.camelCaseName(tableInfo.getColumnName()) + ";//" + tableInfo.getColumnComment() + "\n\n");
        }
        stringBuffer.append("}");
        FileUtil.fileWriter(file, stringBuffer);
    }

    /**
     * 建立vo類
     */
    private void createVo(List<TableInfo> tableInfos) {
        File file = FileUtil.createFile(basePath + "vo\\" + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Vo.java");
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(
                "package " + package_.replaceAll("\\\\", ".") + "vo;\n" +
                        "\n" +
                        "import lombok.Data;\n" +
                        "import java.io.Serializable;\n" +
                        "import java.util.Date;\n" +
                        "\n" +
                        "@Data\n" +
                        "public class " + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Vo implements Serializable {\n"
        );
        //遍歷設置屬性
        for (TableInfo tableInfo : tableInfos) {
            stringBuffer.append("    private " + StringUtil.typeMapping(tableInfo.getDataType()) + " " + StringUtil.camelCaseName(tableInfo.getColumnName()) + ";//" + tableInfo.getColumnComment() + "\n\n");
        }
        stringBuffer.append("}");
        FileUtil.fileWriter(file, stringBuffer);
    }

    /**
     * 建立repository類
     */
    private void createRepository(List<TableInfo> tableInfos) {
        File file = FileUtil.createFile(basePath + "repository\\" + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Repository.java");
        StringBuffer stringBuffer = new StringBuffer();
        String t = "String";
        //遍歷屬性
        for (TableInfo tableInfo : tableInfos) {
            //主鍵
            if ("PRI".equals(tableInfo.getColumnKey())) {
                t = StringUtil.typeMapping(tableInfo.getDataType());
            }
        }
        stringBuffer.append(
                "package " + package_.replaceAll("\\\\", ".") + "repository;\n" +
                        "\n" +
                        "import " + basePackage_.replaceAll("\\\\", ".") + "common.repository.*;\n" +
                        "import " + package_.replaceAll("\\\\", ".") + "pojo." + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + ";\n" +
                        "import org.springframework.stereotype.Repository;\n" +
                        "\n" +
                        "@Repository\n" +
                        "public interface " + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Repository extends CommonRepository<" + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + ", " + t + "> {"
        );
        stringBuffer.append("\n");
        stringBuffer.append("}");
        FileUtil.fileWriter(file, stringBuffer);
    }

    /**
     * 建立service類
     */
    private void createService(List<TableInfo> tableInfos) {
        File file = FileUtil.createFile(basePath + "service\\" + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Service.java");
        StringBuffer stringBuffer = new StringBuffer();
        String t = "String";
        //遍歷屬性
        for (TableInfo tableInfo : tableInfos) {
            //主鍵
            if ("PRI".equals(tableInfo.getColumnKey())) {
                t = StringUtil.typeMapping(tableInfo.getDataType());
            }
        }
        stringBuffer.append(
                "package " + package_.replaceAll("\\\\", ".") + "service;\n" +
                        "\n" +
                        "import " + basePackage_.replaceAll("\\\\", ".") + "common.service.*;\n" +
                        "import " + package_.replaceAll("\\\\", ".") + "pojo." + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + ";\n" +
                        "import " + package_.replaceAll("\\\\", ".") + "vo." + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Vo;\n" +
                        "\n" +
                        "public interface " + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Service extends CommonService<" + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Vo, " + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + ", " + t + "> {"
        );
        stringBuffer.append("\n");
        stringBuffer.append("}");
        FileUtil.fileWriter(file, stringBuffer);

        //Impl
        File file1 = FileUtil.createFile(basePath + "service\\" + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "ServiceImpl.java");
        StringBuffer stringBuffer1 = new StringBuffer();
        stringBuffer1.append(
                "package " + package_.replaceAll("\\\\", ".") + "service;\n" +
                        "\n" +
                        "import " + basePackage_.replaceAll("\\\\", ".") + "common.service.*;\n" +
                        "import " + package_.replaceAll("\\\\", ".") + "pojo." + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + ";\n" +
                        "import " + package_.replaceAll("\\\\", ".") + "vo." + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Vo;\n" +
                        "import " + package_.replaceAll("\\\\", ".") + "repository." + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Repository;\n" +
                        "import org.springframework.beans.factory.annotation.Autowired;\n" +
                        "import org.springframework.stereotype.Service;\n" +
                        "import org.springframework.transaction.annotation.Transactional;\n" +
                        "import javax.persistence.EntityManager;\n" +
                        "import javax.persistence.PersistenceContext;\n" +
                        "\n" +
                        "@Service\n" +
                        "@Transactional\n" +
                        "public class " + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "ServiceImpl extends CommonServiceImpl<" + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Vo, " + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + ", " + t + "> implements " + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Service{"
        );
        stringBuffer1.append("\n\n");
        stringBuffer1.append(
                "    @PersistenceContext\n" +
                        "    private EntityManager em;\n");

        stringBuffer1.append("" +
                "    @Autowired\n" +
                "    private " + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Repository " + StringUtil.camelCaseName(tableName) + "Repository;\n");
        stringBuffer1.append("}");
        FileUtil.fileWriter(file1, stringBuffer1);
    }

    /**
     * 建立controller類
     */
    private void createController(List<TableInfo> tableInfos) {
        File file = FileUtil.createFile(basePath + "controller\\" + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Controller.java");
        StringBuffer stringBuffer = new StringBuffer();
        String t = "String";
        //遍歷屬性
        for (TableInfo tableInfo : tableInfos) {
            //主鍵
            if ("PRI".equals(tableInfo.getColumnKey())) {
                t = StringUtil.typeMapping(tableInfo.getDataType());
            }
        }
        stringBuffer.append(
                "package " + package_.replaceAll("\\\\", ".") + "controller;\n" +
                        "\n" +
                        "import " + basePackage_.replaceAll("\\\\", ".") + "common.controller.*;\n" +
                        "import " + package_.replaceAll("\\\\", ".") + "pojo." + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + ";\n" +
                        "import " + package_.replaceAll("\\\\", ".") + "vo." + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Vo;\n" +
                        "import " + package_.replaceAll("\\\\", ".") + "service." + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Service;\n" +
                        "import org.springframework.beans.factory.annotation.Autowired;\n" +
                        "import org.springframework.web.bind.annotation.*;\n" +
                        "\n" +
                        "@RestController\n" +
                        "@RequestMapping(\"/" + StringUtil.camelCaseName(tableName) + "/\")\n" +
                        "public class " + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Controller extends CommonController<" + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Vo, " + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + ", " + t + "> {"
        );
        stringBuffer.append("\n");
        stringBuffer.append("" +
                "    @Autowired\n" +
                "    private " + StringUtil.captureName(StringUtil.camelCaseName(tableName)) + "Service " + StringUtil.camelCaseName(tableName) + "Service;\n");
        stringBuffer.append("}");
        FileUtil.fileWriter(file, stringBuffer);
    }


    /**
     * 獲取表結構信息
     *
     * @return list
     */
    private List<TableInfo> getTableInfo() {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        ArrayList<TableInfo> list = new ArrayList<>();
        try {
            conn = DBConnectionUtil.getConnection();
            String sql = "select column_name,data_type,column_comment,column_key,extra from information_schema.columns where table_name=?";
            ps = conn.prepareStatement(sql);
            ps.setString(1, tableName);
            rs = ps.executeQuery();
            while (rs.next()) {
                TableInfo tableInfo = new TableInfo();
                //列名,所有轉爲小寫
                tableInfo.setColumnName(rs.getString("column_name").toLowerCase());
                //列類型
                tableInfo.setDataType(rs.getString("data_type"));
                //列註釋
                tableInfo.setColumnComment(rs.getString("column_comment"));
                //主鍵
                tableInfo.setColumnKey(rs.getString("column_key"));
                //主鍵類型
                tableInfo.setExtra(rs.getString("extra"));
                list.add(tableInfo);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            assert rs != null;
            DBConnectionUtil.close(conn, ps, rs);
        }
        return list;
    }

    /**
     * file工具類
     */
    private static class FileUtil {
        /**
         * 建立文件
         *
         * @param pathNameAndFileName 路徑跟文件名
         * @return File對象
         */
        private static File createFile(String pathNameAndFileName) {
            File file = new File(pathNameAndFileName);
            try {
                //獲取父目錄
                File fileParent = file.getParentFile();
                if (!fileParent.exists()) {
                    fileParent.mkdirs();
                }
                //建立文件
                if (!file.exists()) {
                    file.createNewFile();
                }
            } catch (Exception e) {
                file = null;
                System.err.println("新建文件操做出錯");
                e.printStackTrace();
            }
            return file;
        }

        /**
         * 字符流寫入文件
         *
         * @param file         file對象
         * @param stringBuffer 要寫入的數據
         */
        private static void fileWriter(File file, StringBuffer stringBuffer) {
            //字符流
            try {
                FileWriter resultFile = new FileWriter(file, true);//true,則追加寫入 false,則覆蓋寫入
                PrintWriter myFile = new PrintWriter(resultFile);
                //寫入
                myFile.println(stringBuffer.toString());

                myFile.close();
                resultFile.close();
            } catch (Exception e) {
                System.err.println("寫入操做出錯");
                e.printStackTrace();
            }
        }
    }

    /**
     * 字符串處理工具類
     */
    private static class StringUtil {
        /**
         * 數據庫類型->JAVA類型
         *
         * @param dbType 數據庫類型
         * @return JAVA類型
         */
        private static String typeMapping(String dbType) {
            String javaType = "";
            if ("int|integer".contains(dbType)) {
                javaType = "Integer";
            } else if ("float|double|decimal|real".contains(dbType)) {
                javaType = "Double";
            } else if ("date|time|datetime|timestamp".contains(dbType)) {
                javaType = "Date";
            } else {
                javaType = "String";
            }
            return javaType;
        }

        /**
         * 駝峯轉換爲下劃線
         */
        public static String underscoreName(String camelCaseName) {
            StringBuilder result = new StringBuilder();
            if (camelCaseName != null && camelCaseName.length() > 0) {
                result.append(camelCaseName.substring(0, 1).toLowerCase());
                for (int i = 1; i < camelCaseName.length(); i++) {
                    char ch = camelCaseName.charAt(i);
                    if (Character.isUpperCase(ch)) {
                        result.append("_");
                        result.append(Character.toLowerCase(ch));
                    } else {
                        result.append(ch);
                    }
                }
            }
            return result.toString();
        }

        /**
         * 首字母大寫
         */
        public static String captureName(String name) {
            char[] cs = name.toCharArray();
            cs[0] -= 32;
            return String.valueOf(cs);

        }

        /**
         * 下劃線轉換爲駝峯
         */
        public static String camelCaseName(String underscoreName) {
            StringBuilder result = new StringBuilder();
            if (underscoreName != null && underscoreName.length() > 0) {
                boolean flag = false;
                for (int i = 0; i < underscoreName.length(); i++) {
                    char ch = underscoreName.charAt(i);
                    if ("_".charAt(0) == ch) {
                        flag = true;
                    } else {
                        if (flag) {
                            result.append(Character.toUpperCase(ch));
                            flag = false;
                        } else {
                            result.append(ch);
                        }
                    }
                }
            }
            return result.toString();
        }
    }

    /**
     * JDBC鏈接數據庫工具類
     */
    private static class DBConnectionUtil {

        {
            // 一、加載驅動
            try {
                Class.forName(DRIVERCLASSNAME);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }

        /**
         * 返回一個Connection鏈接
         *
         * @return
         */
        public static Connection getConnection() {
            Connection conn = null;
            // 二、鏈接數據庫
            try {
                conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return conn;
        }

        /**
         * 關閉Connection,Statement鏈接
         *
         * @param conn
         * @param stmt
         */
        public static void close(Connection conn, Statement stmt) {
            try {
                conn.close();
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        /**
         * 關閉Connection,Statement,ResultSet鏈接
         *
         * @param conn
         * @param stmt
         * @param rs
         */
        public static void close(Connection conn, Statement stmt, ResultSet rs) {
            try {
                close(conn, stmt);
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 表結構行信息實體類
     */
    private class TableInfo {
        private String columnName;
        private String dataType;
        private String columnComment;
        private String columnKey;
        private String extra;

        TableInfo() {
        }

        String getColumnName() {
            return columnName;
        }

        void setColumnName(String columnName) {
            this.columnName = columnName;
        }

        String getDataType() {
            return dataType;
        }

        void setDataType(String dataType) {
            this.dataType = dataType;
        }

        String getColumnComment() {
            return columnComment;
        }

        void setColumnComment(String columnComment) {
            this.columnComment = columnComment;
        }

        String getColumnKey() {
            return columnKey;
        }

        void setColumnKey(String columnKey) {
            this.columnKey = columnKey;
        }

        String getExtra() {
            return extra;
        }

        void setExtra(String extra) {
            this.extra = extra;
        }
    }

    /**
     * 快速建立,供外部調用,調用以前先設置一下項目的基礎路徑
     */
    private String create() {
        List<TableInfo> tableInfo = getTableInfo();
        createPojo(tableInfo);
        createVo(tableInfo);
        createRepository(tableInfo);
        createService(tableInfo);
        createController(tableInfo);
        return tableName + " 後臺代碼生成完畢!";
    }

    public static void main(String[] args) {
        String[] tables = {"ims_user", "ims_friend", "ims_friend_message"};
        for (int i = 0; i < tables.length; i++) {
            String msg = new CodeDOM(tables[i]).create();
            System.out.println(msg);
        }
    }
}
CodeDOM.java

 

  工程結構git

  工程是一個springboot項目,跟咱們以前同樣使用lombok、thymeleaf等github

  tip.js、tip.css放在static的js、css裏面web

 

  咱們在ims_user表生成的controller、service層新增登陸、登出等幾個接口ajax

@RestController
@RequestMapping("/imsUser/")
public class ImsUserController extends CommonController<ImsUserVo, ImsUser, Integer> {
    @Autowired
    private ImsUserService imsUserService;

    /**
     * 跳轉登陸、註冊頁面
     */
    @RequestMapping("loginPage.html")
    public ModelAndView loginPage() {
        return new ModelAndView("login.html");
    }

    /**
     * 跳轉聊天頁面
     */
    @RequestMapping("socketChart/{username}.html")
    public ModelAndView socketChartPage(@PathVariable String username) {
        return new ModelAndView("socketChart.html","username",username);
    }

    /**
     * 登陸
     */
    @PostMapping("login")
    public Result<ImsUserVo> login(ImsUserVo userVo) {
        //加密後再去對比密文
        userVo.setPassword(MD5Util.getMD5(userVo.getPassword()));
        Result<List<ImsUserVo>> result = list(userVo);
        if(result.isFlag() && result.getData().size() > 0){
            ImsUserVo imsUserVo = result.getData().get(0);
            //置空隱私信息
            imsUserVo.setPassword(null);

            //add WebSocketServer.loginList
            WebSocketServer.loginList.add(imsUserVo.getUserName());
            return Result.of(imsUserVo);
        }else{
            return Result.of(null,false,"帳號或密碼錯誤!");
        }
    }

    /**
     * 登出
     */
    @RequestMapping("logout/{username}")
    public String loginOut(HttpServletRequest request, @PathVariable String username) {
        new WebSocketServer().deleteUserByUsername(username);
        return "退出成功!";
    }

    /**
     * 獲取在線用戶
     */
    @PostMapping("getOnlineList")
    private List<String> getOnlineList(String username) {
        List<String> list = new ArrayList<String>();
        //遍歷webSocketMap
        for (Map.Entry<String, Session> entry : WebSocketServer.getSessionMap().entrySet()) {
            if (!entry.getKey().equals(username)) {
                list.add(entry.getKey());
            }
        }
        return list;
    }
}
ImsUserController.java
@Service
@Transactional
public class ImsUserServiceImpl extends CommonServiceImpl<ImsUserVo, ImsUser, Integer> implements ImsUserService{

    @PersistenceContext
    private EntityManager em;
    @Autowired
    private ImsUserRepository imsUserRepository;

    @Override
    public Result<ImsUserVo> save(ImsUserVo entityVo) {
        //先查詢是否已經存在相同帳號
        ImsUserVo imsUserVo = new ImsUserVo();
        imsUserVo.setUserName(entityVo.getUserName());
        if(list(imsUserVo).getData().size() > 0){
            return Result.of(null,false,"帳號已存在!");
        }
        //存儲密文
        entityVo.setPassword(MD5Util.getMD5(entityVo.getPassword()));
        return super.save(entityVo);
    }
}
ImsUserServiceImpl.java

 

  WebSocketServer也有優化調整

package cn.huanzi.ims.socket;

import cn.huanzi.ims.imsuser.service.ImsUserService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 * WebSocket服務
 */
@RestController
@RequestMapping("/websocket")
@ServerEndpoint(value = "/websocket/{username}", configurator = MyEndpointConfigure.class)
public class WebSocketServer {

    /**
     * 在線人數
     */
    private static int onlineCount = 0;

    /**
     * 在線用戶的Map集合,key:用戶名,value:Session對象
     */
    private static Map<String, Session> sessionMap = new HashMap<String, Session>();

    /**
     * 登陸用戶集合
     */
    public static List<String> loginList = new ArrayList<>();

    public static Map<String, Session> getSessionMap(){
        return sessionMap;
    }

    /**
     * 注入其餘類(換成本身想注入的對象)
     */
    private ImsUserService imsUserService;

    /**
     * 鏈接創建成功調用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        //在webSocketMap新增上線用戶
        sessionMap.put(username, session);

        //在線人數加加
        WebSocketServer.onlineCount++;

        //通知除了本身以外的全部人
        sendOnlineCount(username, "{'type':'onlineCount','onlineCount':" + WebSocketServer.onlineCount + ",username:'" + username + "'}");
    }

    /**
     * 鏈接關閉調用的方法
     */
    @OnClose
    public void onClose(Session session) {
        //下線用戶名
        String logoutUserName = "";

        //從webSocketMap刪除下線用戶
        for (Entry<String, Session> entry : sessionMap.entrySet()) {
            if (entry.getValue() == session) {
                sessionMap.remove(entry.getKey());
                logoutUserName = entry.getKey();
                break;
            }
        }
        deleteUserByUsername(logoutUserName);
    }

    /**
     * 服務器接收到客戶端消息時調用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        try {
            //JSON字符串轉 HashMap
            HashMap hashMap = new ObjectMapper().readValue(message, HashMap.class);

            //消息類型
            String type = (String) hashMap.get("type");

            //來源用戶
            Map srcUser = (Map) hashMap.get("srcUser");

            //目標用戶
            Map tarUser = (Map) hashMap.get("tarUser");

            //若是點擊的是本身,那就是羣聊
            if (srcUser.get("username").equals(tarUser.get("username"))) {
                //羣聊
                groupChat(session,hashMap);
            } else {
                //私聊
                privateChat(session, tarUser, hashMap);
            }

            //後期要作消息持久化

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 發生錯誤時調用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        error.printStackTrace();
    }

    /**
     * 通知除了本身以外的全部人
     */
    private void sendOnlineCount(String username, String message) {
        for (Entry<String, Session> entry : sessionMap.entrySet()) {
            try {
                if (entry.getKey() != username) {
                    entry.getValue().getBasicRemote().sendText(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 私聊
     */
    private void privateChat(Session session, Map tarUser, HashMap hashMap) throws IOException {
        //獲取目標用戶的session
        Session tarUserSession = sessionMap.get(tarUser.get("username"));

        //若是不在線則發送「對方不在線」回來源用戶
        if (tarUserSession == null) {
            session.getBasicRemote().sendText("{\"type\":\"0\",\"message\":\"對方不在線\"}");
        } else {
            hashMap.put("type", "1");
            tarUserSession.getBasicRemote().sendText(new ObjectMapper().writeValueAsString(hashMap));
        }
    }

    /**
     * 羣聊
     */
    private void groupChat(Session session, HashMap hashMap) throws IOException {
        for (Entry<String, Session> entry : sessionMap.entrySet()) {
            //本身就不用再發送消息了
            if (entry.getValue() != session) {
                hashMap.put("type", "2");
                entry.getValue().getBasicRemote().sendText(new ObjectMapper().writeValueAsString(hashMap));
            }
        }
    }

    /**
        刪除用戶
     */
    public void deleteUserByUsername(String username){
        //在線人數減減
        WebSocketServer.onlineCount--;

        WebSocketServer.loginList.remove(username);

        //通知除了本身以外的全部人
        sendOnlineCount(username, "{'type':'onlineCount','onlineCount':" + WebSocketServer.onlineCount + ",username:'" + username + "'}");
    }

}
View Code

 

  先看一下咱們的自定義web彈窗的js、css,跟以前相比有一點小升級

/* web彈窗 */
.tip-msg {
    background-color: rgba(61, 61, 61, 0.93);
    color: #ffffff;
    opacity: 0;
    max-width: 200px;
    position: fixed;
    text-align: center;
    line-height: 25px;
    border-radius: 30px;
    padding: 5px 15px;
    display: inline-block;
    z-index: 10000;
}

.tip-shade {
    z-index: 9999;
    background-color: rgb(0, 0, 0);
    opacity: 0.6;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.tip-dialog {
    z-index: 9999;
    position: fixed;
    display: block;
    background: #e9e9e9;
    border-radius: 5px;
    opacity: 0;
    border: 1px solid #dad8d8;
    box-shadow: 0px 1px 20px 2px rgb(255, 221, 221);
}

.tip-title {
    cursor: move;
    padding: 5px;
    position: relative;
    height: 25px;
    border-bottom: 1px solid #dad8d8;
    user-select: none;
}

.tip-title-text {
    margin: 0;
    padding: 0;
    font-size: 15px;
}

.tip-title-btn {
    position: absolute;
    top: 5px;
    right: 5px;
}

.tip-content {
    padding: 8px;
    position: relative;
    word-break: break-all;
    font-size: 14px;
    overflow-x: hidden;
    overflow-y: auto;
}

.tip-resize {
    position: absolute;
    width: 15px;
    height: 15px;
    right: 0;
    bottom: 0;
    cursor: se-resize;
}
tip.css
/**
 * 自定義web彈窗/層:簡易風格的msg與可拖放的dialog
 * 依賴jquery
 */
var tip = {

    /**
     * 初始化
     */
    init: function () {
        var titleDiv = null;//標題元素
        var dialogDiv = null;//窗口元素
        var titleDown = false;//是否在標題元素按下鼠標
        var resizeDown = false;//是否在縮放元素按下鼠標
        var offset = {x: 0, y: 0};//鼠標按下時的座標系/計算後的座標
        /*
            使用 on() 方法添加的事件處理程序適用於當前及將來的元素(好比由腳本建立的新元素)。
            問題:事件綁定在div上出現div移動速度跟不上鼠標速度,致使鼠標移動太快時會脫離div,從而沒法觸發事件。
            解決:把事件綁定在document文檔上,不管鼠標在怎麼移動,始終是在文檔範圍以內。
        */
        //鼠標在標題元素按下
        $(document).on("mousedown", ".tip-title", function (e) {
            var event1 = e || window.event;
            titleDiv = $(this);
            dialogDiv = titleDiv.parent();
            titleDown = true;
            offset.x = e.clientX - parseFloat(dialogDiv.css("left"));
            offset.y = e.clientY - parseFloat(dialogDiv.css("top"));
        });
        //鼠標移動
        $(document).on("mousemove", function (e) {
            var event2 = e || window.event;
            var eveX = event2.clientX;             // 獲取鼠標相對於瀏覽器x軸的位置
            var eveY = event2.clientY;             // 獲取鼠標相對於瀏覽器Y軸的位置
            // var height = document.body.clientHeight;//表示HTML文檔所在窗口的當前高度;
            // var width = document.body.clientWidth;//表示HTML文檔所在窗口的當前寬度;
            var height = window.innerHeight;//瀏覽器窗口的內部高度;
            var width = window.innerWidth;//瀏覽器窗口的內部寬度;

            //在標題元素按下
            if (titleDown) {

                //處理滾動條
                if (tip.hasXScrollbar()) {
                    height = height - tip.getScrollbarWidth();
                }
                if (tip.hasYScrollbar()) {
                    width = width - tip.getScrollbarWidth();
                }

                //上邊
                var top = (eveY - offset.y);
                if (top <= 0) {
                    top = 0;
                }
                if (top >= (height - dialogDiv.height())) {
                    top = height - dialogDiv.height() - 5;
                }

                //左邊
                var left = (eveX - offset.x);
                if (left <= 0) {
                    left = 0;
                }
                if (left >= (width - dialogDiv.width())) {
                    left = width - dialogDiv.width() - 5;
                }
                dialogDiv.css({
                    "top": top + "px",
                    "left": left + "px"
                });
            }

            //在縮放元素按下
            if (resizeDown) {
                var newWidth = (dialogDiv.resize.width + (eveX - offset.x));
                if (dialogDiv.resize.initWidth >= newWidth) {
                    newWidth = dialogDiv.resize.initWidth;
                }
                var newHeight = (dialogDiv.resize.height + (eveY - offset.y));
                if (dialogDiv.resize.initHeight >= newHeight) {
                    newHeight = dialogDiv.resize.initHeight;
                }

                dialogDiv.css("width", newWidth + "px");
                dialogDiv.find(".tip-content").css("height", newHeight + "px");
            }
        });
        //鼠標彈起
        $(document).on("mouseup", function (e) {
            //清空對象
            titleDown = false;
            resizeDown = false;
            titleDiv = null;
            dialogDiv = null;
            offset = {x: 0, y: 0};
        });
        //阻止按鈕事件冒泡
        $(document).on("mousedown", ".tip-title-min,.tip-title-max,.tip-title-close", function (e) {
            e.stopPropagation();//阻止事件冒泡
        });
        //最小化
        $(document).on("click", ".tip-title-min", function (e) {
            // var height = document.body.clientHeight;//表示HTML文檔所在窗口的當前高度;
            // var width = document.body.clientWidth;//表示HTML文檔所在窗口的當前寬度;
            var height = window.innerHeight;//瀏覽器窗口的內部高度;
            var width = window.innerWidth;//瀏覽器窗口的內部寬度;
            var $parent = $(this).parents(".tip-dialog");
            //顯示瀏覽器滾動條
            document.body.parentNode.style.overflowY = "auto";

            //當前是否爲最大化
            if ($parent[0].isMax) {
                $parent[0].isMax = false;
                $parent.css({
                    "top": $parent[0].topMin,
                    "left": $parent[0].leftMin,
                    "height": $parent[0].heightMin,
                    "width": $parent[0].widthMin
                });
            }
            //當前是否爲最小化
            if (!$parent[0].isMin) {
                $parent[0].isMin = true;
                $parent[0].bottomMin = $parent.css("bottom");
                $parent[0].leftMin = $parent.css("left");
                $parent[0].heightMin = $parent.css("height");
                $parent[0].widthMin = $parent.css("width");
                $parent.css({
                    "top": "",
                    "bottom": "5px",
                    "left": 0,
                    "height": "30px",
                    "width": "95px"
                });
                $parent.find(".tip-title-text").css("display", "none");
                $parent.find(".tip-content").css("display", "none");
            } else {
                $parent[0].isMin = false;
                $parent.css({
                    "top": $parent[0].topMin,
                    "bottom": $parent[0].bottomMin,
                    "left": $parent[0].leftMin,
                    "height": $parent[0].heightMin,
                    "width": $parent[0].widthMin
                });
                $parent.find(".tip-title-text").css("display", "block");
                $parent.find(".tip-content").css("display", "block");
            }
        });
        //最大化
        $(document).on("click", ".tip-title-max", function (e) {
            // var height = document.body.clientHeight;//表示HTML文檔所在窗口的當前高度;
            // var width = document.body.clientWidth;//表示HTML文檔所在窗口的當前寬度;
            var height = window.innerHeight;//瀏覽器窗口的內部高度;
            var width = window.innerWidth;//瀏覽器窗口的內部寬度;
            var $parent = $(this).parents(".tip-dialog");
            //當前是否爲最小化
            if ($parent[0].isMin) {
                $parent[0].isMin = false;
                $parent.css({
                    "top": $parent[0].topMin,
                    "bottom": $parent[0].bottomMin,
                    "left": $parent[0].leftMin,
                    "height": $parent[0].heightMin,
                    "width": $parent[0].widthMin
                });
                $parent.find(".tip-title h2").css("display", "block");
            }
            //當前是否爲最大化
            if (!$parent[0].isMax) {
                //隱藏瀏覽器滾動條
                document.body.parentNode.style.overflowY = "hidden";
                $parent[0].isMax = true;
                $parent[0].topMin = $parent.css("top");
                $parent[0].leftMin = $parent.css("left");
                $parent[0].heightMin = $parent.css("height");
                $parent[0].widthMin = $parent.css("width");
                $parent.css({
                    "top": 0,
                    "left": 0,
                    "height": height - 5 + "px",
                    "width": width - 5 + "px"
                });
            } else {
                //顯示瀏覽器滾動條
                document.body.parentNode.style.overflowY = "auto";
                $parent[0].isMax = false;
                $parent.css({
                    "top": $parent[0].topMin,
                    "left": $parent[0].leftMin,
                    "height": $parent[0].heightMin,
                    "width": $parent[0].widthMin
                });
            }
        });
        //縮放
        $(document).on("mousedown", ".tip-resize", function (e) {
            var event1 = e || window.event;
            dialogDiv = $(this).parent();
            resizeDown = true;
            offset.x = e.clientX;
            offset.y = e.clientY;
            //點擊時的寬高
            dialogDiv.resize.width = dialogDiv.width();
            dialogDiv.resize.height = dialogDiv.find(".tip-content").height();
        });
        //關閉
        $(document).on("click", ".tip-title-close", function (e) {
            $(this).parents(".tip-dialog").parent().remove();
            //顯示瀏覽器滾動條
            document.body.parentNode.style.overflowY = "auto";
        });
        //點擊窗口優先顯示
        $(document).on("click", ".tip-dialog", function (e) {
            $(".tip-dialog").css("z-index","9999");
            $(this).css("z-index","10000");
        });
    },

    /**
     * 是否存在X軸方向滾動條
     */
    hasXScrollbar: function () {
        return document.body.scrollWidth > (window.innerWidth || document.documentElement.clientWidth);
    },

    /**
     * 是否存在Y軸方向滾動條
     */
    hasYScrollbar: function () {
        return document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight);
    },

    /**
     * 計算滾動條的寬度
     */
    getScrollbarWidth: function () {
        /*
            思路:生成一個帶滾動條的div,分析獲得滾動條長度,而後過河拆橋
         */
        var scrollDiv = document.createElement("div");
        scrollDiv.style.cssText = 'width: 99px; height: 99px; overflow: scroll; position: absolute; top: -9999px;';
        document.body.appendChild(scrollDiv);
        var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth;
        document.body.removeChild(scrollDiv);

        return scrollbarWidth;

    },

    /**
     * tip提示
     * tip.msg("哈哈哈哈哈");
     * tip.msg({text:"哈哈哈哈哈",time:5000});
     */
    msg: function (setting) {
        var time = setting.time || 2000; // 顯示時間(毫秒) 默認延遲2秒關閉
        var text = setting.text || setting; // 文本內容

        //組裝HTML
        var tip = "<div class='tip tip-msg'>"
            + text +
            "</div>";

        //刪除舊tip
        $(".tip-msg").remove();

        //添加到body
        $("body").append(tip);

        //獲取jq對象
        var $tip = $(".tip-msg");

        //動畫過渡
        $tip.animate({opacity: 1}, 500);

        //計算位置瀏覽器窗口上下、左右居中
        // var height = document.body.clientHeight;//表示HTML文檔所在窗口的當前高度;
        var width = document.body.clientWidth;//表示HTML文檔所在窗口的當前寬度;
        var height = window.innerHeight;//瀏覽器窗口的內部高度;
        // var width = window.innerWidth;//瀏覽器窗口的內部寬度;
        width = ((width / 2) - ($tip.css("width").replace("px", "") / 2)) / width;
        height = ((height / 2) - ($tip.css("height").replace("px", "") / 2)) / height;
        $tip.css({
            "top": (height * 100) + "%",
            "left": (width * 100) + "%"
        });

        //延遲刪除
        setTimeout(function () {
            //動畫過渡
            $tip.animate({opacity: 0}, 500, function () {
                $tip.remove();
            });
        }, time);
    },

    /**
     * 可拖放窗口
     * tip.dialog({title:"測試彈窗標題",content:"測試彈窗內容"});
     * tip.dialog({title:"測試彈窗標題",class:"myClassName",content:"<h1>測試彈窗內容</h1>",offset: ['100px', '50px'],area:["200px","100px"],shade:0,closeCallBack:function(){console.log('你點擊了關閉按鈕')}});
     */
    dialog: function (setting) {
        var title = setting.title || "這裏是標題"; // 標題
        var clazz = setting.class || ""; // class
        var content = setting.content || "這裏是內容"; // 內容
        var area = setting.area; // 寬高
        var offset = setting.offset || "auto"; // 位置 上、左
        var shade = setting.shade !== undefined ? setting.shade : 0.7;//遮陰 爲0時無遮陰對象

        //組裝HTML
        var tip = "<div>\n" +
            "    <!-- 遮陰層 -->\n" +
            "    <div class=\"tip tip-shade\"></div>\n" +
            "    <!-- 主體 -->\n" +
            "    <div class=\"tip tip-dialog " + clazz + "\">\n" +
            "        <!-- 標題 -->\n" +
            "        <div class=\"tip tip-title\">\n" +
            "            <h2 class=\"tip tip-title-text\"></h2>\n" +
            "            <div class=\"tip tip-title-btn\">\n" +
            "                <button class=\"tip tip-title-min\" title=\"最小化\">--</button>\n" +
            "                <button class=\"tip tip-title-max\" title=\"最大化\">O</button>\n" +
            "                <button class=\"tip tip-title-close\" title=\"關閉\">X</button>\n" +
            "            </div>\n" +
            "        </div>\n" +
            "        <!-- 窗口內容 -->\n" +
            "        <div class=\"tip tip-content\"></div>\n" +
            "        <!-- 右下角改變窗口大小 -->\n" +
            "        <div class=\"tip tip-resize\"></div>\n" +
            "    </div>\n" +
            "</div>";

        var $tip = $(tip);

        //添加到body
        $("body").append($tip);

        //設置遮陰
        $tip.find(".tip-shade").css("opacity", shade);
        if (shade === 0) {
            $tip.find(".tip-shade").css({
                "width": "0",
                "height": "0"
            });
        }

        //獲取dialog對象
        $tip = $tip.find(".tip-dialog");

        //標題
        $tip.find(".tip-title-text").html(title);

        //內容
        $tip.find(".tip-content").append(content);

        //設置初始寬高
        if (area) {
            $tip.css({
                "width": area[0],
            });
            $tip.find(".tip-content").css({
                "height": area[1]
            });
        }

        //動畫過渡
        $tip.animate({opacity: 1}, 500);

        //計算位置瀏覽器窗口上下、左右居中
        if (offset === "auto") {
            // var height = document.body.clientHeight;//表示HTML文檔所在窗口的當前高度;
            var width = document.body.clientWidth;//表示HTML文檔所在窗口的當前寬度;
            var height = window.innerHeight;//瀏覽器窗口的內部高度;
            // var width = window.innerWidth;//瀏覽器窗口的內部寬度;
            width = ((width / 2) - ($tip.css("width").replace("px", "") / 2)) / width;
            height = ((height / 2) - ($tip.css("height").replace("px", "") / 2)) / height;
            $tip.css({
                "top": (height * 100) + "%",
                "left": (width * 100) + "%"
            });
        } else if (Array.isArray(offset)) {
            $tip.css({
                "top": offset[0],
                "left": offset[1]
            });
        }

        //初始值寬高
        $tip.resize.initWidth = $tip.width();
        $tip.resize.initHeight = $tip.find(".tip-content").height();

        //綁定關閉回調
        if(setting.closeCallBack){
            $(".tip-title-close").click(function (e) {
                setting.closeCallBack();
            });
        }
    }
};

//初始化
tip.init();
tip.js

 

  接下來就是登陸/註冊頁面跟聊天頁面的HTML、JS的修改,咱們先定義一個head.html做爲一個公用head在其餘地方引入

<!--此頁面用於放置頁面的公共片斷(fragment)-->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:fragment="static">
    <head>
        <script th:inline="javascript">
            //項目根路徑
            // ctx = /*[[@{/}]]*/'';
            ctx = [[${#request.getContextPath()}]];//應用路徑
        </script>
    </head>

    <head>
        <!-- jquery -->
        <script th:src="@{/js/jquery.min.js}"></script>

        <!-- 自定義web彈窗 CSS、JS 文件 -->
        <link th:href="@{/css/tip.css}" rel="stylesheet" type="text/css"/>
        <script th:src="@{/js/tip.js}"></script>
    </head>

    <head>
        <style>
            body, html {
                margin: 0;
                padding: 0;
                background: #898f92;
            }
        </style>
    </head>

    <head>
        <script>
            /**
             * 拓展表單對象:用於將對象序列化爲JSON對象
             */
            $.fn.serializeObject = function () {
                var o = {};
                var a = this.serializeArray();
                $.each(a, function () {
                    if (o[this.name]) {
                        if (!o[this.name].push) {
                            o[this.name] = [o[this.name]];
                        }
                        o[this.name].push(this.value || '');
                    } else {
                        o[this.name] = this.value || '';
                    }
                });
                return o;
            };

            /**
             * 表單自動回顯
             * 依賴jqury
             * 使用參考:$("#form1").form({"id":"112","username":"ff","password":"111","type":"admin"});
             */
            $.fn.form = function (data) {
                var form = $(this);
                for (var i in data) {
                    var name = i;
                    var value = data[i];
                    if (name !== "" && value !== "") {
                        valuAtion(name, value);
                    }
                }

                function valuAtion(name, value) {
                    if (form.length < 1) {
                        return;
                    }
                    if (form.find("[name='" + name + "']").length < 1) {
                        return;
                    }
                    var input = form.find("[name='" + name + "']")[0];
                    if ($.inArray(input.type, ["text", "password", "hidden", "select-one", "textarea"]) > -1) {
                        $(input).val(value);
                    } else if (input.type == " " || input.type == "checkbox") {
                        form.find("[name='" + name + "'][value='" + value + "']").attr("checked", true);
                    }
                }

            };

            /**
             * 經常使用工具方法
             */
            commonUtil = {
                /**
                 * 獲取當前時間,並格式化輸出爲:2018-05-18 14:21:46
                 * @returns {string}
                 */
                getNowTime: function () {
                    var time = new Date();
                    var year = time.getFullYear();//獲取年
                    var month = time.getMonth() + 1;//或者月
                    var day = time.getDate();//或者天

                    var hour = time.getHours();//獲取小時
                    var minu = time.getMinutes();//獲取分鐘
                    var second = time.getSeconds();//或者秒
                    var data = year + "-";
                    if (month < 10) {
                        data += "0";
                    }
                    data += month + "-";
                    if (day < 10) {
                        data += "0"
                    }
                    data += day + " ";
                    if (hour < 10) {
                        data += "0"
                    }


                    data += hour + ":";
                    if (minu < 10) {
                        data += "0"
                    }
                    data += minu + ":";
                    if (second < 10) {
                        data += "0"
                    }
                    data += second;
                    return data;
                }
            }
        </script>
    </head>

</html>
head.html

 

<!DOCTYPE >
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>登陸/註冊</title>
    <!-- 引入公用部分 -->
    <script th:replace="head::static"></script>

    <!-- login CSS、JS 文件 -->
    <link th:href="@{/css/login.css}" rel="stylesheet" type="text/css"/>
</head>
<body>

<script th:inline="javascript">
</script>
<!-- login CSS、JS 文件 -->
<script th:src="@{/js/login.js}"></script>
</body>
</html>
login.html
h3 {
    margin: 0 0 5px 0;
    text-align: center;
}

.login {
    width: 250px;
    background: #e9e9e9;
    border-radius: 5px;
    margin: 0 auto;
    border: 1px solid #e9e9e9;
    padding: 10px;
}

.login > form {
    margin: 0;
}

.login:focus-within {
    border: 1px solid #10b7f3;
    background: #caefff;
}

.register {
    width: 350px;
    background: #e9e9e9;
    border-radius: 5px;
    margin: 0 auto;
    border: 1px solid #e9e9e9;
    padding: 10px;
    display: none;
}

.register > form {
    margin: 0;
}

.register > table,.login > table {
    margin: 0 auto;
}

.register:focus-within {
    border: 1px solid #10b7f3;
    background: #caefff;
}
login.css
let $login = "<div class=\"login\">\n" +
    "    <h3>登陸</h3>\n" +
    "    <form id=\"loginForm\">\n" +
    "    <table>\n" +
    "            <tr>\n" +
    "                <td>帳號:</td>\n" +
    "                <td><input type=\"text\" name=\"userName\"/></td>\n" +
    "            </tr>\n" +
    "            <tr>\n" +
    "                <td>密碼:</td>\n" +
    "                <td><input type=\"text\" name=\"password\"/></td>\n" +
    "            </tr>\n" +
    "        <tr>\n" +
    "            <td><a href=\"#\" onclick=\"switchover()\">註冊</a></td>\n" +
    "            <td colspan=\"2\"><a href=\"#\" onclick=\"login()\">登陸</a></td>\n" +
    "        </tr>\n" +
    "    </table>\n" +
    "    </form>\n" +
    "</div>";

let $register = "<!-- 註冊 -->\n" +
    "<div class=\"register\">\n" +
    "    <h3>註冊</h3>\n" +
    "\n" +
    "  <form id=\"registerForm\">\n" +
    "    <table>\n" +
    "            <tr>\n" +
    "                <td>帳號:</td>\n" +
    "                <td><label for=\"userName\"></label><input type=\"text\" id=\"userName\" name=\"userName\"/></td>\n" +
    "            </tr>\n" +
    "            <tr>\n" +
    "                <td>密碼:</td>\n" +
    "                <td><input type=\"text\" id=\"password\" name=\"password\"/></td>\n" +
    "            </tr>\n" +
    "            <tr>\n" +
    "                <td>暱稱:</td>\n" +
    "                <td><input type=\"text\" id=\"nickName\" name=\"nickName\"/></td>\n" +
    "            </tr>\n" +
    "            <tr>\n" +
    "                <td>性別:</td>\n" +
    "                <td>\n" +
    "                    男<input type=\"radio\" name=\"gender\" value=\"1\" checked/>\n" +
    "                    女<input type=\"radio\" name=\"gender\" value=\"0\"/>\n" +
    "                </td>\n" +
    "            </tr>\n" +
    "            <tr>\n" +
    "                <td>頭像:</td>\n" +
    "                <td><input type=\"text\" id=\"avatar\" name=\"avatar\"/></td>\n" +
    "            </tr>\n" +
    "            <tr>\n" +
    "                <td>電子郵箱:</td>\n" +
    "                <td><input type=\"email\" id=\"email\" name=\"email\"/></td>\n" +
    "            </tr>\n" +
    "            <tr>\n" +
    "                <td>手機號碼:</td>\n" +
    "                <td><input type=\"text\" id=\"phone\" name=\"phone\"/></td>\n" +
    "            </tr>\n" +
    "            <tr>\n" +
    "                <td>個性簽名:</td>\n" +
    "                <td><textarea id=\"sign\" name=\"sign\"></textarea></td>\n" +
    "            </tr>\n" +
    "            <!-- 兩個隱藏時間 -->\n" +
    "            <input type=\"hidden\" id=\"createdTime\" name=\"createdTime\"/>\n" +
    "            <input type=\"hidden\" id=\"updataTime\" name=\"updataTime\"/>\n" +
    "        <tr>\n" +
    "            <td><a href=\"#\" onclick=\"switchover()\">登陸</a></td>\n" +
    "            <td colspan=\"2\"><a href=\"#\" onclick=\"register()\">註冊</a></td>\n" +
    "        </tr>\n" +
    "    </table>\n" +
    "    </form>\n" +
    "</div>";

tip.dialog({title: "登陸/註冊", content: $login + $register, shade: 0});

//切換登陸、註冊頁面
function switchover() {
    if ($(".login").css("display") === "none") {
        $(".login").show();
        $(".register").hide();
    } else {
        $(".register").show();
        $(".login").hide();
    }
}

//提交註冊
function register() {
    let newTime = commonUtil.getNowTime();
    $("#createdTime").val(newTime);
    $("#updataTime").val(newTime);
    $("#avatar").val("/image/logo.png");

    console.log($("#registerForm").serializeObject());
    $.post(ctx + "/imsUser/save", $("#registerForm").serializeObject(), function (data) {
        if (data.flag) {
            switchover();
        }
        // tip提示
        tip.msg(data.msg);
    });
}

//提交登陸
function login() {
    console.log($("#loginForm").serializeObject());
    $.post(ctx + "/imsUser/login", $("#loginForm").serializeObject(), function (data) {
        if (data.flag) {
            window.location.href = ctx + "/imsUser/socketChart/" + data.data.userName + ".html"
        } else {
            // tip提示
            tip.msg(data.msg);
        }
    });
    return false;
}
login.js

 

<!DOCTYPE>
<!--解決idea thymeleaf 表達式模板報紅波浪線-->
<!--suppress ALL -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>聊天頁面</title>
    <!-- 引入公用部分 -->
    <script th:replace="head::static"></script>

    <!-- socketChart CSS、JS 文件 -->
    <link th:href="@{/css/socketChart.css}" rel="stylesheet" type="text/css"/>
</head>
<body>

</body>
<script type="text/javascript" th:inline="javascript">
    //登陸名
    var username = /*[[${username}]]*/'';
</script>

<!-- socketChart CSS、JS 文件 -->
<script th:src="@{/js/socketChart.js}"></script>

<script type="text/javascript">
    //老的瀏覽器可能根本沒有實現 mediaDevices,因此咱們能夠先設置一個空的對象
    if (navigator.mediaDevices === undefined) {
        navigator.mediaDevices = {};
    }
    //一些瀏覽器部分支持 mediaDevices。咱們不能直接給對象設置 getUserMedia
    //由於這樣可能會覆蓋已有的屬性。這裏咱們只會在沒有getUserMedia屬性的時候添加它。
    if (navigator.mediaDevices.getUserMedia === undefined) {
        navigator.mediaDevices.getUserMedia = function (constraints) {

            // 首先,若是有getUserMedia的話,就得到它
            var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;

            // 一些瀏覽器根本沒實現它 - 那麼就返回一個error到promise的reject來保持一個統一的接口
            if (!getUserMedia) {
                return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
            }

            // 不然,爲老的navigator.getUserMedia方法包裹一個Promise
            return new Promise(function (resolve, reject) {
                getUserMedia.call(navigator, constraints, resolve, reject);
            });
        }
    }
    var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;

    //開啓視頻
    $(document).on("click", "#videoBut", function (event) {
        console.log("開始與" + $("#toUserName").text() + "視頻聊天...");
        var username = $("#toUserName").text();
        // 建立PeerConnection實例 (參數爲null則沒有iceserver,即便沒有stunserver和turnserver,仍可在局域網下通信)
        var pc = new webkitRTCPeerConnection(null);

        // 發送ICE候選到其餘客戶端
        pc.onicecandidate = function (event) {
            if (event.candidate !== null) {
                //轉json字符串
                websocket.send(JSON.stringify({
                    "event": "_ice_candidate",
                    "data": {
                        "candidate": event.candidate
                    }
                }));
                websocket.send(JSON.stringify({
                    "type": "1",
                    "tarUser": {"username": tarUserName},
                    "srcUser": {"username": srcUserName},
                    "message": message
                }));
            }
        };
    });
</script>
</html>
socketChart.html
//追加tip頁面
let hzGroup = "" +
    "<div id=\"hz-group\">\n" +
    "        在線人數:<span id=\"onlineCount\">0</span>\n" +
    "        <!-- 主體 -->\n" +
    "        <div id=\"hz-group-body\">\n" +
    "\n" +
    "        </div>\n" +
    "    </div>";
tip.dialog({title: "<span id=\"talks\">" + username + "</span>", content: hzGroup, offset: ["10%", "80%"], shade: 0,
    closeCallBack: function () {
        console.log("dfasdfasd")
        window.location.href = ctx + "/imsUser/logout/" + username;
    }});

//聊天頁面
let hzMessage = "" +
    "    <div id=\"hz-message\">\n" +
    "        <!-- 主體 -->\n" +
    "        <div id=\"hz-message-body\">\n" +
    "        </div>\n" +
    "        <!-- 功能條 -->\n" +
    "        <div id=\"\">\n" +
    "            <button>表情</button>\n" +
    "            <button>圖片</button>\n" +
    "            <button id=\"videoBut\">視頻</button>\n" +
    "            <button onclick=\"send(this)\" style=\"float: right;\">發送</button>\n" +
    "        </div>\n" +
    "        <!-- 輸入框 -->\n" +
    "        <div contenteditable=\"true\" id=\"hz-message-input\">\n" +
    "\n" +
    "        </div>\n" +
    "    </div>";


//消息對象數組
var msgObjArr = [];

//websocket對象
var websocket = null;

//判斷當前瀏覽器是否支持WebSocket
if ('WebSocket' in window) {
    websocket = new WebSocket("ws://localhost:10086/websocket/" + username);
} else {
    console.error("不支持WebSocket");
}

//鏈接發生錯誤的回調方法
websocket.onerror = function (e) {
    console.error("WebSocket鏈接發生錯誤");
};

//鏈接成功創建的回調方法
websocket.onopen = function () {
    //獲取全部在線用戶
    $.ajax({
        type: 'post',
        url: ctx + "/imsUser/getOnlineList",
        contentType: 'application/json;charset=utf-8',
        dataType: 'json',
        data: {username: username},
        success: function (data) {
            if (data.length) {
                //列表
                for (let i = 0; i < data.length; i++) {
                    var userName = data[i];
                    var text = "<div class=\"hz-group-list\"><img class='left' style='width: 23px;' src='https://avatars3.githubusercontent.com/u/31408183?s=40&v=4'/><span class='hz-group-list-username'>" + userName + "</span><span id=\"" + userName + "-status\" style='color: #497b0f;;'>[在線]</span><div id=\"hz-badge-" + userName + "\" class='hz-badge'>0</div></div>";
                    //把本身排在第一個
                    if (username === userName) {
                        $("#hz-group-body").prepend(text);
                    } else {
                        $("#hz-group-body").append(text);
                    }
                }

                //在線人數
                $("#onlineCount").text(data.length);
            }
        },
        error: function (xhr, status, error) {
            console.log("ajax錯誤!");
        }
    });
};

//接收到消息的回調方法
websocket.onmessage = function (event) {
    var messageJson = eval("(" + event.data + ")");

    //普通消息(私聊)
    if (messageJson.type === "1") {
        //來源用戶
        var srcUser = messageJson.srcUser;
        //目標用戶
        var tarUser = messageJson.tarUser;
        //消息
        var message = messageJson.message;

        //最加聊天數據
        setMessageInnerHTML(srcUser.username, srcUser.username, message);
    }

    //普通消息(羣聊)
    if (messageJson.type === "2") {
        //來源用戶
        var srcUser = messageJson.srcUser;
        //目標用戶
        var tarUser = messageJson.tarUser;
        //消息
        var message = messageJson.message;

        //最加聊天數據
        setMessageInnerHTML(username, tarUser.username, message);
    }

    //對方不在線
    if (messageJson.type === "0") {
        //消息
        var message = messageJson.message;

        $("#hz-message-body").append(
            "<div class=\"hz-message-list\" style='text-align: center;'>" +
            "<div class=\"hz-message-list-text\">" +
            "<span>" + message + "</span>" +
            "</div>" +
            "</div>");
    }

    //在線人數
    if (messageJson.type === "onlineCount") {
        //取出username
        var onlineCount = messageJson.onlineCount;
        var userName = messageJson.username;
        var oldOnlineCount = $("#onlineCount").text();

        //新舊在線人數對比
        if (oldOnlineCount < onlineCount) {
            if ($("#" + userName + "-status").length > 0) {
                $("#" + userName + "-status").text("[在線]");
                $("#" + userName + "-status").css("color", "#497b0f");
            } else {
                $("#hz-group-body").append("<div class=\"hz-group-list\"><span class='hz-group-list-username'>" + userName + "</span><span id=\"" + userName + "-status\" style='color: #497b0f;'>[在線]</span><div id=\"hz-badge-" + userName + "\" class='hz-badge'>0</div></div>");
            }
        } else {
            //有人下線
            $("#" + userName + "-status").text("[離線]");
            $("#" + userName + "-status").css("color", "#9c0c0c");
        }
        $("#onlineCount").text(onlineCount);
    }

};

//鏈接關閉的回調方法
websocket.onclose = function () {
    //alert("WebSocket鏈接關閉");
};

//將消息顯示在對應聊天窗口    對於接收消息來講這裏的toUserName就是來源用戶,對於發送來講則相反
function setMessageInnerHTML(srcUserName, msgUserName, message) {
    //判斷
    var childrens = $("#hz-group-body").children(".hz-group-list");
    var isExist = false;
    for (var i = 0; i < childrens.length; i++) {
        var text = $(childrens[i]).find(".hz-group-list-username").text();
        if (text == srcUserName) {
            isExist = true;
            break;
        }
    }
    if (!isExist) {
        //追加聊天對象
        msgObjArr.push({
            toUserName: srcUserName,
            message: [{username: msgUserName, message: message, date: nowTime()}]//封裝數據
        });
        $("#hz-group-body").append("<div class=\"hz-group-list\"><span class='hz-group-list-username'>" + srcUserName + "</span><span id=\"" + srcUserName + "-status\">[在線]</span><div id=\"hz-badge-" + srcUserName + "\" class='hz-badge'>0</div></div>");
    } else {
        //取出對象
        var isExist = false;
        for (var i = 0; i < msgObjArr.length; i++) {
            var obj = msgObjArr[i];
            if (obj.toUserName == srcUserName) {
                //保存最新數據
                obj.message.push({username: msgUserName, message: message, date: nowTime()});
                isExist = true;
                break;
            }
        }
        if (!isExist) {
            //追加聊天對象
            msgObjArr.push({
                toUserName: srcUserName,
                message: [{username: msgUserName, message: message, date: nowTime()}]//封裝數據
            });
        }
    }

    //恰好有打開的是對應的聊天頁面
    if ($(".tip" + srcUserName).length > 0) {
        $(".tip" + srcUserName + " #hz-message-body").append(
            "<div class=\"hz-message-list\">" +
            "<p class='hz-message-list-username'>" + msgUserName + "</p>" +
            "<img class='left' style='width: 23px;margin: 0 5px 0 0;' src='https://avatars3.githubusercontent.com/u/31408183?s=40&v=4'/>" +
            "<div class=\"hz-message-list-text left\">" +
            "<span>" + message + "</span>" +
            "</div>" +
            "<div style=\" clear: both; \"></div>" +
            "</div>");
    } else {
        //小圓點++
        var conut = $("#hz-badge-" + srcUserName).text();
        $("#hz-badge-" + srcUserName).text(parseInt(conut) + 1);
        $("#hz-badge-" + srcUserName).css("opacity", "1");
    }
}

//發送消息
function send(but) {
    //目標用戶名
    var tarUserName = $(but).parents(".tip-dialog").find("#toUserName").text();
    //登陸用戶名
    var srcUserName = $("#talks").text();
    //消息
    var message = $(but).parents(".tip-dialog").find("#hz-message-input").html();

    websocket.send(JSON.stringify({
        "type": "1",
        "tarUser": {"username": tarUserName},
        "srcUser": {"username": srcUserName},
        "message": message
    }));
    $(".tip" + tarUserName + " #hz-message-body").append(
        "<div class=\"hz-message-list\">" +
        "<img class='right' style='width: 23px;margin: 0 0 0 5px;' src='https://avatars3.githubusercontent.com/u/31408183?s=40&v=4'/>" +
        "<div class=\"hz-message-list-text right\">" +
        "<span>" + message + "</span>" +
        "</div>" +
        "</div>");
    $(".tip" + tarUserName + " #hz-message-input").html("");
    //取出對象
    if (msgObjArr.length > 0) {
        var isExist = false;
        for (var i = 0; i < msgObjArr.length; i++) {
            var obj = msgObjArr[i];
            if (obj.toUserName == tarUserName) {
                //保存最新數據
                obj.message.push({username: srcUserName, message: message, date: nowTime()});
                isExist = true;
                break;
            }
        }
        if (!isExist) {
            //追加聊天對象
            msgObjArr.push({
                toUserName: tarUserName,
                message: [{username: srcUserName, message: message, date: nowTime()}]//封裝數據[{username:huanzi,message:"你好,我是歡子!",date:2018-04-29 22:48:00}]
            });
        }
    } else {
        //追加聊天對象
        msgObjArr.push({
            toUserName: tarUserName,
            message: [{username: srcUserName, message: message, date: nowTime()}]//封裝數據[{username:huanzi,message:"你好,我是歡子!",date:2018-04-29 22:48:00}]
        });
    }
}

//監聽點擊用戶
$("body").on("click", ".hz-group-list", function () {
    var toUserName = $(this).find(".hz-group-list-username").text();
    //彈出聊天頁面
    tip.dialog({
        title: "正在與 <span id=\"toUserName\"></span> 聊天",
        class: "tip" + toUserName,
        content: hzMessage,
        shade: 0
    });

    // $(".hz-group-list").css("background-color", "");
    // $(this).css("background-color", "whitesmoke");
    $(".tip" + toUserName + " #toUserName").text(toUserName);

    //清空小圓點
    $("#hz-badge-" + toUserName).text("0");
    $("#hz-badge-" + toUserName).css("opacity", "0");
    if (msgObjArr.length > 0) {
        for (var i = 0; i < msgObjArr.length; i++) {
            var obj = msgObjArr[i];
            if (obj.toUserName === toUserName) {
                //追加數據
                var messageArr = obj.message;
                if (messageArr.length > 0) {
                    for (var j = 0; j < messageArr.length; j++) {
                        var msgObj = messageArr[j];
                        var leftOrRight = "right";
                        var message = msgObj.message;
                        var msgUserName = msgObj.username;

                        //當聊天窗口與msgUserName的人相同,文字在左邊(對方/其餘人),不然在右邊(本身)
                        if (msgUserName === toUserName) {
                            leftOrRight = "left";
                        }

                        //可是若是點擊的是本身,羣聊的邏輯就不太同樣了
                        if (username === toUserName && msgUserName !== toUserName) {
                            leftOrRight = "left";
                        }

                        if (username === toUserName && msgUserName === toUserName) {
                            leftOrRight = "right";
                        }

                        var magUserName = leftOrRight === "left" ? "<p class='hz-message-list-username'>" + msgUserName + "</p>" : "";

                        $(".tip" + toUserName + " #hz-message-body").append(
                            "<div class=\"hz-message-list\">" +
                            magUserName +
                            "<img class='" + leftOrRight + "' style='width: 23px;margin: 0 5px 0 0;' src='https://avatars3.githubusercontent.com/u/31408183?s=40&v=4'/>" +
                            "<div class=\"hz-message-list-text " + leftOrRight + "\">" +
                            "<span>" + message + "</span>" +
                            "</div>" +
                            "<div style=\" clear: both; \"></div>" +
                            "</div>");
                    }
                }
                break;
            }
        }
    }
});

//獲取當前時間
function nowTime() {
    var time = new Date();
    var year = time.getFullYear();//獲取年
    var month = time.getMonth() + 1;//或者月
    var day = time.getDate();//或者天
    var hour = time.getHours();//獲取小時
    var minu = time.getMinutes();//獲取分鐘
    var second = time.getSeconds();//或者秒
    var data = year + "-";
    if (month < 10) {
        data += "0";
    }
    data += month + "-";
    if (day < 10) {
        data += "0"
    }
    data += day + " ";
    if (hour < 10) {
        data += "0"
    }
    data += hour + ":";
    if (minu < 10) {
        data += "0"
    }
    data += minu + ":";
    if (second < 10) {
        data += "0"
    }
    data += second;
    return data;
}
socketChart.js
#hz-main {
    width: 700px;
    height: 500px;
    background-color: red;
    margin: 0 auto;
}

#hz-message {
    width: 500px;
    float: left;
    background-color: #B5B5B5;
}

#hz-message-body {
    width: 460px;
    height: 340px;
    background-color: #E0C4DA;
    padding: 10px 20px;
    overflow: auto;
}

#hz-message-input {
    width: 500px;
    height: 99px;
    background-color: white;
    overflow: auto;
}

#hz-group {
    width: 200px;
    height: 500px;
    background-color: rosybrown;
    float: right;
}

.hz-message-list {
    min-height: 30px;
    margin: 10px 0;
}

.hz-message-list-text {
    padding: 7px 13px;
    border-radius: 15px;
    width: auto;
    max-width: 85%;
    display: inline-block;
}

.hz-message-list-username {
    margin: 0 0 0 25px;
}

.hz-group-body {
    overflow: auto;
}

.hz-group-list {
    padding: 10px;
    line-height: 23px;
}

.hz-group-list:hover{
    background-color: whitesmoke;
}

.left {
    float: left;
    color: #595a5a;
    background-color: #ebebeb;
}

.right {
    float: right;
    color: #f7f8f8;
    background-color: #919292;
}

.hz-badge {
    width: 20px;
    height: 20px;
    background-color: #FF5722;
    border-radius: 50%;
    float: right;
    color: white;
    text-align: center;
    line-height: 20px;
    font-weight: bold;
    opacity: 0;
}
socketChart.css

 

  演示效果

  註冊、登陸、登出

 

  登陸三個帳號,上下線提示功能

  

  模擬私聊

  

  模擬羣聊(目前點擊本身頭像是羣聊頻道)

 

  總結

  初版先實現到這裏。第二版預計實現消息存儲、離線推送,好友分組,好友搜索、添加,以及一些其餘優化;歡迎你們指出不足之處!

相關文章
相關標籤/搜索