WebSocket高級

9 篇文章 0 订阅
9 篇文章 0 订阅

WebSocket高級

效果展示

用户登录界面

登录成功后的聊天界面(发送消息给指定用户)

一对一消息推送

在这里插入图片描述

用户接收消息界面

用户接收到消息后会显示接收到的并且未读消息条数(未查看才会有)

在这里插入图片描述

当点击查看了消息,未读条数将会消失

此时便实现了一对一消息的发送和接收
右下角通过群发的模式,将消息推送给所有在线的用户

在这里插入图片描述

项目目录及代码展示

在这里插入图片描述

SpringBoot版本号为 2.1.5.RELEASE

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5.RELEASE</version>
</parent>
项目的pom文件
<!--  springboot配置信息  -->
<properties>
    <java.version>1.8</java.version>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

<dependencies>
    <!--  springboot-Web核心  -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- WebSocket组件 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <!-- 热部署依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
        <scope>true</scope>
    </dependency>
    <!-- ****************************以下工具为非必须**************************** -->
    <!--  lang3工具  -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
	<!--  hutool工具  -->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.7.16</version>
    </dependency>
    <!-- slf4j日志处理 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
    </dependency>
    <!--  fastjson  -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.55</version>
    </dependency>
	<!--  mysql  -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
	<!--  mybatis-plus  -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus</artifactId>
        <version>3.2.0</version>
    </dependency>
	<!--  SpringBoot整合MyBatis-Plus  -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.2.0</version>
    </dependency>
	<!--  redis模板  -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
	<!--  thymeleaf模板引擎  -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
	<!--  JWT工具  -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    <!--  httpclient数据加密工具  -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!-- 打jar包时 如果 不配置该插件,打出来的jar包没有清单文件 -->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>
项目中的配置文件
###########################【基础配置】#########################
spring.banner.charset=UTF-8
spring.messages.encoding=UTF-8
server.servlet.encoding.charset=UTF-8
server.servlet.encoding.force=false
server.servlet.encoding.enabled=false
server.port=80
spring.application.name=ws
server.servlet.session.timeout=3600s

###########################【MySQL配置】#########################
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://110.42.177.172:3306/wsapp?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root

###########################【MyBatis】#########################
mybatis.mapper-locations=classpath*:mapper/*.xml
mybatis.type-aliases-package=cn.molu.app.pojo
###########################【MyBatis-Plus】#########################
mybatis-plus.mapper-locations=classpath*:mapper/*.xml
mybatis-plus.type-aliases-package=cn.molu.ws.pojo
# 表名前缀
mybatis-plus.global-config.db-config.table-prefix=tb_
# id策略为自增长
mybatis-plus.global-config.db-config.id-type=auto

###########################【Web】#########################
# 静态资源访问权限
spring.web.resources.static-locations=classpath:/resources/,classpath:/static/,classpath:/templates/

#spring.mvc.servlet.path=classpath:/static/views
spring.thymeleaf.cache=false
spring.thymeleaf.check-template=true
spring.thymeleaf.enabled=true
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML5

# mvc静态资源配置  thymeleaf 前后缀  默认静态页面是在resource/templeats/ 下面的资源
spring.mvc.static-path-pattern=/**
# thymeleaf默认在templates下
# spring.mvc.view.prefix=/templeats/
# thymeleaf默认后缀为.html
# spring.mvc.view.suffix=.html

###########################【Redis】#########################
# Redis数据库索引(默认为0)
spring.redis.database=1
# Redis服务器地址
spring.redis.host=110.42.177.172
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=moluroot
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-idle=10
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=5000ms
# 连接池中的最大空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒) 30s
spring.redis.timeout=30000ms

###########################【Jpa】#########################
# 让hibernate的sql语句显示出来,这样才知道到底是通过 Redis 取到的数据,还是依然是从数据库取到的数据
spring.jpa.show-sql=true

###########################【JWT盐值】#########################
#盐 值    jwt单点登录盐值   生成密文token   根据需求生成MD5密文盐值,该盐值是UUID串
jwt.secret=2d31f8324db94f99b37fdd16c4ac787a

###########################【热部署】#########################
# 重启目录
spring.devtools.restart.additional-paths=src/main/java
# 设置开启热部署
spring.devtools.restart.enabled=true
# 设置字符集
spring.freemarker.charset=utf-8
# 页面不加载缓存,修改后立即生效
spring.freemarker.cache=false
java中的配置类

配置HttpSession对象,方便在WebSocket核心类中使用 HttpSession

import java.util.Map;
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
/**
 * @tite 用来获取HttpSession对象.
 * @author 陌路
 * @date 2022-04-16 上午12:27:38
 */
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {
    /**
	 * @title 该配置可以在不手动传入HttpSession的情况下在websocket服务类中使用
	 */
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, 
                                HandshakeRequest request, 
                                HandshakeResponse response) {
        // 获取httpsession对象
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        // 存放httpsession对象
        Map<String, Object> userProperties = sec.getUserProperties();
        if (httpSession == null || HttpSession.class == null) {
            return;
        }
        userProperties.put(HttpSession.class.getName(), httpSession);
    }
}
Redis序列化配置

对存储在redis中的数据进行序列化的统一配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
 * redis序列化配置类
 * @author 陌路
 * @date 2022-05-02 上午1:55:50
 */
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lcf) {
        RedisTemplate<String, Object> restTemplate = new RedisTemplate<String, Object>();
        // 为String类型的key设置序列化
        restTemplate.setKeySerializer(new StringRedisSerializer());
        // 为String类型的value设置序列化
        restTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        // 为Hash类型的key设置序列化
        restTemplate.setHashKeySerializer(new StringRedisSerializer());
        // 为Hash类型的value设置序列化
        restTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        restTemplate.setConnectionFactory(lcf);
        return restTemplate;
    }
}
WebConfig配置
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
 * @title web配置类 设置默认访问页面
 * @author 陌路
 * @date 2021/8/15
 * @apiNote 配置默认页面
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
	/**
	 * 自定义静态路径,spring.resource.static-locations=classpath:/static/
	 */
	@Override
	public void addViewControllers(ViewControllerRegistry registry) {
		registry.addViewController("/").setViewName("/chat/login");
	}
}
WebSocket核心配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
 * @Desc 首先需要注入 ServerEndpointExporter , 这个bean会自动注册使用 @ServerEndpoint
 *       的注解来声明WebSocket endpoint。
 *       注意:如果使用独立的Servlet容器,而不是直接使用SpringBoot内置容器,就不需要注入
 *       ServerEndpointExporter,因为他将有容器自己提供和管理。
 * @author 陌路
 * @date 2022-04-16
 */
@Configuration
public class WebSocketConfig {
    /**
	 * @title 扫描注册使用 @ServerEndpoint 注解的类
	 * @return ServerEndpointExporter
	 */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
WS中使用Redis

在WebSocket核心类中无法直接使用Redis工具类,需要使用以下配置类

在WebSocket核心类中需要使用 SpringUtils.getBean(RedisUtils.class); 来获取Redis工具类对象
private RedisUtils redisUtils = SpringUtils.getBean(RedisUtils.class);

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

@Component
public final class SpringUtils implements BeanFactoryPostProcessor {

    private static final Logger LOGGER = LoggerFactory.getLogger(SpringUtils.class);
    private static ConfigurableListableBeanFactory beanFactory;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        SpringUtils.beanFactory = beanFactory;
    }

    public static ConfigurableListableBeanFactory getBeanFactory() {
        return beanFactory;
    }

    /**
     * 获取对象
     * @return Object 一个以所给名字注册的bean的实例
     * @throws org.springframework.beans.BeansException
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException {
        if (getBeanFactory() == null) {
            //zhengkai.blog.csdn.net
            LOGGER.info("本地调试Main模式,没有BeanFactory,忽略错误");
            return null;
        } else {
            T result = (T) getBeanFactory().getBean(name);
            return result;
        }
    }

    /**
     * 获取类型为requiredType的对象
     * @throws org.springframework.beans.BeansException
     */
    public static <T> T getBean(Class<T> name) throws BeansException {
        if (getBeanFactory() == null) {
            LOGGER.info("本地调试Main模式,没有BeanFactory,忽略错误");
            return null;
        } else {
            T result = (T) getBeanFactory().getBean(name);
            return result;
        }
    }

    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
     */
    public static boolean containsBean(String name) {
        return getBeanFactory().containsBean(name);
    }

