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+"&nbsp;"+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'>我&nbsp;" + 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>
  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Johnsonbug_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值