SpringBoot +WebSocket实现简单聊天室功能实例)
一、代码来源
从gittee中找到的一个示例
二、依赖下载
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.websocket_chat</groupId>
<artifactId>johnson</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>johnson</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions><!-- 去掉springboot默认配置 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
// springboot 使用这个 mybatisplus
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.20</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.websocket_chat.johnson.JohnsonApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
三、数据库准备(sql)
数据库建表并插入sql
/*
Navicat Premium Data Transfer
Source Server : 公司电脑mysql
Source Server Type : MySQL
Source Server Version : 50734
Source Host : 192.168.1.128:3306
Source Schema : sakila
Target Server Type : MySQL
Target Server Version : 50734
File Encoding : 65001
Date: 09/08/2022 17:38:41
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for staff
-- ----------------------------
DROP TABLE IF EXISTS `staff`;
CREATE TABLE `staff` (
`staff_id` tinyint(3) unsigned NOT NULL AUTO_INCREMENT,
`first_name` varchar(45) CHARACTER SET utf8 NOT NULL,
`last_name` varchar(45) CHARACTER SET utf8 NOT NULL,
`address_id` smallint(5) unsigned NOT NULL,
`picture` blob,
`email` varchar(50) CHARACTER SET utf8 DEFAULT NULL,
`store_id` tinyint(3) unsigned NOT NULL,
`active` tinyint(1) NOT NULL DEFAULT '1',
`username` varchar(16) CHARACTER SET utf8 NOT NULL,
`password` varchar(40) COLLATE utf8_bin DEFAULT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`staff_id`) USING BTREE,
KEY `idx_fk_store_id` (`store_id`) USING BTREE,
KEY `idx_fk_address_id` (`address_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_bin ROW_FORMAT=DYNAMIC;
-- ----------------------------
-- Records of staff
-- ----------------------------
BEGIN;
INSERT INTO `staff` VALUES (1, 'Mike', 'Hillyer', 3, NULL, '', 1, 1, 'Mike', '81dc9bdb52d04dc20036dbd8313ed055', '2017-06-29 14:27:38');
INSERT INTO `staff` VALUES (2, 'Jon', 'Stephens', 4, NULL, '', 2, 1, 'Jon', '1234', '2017-06-29 14:27:38');
INSERT INTO `staff` VALUES (3, 'TOM', 'DSA', 3, NULL, 'DF', 1, 1, 'TOM', '1234', '2016-11-11 11:08:10');
INSERT INTO `staff` VALUES (4, 'John', 'son', 4, NULL, NULL, 1, 1, 'John', '1234', '2022-08-09 09:13:28');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
四、resources文件配置
application.yml 配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.1.128:3306/sakila?serverTimezone=Asia/Shanghai&useSSL=false&characterEncoding=utf8
username: root
password: root
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启SQL语句打印
server:
port: 8080
log4j2xml配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration status="off">
<Properties>
<!-- 日志存储路径 -->
<Property name="baseDir">./logs</Property>
</Properties>
<CustomLevels>
<CustomLevel name="AUDIT" intLevel="50" />
</CustomLevels>
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout charset="UTF-8" pattern="[%-5p] [%d{HH:mm:ss}] %c - %m%n" />
</Console>
<!-- 自定义 -->
<RollingFile name="RollingFileAUDIT"
fileName="${baseDir}/logservice-web/audit-log/audit-log.log" filePattern="${baseDir}/logservice-web/audit-log/audit-log-%i.log">
<ThresholdFilter level="AUDIT" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="[%d{yyyy/MM/dd HH:mm:ssS}][%p][LOGSERVICE][日志系统]%m%n" />
<Policies>
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
<!-- 保存最大文件个数 -->
<DefaultRolloverStrategy max="50" />
</RollingFile>
<!--Trace级别日志输出-->
<RollingFile name="system-trace"
fileName="${baseDir}/logservice-web/trace.log" filePattern="${baseDir}/logservice-web/trace-%i.log">
<Filters>
<ThresholdFilter level="debug" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<PatternLayout charset="UTF-8" pattern="[%d{yyyy/MM/dd HH:mm:ssSSS}][%p][LOGSERVICE][日志系统][%l]%n%m%n" />
<Policies>
<!-- 日志文件大小 -->
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
<!-- 保存最大文件个数 -->
<DefaultRolloverStrategy max="50" />
</RollingFile>
<!--Info级别日志输出-->
<RollingFile name="system-info"
fileName="${baseDir}/logservice-web/info.log"
filePattern="${baseDir}/logservice-web/info-%i.log">
<Filters>
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<PatternLayout charset="UTF-8" pattern="[%d{yyyy/MM/dd HH:mm:ssSSS}][%p][LOGSERVICE][日志系统][%l]%n%m%n" />
<Policies>
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
<DefaultRolloverStrategy max="50" />
</RollingFile>
<!--Debug级别日志输出-->
<RollingFile name="system-debug"
fileName="${baseDir}/logservice-web/debug.log" filePattern="${baseDir}/logservice-web/debug-%i.log">
<Filters>
<ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<PatternLayout charset="UTF-8" pattern="[%d{yyyy/MM/dd HH:mm:ssSSS}][%p][LOGSERVICE][日志系统][%l]%n%m%n" />
<Policies>
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
<DefaultRolloverStrategy max="50" />
</RollingFile>
<!--Error级别日志输出-->
<RollingFile name="system-error"
fileName="${baseDir}/logservice-web/error.log" filePattern="${baseDir}/logservice-web/error-%i.log">
<Filters>
<ThresholdFilter level="AUDIT" onMatch="DENY" onMismatch="NEUTRAL" />
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY" />
</Filters>
<PatternLayout charset="UTF-8" pattern="[%d{yyyy/MM/dd HH:mm:ssSSS}][%p][LOGSERVICE][日志系统][%l]%n%m%n" />
<Policies>
<SizeBasedTriggeringPolicy size="10 MB" />
</Policies>
<DefaultRolloverStrategy max="50" />
</RollingFile>
</Appenders>
<Loggers>
<logger name="io.netty" level="INFO"></logger>
<logger name="org.springframework" level="INFO"></logger>
<logger name="org.elasticsearch" level="INFO"></logger>
<root level="INFO">
<appender-ref ref="console" />
<appender-ref ref="system-info" />
<appender-ref ref="system-trace" />
<appender-ref ref="system-debug" />
<appender-ref ref="system-error" />
<appender-ref ref="RollingFileAUDIT" />
</root>
</Loggers>
</configuration>
由mybatis-x生成 mapper下的 StaffMapper.xml 文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.websocket_chat.johnson.mapper.StaffMapper">
<resultMap id="BaseResultMap" type="com.websocket_chat.johnson.domain.Staff">
<id property="staffId" column="staff_id" jdbcType="TINYINT"/>
<result property="firstName" column="first_name" jdbcType="VARCHAR"/>
<result property="lastName" column="last_name" jdbcType="VARCHAR"/>
<result property="addressId" column="address_id" jdbcType="SMALLINT"/>
<result property="email" column="email" jdbcType="VARCHAR"/>
<result property="storeId" column="store_id" jdbcType="TINYINT"/>
<result property="active" column="active" jdbcType="BOOLEAN"/>
<result property="username" column="username" jdbcType="VARCHAR"/>
<result property="password" column="password" jdbcType="VARCHAR"/>
<result property="lastUpdate" column="last_update" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
staff_id,first_name,last_name,
address_id,email,store_id,
active,username,password,
last_update,picture
</sql>
<select id="getPasswordByUsername" resultType="com.websocket_chat.johnson.domain.Staff">
select * from staff where username =#{name}
</select>
<select id="getUsernameByStaffId" resultType="com.websocket_chat.johnson.domain.Staff">
select * from staff where staff_id =#{id}
</select>
</mapper>
五、主文件
项目结构图
----补充 mybatis-plus + mybatis-x 用法
大家可以参考 这篇文章
https://blog.csdn.net/qq_45134562/article/details/125185816
会生成 domain+mapper+service+ resource.mapper 目录和文件
domain目录下
由mybatis-x 生成的 Staff
/**
*
* @TableName staff
*/
@TableName(value ="staff")
@Data
public class Staff implements Serializable {
/**
*
*/
@TableId(type = IdType.AUTO)
private Long staffId;
/**
*
*/
private String firstName;
/**
*
*/
private String lastName;
/**
*
*/
private Short addressId;
/**
*
*/
private String email;
/**
*
*/
private Integer storeId;
/**
*
*/
private Boolean active;
/**
*
*/
private String username;
/**
*
*/
private String password;
/**
*
*/
private Date lastUpdate;
/**
*
*/
private byte[] picture;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
@Override
public boolean equals(Object that) {
if (this == that) {
return true;
}
if (that == null) {
return false;
}
if (getClass() != that.getClass()) {
return false;
}
Staff other = (Staff) that;
return (this.getStaffId() == null ? other.getStaffId() == null : this.getStaffId().equals(other.getStaffId()))
&& (this.getFirstName() == null ? other.getFirstName() == null : this.getFirstName().equals(other.getFirstName()))
&& (this.getLastName() == null ? other.getLastName() == null : this.getLastName().equals(other.getLastName()))
&& (this.getAddressId() == null ? other.getAddressId() == null : this.getAddressId().equals(other.getAddressId()))
&& (this.getEmail() == null ? other.getEmail() == null : this.getEmail().equals(other.getEmail()))
&& (this.getStoreId() == null ? other.getStoreId() == null : this.getStoreId().equals(other.getStoreId()))
&& (this.getActive() == null ? other.getActive() == null : this.getActive().equals(other.getActive()))
&& (this.getUsername() == null ? other.getUsername() == null : this.getUsername().equals(other.getUsername()))
&& (this.getPassword() == null ? other.getPassword() == null : this.getPassword().equals(other.getPassword()))
&& (this.getLastUpdate() == null ? other.getLastUpdate() == null : this.getLastUpdate().equals(other.getLastUpdate()))
&& (Arrays.equals(this.getPicture(), other.getPicture()));
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((getStaffId() == null) ? 0 : getStaffId().hashCode());
result = prime * result + ((getFirstName() == null) ? 0 : getFirstName().hashCode());
result = prime * result + ((getLastName() == null) ? 0 : getLastName().hashCode());
result = prime * result + ((getAddressId() == null) ? 0 : getAddressId().hashCode());
result = prime * result + ((getEmail() == null) ? 0 : getEmail().hashCode());
result = prime * result + ((getStoreId() == null) ? 0 : getStoreId().hashCode());
result = prime * result + ((getActive() == null) ? 0 : getActive().hashCode());
result = prime * result + ((getUsername() == null) ? 0 : getUsername().hashCode());
result = prime * result + ((getPassword() == null) ? 0 : getPassword().hashCode());
result = prime * result + ((getLastUpdate() == null) ? 0 : getLastUpdate().hashCode());
result = prime * result + (Arrays.hashCode(getPicture()));
return result;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getClass().getSimpleName());
sb.append(" [");
sb.append("Hash = ").append(hashCode());
sb.append(", staffId=").append(staffId);
sb.append(", firstName=").append(firstName);
sb.append(", lastName=").append(lastName);
sb.append(", addressId=").append(addressId);
sb.append(", email=").append(email);
sb.append(", storeId=").append(storeId);
sb.append(", active=").append(active);
sb.append(", username=").append(username);
sb.append(", password=").append(password);
sb.append(", lastUpdate=").append(lastUpdate);
sb.append(", picture=").append(picture);
sb.append(", serialVersionUID=").append(serialVersionUID);
sb.append("]");
return sb.toString();
}
}
自个儿创建的Message
@Data
public class Message {
private String from;
private String to;
private String text;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private Date date;
}
自个儿创建的User
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
Long uid;
String name;
}
mapper目录下
由mybatis-x 生成的StaffMapper
/**
* @Entity generator.domain.Staff
*/
//@Component
public interface StaffMapper extends BaseMapper<Staff> {
Staff getPasswordByUsername(String name);
Staff getUsernameByStaffId(long id);
}
service目录下
由mybatis-x 生成的StaffService 接口
public interface StaffService extends IService<Staff> {
String getPasswordByUsername(String name);
String getUsernameByStaffId(long id);
Long getUidByName(String name);
}
WebSocketService
@ServerEndpoint("/webSocket/{username}")
@Component
public class WebSocketService {
//静态变量,用来记录当前在线用户数,设计为线程安全
private static AtomicInteger onlineNum = new AtomicInteger();
//
private static ConcurrentHashMap<String,Session> sessionPools =new ConcurrentHashMap<>();
//发送消息
public void sendMessage(Session session, String message) throws IOException {
if(session!=null){
synchronized (session){
System.out.println("发送数据:"+message);
session.getBasicRemote().sendText(message);
}
}
}
//给指定用户发送消息
public void sendInfo(String username,String message){
Session session = sessionPools.get(username);
try {
sendMessage(session,message);
} catch (IOException e) {
e.printStackTrace();
}
}
//群发消息
public void broadCast(String message){
for (Session session : sessionPools.values()) {
try {
sendMessage(session,message);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
// 建立连接成功调用
@OnOpen
public void onOpen(Session session, @PathParam("username") String username){
sessionPools.put(username,session);
addOnlineNum();
System.out.println(username+"加入聊天室,现在的人数是:"+onlineNum);
Message message = new Message();
message.setDate(new Date());
message.setTo("0");
message.setText(username);
broadCast(JSON.toJSONString(message));
}
//关闭连接时调用
@OnClose
public void onClose(@PathParam("username") String username){
sessionPools.remove(username);
subOnlineNum();
System.out.println(username+"退出聊天室,现在聊天人数是:"+onlineNum);
Message message = new Message();
message.setText(username);
message.setDate(new Date());
message.setTo("-2");
broadCast(JSON.toJSONString(message));
}
//收到客户端信息后,根据接收人的username来群发或者私发
// -1 为群发
@OnMessage
public void onMessage(String message){
System.out.println("服务器接收到消息:"+message);
Message msg = JSON.parseObject(message,Message.class);
msg.setDate(new Date());
if(msg.getTo().equals("-1")){
//群发
broadCast(JSON.toJSONString(msg,true));
}else {
sendInfo(msg.getTo(), JSON.toJSONString(msg,true));
}
}
@OnError
public void onError(Session session, Throwable throwable){
System.out.println("发生错误");
throwable.printStackTrace();
}
//增加用户数
public static void addOnlineNum(){
onlineNum.incrementAndGet();
}
//减少用户数
public static void subOnlineNum(){
onlineNum.decrementAndGet();
}
public static AtomicInteger getOnlineNum(){
return onlineNum;
}
public static ConcurrentHashMap<String,Session> getSessionPools(){
return sessionPools;
}
}
由mybatis-x生成的Service实现类 impl的StaffServiceImpl
*/
@Service
public class StaffServiceImpl extends ServiceImpl<StaffMapper, Staff>
implements StaffService {
@Autowired
private StaffMapper staffMapper;
@Override
public String getPasswordByUsername(String name) {
Staff staff = staffMapper.getPasswordByUsername(name);
if(staff !=null)
return staff.getPassword();
else
return null;
}
@Override
public String getUsernameByStaffId(long id) {
Staff staff = staffMapper.getUsernameByStaffId(id);
if(staff !=null)
return staff.getUsername();
else
return null;
}
@Override
public Long getUidByName(String name) {
Staff staff = staffMapper.getPasswordByUsername(name);
if(staff !=null)
return staff.getStaffId();
else
return null;
}
}
controller目录下
ChatController
@Controller
public class ChatController {
@Autowired
private StaffService staffService;
@RequestMapping("/onlineusers")
@ResponseBody
public Set<String> onlineUser(@RequestParam("currentuser") String currentUser) {
ConcurrentHashMap<String, Session> sessionPools = WebSocketService.getSessionPools();
Set<String> set = sessionPools.keySet();
Iterator<String> iterator = set.iterator();
HashSet<String> nameSet = new HashSet<>();
while (iterator.hasNext()) {
String next = iterator.next();
if (!next.equals(currentUser)) {
nameSet.add(next);
}
}
return nameSet;
}
@RequestMapping("/getuid")
@ResponseBody
public User getUid(@RequestParam("username") String username){
Long uid = staffService.getUidByName(username);
return new User(uid,null);
}
}
LoginController
@Controller
public class LoginController {
@Autowired
private StaffService staffService;
@RequestMapping("/loginvalidate")
public String loginvalidate(@RequestParam("username") String username, @RequestParam("password") String password, HttpSession httpSession){
if(username==null){
return "login";
}
String realPwd = staffService.getPasswordByUsername(username);
if(realPwd!=null && realPwd.equals(password)){
Long uid = staffService.getUidByName(username);
System.out.println("登录成功:"+uid);
httpSession.setAttribute("uid",uid);
return "chatroom";
}else {
return "fail";
}
}
@RequestMapping("/login")
public String login(){
return "login";
}
@RequestMapping("/logout")
public String logout(){
return "login";
}
@GetMapping("/currentuser")
@ResponseBody
public User currentUser(HttpSession httpSession){
Long uid = (Long) httpSession.getAttribute("uid");
String username = staffService.getUsernameByStaffId(uid);
System.out.println("用户名:"+username);
return new User(uid,username);
}
}
config目录下
WebSocketConfig
@Configuration
public class WebSocketConfig {
// 自动注册使用@ServerEndPoint 注解声明的websocket endpoint
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
main 主入口
JohnsonApplication
@SpringBootApplication
@MapperScan("com.websocket_chat.johnson.mapper")
public class JohnsonApplication {
public static void main(String[] args) {
SpringApplication.run(JohnsonApplication.class, args);
}
}
六、前端渲染文件(暂时还是copy)
前端结构图
static目录下
存放的是前端资源,使用的是 bootstrap+jquery来美化前端。
templates目录下
chatroom
<!DOCTYPE>
<html>
<head>
<title>聊天室</title>
<script src="./js/jquery-1.12.3.min.js"></script>
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css"/>
<script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<style>
body{
margin-top:5px;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-md-3">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">当前登录用户</h3>
</div>
<div class="panel-body">
<div class="list-group">
<a href="#" class="list-group-item">你好,<span id="user"></span></a>
<a href="logout" class="list-group-item">退出</a>
</div>
</div>
</div>
<div class="panel panel-primary" id="online">
<div class="panel-heading">
<h3 class="panel-title">当前在线的其他用户</h3>
</div>
<div class="panel-body">
<div class="list-group" id="users">
</div>
</div>
</div>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">群发系统广播</h3>
</div>
<div class="panel-body">
<input type="text" class="form-control" id="msg" /><br>
<button id="broadcast" type="button" class="btn btn-primary">发送</button>
</div>
</div>
</div>
<div class="col-md-9">
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title" id="talktitle"></h3>
</div>
<div class="panel-body">
<div class="well" id="log-container" style="height:400px;overflow-y:scroll">
</div>
<input type="text" id="myinfo" class="form-control col-md-12" /> <br>
<button id="send" type="button" class="btn btn-primary">发送</button>
</div>
</div>
</div>
</div>
</div>
<script>
Date.prototype.format = function(fmt) {
var o = {
"M+" : this.getMonth()+1, //月份
"d+" : this.getDate(), //日
"h+" : this.getHours(), //小时
"m+" : this.getMinutes(), //分
"s+" : this.getSeconds(), //秒
"q+" : Math.floor((this.getMonth()+3)/3), //季度
"S" : this.getMilliseconds() //毫秒
};
if(/(y+)/.test(fmt)) {
fmt=fmt.replace(RegExp.$1, (this.getFullYear()+"").substr(4 - RegExp.$1.length));
}
for(var k in o) {
if(new RegExp("("+ k +")").test(fmt)){
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));
}
}
return fmt;
}
$(document).ready(function() {
var user;
var uid;
// 指定websocket路径
var websocket;
$.get("/currentuser",function(data){
user = data.name;
uid = data.uid;
$("#user").html(user);
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/webSocket/"+user);
}
websocket.onmessage = function(event) {
var data=JSON.parse(event.data);
if(data.to==0){//上线消息
if(data.text!=user)
{
$("#users").append('<a href="#" οnclick="talk(this)" class="list-group-item">'+data.text+'</a>');
alert(data.text + "上线了");
}
}else if(data.to==-2){//下线消息
if(data.text!=user)
{
$("#users > a").remove(":contains('"+data.text+"')");
alert(data.text + "下线了");
}
}else {
// 普通消息
// 接收服务端的实时消息并添加到HTML页面中
$("#log-container").append("<div class='bg-info'><label class='text-danger'>"+data.from+" "+data.date+"</label><div class='text-success'>"+data.text+"</div></div><br>");
// 滚动条滚动到最低部
scrollToBottom();
}
};
$.post("/onlineusers?currentuser="+user,function(data){
for(var i=0;i<data.length;i++)
$("#users").append('<a href="#" οnclick="talk(this)" class="list-group-item">'+data[i]+'</a>');
});
});
$("#broadcast").click(function(){
var data = {};
data["from"] = "系统消息";
data["to"] = -1;
data["text"] = $("#msg").val();
websocket.send(JSON.stringify(data));
});
$("#send").click(function() {
if ($("body").data("to")==undefined) {
alert("请选择聊天对象");
return false;
}
var data = {};
data["from"] = user;
data["to"] = $("body").data("to");
data["text"] = $("#myinfo").val();
websocket.send(JSON.stringify(data));
$("#log-container").append("<div class='bg-success'><label class='text-info'>我 " + new Date().format("yyyy-MM-dd hh:mm:ss") + "</label><div class='text-info'>" + $("#myinfo").val() + "</div></div><br>");
scrollToBottom();
$("#myinfo").val("");
});
});
function talk(a){
$("#talktitle").text("与"+a.innerHTML+"的聊天");
$("body").data("to",a.innerHTML);
}
function scrollToBottom(){
var div = document.getElementById('log-container');
div.scrollTop = div.scrollHeight;
}
</script>
</body>
</html>
fail
<script>
alert("密码错误");
window.history.go(-1);
</script>
login
<!DOCTYPE>
<html>
<head>
<title>login</title>
<script src="./js/jquery-1.12.3.min.js"></script>
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<style type="text/css">
.vertical-center{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
</head>
<body>
<div class="container vertical-center">
<div class="col-md-6 col-md-offset-3">
<form action="loginvalidate" method="post">
<h2 >登录聊天室</h2>
<label for="inputEmail" class="sr-only">userid</label>
<input type="text" name="username" id="inputEmail" class="form-control" placeholder="userid" required autofocus/>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required/>
<label for="inputEmail" class="sr-only">userid</label>
<button class="btn btn-lg btn-primary btn-block" type="submit">login</button>
</form>
</div>
</div> <!-- /container -->
</body>
</html>
七、学习总结
1 核心 websocket使用
2 设计线程安全方法
八、相关报错
错误一 引入mybatis-plus依赖错误
[ERROR] [18:13:57] org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.websocket_chat.johnson.mapper.StaffMapper.getPasswordByUsername] with root cause
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.websocket_chat.johnson.mapper.StaffMapper.getPasswordByUsername
原因:
开始时我用 如下依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.4.2</version>
</dependency>
需改为
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>