    /**
     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 
     * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
        return getBeanFactory().isSingleton(name);
    }

    /**
     * @return Class 注册对象的类型
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     */
    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
        return getBeanFactory().getType(name);
    }

    /**
     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
        return getBeanFactory().getAliases(name);
    }

}
User实体类对象
/**
 * (User)实体类
 * @author 陌路
 * @since 2022-04-21 16:53:50
 */
@Data
@TableName("tb_user")
public class User implements Serializable {
    private static final long serialVersionUID = 477020946096486016L;
    /** 用户id */
    private Integer id;
    /** 用户名 */
    private String username;
    /** 用户手机号 */
    private String phone;
    /**  用户密码 */
    private String password;
}
Result数据返回对象
/**
 * @Desc 数据的响应类.
 * @author 陌路
 * @date 2022-04-16 上午10:53:15
 */
@Data
public class Result {
	/** 响应标志,成功/失败 */
	private boolean flag;
	/** 响应给前台的消息 */
	private String message;
	/** 用户名 */
	private String username;
	/** 用户id */
	private String userId;
	/** 日期时间  */
	private String dateStr;
}
消息返回实体对象
/**
 * 服务端发送给客户端的消息.
 */
@Data
public class ResultMessage {
	/** 是否是系统消息 */
	private Boolean systemMsgFlag;
	/** 发送方姓名 */
	private String fromName;
	/** 发送方id */
	private String fromId;
	/** 接收方姓名 */
	private String toName;
	/** 接收方id */
	private String toId;
	/** 发送的数据 */
	private Object message;
	/** 接收到消息的日期时间 */
	private String dateStr;
	/** 存储对象数据 */
	private Map<?, ?> map;
}
接收消息实体对象
/**
 * @Desc 浏览器发送给服务器的websocket数据.
 */
@Data
public class Message {
	/** 发送方姓名 */
	private String fromName;
	/** 发送方id */
	private String fromId;
	/** 接收方 */
	private String toName;
	/** 接收方id */
	private String toId;
	/** 发送的数据(接收到的数据) */
	private String message;
	/** 未读消息的数量 */
	private int count;
	/** 接收到消息的日期时间 */
	private String dateStr;
}
Mapper对象
import org.apache.ibatis.annotations.Mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import cn.molu.app.pojo.User;
/**
 * (User)表数据库访问层
 * @author 陌路
 * @since 2022-04-21 16:55:04
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
Service接口类
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import cn.molu.app.pojo.User;
import cn.molu.app.vo.R;
/**
 * (User)表服务接口
 * @author 陌路
 * @since 2022-04-21 16:55:04
 */
public interface UserService {
	/**
	 * 用户登录
	 * @return R
	 */
	R login(String phone, String password, Map<String, Object> params, HttpServletResponse res);
	User queryUserByToken(String token);
    String getToken(User user) throws Exception;
}
Service接口实现类
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.molu.app.mapper.UserMapper;
import cn.molu.app.pojo.User;
import cn.molu.app.service.UserService;
import cn.molu.app.utils.ObjectUtils;
import cn.molu.app.utils.RedisUtils;
import cn.molu.app.vo.R;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

/**
 * (User)表服务实现类
 * @author 陌路
 * @since 2022-04-21 16:55:04
 */
@Service("userService")
public class UserServiceImpl implements UserService {

	private static final ObjectMapper MAPPER = new ObjectMapper();
	@Resource
	private RedisTemplate<String, Object> redisTemplate;
	@Resource
	private UserMapper userMapper;
	@Resource
	private RedisUtils redisUtils;
	@Value("${jwt.secret}")
	private String secret;
    
	@Override
	public R login(String phone, String password, Map<String, Object> params, HttpServletResponse res) {
		User user = userMapper.selectOne(new QueryWrapper<User>().eq("phone", phone).eq("deleted", "0"));
		ObjectUtils.checkNull(res, user, String.format("未获取到%s的数据信息!", phone));
		String md5Pwd = DigestUtils.md5Hex(secret + password);
		if (!StringUtils.equals(md5Pwd, user.getPassword())) {
			return R.err("密码输入错误!");
		}
		user.setPassword("");
		String token = getToken(user);
		String userId = ObjectUtils.getStr(user.getId());
		this.redisUtils.setObj(userId, user, Duration.ofDays(2));
		return R.ok().put("token", token);
	}

