咱們以前已經實現了 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;
自動生成代碼,運行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); } } }
工程結構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; } }
@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); } }
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 + "'}"); } }
先看一下咱們的自定義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; }
/** * 自定義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();
接下來就是登陸/註冊頁面跟聊天頁面的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>
<!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>
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; }
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; }
<!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>
//追加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; }
#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; }
註冊、登陸、登出
登陸三個帳號,上下線提示功能
模擬私聊
模擬羣聊(目前點擊本身頭像是羣聊頻道)
初版先實現到這裏。第二版預計實現消息存儲、離線推送,好友分組,好友搜索、添加,以及一些其餘優化;歡迎你們指出不足之處!