Browse Source

修改域名功能、转接客服功能

master
[yxf] 3 weeks ago
parent
commit
924b74b3b2
  1. 3
      im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java
  2. 40
      im-platform/src/main/java/com/bx/implatform/service/impl/PlatformConfigurationServiceImpl.java
  3. 54
      im-uniapp/App.vue
  4. 44
      im-uniapp/pages/chat/chat-box.vue
  5. 20
      im-uniapp/pages/login/login.vue
  6. 192
      im-uniapp/store/chatStore.js

3
im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java

@ -107,7 +107,7 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
log.info("删除好友,用户id:{},好友id:{}", userId, friendId); log.info("删除好友,用户id:{},好友id:{}", userId, friendId);
} }
@Cacheable(key = "#userId1+':'+#userId2") // @Cacheable(key = "#userId1+':'+#userId2")
@Override @Override
public Boolean isFriend(Long userId1, Long userId2) { public Boolean isFriend(Long userId1, Long userId2) {
LambdaQueryWrapper<Friend> wrapper = Wrappers.lambdaQuery(); LambdaQueryWrapper<Friend> wrapper = Wrappers.lambdaQuery();
@ -117,6 +117,7 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
return this.exists(wrapper); return this.exists(wrapper);
} }
/** /**
* 单向绑定好友关系 * 单向绑定好友关系
* *

40
im-platform/src/main/java/com/bx/implatform/service/impl/PlatformConfigurationServiceImpl.java

@ -1,7 +1,5 @@
package com.bx.implatform.service.impl; package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.implatform.entity.PlatformConfiguration; import com.bx.implatform.entity.PlatformConfiguration;
import com.bx.implatform.mapper.PlatformConfigurationMapper; import com.bx.implatform.mapper.PlatformConfigurationMapper;
@ -11,6 +9,8 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -25,23 +25,51 @@ public class PlatformConfigurationServiceImpl extends ServiceImpl<PlatformConfig
return null; return null;
} }
List<String> inputDomains = Arrays.stream(sourceUrl.split("\n")) List<String> inputList = Arrays.stream(sourceUrl.split("\n"))
.map(String::trim) .map(String::trim)
.filter(StringUtils::hasText) .filter(StringUtils::hasText)
.toList(); .toList();
List<PlatformConfiguration> allConfigs = this.list(); List<PlatformConfiguration> allConfigs = this.list();
for (String input : inputDomains) { for (String input : inputList) {
try {
String inputHost = extractHost(input);
if (inputHost == null) continue;
for (PlatformConfiguration config : allConfigs) { for (PlatformConfiguration config : allConfigs) {
String dbDomain = config.getDomainName(); String dbDomain = config.getDomainName();
if (StringUtils.hasText(dbDomain) && dbDomain.contains(input)) { if (!StringUtils.hasText(dbDomain)) continue;
log.info("【匹配成功】平台:{},匹配规则:{}", config.getPlatformName(), input);
String dbHost = extractHost(dbDomain);
if (inputHost.equals(dbHost)) {
log.info("【精确匹配成功】平台:{},匹配域名:{}", config.getPlatformName(), inputHost);
return config.getPlatformName(); return config.getPlatformName();
} }
} }
} catch (Exception e) {
log.warn("解析域名失败: {}", input);
}
}
return null;
} }
/**
* 通用提取纯净域名方法
*/
private String extractHost(String urlStr) {
try {
// 如果不是http开头,手动补全,防止报错
if (!urlStr.startsWith("http")) {
urlStr = "http://" + urlStr;
}
URL url = new URL(urlStr);
return url.getHost();
} catch (MalformedURLException e) {
return null; return null;
} }
} }
}

54
im-uniapp/App.vue

