该文章只是作者从自己开发的代码中截取的一部分,只是做一个参考;实际上需要自己在该代码基础上进行调整和优化,有疑问可以在评论区进行提问
聊天功能主要涉及到两张表,message和user表,message用来存信息,user表用来关联用户信息,主要是拿来取用户昵称以及头像
message表创建:
CREATE TABLE `chat_message` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '自增主键id',
`send_user_id` varchar(20) NOT NULL COMMENT '发送用户id',
`accept_user_id` varchar(20) NOT NULL COMMENT '接手用户id',
`type` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '消息类型(图片:img,视频:video,文本:text)',
`content` text COMMENT '发送内容',
`readed` int NOT NULL DEFAULT '0' COMMENT '是否阅读',
`delete` int NOT NULL DEFAULT '0' COMMENT '是否删除',
`send_time` datetime NOT NULL COMMENT '发送时间',
PRIMARY KEY (`id`),
KEY `user_id` (`send_user_id`,`accept_user_id`) USING BTREE COMMENT 'userId索引'
) ENGINE=InnoDB AUTO_INCREMENT=160 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
user表创建:
CREATE TABLE `wx_user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '自增id',
`user_id` varchar(20) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL COMMENT '用户ID',
`nickName` varchar(20) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '昵称',
`headImg` varchar(150) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT '头像链接',
`phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '电话',
`openid` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT 'openID',
`unionid` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL COMMENT 'unionID',
`status` int NOT NULL DEFAULT '0' COMMENT '状态 0:使用中 1:冻结中 2:长时间未使用',
`created_time` datetime DEFAULT NULL COMMENT '创建时间',
`modefied_time` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `user_id` (`user_id`),
UNIQUE KEY `openID` (`openid`) USING BTREE,
UNIQUE KEY `unionID` (`unionid`) USING BTREE,
UNIQUE KEY `phone` (`phone`) USING BTREE,
KEY `id` (`id`,`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC;
entity:
Message.java
package com.example.wxapi.entity.MessageEntity;
import com.example.wxapi.entity.UserEntity.WxUser;
import lombok.Data;
@Data
public class ChatMessage {
private Integer id;
private String sendUserId;
private String acceptUserId;
private String type;
private String content;
private Integer soundTIme;
private String sendTime;
private Integer readedNum;
private WxUser wxUser;
}
WxUser.java
package com.example.wxapi.entity.UserEntity;
import lombok.Data;
@Data
public class WxUser {
private Integer id;
private String userId;
private String nickName;
private String headImg;
private String phone;
private String openid;
private String unionid;
private Integer status;
private String createdTime;
private String modefiedTime;
}
mapper层:
MessageMapper.xml文件
UPDATE base.chat_message
SET readed = 1
WHERE
accept_user_id = #{acceptUserId}
AND send_user_id = #{sendUserId}
UPDATE base.chat_message SET `delete` = 1 WHERE id = #{msgId}
INSERT INTO base.chat_message
send_user_id,
accept_user_id,
content,
sound_time,
`type`,
send_time
#{sendUserId},
#{acceptUserId},
#{content},
#{soundTime},
#{type},
#{sendTime}
dao层:
MessageMapper.java
package com.example.wxapi.dao.personIndexMapper;
import com.example.wxapi.entity.MessageEntity.ChatMessage;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface MessageMapper {
List getFriendMsgList(String userId);
List getChatMessage(String sendUserId,String acceptUserId);
Boolean sendMsg(String sendUserId,String acceptUserId,String content,String type,Integer soundTime,String sendTime);
Boolean readedMsg(String sendUserId,String acceptUserId);
int getAllNoReadMsgNum(String userId);
Boolean delMsg(int msgId);
}
service层:
MessageService.java
package com.example.wxapi.service.personService;
import com.example.wxapi.entity.MessageEntity.ChatMessage;
import com.example.wxapi.entity.MessageEntity.SystemMessage;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.Map;
public interface MessageService {
Map getChatMessage(String sendUserId,String acceptUserId, int pageNum, int pageSize);
Map sendMsg(ChatMessage chatMessage);
Map sendFileMsg(String sendUserId, String acceptUserId, String type, Integer time, MultipartFile file);
Map getFriendMsgList(String userId,int pageNum,int pageSize);
Boolean readedMsg(String sendUserId,String acceptUserId);
int getAllNoReadMsgNum(String userId);
}
注入类 MessageServiceImpl.java
package com.example.wxapi.service.implement.personServiceImpl;
import com.example.wxapi.component.UploadFile;
import com.example.wxapi.dao.personIndexMapper.MessageMapper;
import com.example.wxapi.entity.MessageEntity.ChatMessage;
import com.example.wxapi.service.personService.MessageService;
import com.example.wxapi.tools.Time;
import com.example.wxapi.webSocket.WebSocketServer;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
@Slf4j
public class MessageServiceImpl implements MessageService {
@Resource
private MessageMapper messageMapper;
@Resource
private WebSocketServer webSocketServer;
/**
* 获取聊天好友信息
* @param userId
* @param pageNum
* @param pageSize
* @return
*/
@Override
public Map getFriendMsgList(String userId, int pageNum, int pageSize) {
Map resData = new HashMap<>();
PageHelper.startPage(pageNum,pageSize);
PageInfo info = new PageInfo<>(messageMapper.getFriendMsgList(userId));
resData.put("pagesNum",info.getPages());
resData.put("totalNum",info.getTotal());
resData.put("size",info.getSize());
resData.put("data", info.getList());
return resData;
}
/**
* 获取聊天信息
* @param sendUserId
* @param acceptUserId
* @param pageNum
* @param pageSize
* @return
*/
@Override
public Map getChatMessage(String sendUserId,String acceptUserId,int pageNum,int pageSize) {
Map resData = new HashMap<>();
PageHelper.startPage(pageNum,pageSize);
PageInfo info = new PageInfo<>(messageMapper.getChatMessage(sendUserId,acceptUserId));
resData.put("pagesNum",info.getPages());
resData.put("totalNum",info.getTotal());
resData.put("size",info.getSize());
resData.put("data", info.getList());
return resData;
}
/**
* 发送消息
* @param chatMessage
* @return
*/
@Override
public Map sendMsg(ChatMessage chatMessage) {
Map repData = new HashMap<>();
if(messageMapper.sendMsg(chatMessage.getSendUserId(), chatMessage.getAcceptUserId(), chatMessage.getContent(), chatMessage.getType(), chatMessage.getSoundTIme(),Time.getTime("yyyy-MM-dd HH:mm:ss"))) {
try {
List
controller层:
MsgApi.java
package com.example.wxapi.controller.personIndexApi;
import com.example.wxapi.dao.personIndexMapper.MessageMapper;
import com.example.wxapi.entity.MessageEntity.ChatMessage;
import com.example.wxapi.global.JsonResult;
import com.example.wxapi.service.implement.personServiceImpl.MessageServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.Map;
import java.util.Objects;
/**
* JsonResult 为自定义json序列化方法,用自己的方法即可
*
**/
@RestController
@RequestMapping("/msg")
public class MsgApi {
@Resource
private MessageServiceImpl messageService;
@Resource
private MessageMapper messageMapper;
/**
* 获取好友列表API
* @param userId
* @param pageNum
* @param pageSize
* @return
*/
@PostMapping("/getFriendMsgList")
public JsonResult getFriendMsgList(@RequestParam(value = "userId") String userId,
@RequestParam(value = "pageNum") int pageNum,
@RequestParam(value = "pageSize") int pageSize) {
return JsonResult.success(messageService.getFriendMsgList(userId,pageNum,pageSize));
}
/**
* 获取聊天信息API
* @param sendUserId
* @param acceptUserId
* @param pageNum
* @param pageSize
* @return
*/
@PostMapping("/getChatMessage")
public JsonResult getChatMessage(@RequestParam(value = "sendUserId") String sendUserId,
@RequestParam(value = "acceptUserId") String acceptUserId,
@RequestParam(value = "pageNum") int pageNum,
@RequestParam(value = "pageSize") int pageSize) {
return JsonResult.success(messageService.getChatMessage(sendUserId,acceptUserId,pageNum,pageSize));
}
/**
* 发送消息API
* @param chatMessage
* @return
*/
@PostMapping("sendMsg")
public JsonResult sendMsg(@RequestBody ChatMessage chatMessage) {
Map repData = messageService.sendMsg(chatMessage);
if ((Boolean) repData.get("status"))
return JsonResult.success(repData.get("returnMsg"));
return JsonResult.fail();
}
/**
* 发送聊天文件API
* @param sendUserId
* @param acceptUserId
* @param type
* @param time
* @param file
* @return
*/
@PostMapping("/sendFileMsg")
public JsonResult sendFileMsg(@RequestParam(value = "sendUserId") String sendUserId,
@RequestParam(value = "acceptUserId") String acceptUserId,
@RequestParam(value = "type") String type,
@RequestParam(value = "time", required = false) Integer time,
@RequestParam(value = "file")MultipartFile file
) {
Map resData = messageService.sendFileMsg(sendUserId,acceptUserId,type,time,file);
if ((Boolean) resData.get("status"))
return JsonResult.success(resData.get("returnMsg"));
else
return JsonResult.fail("发送失败!");
}
/**
* 已读消息API
* @param sendUserId
* @param acceptUserId
* @return
*/
@GetMapping("/readedMsg")
public JsonResult readedMsg(@RequestParam("sendUserId") String sendUserId,
@RequestParam("acceptUserId") String acceptUserId) {
if (messageService.readedMsg(sendUserId,acceptUserId))
return JsonResult.success();
return JsonResult.fail(200,"已读失败");
}
/**
* 获取所有消息未读数API
* @param userId
* @return
*/
@GetMapping("/getAllNoReadMsgNum")
public JsonResult getAllNoReadMsgNum(@RequestParam("userId") String userId) {
return JsonResult.success(messageService.getAllNoReadMsgNum(userId));
}
/**
* 删除消息
* @param msgId
* @return
*/
@DeleteMapping("/delMsg")
public JsonResult delMsg(int msgId) {
if (messageMapper.delMsg(msgId))
return JsonResult.success();
return JsonResult.fail();
}
}
最重要的东西来了,Websocket服务
WebSocketServer.java
package com.example.wxapi.webSocket;
import com.alibaba.fastjson2.JSON;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@ServerEndpoint("/websocket/{uid}")
@Component
public class WebSocketServer {
private static int onlineCount = 0;
private Session session;
private String uid;
private static final ConcurrentHashMap
上传文件到文件服务器的uploadFile方法
UploadFile.java
package com.example.wxapi.component;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.WebResource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
@Component
@Slf4j
public class UploadFile {
//远程文件服务器地址
private static final String FILE_URL="http://xxxxxxxx"
public static Map doRemoteUpload(MultipartFile File,String fileType){
Map map = new HashMap<>();
//文件服务器url
String path = FILE_URL;
//为上传到服务器的文件取名,使用UUID防止文件名重复
String type= Objects.requireNonNull(File.getOriginalFilename()).substring(File.getOriginalFilename().lastIndexOf("."));
String fileNicKName= UUID.randomUUID() +type;
String fileName = File.getOriginalFilename();
String fileUrl = path + fileType + fileNicKName;
try{
//使用Jersey客户端上传文件
Client client = Client.create();
WebResource webResource = client.resource(path + fileType + URLEncoder.encode(fileNicKName, StandardCharsets.UTF_8));
webResource.put(File.getBytes());
map.put("status",true);
map.put("fileName",fileName);
map.put("fileUrl",fileUrl);
log.info("文件名:{} =======> 文件上传路径: {}",fileName,fileUrl);
}catch(Exception e){
e.printStackTrace();
map.put("status",false);
map.put("Msg","上传失败!");
}
return map;
}
}
聊天页面
chatIndex.vue
{{changeTime(item.sendTime)}}
{{item.content}}
{{ item.soundTIme }}''
{{item.content}}
{{ item.soundTIme }}''
照片
拍摄
{{ voice.length }}s
上滑取消发送
timeMethod.js方法 聊天界面需要引入
class TimeMethod {
constructor() {}
//日期格式化
addZero(data) {
if (parseInt(data) < 10) {
return "0" + String(data);
}
return data;
}
/**
* 获取当前日期
*/
getNowTime() {
var myDate = new Date();
let year = myDate.getFullYear();
let mouth = this.addZero(myDate.getMonth());
let day = this.addZero(myDate.getDate());
let hour = this.addZero(myDate.getHours());
let minute = this.addZero(myDate.getMinutes());
let second = this.addZero(myDate.getSeconds());
return year + '-' + String((parseInt(mouth)+1)) + '-' + day + 'T' + hour+ ':' + minute+ ':' + second
}
/**
* @param {Object} timestamp
* @param {Object} type
* 时间戳转时间
*/
timestampToTime(timestamp,type) {
if(String(timestamp).length===10) {
//时间戳为10位需*1000
var date = new Date(timestamp * 1000);
}else {
var date = new Date(timestamp);
}
var Y = date.getFullYear() + '-';
var M = (date.getMonth()+1 < 10 ? '0'+(date.getMonth()+1) : date.getMonth()+1) + '-';
var D = date.getDate() + ' ';
var h = date.getHours() + ':';
var m = date.getMinutes() + ':';
var s = date.getSeconds();
if(type==="date") {
return Y+M+D;
}else {
return Y+M+D+h+m+s;
}
}
/**
* @param {Object} time
* 时间转时间戳
*/
timeToTimestamp(time) {
//精确到秒,毫秒用000代替 :Date.parse(date);
return new Date(time).getTime();
}
/**
* @param {Object} startTime
* @param {Object} endTime
* 日期计算
*/
calculateTime(startTime,endTime) {
return new Date(startTime) - new Date(endTime)
}
/**
* @param {Object} time
* 日期转星期
*/
getDateToWeek(time) {
let weekArrayList = [
{"weekID":7,"weekName":"星期日"},
{"weekID":1,"weekName":"星期一"},
{"weekID":2,"weekName":"星期二"},
{"weekID":3,"weekName":"星期三"},
{"weekID":4,"weekName":"星期四"},
{"weekID":5,"weekName":"星期五"},
{"weekID":6,"weekName":"星期六"}];
return weekArrayList[new Date(time).getDay()]
}
/**
* @param {Object} date
* yyyy-MM-dd HH:mm:ss转为 yyyy-MM-ddTHH:mm:ss
*/
timeFormat(date,type) {
if (type == "T")
return date.replace(" ","T")
else
return date.replace("T"," ")
}
/**
* @param {Object} time
* 定时器
*/
timeSleep(time) {
return new Promise((resolve)=>setTimeout(resolve,time))
}
}
export default new TimeMethod();
消息组件 ChatMsgMenu.vue 使用uniapp 的 easycom模式 直接引用
{{ item.name }}
最重要的webSocket.js,这个需要直接挂在main.js上 在里面直接导入 import "@/webSocket/webSocket.js"就行
class WebSocket {
constructor() {
let userId = uni.getStorageSync("userId")
if (userId.length!==0) {
this.connect(userId);
}
}
/**
* 连接
*/
connect(userId) {
uni.connectSocket({
url: `ws://127.0.0.1:8080/${userId}`,
header: {
'content-type': 'application/json'
},
method: 'GET',
success() {
console.log("socket连接成功!");
},
fail() {
console.log("socket连接失败!");
}
})
uni.onSocketOpen(res=>{
console.log("监测到已连接上websocket")
})
uni.onSocketError(res=>{
console.log(res)
console.log("监测到连接websocket错误")
})
uni.onSocketClose(res=>{
console.log("监测到连接websocket已关闭")
})
}
}
export default new WebSocket();