	/**
	 * 解析token,获取user数据信息 对token进行检测,如果token存在,则解析出user数据信息 
	 * 如果token不存在,则return null
	 * 除注册和发送验证码外不需要检测token外,其他功能均需要检测token
	 */
	@Override
	public User queryUserByToken(String token) {
		try {
			String redisTokenKey = "TOKEN_" + token;
			String cacheData = ObjectUtils.getStr(this.redisTemplate.opsForValue().get(redisTokenKey));
			if (StringUtils.isNotEmpty(cacheData)) {
				this.redisTemplate.expire(redisTokenKey, 1, TimeUnit.HOURS);
				return MAPPER.readValue(cacheData, User.class);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 生成Token,并把Token放到Redis中
	 */
	@Override
	public String getToken(User user) {
		String token = "";
		Map<String, Object> claims = new HashMap<>();
		claims.put("id", user.getId());
		claims.put("name", user.getUsername());
		claims.put("phone", user.getPhone());
		claims.put("userCode", user.getUserCode());
		token = Jwts.builder().setClaims(claims)
				.signWith(SignatureAlgorithm.HS256, secret)
				.compact();
		String redisTokenKey = "TOKEN_" + token;
		try {
			this.redisTemplate.opsForValue().set(redisTokenKey, MAPPER.writeValueAsString(user), Duration.ofHours(3));
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}
		String loginState = "LOGIN_STATE_" + user.getPhone();
		String stateVal = "1&@&" + redisTokenKey;
		this.redisTemplate.opsForValue().set(loginState, stateVal, Duration.ofDays(2));
		return token;
	}
}
后台访问接口类
import java.time.Duration;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import cn.hutool.core.lang.Validator;
import cn.molu.app.pojo.User;
import cn.molu.app.service.UserService;
import cn.molu.app.utils.ObjectUtils;
import cn.molu.app.utils.RedisUtils;
import cn.molu.app.vo.R;

@Controller
@RequestMapping("index")
public class LoginController {
private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);
	@Resource
	private UserService userService;
	@Resource
	private RedisUtils redisUtils;

	/**
	 * 处理用户登录请求.
	 * @param params 用户登录数据
	 * @param resp
	 */
	@ResponseBody
	@PostMapping("/login")
	public R login(@RequestParam Map<String, Object> params, 
                   					HttpServletRequest req, 
                   					HttpServletResponse res) {
		ObjectUtils.checkNull(res, params, "手机号和密码不能为空...");
		String phone = ObjectUtils.getStr(params.get("phone"));
		String pwd = ObjectUtils.getStr(params.get("password"));
		ObjectUtils.checkNull(res, phone, pwd, "手机号或密码输入错误...");
		boolean isMobile = Validator.isMobile(phone);
		if (!isMobile) {
			return R.err("手机号格式错误...");
		}
		return this.userService.login(phone, pwd, params, res);
	}

	/**
	 * 登录成功后跳转到聊天页面.
	 * @return String 跳转到聊天室,如果没有登录,则返回登录页面
	 */
	@GetMapping("/toChatroom/{token}")
	public ModelAndView toChatroom(@PathVariable("token") String token, HttpSession session) {
		ModelAndView mv = null;
		if (ObjectUtils.isEmpty(token)) {
			return new ModelAndView("/chat/login");
		}
		User user = this.userService.queryUserByToken(token);
		// token过期
		if (ObjectUtils.isEmpty(user)) {
			return new ModelAndView("/chat/login");
		}
		String userId = ObjectUtils.getStr(user.getId());
		String username = user.getUsername();
		mv = new ModelAndView("/views/chat");
		mv.addObject("name", username);
		mv.addObject("id", userId);
		redisUtils.setObj(userId, user, Duration.ofDays(1));
		return mv;
	}
    
    // 清除未读消息的条数
	@ResponseBody
	@PostMapping("/clearCount")
	public void setCount(HttpServletRequest req, HttpServletResponse res) {
		String fromId = req.getParameter("fromId"); 
		String userId = req.getParameter("userId");
		if (StringUtils.isNotBlank(fromId) && StringUtils.isNotBlank(userId)) {
			try {
				String redisCountKey = "UN_READ_MSG_COUT_" + fromId + "_" + userId;
				if (this.redisUtils.isExists(redisCountKey)) {
					this.redisUtils.remove(redisCountKey);
				}
				ObjectUtils.printJsonMsg(res, true, "清除成功", 0);
			} catch (Exception e) {
				LOGGER.info("清除未读消息时出现异常...{}", e.getMessage());
				e.printStackTrace();
			}
		}
	}

    // 设置未读消息条数
	@ResponseBody
	@PostMapping("/setCount")
	public void getCount(HttpServletRequest req, HttpServletResponse res) {
		String fromId = req.getParameter("fromId"); 
		String userId = req.getParameter("userId");
		String count = ObjectUtils.getStr(req.getParameter("count"));
		if (StringUtils.isNotBlank(fromId) && StringUtils.isNotBlank(userId)) {
			try {
				count = ObjectUtils.getStr(Integer.valueOf(count) + 1);
				String redisCountKey = "UN_READ_MSG_COUT_" + fromId + "_" + userId;
				this.redisUtils.setStr(redisCountKey, count, Duration.ofDays(7));// 有效期为7天
				ObjectUtils.printJsonMsg(res, true, "设置成功", count);
			} catch (Exception e) {
				LOGGER.info("获取未读消息时出现异常...{}", e.getMessage());
				e.printStackTrace();
			}
		}
	}
}
Object工具类封装
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializeConfig;

import cn.molu.app.handle.BusinessException;
import cn.molu.app.vo.ResponseVo;
import lombok.extern.slf4j.Slf4j;

/**
 * 对象工具类
 * 
 * @Description
 * @author 陌路
 * @date 2022-07-22 下午6:50:47
 */
@Slf4j
public class ObjUtils {

	/**
	 * 获取字符串数据
	 * 
	 * @desc 对象转为字符串
	 * @param obj
	 * @return String
	 */
	public static String getStr(Object obj) {
		if (null == obj)
			return "";
		if (obj instanceof String)
			if ("".equals(obj) || obj.toString().trim().length() < 1 || StringUtils.isBlank((String) obj))
				return "";
		return obj.toString().trim();
	}

	/**
	 * 判断对象是否为空
	 * 
	 * @desc 仅当对象为null或者没值时返回true
	 * @param obj
	 * @return boolean
	 */
	public static boolean isEmpty(Object obj) {
		if (null == obj)
			return true;
		if (obj instanceof String)
			return StringUtils.isBlank(getStr(obj));
		if (obj instanceof Collection<?>)
			return ((Collection<?>) obj).isEmpty();
		if (obj instanceof Map<?, ?>)
			return ((Map<?, ?>) obj).isEmpty();
		if (obj instanceof Object[])
			return Arrays.asList((Object[]) obj).isEmpty();
		return ObjectUtils.isEmpty(obj);
	}

	/**
	 * 判断集合数据内容是否为空
	 * 
	 * @desc 仅当所有值都为null||""时返回true
	 * @param c
	 * @return boolean
	 */
	public static boolean collIsBlank(Collection<?> c) {
		if (null == c || c.isEmpty())
			return true;
		int i = 0;
		for (Object v : c = new HashSet<>(c)) {
			if (isEmpty(v))
				i++;
		}
		return c.size() == i;
	}

	/**
	 * 判断Map集合内容是否为空
	 * 
	 * @desc 仅当所有值都为null||""时返回true
	 * @param m
	 * @return boolean
	 */
	public static boolean mapIsBlank(Map<?, ?> m) {
		if (null == m || m.isEmpty())
			return true;
		if (m.keySet().isEmpty())
			return true;
		return collIsBlank(m.values());
	}

	/**
	 * @desc 仅当有一个值都为null||""时返回true
	 * @param obj
	 * @return boolean
	 */
	public static boolean isEmpty(Object... obj) {
		if (null == obj)
			return true;
		for (Object o : obj) {
			if (StringUtils.isBlank(getStr(o)))
				return true;
		}
		return false;
	}

	/**
	 * JSON数据转Map集合
	 * 
	 * @param json
	 * @return Map<?,?>
	 */
	public static Map<?, ?> jsonToMap(String s) {
		if (StringUtils.isBlank(s))
			return new HashMap<String, String>();
		return JSONObject.parseObject(s, Map.class);
	}

	/**
	 * Map集合转Object
	 * 
	 * @return T
	 */
	public static <T> T mapToObj(Map<String, Object> map, Class<T> obj) {
		if (null == obj || null == map || map.isEmpty())
			return null;
		return JSONObject.parseObject(toJSON(map), obj);
	}

	/**
	 * @title 对象数据转JSONString数据
	 * @Desc 实现了 SerializeConfig.globalInstance
	 */
	public static String toJSON(Object obj) {
		if (isEmpty(obj))
			return "";
		return getStr(JSONObject.toJSON(obj, SerializeConfig.globalInstance));
	}

	/**
	 * JSON数据转对象
	 * 
	 * @return T
	 */
	public static <T> T toObj(String jsonStr, Class<T> obj) {
		if (StringUtils.isBlank(jsonStr) || null != obj)
			return null;
		return JSONObject.parseObject(jsonStr, obj);
	}

	/**
	 * 数据检验,有一个值为空则结束执行,并抛出errMsg异常
	 * 
	 * @desc 为true时校验通过
	 * @return boolean
	 */
	public static boolean checkData(String errMsg, Object... params) {
		if (StringUtils.isBlank(errMsg))
			errMsg = ResponseVo.PARAM_ERR_STR;
		if (null == params || isEmpty(params)) {
			log.error("数据校验失败。。。");
			throw new BusinessException(errMsg);
		}
		return true;
	}

	/**
	 * 比较两个对象是否相等
	 * 
	 * @return boolean
	 */
	public static boolean equals(Object o1, Object o2) {
		if (StringUtils.equals(getStr(o1), getStr(o2)) || o1.equals(o2))
			return true;
		return false;
	}

	/**
	 * 比较两个对象是否相等
	 * 
	 * @return boolean
	 */
	public static boolean equalsIgnoreCase(Object o1, Object o2) {
		if (StringUtils.equalsIgnoreCase(getStr(o1), getStr(o2)))
			return true;
		return false;
	}

	/**
	 * 把Map集合转为XML字符串
	 * 
	 * @desc Map的Key和Value必须为String类型
	 * @param m
	 * @return String
	 */
	public static String mapToXML(Map<String, String> m) {
		if (isEmpty(m))
			return "";
		StringBuffer sbf = new StringBuffer();
		m.forEach((k, v) -> {
			if (!isEmpty(k))
				sbf.append(String.format("<%s>%s</%s>", k, getStr(v), k));
		});
		return sbf.toString();
	}

	/**
	 * 替换字符串中的?
	 * 
	 * @param str
	 * @param val
	 * @return String
	 */
	public static String getVal(String str, String... val) {
		String format = "";
		for (String v : val) {
			format = String.format(str, v);
		}
		return format;
	}

	/**
	 * 获取格式化日期 y-M-d H:m:s
	 * 
	 * @return String
	 */
	public static String dateFormat() {
		return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
	}

	/**
	 * @Title 格式化日期
	 * @return String
	 */
	public static String dateFormat(Date date, String pattern) {
		date = "".equals(getStr(date)) ? new Date() : date;
		pattern = "".equals(getStr(pattern)) ? "yyyy-MM-dd HH:mm:ss" : pattern;
		return new SimpleDateFormat(pattern).format(date);
	}

	/**
	 * 获取用户真实IP地址
	 * 
	 * @param request
	 * @desc 不使用request.getRemoteAddr();的原因是有可能用户使用了代理软件方式避免真实IP地址,
	 *       如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值,哪个才是真实IP?
	 *       答案是取X-Forwarded-For中第一个非unknown的有效IP字符串。
	 *       如:X-Forwarded-For:192.168.1.110, 192.168.1.120, 192.168.1.130,
	 *       192.168.1.100 用户真实IP为: 192.168.1.110
	 */
	public static String getIp(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if (isEmpty(ip) || "unknown".equalsIgnoreCase(ip))
			ip = request.getHeader("Proxy-Client-IP");
		if (isEmpty(ip) || "unknown".equalsIgnoreCase(ip))
			ip = request.getHeader("WL-Proxy-Client-IP");
		if (isEmpty(ip) || "unknown".equalsIgnoreCase(ip))
			ip = request.getHeader("HTTP_CLIENT_IP");
		if (isEmpty(ip) || "unknown".equalsIgnoreCase(ip))
			ip = request.getHeader("HTTP_X_FORWARDED_FOR");
		if (isEmpty(ip) || "unknown".equalsIgnoreCase(ip))
			ip = request.getRemoteAddr();
		if (ip.replace(" ", "").replace("unknown,", "").indexOf(",") > 0)
			ip = ip.substring(0, ip.indexOf(","));
		return getStr(ip);
	}

}
RedisUtils工具类
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.DefaultTypedTuple;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

/**
 * redis工具类
 * @Description
 * @author 陌路
 * @date 2022-05-02 下午1:53:20
 */
@Component
public class RedisUtils {

	@Resource
	private RedisTemplate<String, Object> redisTemplate;

	/******************************* String *******************************/

	/**
	 * @title 存储为JSON类型,并设置失效时间
	 * @param Duration.ofDays(0);     天
	 * @param Duration.ofHours(0L);   小时
	 * @param Duration.ofMinutes(0L); 分钟
	 * @param Duration.ofSeconds(0L); 秒
	 * @param Duration.ofMillis(0L);  毫秒
	 */
	public void setJsonObj(String key, Object obj, Duration timeout) {
		String jsonStr = ObjectUtils.toJSON(obj);
		if (ObjectUtils.isEmpty(timeout)) {
			this.redisTemplate.opsForValue().set(key, jsonStr);
		} else {
			this.redisTemplate.opsForValue().set(key, jsonStr, timeout);
		}
	}

	/**
	 * @title 存储为对象类型,并设置失效时间
	 * @param Duration.ofDays(0);     天
	 * @param Duration.ofHours(0L);   小时
	 * @param Duration.ofMinutes(0L); 分钟
	 * @param Duration.ofSeconds(0L); 秒
	 * @param Duration.ofMillis(0L);  毫秒
	 */
	public void setObj(String key, Object obj, Duration timeout) {
		if (ObjectUtils.isEmpty(timeout)) {
			this.redisTemplate.opsForValue().set(key, obj);
		} else {
			this.redisTemplate.opsForValue().set(key, obj, timeout);
		}
	}

	/**
	 * @title 存储为Long类型,并设置失效时间
	 * @param Duration.ofDays(0);     天
	 * @param Duration.ofHours(0L);   小时
	 * @param Duration.ofMinutes(0L); 分钟
	 * @param Duration.ofSeconds(0L); 秒
	 * @param Duration.ofMillis(0L);  毫秒
	 */
	public void setLong(String key, Long num, Duration timeout) {
		this.redisTemplate.opsForValue().set(key, num, timeout);
	}

	/**
	 * @title 存储为String类型,并设置失效时间
	 * @param Duration.ofDays(0);     天
	 * @param Duration.ofHours(0L);   小时
	 * @param Duration.ofMinutes(0L); 分钟
	 * @param Duration.ofSeconds(0L); 秒
	 * @param Duration.ofMillis(0L);  毫秒
	 */
	public void setStr(String key, String val, Duration timeout) {
		this.redisTemplate.opsForValue().set(key, val, timeout);
	}

	/**
	 * @title 存储多条数据
	 * @param map.put("name", "zs"); map.put("age", 18); // key - val
	 */
	public void setMap(Map<String, Object> map) {
		this.redisTemplate.opsForValue().multiSet(map);
	}

	/**
	 * @title 获取多条数据
	 * @param keys.add("name"); keys.add("age"); // 需要获取的key
	 */
	public List<Object> getMap(Collection<String> keys) {
		List<Object> multiGet = this.redisTemplate.opsForValue().multiGet(keys);
		return multiGet;
	}

	/**
	 * @title 存储为Long类型
	 */
	public void setLong(String key, Long num) {
		setLong(key, num, null);
	}

	/**
	 * @title 存储为对象类型
	 */
	public void setObj(String key, Object obj) {
		setObj(key, obj, null);
	}

	/**
	 * @title 存储为JSON类型
	 */
	public void setJsonObj(String key, Object obj) {
		setJsonObj(key, obj, null);
	}

	/**
	 * @title 存储为String类型
	 */
	public void setStr(String key, String val) {
		setJsonObj(key, val);
	}

	/**
	 * @title 根据key获取String
	 */
	public String getStr(String key) {
		return ObjectUtils.getStr(this.redisTemplate.opsForValue().get(key));
	}

	/**
	 * @title 根据key获取JSON
	 */
	public String getJson(String key) {
		return getStr(key);
	}

	/**
	 * @title 根据key获取Object对象
	 */
	public Object getObj(String key) {
		return ObjectUtils.getObj(this.redisTemplate.opsForValue().get(key));
	}

	/**
	 * @title 根据key删除数据
	 */
	public boolean remove(String key) {
		if (ObjectUtils.isEmpty(key)) {
			return false;
		}
		return this.redisTemplate.delete(key);
	}

	/**
	 * @title 删除多条缓存数据
	 */
	public boolean remove(Collection<String> keys) {
		Long del = this.redisTemplate.delete(keys);
		if (del > 0) {
			return true;
		}
		return false;
	}

	/**
	 * @title 删除多条缓存数据
	 */
	public boolean remove(String... keys) {
		if (ObjectUtils.checkArr(keys)) {
			return false;
		}
		Set<String> set = new HashSet<String>(Arrays.asList(keys));
		return remove(set);
	}

	/******************************* List *******************************/

	/**
	 * @title list->左添加
	 */
	public void lPush(String key, Object val) {
		this.redisTemplate.opsForList().leftPush(key, val);
	}

	/**
	 * @title list->左添加,添加多条数据
	 * @return 添加条数
	 */
	public Long lPushAll(String key, Object... val) {
		Long count = this.redisTemplate.opsForList().leftPushAll(key, val);
		if (ObjectUtils.isBlank(count)) {
			return 0L;
		}
		return count;
	}

	/**
	 * @title list->左添加,将newVal添加到existVal的左边
	 * @Desc 左添加 第三个参数会被添加到第二个参数的左边
	 * @return 添加条数
	 */
	public Long lPush(String key, Object existVal, Object newVal) {
		Long count = this.redisTemplate.opsForList().leftPush(key, existVal, newVal);
		return count;
	}

	/**
	 * @title list->右添加
	 */
	public void rPush(String key, Object val) {
		this.redisTemplate.opsForList().rightPush(key, val);
	}

	/**
	 * @title list->右添加,添加多条数据
	 * @return 添加条数
	 */
	public Long rPushAll(String key, Object... val) {
		Long count = this.redisTemplate.opsForList().rightPushAll(key, val);
		if (ObjectUtils.isBlank(count)) {
			return 0L;
		}
		return count;
	}

	/**
	 * @title list->右添加,将newVal添加到existVal的右边
	 * @Desc 右添加 第三个参数会被添加到第二个参数的右边
	 * @return 添加条数
	 */
	public Long rPush(String key, Object existVal, Object newVal) {
		Long count = this.redisTemplate.opsForList().rightPush(key, existVal, newVal);
		return count;
	}

	/**
	 * @title list->获取指定索引位置的数据
	 * @Desc redis的key,开始索引,结束索引
	 * @return 获取到的数据集合
	 */
	public List<Object> getList(String key, long startIndex, long endIndex) {
		List<Object> list = this.redisTemplate.opsForList().range(key, startIndex, endIndex);
		return list;
	}

	/**
	 * @title list->获取List中的所有数据
	 * @return 获取到的数据集合
	 */
	public List<Object> getListAll(String key) {
		List<Object> list = this.redisTemplate.opsForList().range(key, 0, -1);
		return list;
	}

	@SuppressWarnings("unchecked")
	public <T> T getListAlls(String key) {
		return (T) this.redisTemplate.opsForList().range(key, 0, -1);
	}

	/**
	 * @title list->获取List中的数据条数
	 * @return 数据条数
	 */
	public Long getListSize(String key) {
		Long size = this.redisTemplate.opsForList().size(key);
		return ObjectUtils.isBlank(size) ? 0L : size;
	}

	/**
	 * @title list->删除List中的数据
	 * @param redis的key,count删除的条数,val删除的值
	 * @return 删除的数据条数
	 */
	public Long removeList(String key, Long count, Object val) {
		Long rem = this.redisTemplate.opsForList().remove(key, count, val);
		return ObjectUtils.isBlank(rem) ? 0L : rem;
	}

	/**
	 * @title list->删除List中的数据
	 * @param redis的key,val删除的值,默认只删除一条
	 * @return 删除成功返回true,删除失败返回false
	 */
	public boolean removeList(String key, Object val) {
		Long rem = this.redisTemplate.opsForList().remove(key, 1, val);
		return ObjectUtils.isBlank(rem);
	}

	/**
	 * @title list->删除左边的第一条数据
	 * @param redis的key
	 * @return 返回值为删除的值
	 */
	public Object lPop(String key) {
		Object obj = this.redisTemplate.opsForList().leftPop(key);
		return obj;
	}

	/**
	 * @title list->删除右边的第一条数据
	 * @param redis的key
	 * @return 返回值为删除的值
	 */
	public Object rPop(String key) {
		Object obj = this.redisTemplate.opsForList().rightPop(key);
		return obj;
	}

	/******************************* Set *******************************/

	/**
	 * @title set->添加数据
	 * @param redis的key,数组
	 * @return 返回值为添加的条数
	 * @Desc 数据类型 ("redisKey", "val1", "val2", "val3"...)
	 */
	public Long sAdd(String key, Object[] obj) {
		if (ObjectUtils.isBlank(obj)) {
			return 0L;
		}
		return sAdds(key, obj);
	}

	/**
	 * @title set->多数据添加数据
	 * @param redis的key,数据值...
	 * @return 返回值为添加的条数
	 * @Desc 数据类型 ("redisKey", "val1", "val2", "val3"...)
	 */
	public Long sAdds(String key, Object... obj) {
		if (ObjectUtils.isBlank(obj)) {
			return 0L;
		}
		Long count = this.redisTemplate.opsForSet().add(key, obj);
		return count;
	}

	/**
	 * @title set->获取数据
	 * @param redis的key
	 * @return 返回Set集合
	 * @Desc 数据类型 ("redisKey", "val1", "val2", "val3"...)
	 */
	public Set<Object> getSet(String key) {
		if (ObjectUtils.isBlank(key)) {
			return new HashSet<Object>();
		}
		Set<Object> set = this.redisTemplate.opsForSet().members(key);
		return set;
	}

	/**
	 * @title set->获取set集合中的数据条数
	 * @param redis的key
	 * @return 返回Set集合的数据条数
	 * @Desc 数据类型 ("redisKey", "val1", "val2", "val3"...)
	 */
	public Long getSetSize(String key) {
		if (ObjectUtils.isBlank(key)) {
			return 0L;
		}
		Long size = this.redisTemplate.opsForSet().size(key);
		return ObjectUtils.isBlank(size) ? 0L : size;
	}

	/**
	 * @title set->删除set集合中的数据
	 * @param redis的key
	 * @return 返回删除的条数
	 */
	public Long removeSet(String key, Object... obj) {
		if (ObjectUtils.isBlank(obj)) {
			return 0L;
		}
		Long rem = this.redisTemplate.opsForSet().remove(key, obj);
		return ObjectUtils.isBlank(rem) ? 0L : rem;
	}

	/******************************* ZSet *******************************/

	/**
	 * @title ZSet->添加ZSet数据
	 * @param redis的key
	 * @return 成功返回true,失败返回false
	 */
	public boolean addZSet(String key, Object obj, double val) {
		if (ObjectUtils.isBlank(key)) {
			return false;
		}
		return this.redisTemplate.opsForZSet().add(key, obj, val);
	}

	/**
	 * @title ZSet->添加ZSet数据
	 * @param redis的key
	 * @return 成功返回添加的条数
	 */
	public Long addZSet(String key, Map<String, Double> map) {
		if (ObjectUtils.isBlank(map)) {
			return 0L;
		}
		Set<ZSetOperations.TypedTuple<Object>> tupleSet = new HashSet<>();
		for (String k : map.keySet()) {
			tupleSet.add(new DefaultTypedTuple<>(k, map.get(k)));
		}
		Long count = this.redisTemplate.opsForZSet().add(key, tupleSet);
		return ObjectUtils.isBlank(count) ? 0L : count;
	}

	/**
	 * @title ZSet->获取指定的ZSet数据
	 * @param redis的key,开始索引,结束索引
	 * @return 获取到的数据集合
	 */
	public Set<Object> getZSet(String key, long startIndex, long endIndex) {
		if (ObjectUtils.isBlank(key)) {
			return new HashSet<Object>();
		}
		Set<Object> set = this.redisTemplate.opsForZSet().range(key, startIndex, endIndex);
		return set;
	}

	/**
	 * @title ZSet->获取所有ZSet数据
	 * @param redis的key
	 * @return 获取到的数据集合
	 */
	public Set<Object> getZSetAll(String key) {
		if (ObjectUtils.isBlank(key)) {
			return new HashSet<Object>();
		}
		Set<Object> set = getZSet(key, 0, -1);
		return set;
	}

	/**
	 * @title ZSet->删除数据
	 * @return 成功返回删除的条数
	 */
	public Long removeZSet(String key, Object[] obj) {
		if (ObjectUtils.isBlank(obj)) {
			return 0L;
		}
		Long rem = this.redisTemplate.opsForZSet().remove(key, obj);
		return ObjectUtils.isBlank(rem) ? 0L : rem;
	}

	/**
	 * @title ZSet->删除数据
	 * @param remove("key", "val1", "val2");
	 * @return 成功返回删除的条数
	 */
	public Long removeZSets(String key, Object... obj) {
		if (ObjectUtils.isBlank(obj)) {
			return 0L;
		}
		return removeZSet(key, obj);
	}

	/******************************* Expire *******************************/

	/**
	 * @title 给指定的缓存数据添加失效时间
	 * @param TimeUnit.DAYS;         天
	 * @param TimeUnit.HOURS;        小时
	 * @param TimeUnit.MINUTES;      分钟
	 * @param TimeUnit.SECONDS;      秒
	 * @param TimeUnit.MILLISECONDS; 毫秒
	 */
	public void setExpire(String key, Long timeout, TimeUnit unit) {
		this.redisTemplate.expire(key, timeout, unit);
	}

	/**
	 * @title 给指定的缓存数据添加失效时间
	 * @param TimeUnit.DAYS;         天
	 * @param TimeUnit.HOURS;        小时
	 * @param TimeUnit.MINUTES;      分钟
	 * @param TimeUnit.SECONDS;      秒
	 * @param TimeUnit.MILLISECONDS; 毫秒
	 */
	public void setTimeout(String key, Long timeout, TimeUnit unit) {
		setExpire(key, timeout, unit);
	}

	/**
	 * @title 获取失效时间
	 * @param redis的key
	 * @return 失效时长
	 */
	public Long getExpire(String key) {
		if (ObjectUtils.isBlank(key)) {
			return 0L;
		}
		Long timeout = this.redisTemplate.getExpire(key);
		return ObjectUtils.isBlank(timeout) ? 0L : timeout;
	}

	/**
	 * @title 获取指定单位的失效时间
	 * @param TimeUnit.DAYS;         天
	 * @param TimeUnit.HOURS;        小时
	 * @param TimeUnit.MINUTES;      分钟
	 * @param TimeUnit.SECONDS;      秒
	 * @param TimeUnit.MILLISECONDS; 毫秒
	 */
	public Long getExpire(String key, TimeUnit unit) {
		if (ObjectUtils.isBlank(key)) {
			return 0L;
		}
		Long timeout = this.redisTemplate.getExpire(key, unit);
		return ObjectUtils.isBlank(timeout) ? 0L : timeout;
	}

	/******************************* Key *******************************/
	/**
	 * @Title 判断redis中是否包含这个Key
	 * @return Boolean
	 */
	public Boolean isExists(String key) {
		if (StringUtils.isNotBlank(key)) {
			return this.redisTemplate.hasKey(key);
		}
		return false;
	}

	/******************************* END *******************************/

	/**
	 * @title 获取JSON转为对象后的数据
	 */
	@SuppressWarnings("unchecked")
	public <T> T getJsonToObj(String key, Class<T> obj) {
		String jsonStr = ObjectUtils.getStr(this.redisTemplate.opsForValue().get(key));
		if (ObjectUtils.isNotEmpty(jsonStr)) {
			return ObjectUtils.toObj(jsonStr, obj);
		}
		return (T) obj.getClass();
	}
}
数据返回实体封装
import java.io.Serializable;
import java.util.HashMap;

/**
 * @title 数据返回类
 * @author 陌路
 * @date 2022-04-16 下午3:51:04
 */
public class R extends HashMap<String, Object> implements Serializable {

	private static final long serialVersionUID = 1L;
	/** 只返回code值 */
	public static final R OK = R.set("code", 200);
	/** 返回flag和code值 */
	public static final R ok = R.set("code", 200).put("flag", true);

	public static final String PARAM_ERROR = "输入参数错误!";
	public static final String UNKNOW_ERROR = "未知的错误请联系管理员!";

	public R() {
	}

	/**
	 * @title 返回默认信息 flag msg code data
	 * @return R
	 */
	public static R ok() {
		R r = new R();
		r.put("flag", true);
		r.put("msg", "成功!");
		r.put("code", 200);
		r.put("data", null);
		return r;
	}

	/**
	 * @param <T>
	 * @title 添加返回值对象data
	 * @return R
	 */
	public static <T> R ok(T data) {
		R r = new R();
		r.put("flag", true);
		r.put("msg", "成功!");
		r.put("code", 200);
		r.put("data", data);
		return r;
	}

	/**
	 * @title 添加返回值信息
	 * @return R
	 */
	public static R ok(String msg) {
		R r = new R();
		r.put("flag", true);
		r.put("msg", msg);
		r.put("code", 200);
		r.put("data", null);
		return r;
	}

	/**
	 * @title 自定返回值对象key value
	 * @return R
	 */
	public static R ok(String key, String value) {
		R r = new R();
		r.put("flag", true);
		r.put("msg", "成功!");
		r.put("code", 200);
		r.put("data", null);
		r.put(key, value);
		return r;
	}

	/**
	 * @title 添加返回值对象和提示信息
	 * @return R
	 */
	public static R ok(Object data, String msg) {
		R r = new R();
		r.put("flag", true);
		r.put("msg", msg);
		r.put("code", 200);
		r.put("data", data);
		return r;
	}

	/**
	 * @title 返回默认错误信息 flag msg code
	 * @return R
	 */
	public static R err() {
		R r = new R();
		r.put("flag", false);
		r.put("msg", "服务器返回失败!");
		r.put("code", 500);
		r.put("data", null);
		return r;
	}

	/**
	 * @title 添加自定义错误信息 msg
	 * @return R
	 */
	public static R err(String msg) {
		R r = new R();
		r.put("flag", false);
		r.put("msg", msg);
		r.put("code", 500);
		return r;
	}

	@Override
	public R put(String key, Object value) {
		super.put(key, value);
		return this;
	}

	/**
	 * @title 定义静态方法
	 * @param key
	 * @param value
	 * @return R
	 */
	public static R set(String key, Object value) {
		R r = new R();
		r.put(key, value);
		return r;
	}
}
WebSocket核心类
import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.RemoteEndpoint.Basic;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.molu.app.config.GetHttpSessionConfigurator;
import cn.molu.app.pojo.Message;
import cn.molu.app.pojo.Result;
import cn.molu.app.pojo.ResultMessage;
import cn.molu.app.pojo.User;
import cn.molu.app.utils.ObjectUtils;
import cn.molu.app.utils.RedisUtils;
import cn.molu.app.utils.SpringUtils;

/**
 * @Description 注解 @ServerEndpoint 配置对外暴露访问地址,外部访问格式为(ws://localhost:80/chat)
 * @author 陌路
 * @date 2022-04-16 上午11:53:40
 */
@Component
@ServerEndpoint(value = "/webSocket/{userId}", configurator = GetHttpSessionConfigurator.class)
public class ChatEndpoint {
	private final static Logger LOGGER = LogManager.getLogger(ChatEndpoint.class);
	private HttpSession httpSession;
	public static Map<String, Session> onLineUser = new ConcurrentHashMap<String, Session>();
	@Resource
	private RedisUtils redisUtils = SpringUtils.getBean(RedisUtils.class);

	/**
	 * 连接建立时 会调用该方法
	 */
	@OnOpen
	public void onOpen(Session session, @PathParam("userId") String userId, EndpointConfig endpointConfig) {
		HttpSession httpSession = (HttpSession) endpointConfig.getUserProperties().get(HttpSession.class.getName());
		this.httpSession = httpSession;
		if (ObjectUtils.isBlank(httpSession) || ObjectUtils.isEmpty(httpSession.getId())) {
			return;
		}
		if (StringUtils.isBlank(userId)) {
			return;
		}
		Object obj = this.redisUtils.getObj(userId);
		if (obj == null) {
			return;
		}
		User user = (User) obj;
		if (ObjectUtils.isBlank(user)) {
			return;
		}
		String username = ObjectUtils.getStr(user.getUsername());
		Result res = new Result();
		res.setUserId(userId);
		res.setUsername(username);
		res.setDateStr();
		res.setMessage(String.format("用户%s上线了!", username));
		// 缓存数据
		List<Object> listAll = this.redisUtils.getListAll("onLineUsers");
		if (!onLineUser.containsKey(userId)) {
			if (!isContains(listAll, res)) {
				this.redisUtils.lPush("onLineUsers", res);
			}
		}
		this.redisUtils.setObj(httpSession.getId(), res);
		onLineUser.put(userId, session);
		// 获取未读数据条数
		Map<String, Integer> map = getUnReadCount(userId, getIds());
		// 1. 获取消息,该消息为系统消息,推送给所有用户
		String message = getSysMessage(getusers(), map);
		// 2. 调用方法进行系统消息的推送
		broadcastAllUsers(message);
		LOGGER.info("系统消息推送。。。{}	", message);
	}

	/**
	 * 接收到客户端发送的数据时 会调用此方法
	 */
	@OnMessage
	public void onMessage(String message, Session session) throws IOException {
		LOGGER.info("来自客户端的消息:{}", message);
		if (StringUtils.isBlank(message)) {
			return;
		}
		if ("PING".toUpperCase().equals(message.toUpperCase())) {
			heartCheck(session);
			return;
		}
		Message msgObj = JSON.parseObject(message, Message.class);
		String toId = msgObj.getToId();
		String text = msgObj.getMessage();
		msgObj.setDateStr(new Date());
		if (ObjectUtils.isBlank(toId)) {
			Map<String, String> map = new HashMap<String, String>();
			map.put("sendErr", "消息发送失败,请稍后重试。。。");
			ObjectMapper objectMapper = new ObjectMapper();
			if (session != null) {
				if (session.isOpen()) {
					Basic basicRemote = toSession.getBasicRemote();
					basicRemote.sendText(objectMapper.writeValueAsString(map));
					return;
				}
			}
		}
		// 获取推送指定用户数据
		String resultMessage = getMessage(msgObj, text);
		LOGGER.info("接收到好友发来的数据:{}", resultMessage);
		// 点对点发送数据(给指定用户发送消息)
		Session toSession = onLineUser.get(toId);
		if (toSession != null) {
			if (toSession.isOpen()) {
				Basic basicRemote = toSession.getBasicRemote();
				basicRemote.sendText(resultMessage);
			}
		}
	}

	/**
	 * 连接关闭时 调用此方法
	 */
	@OnClose
	public void onClose(Session session) {
		if (httpSession == null || ObjectUtils.isBlank(httpSession.getId())) {
			return;
		}
		Object obj = redisUtils.getObj(httpSession.getId());
		if (obj == null) {
			return;
		}
		Result res = (Result) obj;
		String username = res.getUsername();
		String userId = res.getUserId();
		// 移除已关闭连接的用户
		onLineUser.remove(userId);
		this.redisUtils.removeList("onLineUsers", res);
		if (ObjectUtils.isBlank(onLineUser)) {
			this.redisUtils.remove("onLineUsers");
		}
		this.redisUtils.remove(httpSession.getId());
		Message msgObj = new Message();
		msgObj.setMessage(String.format("%s离线了!", username));
		msgObj.setFromId(userId);
		msgObj.setFromName(username);
		msgObj.setDateStr(new Date());
		Map<String, Integer> map = getUnReadCount(userId, getIds());
		String message = getSysMessage(getusers(), map);
		broadcastAllUsers(message);
	}

	/**
	 * 出现错误时调用改方法
	 */
	@OnError
	public void onError(Session session, Throwable error) {
		LOGGER.info("连接出错了......{}", error);
	}

	/**
	 * 获取所有的用户名
	 */
	private Set<String> getIds() {
		return ChatEndpoint.onLineUser.keySet();
	}

	/**
	 * 获取所有用户 如果是系统消息就返回这个
	 */
	private Collection<Result> getusers() {
		List<Result> listAll = this.redisUtils.getListAlls("onLineUsers");
		return listAll;
	}

	/**
	 * 消息的推送,将消息推送给所有用户
	 */
	private void broadcastAllUsers(String message) {
		try {
			// 将消息推送给所有的客户端
			Set<String> ids = getIds();
			for (String id : ids) {
				Session session = onLineUser.get(id);
				// 判断用户是否是连接状态
				if (session.isOpen()) {
					session.getBasicRemote().sendText(message);
				}
			}
		} catch (Exception e) {
			LOGGER.error("广播发送系统消息失败!{}", e);
			e.printStackTrace();
		}
	}

	/**
	 * @title 组织消息内容
	 */
	public static String getMessage(Message msgData, String message) {
		ResultMessage resultMessage = new ResultMessage();
		resultMessage.setSystemMsgFlag(false);
		// 消息发送人id
		String fromId = msgData.getFromId();
		// 消息发送人姓名
		String fromName = msgData.getFromName();
		resultMessage.setFromId(fromId);
		resultMessage.setFromName(fromName);
		resultMessage.setMessage(message);
		resultMessage.setDateStr(new Date());
		ObjectMapper objectMapper = new ObjectMapper();
		try {
			return objectMapper.writeValueAsString(resultMessage);
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}
		return "";
	}

	/**
	 * @Title 组织系统推送数据信息
	 */
	public static String getSysMessage(Collection<Result> collection, Map<String, Integer> map) {
		ResultMessage resultMessage = new ResultMessage();
		resultMessage.setSystemMsgFlag(true);
		resultMessage.setMessage(collection);
		resultMessage.setDateStr();
		resultMessage.setMap(map);
		ObjectMapper objectMapper = new ObjectMapper();
		try {
			return objectMapper.writeValueAsString(resultMessage);
		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}
		return "";
	}

	/**
	 * @Title 心跳检测机制
	 */
	public static void heartCheck(Session session) {
		try {
			Map<String, Object> params = new HashMap<String, Object>();
			params.put("type", "PONG");
			session.getAsyncRemote().sendText(JSON.toJSONString(params));
			LOGGER.info("应答客户端的消息:{}", JSON.toJSONString(params));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * @Title isContains
	 */
	private boolean isContains(List<Object> listAll, Result res) {
		if (ObjectUtils.isBlank(listAll) || ObjectUtils.isBlank(res)) {
			return false;
		}
		for (Object object : listAll) {
			Result obj = ObjectUtils.getObj(object);
			if (res.getUserId().equals(obj.getUserId())) {
				return true;
			}
		}
		return false;
	}

    /**
     * 获取未读消息条数
     */
	public Map<String, Integer> getUnReadCount(String userId, Set<String> ids) {
		Map<String, Integer> map = new HashMap<String, Integer>();
		if (!ObjectUtils.isBlank(ids) && !ObjectUtils.isBlank(userId)) {
			for (String id : ids) {
				// 主动刷新页面,获取未读数据条数
				String redisCountKey1 = "UN_READ_MSG_COUT_" + id + "_" + userId;
				// 好友刷新页面,推送未读数据条数
				String redisCountKey2 = "UN_READ_MSG_COUT_" + userId + "_" + id;
				if (this.redisUtils.isExists(redisCountKey1) || this.redisUtils.isExists(redisCountKey2)) {
					// 主动刷新页面,获取未读数据条数
					String count1 = ObjectUtils.getStr(this.redisUtils.getStr(redisCountKey1));
					if (StringUtils.isNotBlank(count1)) {
						map.put(id, Integer.valueOf(count1));
					}
					// 好友刷新页面,推送未读数据条数
					String count2 = ObjectUtils.getStr(this.redisUtils.getStr(redisCountKey2));
					if (StringUtils.isNotBlank(count2)) {
						map.put(userId, Integer.valueOf(count2));
					}
				}
			}
		}
		return map;
	}
}
前端核心Html代码
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Chat聊天窗</title>
    <link th:href="@{/css/chat.css}" rel="stylesheet">
    <script type="text/javascript" th:src="@{/js/jquery.js}"></script>
    <script type="text/javascript" th:src="@{/js/div.move.js}"></script>
    <script type="text/javascript" th:src="@{/js/molu.js}" ></script>
    <script type="text/javascript" th:src="@{/js/chat.js}"></script>
	<!-- emoji表情框滚动 -->
	<link rel="stylesheet" th:href="@{/lib/meoji/css/jquery.mCustomScrollbar.min.css}" />
	<link rel="stylesheet" th:href="@{/lib/meoji/css/jquery.emoji.css}" />
	<!-- emoji表情框滚动 -->
	<script type="text/javascript" th:src="@{/lib/meoji/script/jquery.mCustomScrollbar.min.js}"></script>
	<script type="text/javascript" th:src="@{/lib/meoji/script/jquery.emoji.min.js}"></script>
</head>
    <body>
        <input type="hidden" name="uname" class="uname" th:value="${name}" />
        <input type="hidden" name="uid" class="uid" th:value="${id}" />
        <input type="hidden" name="count" class="count" />
        <div class="box">
            <div class="title">
                <p class="chating">
                    <span style="float: left; padding-left: 5px; color: darkblue;" th:text="${name}"></span>
                    <font>请选择<span class="chating-user">用户</span>聊天</font>
                </p>
                <span class="min_hide" title="最小化"></span>
                <span class="close" title="关闭">X</span>
            </div>
            <div class="friend-list">
                <ul class="friend-ul"></ul>
            </div>
            <div class="chat-main">
                <ul class="chating-main-msg"></ul>
            </div>
            <div class="sys-msg">
                <span>系统消息</span>
                <ul class="sys-msg-ul">
                </ul>
            </div>
            <div class="tools">
                <span class="tools-list">
                    <a class="tools-list-a face" href="javascript:;" title="表情"></a>
                </span>
            </div>
            <div class="input-text">
                <div class="edit-msg" contenteditable></div>
            </div>
            <div class="bottom-bar">
                <button class="closeDiv close-click">关闭</button>
                <button class="sendMsg">发送</button>
            </div>
        </div>
    </body>
</html>
前端核心 JS 代码
let username, userId, toName, toId;
let currenChattUser = { id: '', name: '', count: 0 };
let unReadMsgUser = { id: '', count: 0 }
let ids=[];
let imgUrl = "../../imgs/chatIc.png";
let webSocket;
let lockReconnect = false; // 网络断开重连
let wsCreateHandler = null;
let reconnectCount = 0;
$(function() {
    // 关闭异步请求方式,使用同步请求
    username = $(".uname").val();
    userId = $(".uid").val();

    //移除右下角标,去除大小调节功能
    $(".bg_change_size").remove();

    // 建立连接
    createWebSocket();

    // 发送消息
    $(".sendMsg").on("click", function() {
        sendMsg(webSocket);
    });
    emojiFace();
});

// 建立连接
function createWebSocket(){
    try {
        // 获取访问路径 带有端口号  ws://localhost/webSocket/001
        let host = window.location.host;
        // 创建WebSocket连接对象
        webSocket = new WebSocket(`ws://${host}/webSocket/${userId}`);
        // 加载组件
        initWsEventHandle();
    } catch (e) {
        writeToScreen("连接出错,正在尝试重新连接,请稍等。。。");
        // 尝试重新连接服务器
        reconnect();
    }
}

// 初始化组件
function initWsEventHandle() {
    try {
        // 建立连接
        webSocket.onOpen = function(evt) {
            onWsOpen(evt);
            // 建立连接之后,开始传输心跳包
            heartCheck.start();
            writeToScreen("连接成功。。。");
        };
        // 传送消息
        webSocket.onmessage = function(evt) {
            // 发送消息
            onWsMessage(evt);
            // 接收消息后 也需要心跳包的传送
            heartCheck.start();
            isConn = true;
        };
        // 关闭连接
        webSocket.onclose = function(evt) {
            // 关闭连接,可能是异常关闭,需要重新连接
            onWsClose(evt);
        };
        // 连接出错
        webSocket.onerror = function(evt) {
            // 连接出错
            onWsError(evt);
            // 尝试重新连接
            reconnect();
        }
    } catch (e) {
        writeToScreen("初始化组件失败,正在重试,请稍后。。。");
        // 尝试重新创建连接
        reconnect();
    }

}

function onWsOpen(e) {
    //writeToScreen("连接成功。。。");
}

function onWsMessage(e) {
    //接收到服务器推送的消息后触发事件
    message(e);
}

function onWsClose(e) {
    closeFun(e);
}

function onWsError(e) {
    if(reconnectCount == 2){
        writeToScreen("连接出错,正在尝试重新连接服务器,请稍侯。。。" );
    }
}

function writeToScreen(message) {
    ml.msgBox(message, 5,3);
}

function reconnect() {
    if (lockReconnect) {
        return;
    }
    if(reconnectCount >= 30){
        writeToScreen("未收到服务器的响应,连接关闭。。。");
        webSocket.close();
        clearTimeout(wsCreateHandler);
        reconnectCount = 0;
        return false;
    }
    console.log("正在重新连接。。。"+reconnectCount);
    lockReconnect = true;
    // 没链接上会一直连接,设置延迟,避免过多请求
    wsCreateHandler && clearTimeout(wsCreateHandler);
    wsCreateHandler = setTimeout(function() {
        createWebSocket();
        lockReconnect = false;
        reconnectCount++;
    }, 3000);
}

function message(event){
    //获取服务端推送过来的消息
    let result = event.data;
    // 将message转为JSON对象
    let res = JSON.parse(result);
    // 组织好友列表
    let friendList = "";
    // 组织系统通知内容
    let sysMsg = "";
    if(res.type && res.type == "PONG"){
        return;
    }
    if(res.sendErr){
        writeToScreen(res.sendErr);
        return;
    }
    // 是否为系统消息
    if (res.systemMsgFlag) {
        //为系统消息则:1. 好友列表展示   2. 系统推广
        let allUser = res.message;// name = username_userId
        for (let user of allUser) {
            let count = 0;
            let isShow = "";
            $.each(res.map, function(item, num) {
                if (item && item == user.userId) {
                    count = Number(num) > 99 ? 99 : Number(num);
                    isShow = `style="display:inline;"`;
                    if(ids.indexOf(user.userId) == -1){
                        ids.push(user.userId);
                    }
                }
            })
            if (user.userId != userId) {
                // 组织好友列表
                friendList += `<li class="friend-li" οnclick='chatWith("${user.userId}","${user.username}",this);'>
								<span><img class="friend-img" src="${imgUrl}"/></span>
								<span>${user.username}</span>
								<span class="msg-count msg-count-${user.userId}" ${isShow} title="${count}条信息未读">${count}</span>
							</li>`;
                // 组织系统通知
                sysMsg += `<li class="sys-msg-li" style="color:#9d9d9d;font-family:宋体;">
						 	<span style="font-size:5px;color:#999;">${user.dateStr}</span><br/>
						 	<span>好友<font style="color:blue;">${user.username}</font>上线了</span>
						 </li>`;
                tips("sendMsg", `用户:${user.username}上线了`);
            }
        }
        $(".friend-ul").html(friendList);
        $(".sys-msg-ul").html(sysMsg);
        // 不是系统消息
    } else {
        let contextMsg = res.message;
        let chatMsg = `<li class="friend-msg-li">
						<span><img class="friend-msg-img" src="${imgUrl}" /></span>
						<span class="friend-msg-span">${contextMsg}</span>
					</li>`;
        if (!currenChattUser.id || currenChattUser.id != res.fromId) {
            let count = Number($(".msg-count-" + res.fromId).text());
            if (!count || count == NaN) {
                count = 0;
            }
            unReadMsgUser.id = res.fromId;
            $.post("/index/setCount", { fromId: res.fromId, userId: userId, count: count }, function(resData) {
                if (resData.flag) {
                    count = resData.data > 99 ? 99 : resData.data;
                    currenChattUser.count = count;
                    unReadMsgUser.count = count;
                    $(".msg-count-" + res.fromId).text(count);
                    $(".msg-count-" + res.fromId).show();
                    $(".msg-count-" + res.fromId).attr("title", count + "条信息未读");
                }
            }).catch(function(err) {
                console.log(err);
            })
        }
        if (toId === res.fromId) {
            $(".chating-main-msg").append(chatMsg);
        }
        scrollIntoView();
        // 获取 sessionStorage 中存放的系统缓存消息
        let chatData = sessionStorage.getItem(res.fromId);
        if (chatData) {
            chatMsg = chatData + chatMsg;
        }
        // 将系统消息存放到 sessionStorage 中
        sessionStorage.setItem(res.fromId, chatMsg);
    }
}

var heartCheck = {
    // 在15s内若没收到服务端消息,则认为连接断开,需要重新连接
    timeout: 15000, // 心跳检测触发时间
    timeoutObj: null,
    serverTimeoutObj: null,
    // 重新连接
    reset: function() {
        clearTimeout(this.timeoutObj);
        clearTimeout(this.serverTimeoutObj);
        this.start();
    },
    // 开启定时器
    start: function() {
        let self = this;
        this.timeoutObj && clearTimeout(this.timeoutObj);
        this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
        this.timeoutObj = setTimeout(function() {
            try {
                webSocket.send("PING");
            } catch (e) {
                writeToScreen("连接服务器出错。。。");
            }
            //内嵌定时器
            self.serverTimeoutObj = setTimeout(function() {
                //writeToScreen("未收到服务器的响应,连接关闭。。。");
                //webSocket.close();
                reconnect();
            }, self.timeout);
        }, this.timeout);
    }
};


/**
 * 选择好友
 * @param id 好友id
 * @param name 好友名称
 * @param obj
 */
function chatWith(id, name, obj) {
    toName = name;
    toId = id;
    currenChattUser = { id: id, name: name, count: 0 };
    $(".msg-count-" + id).hide();
    $(obj).addClass("selected-li").siblings().removeClass("selected-li");
    let chatNow = `正在和<span style="color: #db41ca;">${name}</span>聊天`;
    $(".chating>font").html(chatNow);
    $(".chating-main-msg").html("");
    let chatData = sessionStorage.getItem(id);
    if (chatData) {
        //渲染聊天数据到聊天区
        $(".chating-main-msg").html(chatData);
    }
    let idx = ids.indexOf(id);
    if (idx != -1 && id == ids[idx]) {
        $.post("/index/clearCount", { fromId: id, userId: userId }, function(data) {
            $(".msg-count-" + id).text(data);
        })
    }
    scrollIntoView();
}

/**
 * 发送消息事件
 */
function sendMsg(ws) {
    if (!toId || !toName) {
        tips("sendMsg", "请选择好友...");
        return;
    }
    let msg = $(".edit-msg").html();
    if (!msg) {
        tips("sendMsg", "请输入内容...");
        return;
    }
    let jsonMessage = {
        "fromName": username, //消息发送人姓名
        "fromId": userId, //消息发送人id
        "toName": toName, //消息接收人姓名
        "toId": toId, //消息接收人id
        "message": msg //发送的消息内容
    };
    // 发送数据给服务器
    ws.send(JSON.stringify(jsonMessage));
    // 显示发送数据
    let img = `<span><img src='${imgUrl}' class="myself-msg-img"/></span>`;
    let li = `<li class='myself-li'><span class="myself-msg-span">${msg}</span>${img}</li>`;
    $(".chating-main-msg").append(li);
    // 获取 sessionStorage 中的缓存消息
    let chatData = sessionStorage.getItem(toId);
    if (chatData) {
        li = chatData + li;
    }
    // 将最新的消息存放到 sessionStorage 中
    sessionStorage.setItem(toId, li);
    //$(".edit-msg").val('');
    $(".edit-msg").html('');
    scrollIntoView();
}

/**
 * 发送emoji表情
 */
function emojiFace() {
    $(".face").on('click', function() {
        // 初始化emoji插件
        $(".edit-msg").emoji({
            // 触发表情的按钮
            button: '.face',
            showTab: false,
            animation: 'slide',
            position: 'topLeft',
            icons: [
                {
                    name: 'QQ表情', //表情名,表情框中显示的名字
                    path: '../../lib/meoji/img/qq/', // 表情包所在的路径
                    maxNum: 91,
                    excludeNums: [41, 45, 54],
                    file: '.gif',
                    placeholder: '#qq_{alias}#'
                },
                {
                    name: '贴吧表情',
                    path: '../../lib/meoji/img/tieba/',
                    maxNum: 50,
                    excludeNums: [41, 45, 54],
                    file: '.jpg',
                    placeholder: '#tieba_{alias}#'
                }
            ]
        });
    })
}

// 关闭连接
function closeFun(e) {
    //let tips = `<span style='font-size:5px;color:black;'>${new Date()}</span><br/>`;
    $(".sys-msg").html(`用户:${username}<span style="float:right;color:red;">离开了</span>`);
}

/**
 * 滚动条显示在最底部
 */
function scrollIntoView() {
    $(".chat-main")[0].scrollTop = $(".chat-main")[0].scrollHeight;
}

/**
 * 消息提示
 */
function tips(clazz, msg) {
    $(".fun_win_div_tips_msg").remove();
    let html = `<p class="fun_win_div_tips_msg" style="z-index:10;margin: 5px -80px;">
			<span style="padding:3px 8px;text-align:center;
					box-shadow:0px 0px 2px 1px #6a5700;padding: 3px 8px;
			text-align:center;background-color: #fff;">
				${msg}
			</span>
		</p>`;
    setTimeout(function() {
        $(".fun_win_div_tips_msg").remove();
    }, 1800);
    $("." + clazz).after(html);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值