赞
踩
项目 实现了登录注册与聊天功能
文本输入支持markdown语法,实现代码高亮
该模块实现聊天室相关代码
选择版本和依赖
该模块实现登录、注册(邮箱验证码注册)、上传图片
版本与依赖
后端项目创建好后如下:
注册功能和其他依赖参考这篇文章:
基于spring boot的邮箱验证码注册功能 - 后端代码
注册功能测试
/** * 用户登录 * @param user 用户数据 * @return 返回用户信息(用户名与id) */ Result login(UserRegisterRequest user); @Override public Result login(UserRegisterRequest user) { if(StringUtils.isAnyBlank(user.getUserAccount(),user.getUserPassword())){ return Result.error("用户名或密码为空"); } user.setUserPassword(DigestUtils.md5DigestAsHex((SALT + user.getUserPassword()) .getBytes())) ; LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(User::getUseraccount,user.getUserAccount()); queryWrapper.eq(User::getUserpassword,user.getUserPassword()); User flag = getOne(queryWrapper); if (null == flag) { return Result.error("密码错误"); } UserVo userVo = new UserVo(); userVo.setUsername(flag.getUseraccount()); userVo.setId(flag.getId()); return Result.ResultOk(userVo); }
@PostMapping("/login")
public Result userLogin(@RequestBody UserRegisterRequest user){
log.info("登录请求");
if (null == user) return Result.error("参数不能为空");
return userService.login(user);
}
测试
因为我前端输入框是markdown格式的
Markdown 文件的图片以下两种形式保存:
外部链接形式
可以通过指定一个外部链接的方式来引用网络上的图片。示例如下:
![image.png](https://zyqaq-blog.oss-cn-chengdu.aliyuncs.com/2023/05/16/de14b04d259d40848a1a9af977b2b226.png)
本地文件形式
可以将图片文件保存在本地,并通过相对路径或绝对路径的方式引用。示例如下:
![image-20230516125050769](assets/image-20230516125050769.png)
这里我们只能采用第一种
引入阿里云oss依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.15.1</version>
</dependency>
编写路径生成工具类
public class PathUtils {
public static String generateFilePath(String fileName){
//根据日期生成路径 2022/1/15/
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
String datePath = sdf.format(new Date());
//uuid作为文件名
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
//后缀和文件后缀一致
int index = fileName.lastIndexOf(".");
// test.jpg -> .jpg
String fileType = fileName.substring(index);
return new StringBuilder().append(datePath).append(uuid).append(fileType).toString();
}
}
配置文件配置好阿里云oss的参数
oss:
endpoint: oss-cn-chengdu.aliyuncs.com
keyid: LTAI5tDwkMpEN267sGWBDB2q
keysecret: sIVcsIMjXc4rZoDwjhxU5mFs4QDgz9
bucketname: zyqaq-blog
编写FileController、FileService、FileServiceImpl 代码
@RestController public class FileController { @Autowired private FileService fileService; @PostMapping("/upload") public Result uploadImg(@RequestParam("img") MultipartFile multipartFile) { System.out.println("上传图片"); return fileService.uploadImg(multipartFile); } } public interface FileService { Result uploadImg(MultipartFile img); } @Service @Data @ConfigurationProperties(prefix = "oss") public class FileServiceImpl implements FileService { String endpoint; String keyid; String keysecret; String bucketname; @Override public Result uploadImg(MultipartFile img) { String originalFilename = img.getOriginalFilename(); String filePath = PathUtils.generateFilePath(originalFilename); String url = upload(img, filePath);// 2099/2/3/wqeqeqe.png System.out.println(url); return Result.ResultOk(url); } public String upload(MultipartFile file, String filePath) { try { // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, keyid, keysecret); // 上传文件流。 InputStream inputStream = file.getInputStream(); // String fileName = file.getOriginalFilename(); //生成随机唯一值,使用uuid,添加到文件名称里面,不会导致重名 // String uuid = UUID.randomUUID().toString().replaceAll("-",""); // fileName = uuid+fileName; //调用方法实现上传 ossClient.putObject(bucketname, filePath, inputStream); // 关闭OSSClient。 ossClient.shutdown(); //上传之后文件路径 // https://yygh-cccwm.oss-cn-shenzhen.aliyuncs.com/01.jpg String url = "https://" + bucketname + "." + endpoint + "/" + filePath; //返回 return url; } catch (IOException e) { e.printStackTrace(); return null; } } }
测试
引入fastjson依赖(将前端发来的信息转换成对象)
<!--fastjson依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.33</version>
</dependency>
全局响应类与json工具类
@Data public class Result<T> implements Serializable { private Integer code; private String id; private String msg; private T data; public Result() { } public static Result error(CodeEnum enums){ Result result = new Result(); result.setCode(enums.getCode()); result.setMsg(enums.getMsg()); return result; } public static Result ok(Object data){ Result result = new Result(); result.setData(data); result.setCode(CodeEnum.SUCCESS.getCode()); result.setMsg(CodeEnum.SUCCESS.getMsg()); return result; } public static Result set(CodeEnum enums,String id,Object data){ Result result = new Result(); result.setCode(enums.getCode()); result.setMsg(enums.getMsg()); result.setData(data); result.setId(id); return result; } } public class JsonUtils { public static String toJson(Object object) throws Exception { return JSON.toJSONString(object); } public static <T> T parse(String string, Class<T> resultClass) { return JSON.parseObject(string, resultClass); } }
public enum CodeEnum { // 成功 SERVER_TO(0,"首次连接,推送消息"), SESSION_ID(1,"连接id"), MESSAGE(2,"消息"), ONLINE_USERS(3,"在线用户"), NOTICE(4,"公告"), NOT_USERNAME(401,"没有用户名"), SUCCESS(200,"操作成功"); int code; String msg; CodeEnum(int code, String errorMessage) { this.code = code; this.msg = errorMessage; } public int getCode() { return code; } public String getMsg() { return msg; } }
// 返回前端的消息类 @Data public class Message { private long id ; private String sender ; private String time; private String text; } // 公告 @Data public class Notice { private String gg; private String time; } // 用户 @Data public class User { private long id; private String username; } // 接收前端传来的消息类 @Data public class userMsg { private User user; private String messageInput; }
编写WebSocketHandler的实现类ChatWebSocketHandler
该类处理websocket请求
@Slf4j public class ChatWebSocketHandler implements WebSocketHandler { // 存放 sessionId 与 session private static Map<String,WebSocketSession> SESSIONS = new ConcurrentHashMap<>(); // 在线用户列表 private static List<User> ONLINE_USERS = new ArrayList<>(); // 消息列表 private static List <Message> msgList = new ArrayList<>(); // 公告 private static Notice notice = new Notice(); /** * WebSocket 连接建立后调用的方法,通常用于处理连接建立后的业务逻辑。 * @param session * @throws Exception */ @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { log.info("WebSocket 连接已打开:" + session.getId()); // 获取请求路径 判断是否携带用户名 String uri = session.getUri().toString(); // ws://localhost:8080/username=zxwy&id=1 log.info(uri); // 获取id 与 用户名 String id = uri.substring(uri.lastIndexOf('=')+1); uri = uri.substring(0,uri.lastIndexOf('&')); String username = uri.substring(uri.lastIndexOf('=')+1); if ("".equals(username)){ session.sendMessage(new TextMessage(JsonUtils.toJson(Result.error(CodeEnum.NOT_USERNAME)))); return; } User user = new User(); user.setUsername(username); user.setId(Integer.valueOf(id)); // 判断当前用户是否已经连接过 List<User> onlineUser = ONLINE_USERS.stream() .filter(tmp -> tmp.getId()==user.getId()) .collect(Collectors.toList()); // 如果存在相同用户已经登录 删除之前登录的session并关闭 if (onlineUser.size() != 0){ delSessionById(onlineUser.get(0).getId()); } SESSIONS.put(session.getId(),session); // 将用户添加到在线列表 ONLINE_USERS.add(user); session.getAttributes().put(session.getId(),user); session.getAttributes().put("sessionId",session.getId()); // 将连接id推送给前端 session.sendMessage(new TextMessage(JsonUtils.toJson(Result.set(CodeEnum.SESSION_ID,session.getId(),null)))); // 推送在线列表 pushOnlineUser(); // 推送公告 pushNotice(session); // 首次连接推送所有消息 session.sendMessage(new TextMessage(JsonUtils.toJson(Result.set(CodeEnum.SERVER_TO,null,msgList)))); } /** * handleTextMessage: 处理接收到的文本消息。 * @param session * @param message 前端发送的消息 * @throws Exception */ @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { log.info("收到 WebSocket 消息:" + message.getPayload().toString()); Result result = JsonUtils.parse(message.getPayload().toString(),Result.class); userMsg userMsg = JsonUtils.parse(result.getData().toString(), userMsg.class); String username = userMsg.getUser().getUsername(); long id = userMsg.getUser().getId(); if (username == null || "".equals(username)){ session.sendMessage(new TextMessage(JsonUtils.toJson(Result.error(CodeEnum.NOT_USERNAME)))); } String mtext = userMsg.getMessageInput(); // 指令 清空消息 if (mtext.substring(0,1).equals("$")){ if (mtext.equals("$clear")&&id==1){ msgList.removeAll(msgList); broadcast(JsonUtils.toJson(Result.set(CodeEnum.SERVER_TO,null,msgList))); return; } // 指令 发送公告 if (mtext.substring(0,3).equals("$gg")&&id==1){ notice.setTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"))); notice.setGg(mtext.substring(3)); broadcast(JsonUtils.toJson(Result.set(CodeEnum.NOTICE,null,notice))); return; } } // 普通消息 Message msg = new Message(); msg.setId(msgList.size()); msg.setSender(username); msg.setTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"))); msg.setText(mtext); // 广播消息给所有连接的客户端 msgList.add(msg); if (msgList.size()==60) msgList.remove(0); broadcast(JsonUtils.toJson(Result.set(CodeEnum.MESSAGE,session.getId(),msg))); } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { log.info("WebSocket 连接错误:" + session.getId() + ", " + exception.getMessage()); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { log.info("WebSocket 连接已关闭:" + session.getId()); // 移除session id与websocket连接的映射关系 User user = (User) session.getAttributes().get(session.getId()); ONLINE_USERS.remove(user); String sessionId = (String) session.getAttributes().get("sessionId"); if (sessionId != null) { SESSIONS.remove(sessionId); session.close(); } pushOnlineUser(); } @Override public boolean supportsPartialMessages() { return false; } private void broadcast(String message) throws IOException { Set<Map.Entry<String, WebSocketSession>> entries = SESSIONS.entrySet(); for (Map.Entry<String, WebSocketSession> sessions : entries) { if(sessions.getValue().isOpen()){ sessions.getValue().sendMessage(new TextMessage(message)); } } } // 推送在线列表 private void pushOnlineUser() throws Exception{ broadcast(JsonUtils.toJson(Result.set(CodeEnum.ONLINE_USERS,null,ONLINE_USERS))); } // 推送公告 private void pushNotice(WebSocketSession session) throws Exception{ session.sendMessage(new TextMessage(JsonUtils.toJson (Result.set(CodeEnum.NOTICE,null,notice)))); } private void delSessionById(long id) throws Exception{ Set<Map.Entry<String, WebSocketSession>> entries = SESSIONS.entrySet(); for (Map.Entry<String, WebSocketSession> sessions : entries) { User user = (User)sessions.getValue().getAttributes().get(sessions.getValue().getId()); if (user.getId()==id){ String sessionId = (String) sessions.getValue().getAttributes().get("sessionId"); if (sessionId != null) { SESSIONS.remove(sessionId); sessions.getValue().close(); } } } } }
vue create chatroom
编辑vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
// 加上这条
lintOnSave: false,
})
修改package.json
"serve": "vue-cli-service serve --port 9999",// 防止和后端端口冲突
根据以下目录结构创建文件
src
- pages
-login.vue
-register.vue
-chatroom.vue
- router
- router.js
- store
-store.js
router.js
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) // 定义路由规则 const routes = [ { path: '/', redirect: '/login' }, { path: '/chatroom', component: ()=>import('../pages/chatroom.vue') }, { path: '/login', component: ()=>import('../pages/login.vue') }, { path: '/register', component: ()=>import('../pages/register.vue') }, ] // 创建 router 实例 const router = new VueRouter({ mode: 'history', // 路由模式 routes // 路由规则 }) // 导出 router 实例 export default router
npm install axios
npm install element-ui
npm install vue-router
npm install vuex
import Vue from 'vue'
import App from './App.vue'
import router from './router/router' // 导入 router 实例
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import axios from 'axios';
// 配置axios请求的根路径
axios.defaults.baseURL = 'http://localhost:8888';
Vue.use(ElementUI)
Vue.config.productionTip = false
new Vue({
router, // 注册 router 实例
render: h => h(App)
}).$mount('#app')
<template>
<div id="app">
<router-view></router-view> <!-- 显示路由视图 -->
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
实现login.vue与chatroom.vue的数据共享
import Vue from "vue" import Vuex from "vuex" Vue.use(Vuex); export default new Vuex.Store({ state:{ user:{ username:'', id:'' } }, mutations:{ setUsername(state,user){ state.user=user } } })
<template> <div> <div id="zc"> <h1>注册</h1> </div> <div class="register"> <el-form ref="form" :model="form" :rules="rules" label-width="80px"> <el-form-item label="用户名" prop="userAccount"> <el-input v-model="form.userAccount"></el-input> </el-form-item> <el-form-item label="邮箱" prop="email"> <el-input v-model="form.email"></el-input> </el-form-item> <el-form-item label="验证码" prop="code"> <el-row> <el-col :span="16"> <el-input v-model="form.code"></el-input> </el-col> <el-col :span="8"> <el-button @click="getCode">获取验证码</el-button> </el-col> </el-row> </el-form-item> <el-form-item label="密码" prop="userPassword"> <el-input type="userPassword" v-model="form.userPassword"></el-input> </el-form-item> <el-form-item label="确认密码" prop="checkPassword"> <el-input type="userPassword" v-model="form.checkPassword"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="register">注册</el-button> </el-form-item> </el-form> </div> </div> </template> <script> import axios from "axios"; export default { data() { return { form: { userAccount: '', email: '', code: '', userPassword: '', checkPassword: '' }, rules: { userAccount: [{ required: true, message: '请输入用户名', trigger: 'blur' }], email: [ { required: true, message: '请输入邮箱', trigger: 'blur' }, { type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' } ], code: [{ required: true, message: '请输入验证码', trigger: 'blur' }], userPassword: [ { required: true, message: '请输入密码', trigger: 'blur' }, { min: 6, message: '密码长度不能少于6位', trigger: 'blur' } ], checkPassword: [ { required: true, message: '请再次输入密码', trigger: 'blur' }, { validator: (rule, value, callback) => { if (value === '') { callback(new Error('请再次输入密码')) } else if (value !== this.form.userPassword) { callback(new Error('两次输入的密码不一致')) } else { callback() } }, trigger: 'blur' } ] } } }, methods: { register() { axios.post('/user/register', { userAccount: this.form.userAccount, email: this.form.email, userPassword: this.form.userPassword, checkPassword: this.form.checkPassword, code: this.form.code }) .then(res => { // 处理注册成功的逻辑 if (res.data.code===200){ console.log("注册成功") this.$router.push('/login'); } }) }, getCode() { axios.post('/mail',{ to: this.form.email }).then(res =>{ if (res.data.code == 200){ console.log("验证码发送成功") this.$message({ message: '验证码发送成功', type: 'success' }); }else { this.$message.error('验证码发送失败'); } }) } } } </script> <style> .register{ display: flex; justify-content: center; /* 水平居中 */ align-items: center; /* 垂直居中 */ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } #zc{ justify-content: center; /* 水平居中 */ position: absolute; left: 50%; top: 10%; } </style>
<template> <div> <div id="bt"> <h1>登录</h1> </div > <div class="login"> <el-form ref="form" :model="form" :rules="rules" label-width="80px"> <el-form-item label="用户名" prop="userAccount"> <el-input v-model="form.userAccount"></el-input> </el-form-item> <el-form-item label="密码" prop="userPassword"> <el-input type="password" v-model="form.userPassword"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="login">登录</el-button> <el-button type="primary"> <router-link to="/register" style="text-decoration: none;color: white">注册 </router-link></el-button> </el-form-item> </el-form> </div> </div> </template> <script> import axios from "axios"; export default { data() { return { form: { userAccount: '', userPassword: '' }, rules: { userAccount: [{ required: true, message: '请输入用户名', trigger: 'blur' }], userPassword: [{ required: true, message: '请输入密码', trigger: 'blur' }] } } }, methods: { login() { this.$message({ message: '请等待', type: 'success' }); axios.post('/user/login', { userAccount: this.form.userAccount, userPassword: this.form.userPassword, }).then(res => { // 处理注册成功的逻辑 if (res.data.code == 200){ console.log("登录成功") this.$message({ message: '登录成功', type: 'success' }); console.log(res.data.data) this.$store.commit('setUsername',res.data.data) console.log(this.$store.state.username) this.$router.push('/chatroom'); } }) } } } </script> <style> .login{ display: flex; justify-content: center; /* 水平居中 */ align-items: center; /* 垂直居中 */ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } #bt{ justify-content: center; /* 水平居中 */ position: absolute; left: 50%; top: 20%; } </style>
npm install markdown-it
npm install mavon-editor
npm install marked
npm install highlight.js
npm install github-markdown-css
<template> <div class="chatroom"> <el-row> <el-col :span="6"> <el-card class="chatroom-users"> <div class="chatroom-users-header">在线用户</div> <div class="chatroom-users-body"> <div class="chatroom-user" v-for="user in users" :key="user.id" @click="sengById(user)">{{user.username}}</div> </div> </el-card> <el-card class="chatroom-users"> <div class="chatroom-users-header" >公告</div> <div id="gg"> <div class="preview" v-html="show(notice.gg)"/> </div> </el-card> </el-col> <el-col :span="16"> <el-card class="chatroom-message"> <div class="chatroom-message-header">{{ roomName }}</div> <div class="chatroom-message-body" id="message-box"> <div class="chatroom-message-item" v-for="message in messages" :key="message.id"> <div class="chatroom-message-sender">{{ message.sender }}</div> <div class="chatroom-message-time">{{ message.time }}</div> <div class="chatroom-message-text"> <div class="preview" v-html="show(message.text)"/> </div> </div> </div> </el-card> <div class="chatroom-input"> <mavon-editor id="edit" ref="myEditor" v-model="tomsg.messageInput" defaultOpen="edit" :toolbars="toolbars" @imgAdd="addImg" /> <el-button type="primary" @click="sendMessage">发送</el-button> </div> </el-col> </el-row> </div> </template> <script> window.onbeforeunload = function (e) { return e; }; import MarkdownIt from 'markdown-it' import mavonEditor from 'mavon-editor' import 'mavon-editor/dist/css/index.css' import axios from 'axios'; import { marked } from 'marked' import hljs from 'highlight.js' // 代码块高亮 import 'highlight.js/styles/github.css' // 代码块高亮样式 import 'github-markdown-css' // 整体 markdown 样式 export default { components: { 'mavon-editor': mavonEditor.mavonEditor, }, data() { return { // 输入框工具类 toolbars :{ preview: true, // 预览 }, // 公告 notice:{ gg:"", time:"" }, // 发送消息 tomsg: { user:{ username:'', id:'' }, messageInput: '' }, // 收到消息 sendMsg: { code: "", id: "", msg: "", data: {} }, //TODO 暂时用来判断是否需要跳转到消息最下面(以后用用户id判断) //存放sessionId 以后用来实现私聊,私聊对象的sessionId(或用户id) id: "", roomName: "聊天室", messages: [ /*{ id: 1, sender: "张三", time: "10:30", text: "大家好啊!" },*/ ], users: [ { id:'',username:'' } ], }; }, created() { if (!mavonEditor.markdownIt) { mavonEditor.markdownIt = new MarkdownIt(); } this.tomsg.user = this.$store.state.user this.websocket = new WebSocket('ws://localhost:8080/chatroom?username=' + this.tomsg.user.username+"&id="+this.tomsg.user.id); this.websocket.addEventListener('open', this.onOpen); // 监听 WebSocket 消息事件 this.websocket.addEventListener('message', this.onMessage); // 监听 WebSocket 连接关闭事件 this.websocket.addEventListener('close', this.onClose); // 监听 WebSocket 连接错误事件 this.websocket.addEventListener('error', this.onError); }, mounted() { }, methods: { uploadImg(img) { const formData = new FormData() formData.append('img', img) return axios.post('/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(response => { console.log('上传图片', response.data); return response.data.data }).catch(error => { throw new Error(error.message) }) }, // 绑定@imgAdd event addImg(pos, file) { console.log("pos",pos) // 第一步.将图片上传到服务器. this.uploadImg(file).then(response => { // TODO 图片能成功上传,但是这里转成url有问题 this.$refs.myEditor.$img2Url(pos, response) }).catch(error => { this.$message.error(error.msg) }) }, show(text){ if (text === '' || text === undefined) return return marked(text, { highlight: function (code, lang) { const language = hljs.getLanguage(lang) ? lang : 'plaintext'; return hljs.highlight(code, { language }).value; }, }); }, // 私聊 暂不实现 sengById(user){ alert("id:"+user.id+"\t用户名:"+user.username) }, onOpen(event) { console.log('WebSocket 连接已打开', event); }, onMessage(event) { console.log('收到 WebSocket 消息', event); const temp = JSON.parse(event.data) console.log('temp', temp); if (temp.code === 401) { this.$message.error('登录过时请重新登录'); this.$router.push('/login'); return } else if (temp.code === 1) { console.log('返回id',temp.id); this.id = temp.id } else if (temp.code === 2) { console.log(temp.data.text ); const newMessage = temp.data; newMessage.id = this.messages.length + 1; this.messages.push(newMessage) } else if (temp.code === 0) { this.messages = temp.data } else if (temp.code === 3) { console.log('返回在线列表'); this.users = temp.data } else if (temp.code === 4){ console.log('公告'); this.notice = temp.data } console.log('temp',temp.id); console.log('this',this.id); if (temp.id === this.id) { setTimeout(() => { this.moveHuaLun() }, 20); } }, onClose(event) { console.log('WebSocket 连接已关闭', event); this.$message.error('WebSocket 连接已关闭'); this.$router.push('/login'); }, onError(event) { console.error('WebSocket 连接错误', event); }, sendMessage() { if (this.tomsg.messageInput.trim() === "") { return; } this.sendMsg.code = 2; this.sendMsg.data = this.tomsg; // 发送消息到 WebSocket 服务器 this.websocket.send(JSON.stringify(this.sendMsg)); this.tomsg.messageInput = ""; }, moveHuaLun() { //获取消息框元素 const messageBox = document.getElementById("message-box"); if (messageBox) { //将滚动条滚动到消息框底部 messageBox.scrollTop = messageBox.scrollHeight; } else { console.error("Element with ID 'message-box' not found."); } } }, beforeUnmount() { // 关闭 WebSocket 连接 this.websocket.close(); }, }; </script> <style> #edit{ width: 1200px; resize: none; /* 禁止拖动 */ } #gg{ margin-top: 30px; height: 100%; } .chatroom { margin-left: 5%; margin-right: 5%; margin-top: 2%; height: 100%; display: flex; flex-direction: column; justify-content: space-between; } .chatroom-message { height: 500px; margin-bottom: 10px; } .chatroom-message-header { font-weight: bold; margin-bottom: 5px; font-size: 18px; } .chatroom-message-body { max-height: 460px; /* 最大高度为400像素 */ overflow-y: auto; /* 显示垂直滚动条,只有在内容溢出时才显示 */ height: calc(100% - 30px); overflow-y: auto; } .chatroom-message-item { margin-bottom: 10px; } .chatroom-message-sender { font-weight: bold; margin-right: 5px; } .chatroom-message-time { font-weight: normal; } .chatroom-message-text { word-break: break-all; border: 1px solid black; background-color: aliceblue; zoom: 0.9; padding: 5px; } .chatroom-input { display: flex; margin-top: 20px; justify-content: space-between; } .chatroom-users { max-height: 400px; /* 最大高度为400像素 */ max-width: 260px; overflow-y: auto; /* 显示垂直滚动条,只有在内容溢出时才显示 */ height: 100%; } .chatroom-users-header { /* 指定文本的粗细程度 */ width: 100px; font-weight: bold; margin-bottom: 5px; font-size: 18px; } .chatroom-users-body { height: calc(100% - 30px); overflow-y: auto; } .chatroom-user { margin-bottom: 5px; cursor: pointer; } </style>
启动项目
注册
成功登录
发送代码
发送图片
添加了发送文件的功能,文件大小限制在5mb,以链接的方式展示在聊天框
在FileController中添加以下代码:
@PostMapping("/upload/file")
public Result uploadFile(@RequestParam("file") MultipartFile multipartFile) {
System.out.println("上传文件");
return fileService.uploadFile(multipartFile);
}
在FileService中添加以下代码:
Result uploadFile(MultipartFile multipartFile); @Override public Result uploadFile(MultipartFile multipartFile) { String fileName = multipartFile.getOriginalFilename(); fileName = fileName.replaceAll(" ", "") // 文件名中的+号一定要替换 .replaceAll("\\+","-"); // String fileName = file.getOriginalFilename(); //生成随机唯一值,使用uuid,添加到文件名称里面,不会导致重名 String uuid = UUID.randomUUID() .toString() .replaceAll("-", ""); SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/"); String datePath = sdf.format(new Date()); fileName = datePath + uuid + "-" + fileName; String url = upload(multipartFile, fileName); return Result.ResultOk(url); }
在application.yml中添加以下配置:
spring:
multipart:
max-file-size: 5MB
max-request-size: 5MB
修改chatroom.vue
在公告下面添加文件发送组件
<el-card class="chatroom-users">
<div>
<div class="chatroom-users-header">文件发送</div>
<div>
<label for="upload-file" class="custom-upload">
<input id="upload-file" type="file" @change="handleFileChange" ref="fileInput" />
</label>
<div v-if="selectedFileName" class="selected-file">{{ selectedFileName }}</div>
<el-button v-if="selectedFileName" type="primary" plain @click="clearFile">清除</el-button>
</div>
<div id="wj">
<el-button type="primary" plain @click="uploadFile">发送文件</el-button>
</div>
</div>
</el-card>
添加两个属性
data() {
return {
selectedFileName: '' ,// 存储已选择的文件名
file: null, // 选择的文件
}
}
推荐3个函数
methods: { uploadFile() { if (this.file){ const formData = new FormData() formData.append('file', this.file) return axios.post('/upload/file', formData, { headers: { 'Content-Type': 'multipart/form-data' } }).then(response => { this.tomsg.messageInput = response.data.data; this.sendMessage() this.$message.success("文件发送成功") this.clearFile() }).catch(error => { throw new Error(error.message) }) }else { this.$message.error('文件为空'); } }, clearFile() { this.selectedFileName = ''; // 清除文件输入框的值 this.$refs.fileInput.value = ''; this.file =null }, handleFileChange(event) { this.file = event.target.files[0]; if (this.file && this.file.size > 5 * 1024 * 1024){ this.$message.error('文件大小超过限制,建议小于5mb'); this.file = null return } this.selectedFileName = this.file.name; }, }
选择文件
发送文件
点击后自动下载
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。