本人是java小白,本章节属于我得毕设项目中的小块个功能点,如果有编写不对的地方还请指出,非常感谢!!!
聊天系统采用最简单的scoket插件写的,真正的聊天系统最好使用原生udp/tcp等方式编写
本章默认已经将SpringBoot项目搭建完成,下面操作是在SpringBoot的基础上编写的
1、创建SpringBoot项目,添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2、创建WebSocketConfig配置
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3、定义保存用户会话信息的map
// 保存用户的map
public static ConcurrentHashMap<String,MyWebSocket> users = new ConcurrentHashMap<>();
// 保存客服的map
public static ConcurrentHashMap<String,MyWebSocket> kf = new ConcurrentHashMap<>();
4、创建控制类
/**
* @author: 赵帅峰
* @create: 2023/10/5
* @FileName: MyWebSocket
*/
@ServerEndpoint(value = "/websocket/{sid}/{identify}",encoders = {ServerEncoder.class})
@Component
public class MyWebSocket extends ChatOperate {
private static final Lock lock = new ReentrantLock();
private Session session;
private String identify;
private String sid = "";
private static LoginService loginService;
@Autowired
public void setINoticeService(LoginService loginService) {
MyWebSocket.loginService = loginService;
}
@OnOpen
public void onOpen(@PathParam("sid") String sid, @PathParam("identify") String identify, Session session) throws IOException {
this.session = session;
this.sid = sid;
this.identify = identify;
if(this.identify.equals("3")){
WebSocketUser.kf.put(this.sid,this);
}else{
WebSocketUser.users.put(this.sid,this);
}
if(this.identify.equals("3")){
this.sendMessage(getServiceDialog(sid),0);
this.AllUserSend();
// AllSendMessage(this.sid,"客服","客服已上线",1);
}else {
this.sendMessage(getUserDialog(sid),0);
this.AllKfSend();
// UserIdToSend(sid,sid,identify,"","已上线",1);
}
}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
HashMap hashMap = JSON.parseObject(message, HashMap.class);
// 这是接收人的id
String id = (String) hashMap.get("id");
// 这是发送人的id
String sid = (String) hashMap.get("sid");
// 这是发送人的标识
String identify = (String) hashMap.get("identify");
// 这是发送人的用户名
String name = (String) hashMap.get("name");
String context = (String) hashMap.get("context");
Dialogue dialogue = new Dialogue(Integer.valueOf(sid), Integer.valueOf(id), name, "", context, Integer.valueOf(identify));
if(id.equals("All") || id.equals("all")){
// this.AllSendMessage(sid,name,context,2);
}else{
// this.sendMessage(dialogue);
this.UserIdToSend(dialogue);
// this.UserIdToSend(sid,id,identify,name,context,2);
}
}
public void UserIdToSend(Dialogue dialogue) throws IOException {
// 将信息保存Redis
saveService(dialogue);
saveUserMessage(dialogue);
MyWebSocket myWebSocket = null;
if(dialogue.getIdentify() == 3){
myWebSocket = WebSocketUser.users.get(String.valueOf(dialogue.getRecipient()));
if(myWebSocket==null){
dialogue.setContext("不在线");
}else{
myWebSocket.sendMessage(dialogue,1);
}
WebSocketUser.kf.get(String.valueOf(dialogue.getSender())).sendMessage(dialogue,1);
}else{
myWebSocket = WebSocketUser.kf.get(String.valueOf(dialogue.getRecipient()));
if(myWebSocket==null){
dialogue.setContext("不在线");
}else{
myWebSocket.sendMessage(dialogue,1);
}
WebSocketUser.users.get(String.valueOf(dialogue.getSender())).sendMessage(dialogue,1);
}
}
public void sendMessage(Object o,int flag) throws IOException {
HashMap<String, Object> result = new HashMap<>();
if("3".equals(this.identify)){
result.put("user",WebSocketUser.users.keys());
}else {
result.put("user",WebSocketUser.kf.keys());
}
result.put("flag",flag);
result.put("data",o);
lock.lock();
try{
this.session.getBasicRemote().sendObject(result);//同步
//this.session.getAsyncRemote().sendText(message);//异步
} catch (EncodeException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void AllKfSend(){
WebSocketUser.kf.forEach(new BiConsumer<String, MyWebSocket>() {
@SneakyThrows
@Override
public void accept(String s, MyWebSocket myWebSocket) {
myWebSocket.sendMessage(null,1);
}
});
}
public void AllUserSend(){
WebSocketUser.users.forEach(new BiConsumer<String, MyWebSocket>() {
@SneakyThrows
@Override
public void accept(String s, MyWebSocket myWebSocket) {
myWebSocket.sendMessage(null,1);
}
});
}
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误");
error.printStackTrace();
}
@OnClose
public void onclose(Session session) throws IOException {
if (!sid.equals("")) {
WebSocketUser.users.remove(sid); //从set中删除
WebSocketUser.kf.remove(sid); //从set中删除
}
}
}
利用Redis设置会话持久
5、添加redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
6、创建RedisConfig配置
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory con){
RedisTemplate<String,Object> template = new RedisTemplate<String,Object>();
template.setConnectionFactory(con);
template.setKeySerializer(new StringRedisSerializer()); //key采用字符串方式管理
template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); //value使用jdk的序列化
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
7、在application.properties中添加redis的配置项
#配置redis
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0
8、定义存储的json格式
客服:
{
客服id:{
用户Id_1:[{},{}],
用户Id_2:[{},{}]
}
客服2:{
用户Id_1:[{内容},{内容}],
用户Id_2:[{内容},{内容}]
}
}
客户:{
用户Id_1:[{内容},{内容},{内容}],
用户Id_2:[{内容},{内容}]
}
9、添加实体类,并实现序列化,get和set方法自己添加
public class Dialogue implements Serializable {
// 发送者id
private int sender = -1;
// 接收者id
private int recipient = -1;
// 发送者名称
private String SenderName;
// 接收者名称
private String recipientName;
// 内容
private String message;
// 0:客服 1:用户
private int isCustomer;
// 创建时间
private String createDate;
public Dialogue(){}
public Dialogue(int sender, int recipient, String senderName, String recipientName, String message,int isCustomer) {
this.sender = sender;
this.recipient = recipient;
this.SenderName = senderName;
this.recipientName = recipientName;
this.message = message;
this.isCustomer = isCustomer;
Date date = new Date();
this.createDate = new SimpleDateFormat("MM-dd hh:mm:ss").format(date);;
}
@Override
public String toString() {
return "Dialogue{" +
"sender=" + sender +
", recipient=" + recipient +
", SenderName='" + SenderName + '\'' +
", recipientName='" + recipientName + '\'' +
", message='" + message + '\'' +
", isCustomer=" + isCustomer +
", createDate='" + createDate + '\'' +
'}';
}
}
9、编写对会话存储的代码
/**
* @author: 赵帅峰
* @create: 2023/12/13
* @FileName: ChatOperate
*/
@Component
public class ChatOperate {
private static RedisTemplate redisTemplate;
@Autowired
public void setRedisTemplate(RedisTemplate redisTemplate) {
ChatOperate.redisTemplate = redisTemplate;
}
/**
* 这是在redis中添加客服聊天记录
* @param dialogue
* @return
*/
public Boolean saveService(Dialogue dialogue){
if(dialogue.getSender() == -1 || dialogue.getRecipient()== -1){
throw new RuntimeException("添加异常,请查看信息是否正确");
}
// 判断当前信息是用户发的还是客服发的,这里判断客户id
String recipient = String.valueOf(dialogue.getIdentify()==3 ? dialogue.getSender() : dialogue.getRecipient());
// 判断当前信息是用户发的还是客服发的,这里判断用户id
String Sender = String.valueOf(dialogue.getIdentify()==3 ? dialogue.getRecipient() : dialogue.getSender());
// 如果记录为空,则直接添加,否则原有基础上添加
if (redisTemplate.opsForHash().get("chat:service", recipient)==null){
HashMap<String, List> map = new HashMap<>();
ArrayList<Dialogue> d = new ArrayList<>();
d.add(dialogue);
map.put(Sender,d);
redisTemplate.opsForHash().put("chat:service",recipient,map);
}else{
// 查询redis数据库中改客服与该用户的聊天记录
HashMap<String,List> dialogues = (HashMap<String,List>) redisTemplate.opsForHash().get("chat:service", recipient);
List<Dialogue> arr = dialogues.get(Sender)!=null ? dialogues.get(Sender) : new ArrayList<>();
arr.add(dialogue);
dialogues.put(Sender,arr);
redisTemplate.opsForHash().put("chat:service",recipient,dialogues);
}
return true;
}
/**
* 这是在redis中添加用户聊天记录
* @param dialogue
* @return
*/
public Boolean saveUserMessage(Dialogue dialogue){
if(dialogue.getSender() == -1 || dialogue.getRecipient()== -1){
throw new RuntimeException("添加异常,请查看信息是否正确");
}
// 判断当前信息是用户发的还是客服发的,这里判断用户
String recipient = String.valueOf(dialogue.getIdentify()!=3 ? dialogue.getSender() : dialogue.getRecipient());
if(redisTemplate.opsForHash().get("chat:user", recipient) == null){
ArrayList<Dialogue> d = new ArrayList<>();
d.add(dialogue);
redisTemplate.opsForHash().put("chat:user",recipient,d);
}else{
List dialogues = (List) redisTemplate.opsForHash().get("chat:user", recipient);
dialogues.add(dialogue);
redisTemplate.opsForHash().put("chat:user",recipient,dialogues);
}
return true;
}
/**
* 按照id查找客服的聊天记录
* @param id
* @return
*/
public HashMap<String,List<Dialogue>> getServiceDialog(String id){
if(redisTemplate.opsForHash().get("chat:service", id) == null){
return null;
}
return (HashMap) redisTemplate.opsForHash().get("chat:service", id);
}
/**
* 按照id查找用户的聊天记录
* @param id
* @return
*/
public List<Dialogue> getUserDialog(String id){
if(redisTemplate.opsForHash().get("chat:user", id) == null){
return null;
}
return (List<Dialogue>) redisTemplate.opsForHash().get("chat:user", id);
}
/**
* 根据指定id删除客服记录
* @param id
* @return
*/
public Long deleteServiceById(String id){
if(redisTemplate.opsForHash().get("chat:service",id)==null) return 0L;
return redisTemplate.opsForHash().delete("chat:service",id);
}
/**
* 根据指定id删除用户记录
* @param id
* @return
*/
public Long deleteUserById(String id){
if(redisTemplate.opsForHash().get("chat:user",id)==null) return 0L;
return redisTemplate.opsForHash().delete("chat:user",id);
}
}
10、测试方法
@Test
public void 添加记录(){
Dialogue dialogue = new Dialogue(3,5,"admin","zsf1","nihao",0);
saveUserMessage(dialogue);
saveService(dialogue);
Dialogue dialogue1 = new Dialogue(5,3,"zsf","admin","你好,我在呢",1);
saveUserMessage(dialogue1);
saveService(dialogue1);
}
@Test
public void 根据id删除记录(){
System.out.println(deleteServiceById("3"));
System.out.println(deleteUserById("4"));
}
@Test
public void 查询用户记录(){
System.out.println(getServiceDialog("3"));
System.out.println(getUserDialog("4"));
}
11、前端调用
前端调用时直接用websocket调用即可,将自己的id和身份标识传来
标识:3 -- 客服人员 other(其他) -- 用户