@ -42,7 +42,9 @@ export default {
this.configStore.setAppInit(true); this.configStore.setAppInit(true);
} }
}); });
// console.log(cmd, msgInfo);
wsApi.onMessage((cmd, msgInfo) => { wsApi.onMessage((cmd, msgInfo) => {
console.log(cmd, msgInfo);
if (cmd == 2) { if (cmd == 2) {
// 线 // 线
// uni.showModal({ // uni.showModal({
@ -79,40 +81,17 @@ export default {
this.configStore.setAppInit(false); this.configStore.setAppInit(false);
} }
}) })
wsApi.onMessage((cmd, msgInfo) => {
if (cmd == 2) {
//
this.exit();
} else if (cmd == 3) {
//
this.handlePrivateMessage(msgInfo);
} else if (cmd == 4) {
//
this.handleGroupMessage(msgInfo);
} else if (cmd == 5) {
//
this.handleSystemMessage(msgInfo);
} else if (cmd == 6) {
//
this.handleCustomerChanged(msgInfo);
}
});
}, },
// //
//
handleCustomerChanged(msgInfo) { handleCustomerChanged(msgInfo) {
console.log('【客服转接】后台下发变更通知', msgInfo);
// msgInfo oldKfId idnewKfId id
const oldKfId = msgInfo.oldKfId; const oldKfId = msgInfo.oldKfId;
const newKfId = msgInfo.newKfId; const newKfId = msgInfo.newKfId;
// ========== -> ==========
if (oldKfId && newKfId && oldKfId != newKfId) { if (oldKfId && newKfId && oldKfId != newKfId) {
const oldChat = this.chatStore.findChatByFriend(oldKfId); const oldChat = this.chatStore.findChatByFriend(oldKfId);
const newChat = this.chatStore.findChatByFriend(newKfId); const newChat = this.chatStore.findChatByFriend(newKfId);
if (oldChat && oldChat.messages.length > 0) { if (oldChat && oldChat.messages.length > 0) {
console.log('【开始强制合并】旧客服', oldKfId, '→ 新客服', newKfId);
// //
this.chatStore.mergeOldCustomerToNew(oldKfId, newKfId); this.chatStore.mergeOldCustomerToNew(oldKfId, newKfId);
} }
@ -212,18 +191,23 @@ export default {
}) })
}, },
handlePrivateMessage(msg) { handlePrivateMessage(msg) {
//
msg.selfSend = msg.sendId == this.userStore.userInfo.id; msg.selfSend = msg.sendId == this.userStore.userInfo.id;
// id // id
let friendId = msg.selfSend ? msg.recvId : msg.sendId; let friendId = msg.selfSend ? msg.recvId : msg.sendId;
if (!msg.selfSend) {
let friend = this.friendStore.findFriend(friendId);
if (!friend) {
this.chatStore.forceMergeAllOldCustomerToNew(friendId);
// //
let existingFriend = this.friendStore.findFriend(friendId); uni.showToast({
if (!existingFriend && !msg.selfSend) { title: "客服已更换,刷新中...",
console.log("收到未知用户消息,刷新应用:", friendId); icon: "none",
duration: 1500
});
this.loadStore().then(() => { setTimeout(() => {
// #ifdef H5 // #ifdef H5
window.location.reload(); window.location.reload();
// #endif // #endif
@ -237,9 +221,10 @@ export default {
url: '/pages/chat/chat' url: '/pages/chat/chat'
}); });
// #endif // #endif
}); }, 800);
return; // return;
}
} }
// //
let chatInfo = { let chatInfo = {
@ -470,7 +455,6 @@ export default {
return group; return group;
}, },
exit() { exit() {
console.log("exit");
this.isExit = true; this.isExit = true;
wsApi.close(3099); wsApi.close(3099);
uni.removeStorageSync("loginInfo"); uni.removeStorageSync("loginInfo");
@ -543,7 +527,6 @@ export default {
this.pullOfflineMessage(); this.pullOfflineMessage();
this.configStore.setAppInit(true); this.configStore.setAppInit(true);
}).catch((e) => { }).catch((e) => {
console.log(e);
this.exit(); this.exit();
}) })
}, },
@ -585,14 +568,9 @@ export default {
this.$nextTick(() => { this.$nextTick(() => {
const btn = document.querySelector('.btn-send'); const btn = document.querySelector('.btn-send');
if (btn) { if (btn) {
console.log('按钮找到:', btn);
//
const listeners = getEventListeners?.(btn); const listeners = getEventListeners?.(btn);
console.log('按钮事件监听器:', listeners);
//
btn.addEventListener('click', (e) => { btn.addEventListener('click', (e) => {
console.log('原生点击事件触发');
this.sendTextMessage(); this.sendTextMessage();
}); });
} }

44
im-uniapp/pages/chat/chat-box.vue

@ -227,7 +227,10 @@
<script> <script>
import UNI_APP from "@/.env.js"; import UNI_APP from "@/.env.js";
import useFriendStore from "@/store/friendStore.js";
import useUserStore from "@/store/userStore.js";
import useChatStore from "@/store/chatStore.js";
import useGroupStore from "@/store/groupStore.js";
export default { export default {
data() { data() {
return { return {
@ -285,6 +288,43 @@ export default {
}; };
}, },
methods: { methods: {
//
autoMergeOldCustomerOnRefresh() {
try {
const userStore = useUserStore();
const chatStore = useChatStore();
if (!this.isPrivate || !userStore.userInfo?.id) return;
const currentKfId = this.currentTargetId;
if (!currentKfId) return;
//
chatStore.forceMergeAllOldCustomerToNew(currentKfId);
//
setTimeout(() => {
this.reloadChatMessages(currentKfId);
}, 300);
} catch (e) {
console.log("自动合并失败", e);
}
},
reloadChatMessages(targetId) {
try {
const chat = this.chatStore.chats.find(
(c) => c.targetId == targetId && c.type === "PRIVATE"
);
if (chat) {
// 500
this.$forceUpdate();
this.$nextTick(() => {
this.scrollToBottom();
});
}
} catch (e) {}
},
// //
selectLang(lang) { selectLang(lang) {
this.currentLang = lang; this.currentLang = lang;
@ -1656,7 +1696,7 @@ export default {
await this.getSetting(); await this.getSetting();
this.$nextTick(() => this.scrollToBottom()); this.$nextTick(() => this.scrollToBottom());
this.autoMergeOldCustomerOnRefresh();
} catch (err) { } catch (err) {
console.error("错误:", err); console.error("错误:", err);
} finally { } finally {

20
im-uniapp/pages/login/login.vue

@ -20,24 +20,28 @@ export default {
password: '', password: '',
ip: '', ip: '',
sourceUrl: '', sourceUrl: '',
uniqueToken: '' ,// token uniqueToken: '',
kefuid: '' // ID kefuid: ''
} }
} }
}, },
methods: { methods: {
// //
getSourceUrl() { getSourceUrl() {
// #ifdef H5
const referrer = document.referrer;
if (referrer) {
this.dataForm.sourceUrl = referrer;
} else {
const pages = getCurrentPages(); const pages = getCurrentPages();
const currentPage = pages[pages.length - 1]; const currentPage = pages[pages.length - 1];
const options = currentPage.options || {}; const options = currentPage.options || {};
if (options.from) { if (options.from) {
this.dataForm.sourceUrl = options.from; this.dataForm.sourceUrl = options.from;
} } else {
// #ifdef H5
this.dataForm.sourceUrl = window.location.origin; this.dataForm.sourceUrl = window.location.origin;
}
}
// #endif // #endif
// #ifdef APP-PLUS // #ifdef APP-PLUS
@ -65,12 +69,10 @@ export default {
if (options.token) { if (options.token) {
this.dataForm.uniqueToken = options.token; this.dataForm.uniqueToken = options.token;
console.log("从options获取到token:", this.dataForm.uniqueToken);
} }
if (options.kefuid) { if (options.kefuid) {
this.dataForm.kefuid = options.kefuid; this.dataForm.kefuid = options.kefuid;
console.log("从options获取到kefuid:", this.dataForm.kefuid);
} }
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
@ -79,12 +81,10 @@ export default {
if (token) { if (token) {
this.dataForm.uniqueToken = token; this.dataForm.uniqueToken = token;
console.log("从URL解析获取到token:", this.dataForm.uniqueToken);
} }
if (kefuid) { if (kefuid) {
this.dataForm.kefuid = kefuid; this.dataForm.kefuid = kefuid;
console.log("从URL解析获取到kefuid:", this.dataForm.kefuid);
} }
// #endif // #endif

192
im-uniapp/store/chatStore.js

@ -30,43 +30,68 @@ export default defineStore('chatStore', {
this.groupMsgMaxId = chatsData.groupMsgMaxId || 0; this.groupMsgMaxId = chatsData.groupMsgMaxId || 0;
}, },
mergeOldCustomerToNew(oldKfId, newKfId) { // 未知客服触发:全量合并所有旧客服到新客服
const oldChat = this.findChatByFriend(oldKfId); forceMergeAllOldCustomerToNew(newKfId) {
const newChat = this.findChatByFriend(newKfId); const userStore = useUserStore();
if (!oldChat || !newChat) { const uid = userStore.userInfo.id;
console.warn('合并失败:旧/新会话不存在', oldKfId, newKfId); const prefix = `chats-app-${uid}-PRIVATE-`;
return; const rootKey = `chats-app-${uid}`;
const allKeys = uni.getStorageInfoSync().keys;
const oldKeys = allKeys.filter(k =>
k.startsWith(prefix) &&
!k.endsWith('-hot') &&
!k.includes(`-${newKfId}`)
);
if (oldKeys.length === 0) return;
let newChat = this.findChatByFriend(newKfId);
if (!newChat) {
newChat = {
targetId: newKfId,
type: "PRIVATE",
messages: [],
unreadCount: 0,
lastContent: "",
lastSendTime: 0,
hotMinIdx: 0,
readedMessageIdx: 0,
atMe: false,
atAll: false,
stored: false
};
this.chats.unshift(newChat);
} }
newChat.messages = [...oldChat.messages, ...newChat.messages]; // 读取每一个旧客服 【冷+热完整消息】
let allOldMessages = [];
oldKeys.forEach(oldKey => {
const oldCold = uni.getStorageSync(oldKey);
const oldHot = uni.getStorageSync(oldKey + "-hot");
// 旧客服完整消息:冷历史 + 热新消息
const oldFullMsg = [...(oldCold?.messages || []), ...(oldHot?.messages || [])];
allOldMessages = [...oldFullMsg, ...allOldMessages];
newChat.unreadCount += oldChat.unreadCount; // 合并完成后才删除旧缓存,绝不提前删!
newChat.lastContent = oldChat.lastContent || newChat.lastContent; uni.removeStorageSync(oldKey);
newChat.lastSendTime = Math.max(oldChat.lastSendTime || 0, newChat.lastSendTime || 0); uni.removeStorageSync(oldKey + "-hot");
newChat.atMe = newChat.atMe || oldChat.atMe; });
newChat.atAll = newChat.atAll || oldChat.atAll;
const keepHot = 300; newChat.messages = [...allOldMessages, ...newChat.messages];
const totalMsg = newChat.messages.length;
newChat.hotMinIdx = totalMsg <= keepHot ? 0 : Math.max(0, totalMsg - keepHot);
newChat.readedMessageIdx = Math.min(oldChat.readedMessageIdx || 0, newChat.readedMessageIdx || 0);
this.clearCustomerCache(oldKfId); // 根缓存只保留最新客服
this.removePrivateChat(oldKfId); const root = uni.getStorageSync(rootKey);
if (root) {
root.chatKeys = [`${prefix}${newKfId}`];
uni.setStorageSync(rootKey, root);
}
newChat.stored = false; newChat.stored = false;
// 按照原生冷热规则拆分保存
this.saveToStorage(true); this.saveToStorage(true);
console.log('【合并完成】旧客服消息全部转入新客服', newChat.messages.length);
}, },
clearCustomerCache(userId) {
const userStore = useUserStore();
const currentUserId = userStore.userInfo.id;
const key = `chats-app-${currentUserId}-PRIVATE-${userId}`;
uni.removeStorageSync(key);
uni.removeStorageSync(key + '-hot');
},
openChat(chatInfo) { openChat(chatInfo) {
let chats = this.curChats; let chats = this.curChats;
let chat = null; let chat = null;
@ -117,7 +142,6 @@ export default defineStore('chatStore', {
this.saveToStorage(); this.saveToStorage();
} }
} }
}, },
readedMessage(pos) { readedMessage(pos) {
let chat = this.findChatByFriend(pos.friendId); let chat = this.findChatByFriend(pos.friendId);
@ -384,10 +408,9 @@ export default defineStore('chatStore', {
} }
this.fliterMessage(chats, 3000, 500); this.fliterMessage(chats, 3000, 500);
chats.forEach(chat => { chats.forEach(chat => {
if (!chat.hotMinIdx || chat.hotMinIdx != chat.messages.length) { // 原生规则:所有消息默认都放入热缓存,hotMinIdx归0
chat.hotMinIdx = chat.messages.length; chat.hotMinIdx = 0;
chat.stored = false; chat.stored = false;
}
}); });
this.chats = chats; this.chats = chats;
cacheChats = null; cacheChats = null;
@ -417,29 +440,40 @@ export default defineStore('chatStore', {
} }
}) })
}, },
saveToStorage(withColdMessage) { saveToStorage(withColdMessage) {
if (this.loading) { if (this.loading) {
return; return;
} }
const userStore = useUserStore(); const userStore = useUserStore();
let userId = userStore.userInfo.id; let userId = userStore.userInfo.id;
let key = "chats-app-" + userId; let rootKey = "chats-app-" + userId;
let chatKeys = []; let chatKeys = [];
const keepHotCount = 300; // 原生热缓存保留条数
this.chats.forEach((chat) => { this.chats.forEach((chat) => {
let chatKey = `${key}-${chat.type}-${chat.targetId}` let chatKey = `${rootKey}-${chat.type}-${chat.targetId}`
if (!chat.stored) { if (!chat.stored) {
if (chat.delete) { if (chat.delete) {
uni.removeStorageSync(chatKey); uni.removeStorageSync(chatKey);
uni.removeStorageSync(chatKey + '-hot');
} else { } else {
// 原生标准切割:hotMinIdx = 总消息长度 - 最新300条
const totalMsgLen = chat.messages.length;
chat.hotMinIdx = Math.max(0, totalMsgLen - keepHotCount);
// cold:前面所有久远历史消息
const coldMsg = chat.messages.slice(0, chat.hotMinIdx);
// hot:后面最新的300条消息(所有新发消息全在这里!)
const hotMsg = chat.messages.slice(chat.hotMinIdx);
// 写入冷缓存
if (withColdMessage) { if (withColdMessage) {
let coldChat = Object.assign({}, chat); let coldChat = { ...chat, messages: coldMsg };
coldChat.messages = chat.messages.slice(0, chat.hotMinIdx); uni.setStorageSync(chatKey, coldChat);
uni.setStorageSync(chatKey, coldChat)
} }
let hotKey = chatKey + '-hot'; // 写入热缓存(新发消息100%保存)
let hotChat = Object.assign({}, chat); let hotChat = { ...chat, messages: hotMsg };
hotChat.messages = chat.messages.slice(chat.hotMinIdx) uni.setStorageSync(chatKey + '-hot', hotChat);
uni.setStorageSync(hotKey, hotChat);
} }
chat.stored = true; chat.stored = true;
} }
@ -447,14 +481,17 @@ export default defineStore('chatStore', {
chatKeys.push(chatKey); chatKeys.push(chatKey);
} }
}) })
// 更新根会话列表
let chatsData = { let chatsData = {
privateMsgMaxId: this.privateMsgMaxId, privateMsgMaxId: this.privateMsgMaxId,
groupMsgMaxId: this.groupMsgMaxId, groupMsgMaxId: this.groupMsgMaxId,
chatKeys: chatKeys chatKeys: chatKeys
} }
uni.setStorageSync(key, chatsData) uni.setStorageSync(rootKey, chatsData)
this.chats = this.chats.filter(chat => !chat.delete) this.chats = this.chats.filter(chat => !chat.delete)
}, },
clear(state) { clear(state) {
cacheChats = []; cacheChats = [];
this.chats = []; this.chats = [];
@ -467,34 +504,59 @@ export default defineStore('chatStore', {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let userStore = useUserStore(); let userStore = useUserStore();
let userId = userStore.userInfo.id; let userId = userStore.userInfo.id;
let chatsData = uni.getStorageSync("chats-app-" + userId) const rootCacheKey = `chats-app-${userId}`;
if (chatsData) { let chatsData = uni.getStorageSync(rootCacheKey);
if (chatsData.chatKeys) {
chatsData.chats = []; // 安全处理:没有数据直接返回
chatsData.chatKeys.forEach(key => { if (!chatsData) {
let coldChat = uni.getStorageSync(key); chatsData = {
let hotChat = uni.getStorageSync(key + '-hot'); privateMsgMaxId: 0,
if (!coldChat && !hotChat) { groupMsgMaxId: 0,
return; chatKeys: []
};
} }
hotChat && hotChat.messages.forEach(msg => {
if (msg.status == MESSAGE_STATUS.SENDING) { // 安全处理:chatKeys 必须是数组
msg.status = MESSAGE_STATUS.FAILED if (!chatsData.chatKeys || !Array.isArray(chatsData.chatKeys)) {
chatsData.chatKeys = [];
} }
})
let chat = Object.assign({}, coldChat, hotChat); uni.removeStorageSync("currentActiveCustomerId");
if (hotChat && coldChat) {
chat.messages = coldChat.messages.concat(hotChat.messages) let finalChats = [];
chatsData.chatKeys.forEach(key => {
try {
const coldChat = uni.getStorageSync(key) || {};
const hotChat = uni.getStorageSync(key + "-hot") || {};
// 修复发送中状态
if (hotChat.messages && Array.isArray(hotChat.messages)) {
hotChat.messages.forEach(msg => {
if (msg.status === MESSAGE_STATUS.SENDING) {
msg.status = MESSAGE_STATUS.FAILED;
} }
chat.readedMessageIdx = chat.readedMessageIdx || 0; });
chatsData.chats.push(chat);
})
} }
// 消息合并(绝对安全)
const coldMsg = Array.isArray(coldChat.messages) ? coldChat.messages : [];
const hotMsg = Array.isArray(hotChat.messages) ? hotChat.messages : [];
const fullMsg = [...coldMsg, ...hotMsg];
let chat = { ...coldChat, ...hotChat };
chat.messages = fullMsg;
chat.hotMinIdx = coldMsg.length;
chat.readedMessageIdx = chat.readedMessageIdx || 0;
finalChats.push(chat);
} catch (e) {}
});
chatsData.chats = finalChats;
this.initChats(chatsData); this.initChats(chatsData);
} resolve();
resolve() });
}) },
}
}, },
getters: { getters: {
curChats: (state) => { curChats: (state) => {
@ -558,4 +620,4 @@ export default defineStore('chatStore', {
return null; return null;
} }
} }
}); })
Loading…
Cancel
Save