2024-06-28 18:08:35 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<div style="padding: 10px; margin-bottom: 50px">
|
|
|
|
|
|
<el-button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
size="mini"
|
|
|
|
|
|
@click.prevent="handleMenuSelect('/User')"
|
|
|
|
|
|
>用户</el-button
|
|
|
|
|
|
>
|
2024-07-03 11:08:10 +08:00
|
|
|
|
<el-row v-loading="loading" element-loading-text="正在连接....">
|
2024-06-28 18:08:35 +08:00
|
|
|
|
<el-col :span="16">
|
|
|
|
|
|
<div
|
|
|
|
|
|
style="
|
|
|
|
|
|
width: 800px;
|
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
background-color: white;
|
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
|
box-shadow: 0 0 10px #ccc;
|
|
|
|
|
|
"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style="text-align: center; line-height: 50px">
|
2024-07-02 15:34:15 +08:00
|
|
|
|
({{ chatUser }})
|
2024-06-28 18:08:35 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style="height: 350px; overflow: auto; border-top: 1px solid #ccc"
|
|
|
|
|
|
v-html="content"
|
|
|
|
|
|
></div>
|
|
|
|
|
|
<div style="height: 200px">
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="text"
|
|
|
|
|
|
style="
|
|
|
|
|
|
height: 160px;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
padding: 20px;
|
|
|
|
|
|
border: none;
|
|
|
|
|
|
border-top: 1px solid #ccc;
|
|
|
|
|
|
border-bottom: 1px solid #ccc;
|
|
|
|
|
|
outline: none;
|
|
|
|
|
|
"
|
|
|
|
|
|
></textarea>
|
|
|
|
|
|
<div style="text-align: right; padding-right: 10px">
|
|
|
|
|
|
<el-button type="primary" size="mini" @click="send"
|
|
|
|
|
|
>发送</el-button
|
|
|
|
|
|
>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-row>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
|
|
|
import { ref, onMounted, inject, onUnmounted } from "vue";
|
|
|
|
|
|
import { getImKeyService } from "@/api/im.js";
|
|
|
|
|
|
import router from "@/router/index.js";
|
|
|
|
|
|
import * as crypto from "crypto";
|
|
|
|
|
|
import CryptoJS from "crypto-js";
|
2024-07-02 15:34:15 +08:00
|
|
|
|
import { ElLoading } from "element-plus";
|
2024-07-03 11:08:10 +08:00
|
|
|
|
import Cookies from "js-cookie";
|
2024-06-28 18:08:35 +08:00
|
|
|
|
export default {
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
circleUrl:
|
|
|
|
|
|
"https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png",
|
|
|
|
|
|
user: {},
|
|
|
|
|
|
isCollapse: false,
|
|
|
|
|
|
users: [{ username: "admin" }, { username: "zhang" }],
|
|
|
|
|
|
chatUser: "2002",
|
|
|
|
|
|
text: "",
|
|
|
|
|
|
messages: [],
|
|
|
|
|
|
session: "",
|
2024-07-02 15:34:15 +08:00
|
|
|
|
loading: ref(true),
|
2024-06-28 18:08:35 +08:00
|
|
|
|
imKey: "testimkey",
|
|
|
|
|
|
socket: null,
|
|
|
|
|
|
content: "",
|
|
|
|
|
|
to_user_id: 0,
|
|
|
|
|
|
to_user_name: "",
|
|
|
|
|
|
cnt: 0,
|
|
|
|
|
|
intervalId: null,
|
|
|
|
|
|
tokenData: {
|
|
|
|
|
|
token: localStorage.getItem("token"),
|
|
|
|
|
|
ip: localStorage.getItem("ip"),
|
|
|
|
|
|
userId: localStorage.getItem("userId"),
|
|
|
|
|
|
username: localStorage.getItem("username"),
|
|
|
|
|
|
to_user_id: this.to_user_id,
|
|
|
|
|
|
},
|
|
|
|
|
|
intervalId: ref(null),
|
|
|
|
|
|
timerId: null, // 用于存储定时器的ID
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
async send() {
|
|
|
|
|
|
if (!this.text) {
|
|
|
|
|
|
this.$message({ type: "warning", message: "请输入内容" });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
if (typeof WebSocket == "undefined") {
|
|
|
|
|
|
console.log("您的浏览器不支持WebSocket");
|
|
|
|
|
|
} else {
|
2024-07-02 15:34:15 +08:00
|
|
|
|
var aesEnc = await this.aesEncrypt(this.text, this.imKey, this.imKey);
|
2024-06-28 18:08:35 +08:00
|
|
|
|
let data =
|
|
|
|
|
|
'{"type":"msg","data":"' +
|
|
|
|
|
|
aesEnc +
|
|
|
|
|
|
'","to_user_id":' +
|
|
|
|
|
|
this.to_user_id +
|
|
|
|
|
|
',"from_user_id":' +
|
|
|
|
|
|
this.tokenData.userId +
|
|
|
|
|
|
',"session":"' +
|
|
|
|
|
|
this.session +
|
|
|
|
|
|
'"}';
|
|
|
|
|
|
this.socket.send(data); // 将组装好的json发送给服务端,由服务端进行转发
|
|
|
|
|
|
this.messages.push(JSON.parse(data));
|
|
|
|
|
|
// 构建消息内容,本人消息
|
|
|
|
|
|
this.createContent(null, this.tokenData.username, this.text);
|
|
|
|
|
|
this.text = "";
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
startInterval() {
|
|
|
|
|
|
this.timerId = setInterval(() => {
|
|
|
|
|
|
this.getIMKey();
|
|
|
|
|
|
}, 1000); // 每秒(1000毫秒)执行一次
|
|
|
|
|
|
},
|
|
|
|
|
|
handleMenuSelect(val) {
|
|
|
|
|
|
router.push(val);
|
|
|
|
|
|
},
|
|
|
|
|
|
stopInterval() {
|
|
|
|
|
|
clearInterval(this.timerId);
|
|
|
|
|
|
},
|
|
|
|
|
|
async getIMKey() {
|
|
|
|
|
|
let result = {};
|
|
|
|
|
|
try {
|
|
|
|
|
|
let req = {
|
|
|
|
|
|
to_user_id: this.to_user_id,
|
|
|
|
|
|
token: this.tokenData.token,
|
|
|
|
|
|
};
|
|
|
|
|
|
result = await getImKeyService(req);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.log(e);
|
|
|
|
|
|
}
|
|
|
|
|
|
let data = result.data;
|
|
|
|
|
|
if (result.code != 0) {
|
|
|
|
|
|
this.cnt++;
|
|
|
|
|
|
if (this.cnt > 10) {
|
|
|
|
|
|
//暂停定时器
|
|
|
|
|
|
this.stopInterval();
|
|
|
|
|
|
alert("连接失败,请重试!");
|
|
|
|
|
|
router.push("/user");
|
|
|
|
|
|
}
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (data.is_read == 1) {
|
|
|
|
|
|
// 读取到了新的消息
|
|
|
|
|
|
this.imKey = data.im_key;
|
|
|
|
|
|
this.session = data.im_session;
|
|
|
|
|
|
localStorage.setItem("imkey_" + this.to_user_id, this.imKey);
|
|
|
|
|
|
//记录过期时间
|
|
|
|
|
|
localStorage.setItem(
|
|
|
|
|
|
"imkey_" + this.to_user_id + "_expire",
|
|
|
|
|
|
data.expire
|
|
|
|
|
|
);
|
|
|
|
|
|
this.session = data.im_session;
|
|
|
|
|
|
//暂停定时器
|
|
|
|
|
|
this.stopInterval();
|
|
|
|
|
|
this.connectWebSocket();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
this.cnt++;
|
|
|
|
|
|
if (this.cnt > 30) {
|
|
|
|
|
|
//暂停定时器
|
|
|
|
|
|
this.stopInterval();
|
2024-07-02 15:34:15 +08:00
|
|
|
|
confirm("连接失败,请重试!");
|
2024-06-28 18:08:35 +08:00
|
|
|
|
router.push("/user");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.log(e);
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
createContent(remoteUser, nowUser, text) {
|
|
|
|
|
|
// 这个方法是用来将 json的聊天消息数据转换成 html的。
|
|
|
|
|
|
var html;
|
|
|
|
|
|
// 当前用户消息
|
|
|
|
|
|
if (nowUser) {
|
|
|
|
|
|
// nowUser 表示是否显示当前用户发送的聊天消息,绿色气泡
|
|
|
|
|
|
html =
|
|
|
|
|
|
'<div class="el-row" style="padding: 5px 0">\n' +
|
|
|
|
|
|
' <div class="el-col el-col-22" style="text-align: right; padding-right: 10px">\n' +
|
|
|
|
|
|
' <div class="tip left">' +
|
|
|
|
|
|
text +
|
|
|
|
|
|
"</div>\n" +
|
|
|
|
|
|
" </div>\n" +
|
|
|
|
|
|
' <div class="el-col el-col-2">\n' +
|
|
|
|
|
|
' <span class="el-avatar el-avatar--circle" style="height: 40px; width: 40px; line-height: 40px;">\n' +
|
|
|
|
|
|
' <img src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png" style="object-fit: cover;">\n' +
|
|
|
|
|
|
" </span>\n" +
|
|
|
|
|
|
" </div>\n" +
|
|
|
|
|
|
"</div>";
|
|
|
|
|
|
} else if (remoteUser) {
|
|
|
|
|
|
// remoteUser表示远程用户聊天消息,蓝色的气泡
|
|
|
|
|
|
html =
|
|
|
|
|
|
'<div class="el-row" style="padding: 5px 0">\n' +
|
|
|
|
|
|
' <div class="el-col el-col-2" style="text-align: right">\n' +
|
|
|
|
|
|
' <span class="el-avatar el-avatar--circle" style="height: 40px; width: 40px; line-height: 40px;">\n' +
|
|
|
|
|
|
' <img src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png" style="object-fit: cover;">\n' +
|
|
|
|
|
|
" </span>\n" +
|
|
|
|
|
|
" </div>\n" +
|
|
|
|
|
|
' <div class="el-col el-col-22" style="text-align: left; padding-left: 10px">\n' +
|
|
|
|
|
|
' <div class="tip right">' +
|
|
|
|
|
|
text +
|
|
|
|
|
|
"</div>\n" +
|
|
|
|
|
|
" </div>\n" +
|
|
|
|
|
|
"</div>";
|
|
|
|
|
|
}
|
|
|
|
|
|
this.content += html;
|
|
|
|
|
|
},
|
|
|
|
|
|
aesEncrypt(text, password, iv) {
|
|
|
|
|
|
// CryptoJS使用WordArray而不是Buffer,所以我们需要将密码和IV转换为WordArray
|
|
|
|
|
|
const passwordWordArray = CryptoJS.enc.Utf8.parse(password);
|
|
|
|
|
|
const ivWordArray = CryptoJS.enc.Utf8.parse(iv);
|
|
|
|
|
|
|
|
|
|
|
|
// 使用CryptoJS创建AES加密实例
|
|
|
|
|
|
const cipher = CryptoJS.AES.encrypt(
|
|
|
|
|
|
CryptoJS.enc.Utf8.parse(text), // 加密的数据
|
|
|
|
|
|
passwordWordArray, // 密钥
|
|
|
|
|
|
{
|
|
|
|
|
|
iv: ivWordArray, // 初始化向量
|
|
|
|
|
|
mode: CryptoJS.mode.CBC, // 加密模式
|
|
|
|
|
|
padding: CryptoJS.pad.Pkcs7, // 填充方式
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// CryptoJS默认返回的是CipherParams对象,我们需要将其转换为Base64字符串
|
|
|
|
|
|
const encrypted = cipher.toString();
|
|
|
|
|
|
|
|
|
|
|
|
return encrypted;
|
|
|
|
|
|
},
|
|
|
|
|
|
decryptAES(encryptedData, secretKey, iv) {
|
|
|
|
|
|
// 确保密钥和偏移量(IV)是WordArray对象
|
|
|
|
|
|
const key = CryptoJS.enc.Utf8.parse(secretKey);
|
|
|
|
|
|
const ivData = CryptoJS.enc.Utf8.parse(iv);
|
|
|
|
|
|
|
|
|
|
|
|
// Base64 解码加密数据
|
|
|
|
|
|
// 注意:CryptoJS.enc.Base64.parse 实际上是用于将WordArray转换为Base64字符串的,
|
|
|
|
|
|
// 对于从Base64字符串解码,应该使用 CryptoJS.enc.Base64.parse(CryptoJS.enc.Base64.stringify(someWordArray)),
|
|
|
|
|
|
// 但在这里我们直接使用字符串作为输入,因为encryptedData已经是Base64编码的字符串。
|
|
|
|
|
|
try {
|
|
|
|
|
|
const decrypted = CryptoJS.AES.decrypt(encryptedData, key, {
|
|
|
|
|
|
iv: ivData,
|
|
|
|
|
|
mode: CryptoJS.mode.CBC,
|
|
|
|
|
|
padding: CryptoJS.pad.Pkcs7,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 检查解密是否成功
|
|
|
|
|
|
if (decrypted.sigBytes === 0) {
|
|
|
|
|
|
throw new Error("Decryption failed. Invalid data or password.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将解密后的 WordArray 转换为 UTF-8 字符串
|
|
|
|
|
|
const decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
|
|
|
|
|
|
return decryptedText;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
// 捕获并处理异常
|
|
|
|
|
|
console.error("Decryption error:", error);
|
|
|
|
|
|
throw error; // 或者返回一个错误消息或空字符串等
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
connectWebSocket() {
|
|
|
|
|
|
// 连接WebSocket
|
|
|
|
|
|
let _this = this;
|
|
|
|
|
|
if (typeof WebSocket == "undefined") {
|
2024-07-03 11:08:10 +08:00
|
|
|
|
console.log("浏览器不支持WebSocket");
|
2024-06-28 18:08:35 +08:00
|
|
|
|
} else {
|
2024-07-03 11:08:10 +08:00
|
|
|
|
console.log("浏览器支持WebSocket");
|
2024-06-28 18:08:35 +08:00
|
|
|
|
let socketUrl =
|
|
|
|
|
|
"wss://gep.ljsea.top/im/ws?to_user_id=" +
|
|
|
|
|
|
this.to_user_id +
|
|
|
|
|
|
"&token=" +
|
|
|
|
|
|
this.tokenData.token;
|
|
|
|
|
|
|
2024-07-02 15:34:15 +08:00
|
|
|
|
// console.log("socketUrl:", socketUrl);
|
2024-06-28 18:08:35 +08:00
|
|
|
|
if (this.socket != null) {
|
|
|
|
|
|
this.socket.close();
|
|
|
|
|
|
this.socket = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
// 开启一个websocket服务
|
|
|
|
|
|
this.socket = new WebSocket(socketUrl);
|
|
|
|
|
|
//打开事件
|
|
|
|
|
|
this.socket.onopen = function () {
|
2024-07-02 15:34:15 +08:00
|
|
|
|
this.loading = false;
|
2024-07-03 11:08:10 +08:00
|
|
|
|
confirm("连接成功");
|
2024-06-28 18:08:35 +08:00
|
|
|
|
};
|
|
|
|
|
|
this.socket.onerror = (error) => {
|
|
|
|
|
|
console.error("WebSocket Error:", error);
|
|
|
|
|
|
};
|
2024-07-02 15:34:15 +08:00
|
|
|
|
//关闭事件
|
|
|
|
|
|
this.socket.onclose = function () {
|
|
|
|
|
|
alert("连接已关闭!");
|
|
|
|
|
|
router.push("/user");
|
|
|
|
|
|
};
|
2024-06-28 18:08:35 +08:00
|
|
|
|
// 浏览器端收消息,获得从服务端发送过来的文本消息
|
|
|
|
|
|
this.socket.onmessage = async function (msg) {
|
2024-07-02 15:34:15 +08:00
|
|
|
|
//console.log("收到数据====" + msg.data);
|
2024-06-28 18:08:35 +08:00
|
|
|
|
let data = JSON.parse(msg.data); // 对收到的json数据进行解析, 类似这样的:
|
2024-07-03 11:08:10 +08:00
|
|
|
|
// 如果服务器端发送过来的json数据
|
|
|
|
|
|
if (data.type == "msg") { // 如果是消息类型,解密消息内容
|
|
|
|
|
|
data.data = await _this.decryptAES(
|
|
|
|
|
|
data.data,
|
|
|
|
|
|
_this.imKey,
|
|
|
|
|
|
_this.imKey
|
|
|
|
|
|
);
|
|
|
|
|
|
_this.messages.push(data);
|
|
|
|
|
|
//console.log("收到数据====" + msg.data);
|
|
|
|
|
|
// 构建消息内容
|
|
|
|
|
|
_this.createContent(_this.to_user_name, null, data.data);
|
|
|
|
|
|
}
|
2024-06-28 18:08:35 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
// 生命周期钩子,在组件挂载完成后被调用
|
|
|
|
|
|
mounted() {
|
|
|
|
|
|
this.to_user_id = localStorage.getItem("to_user_id");
|
|
|
|
|
|
this.to_user_name = localStorage.getItem("to_user_name");
|
|
|
|
|
|
this.chatUser = this.to_user_name;
|
|
|
|
|
|
//console.log("to_user_id:", this.to_user_id, this.to_user_name);
|
|
|
|
|
|
this.startInterval();
|
2024-07-03 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
setTimeout(() => this.loading=false, 3000);
|
|
|
|
|
|
|
|
|
|
|
|
//this.connectWebSocket();
|
2024-06-28 18:08:35 +08:00
|
|
|
|
},
|
|
|
|
|
|
// 生命周期钩子,在组件卸载之前被调用
|
2024-07-02 15:34:15 +08:00
|
|
|
|
onUnmounted() {
|
2024-06-28 18:08:35 +08:00
|
|
|
|
this.stopInterval();
|
|
|
|
|
|
this.socket.close();
|
2024-07-02 15:34:15 +08:00
|
|
|
|
this.loading = false;
|
2024-06-28 18:08:35 +08:00
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
</script>
|
|
|
|
|
|
<style>
|
|
|
|
|
|
.tip {
|
|
|
|
|
|
color: white;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
font-family: sans-serif;
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
width: auto;
|
|
|
|
|
|
display: inline-block !important;
|
|
|
|
|
|
display: inline;
|
|
|
|
|
|
}
|
|
|
|
|
|
.right {
|
|
|
|
|
|
background-color: deepskyblue;
|
|
|
|
|
|
}
|
|
|
|
|
|
.left {
|
|
|
|
|
|
background-color: forestgreen;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|