该文章只是作者从自己开发的代码中截取的一部分,只是做一个参考;实际上需要自己在该代码基础上进行调整和优化,有疑问可以在评论区进行提问
聊天功能主要涉及到两张表,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 { ListgetFriendMsgList(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 { MapgetChatMessage(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 MapgetFriendMsgList(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) { MaprepData = 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 MapdoRemoteUpload(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();