Browse Source

修改每个客服一个链接 token后面拼kefuuid与客服回答

master
[yxf] 4 weeks ago
parent
commit
3b211e2585
  1. 3
      im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java
  2. 4
      im-platform/src/main/java/com/bx/implatform/dto/PrivateMessageDTO.java
  3. 82
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  4. 64
      im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java
  5. 180
      im-uniapp/pages/chat/chat-box.vue
  6. 38
      im-uniapp/pages/login/login.vue

3
im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java

@ -34,4 +34,7 @@ public class LoginDTO {
@Schema(description = "token标识") @Schema(description = "token标识")
private String uniqueToken; private String uniqueToken;
@Schema(description = "客服ID")
private String keFuId;
} }

4
im-platform/src/main/java/com/bx/implatform/dto/PrivateMessageDTO.java

@ -13,6 +13,9 @@ public class PrivateMessageDTO {
@Schema(description = "临时id") @Schema(description = "临时id")
private String tmpId; private String tmpId;
@Schema(description = "发送用户id(可选,不传则使用当前登录用户)")
private Long sendId; // 新增字段
@NotNull(message = "接收用户id不可为空") @NotNull(message = "接收用户id不可为空")
@Schema(description = "接收用户id") @Schema(description = "接收用户id")
private Long recvId; private Long recvId;
@ -26,4 +29,5 @@ public class PrivateMessageDTO {
@Schema(description = "消息类型 0:文字 1:图片 2:文件 3:语音 4:视频") @Schema(description = "消息类型 0:文字 1:图片 2:文件 3:语音 4:视频")
private Integer type; private Integer type;
} }

82
im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java

@ -51,44 +51,106 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
private final UserMapper userMapper; private final UserMapper userMapper;
private static final ScheduledThreadPoolExecutor EXECUTOR = ThreadPoolExecutorFactory.getThreadPoolExecutor(); private static final ScheduledThreadPoolExecutor EXECUTOR = ThreadPoolExecutorFactory.getThreadPoolExecutor();
// @Override
// public PrivateMessageVO sendMessage(PrivateMessageDTO dto) {
// UserSession session = SessionContext.getSession();
// Long sendUserId = session.getUserId();
// Long recvUserId = dto.getRecvId();
// Boolean isFriends = friendService.isFriend(session.getUserId(), dto.getRecvId());
// if (Boolean.FALSE.equals(isFriends)) {
// throw new GlobalException("您已不是对方好友,无法发送消息");
// }
// User sendUser = userMapper.selectById(sendUserId);
// User recvUser = userMapper.selectById(recvUserId);
// // 打印发送用户和接收用户信息
// log.info("发送私聊消息 - 发送用户ID: {}, 接收用户ID: {}",
// recvUser, sendUser);
//
// if(!recvUser.getUniqueToken().equals(sendUser.getUniqueToken())){
// throw new GlobalException("非法客服,发送失败");
// }
// // 保存消息
// PrivateMessage msg = BeanUtils.copyProperties(dto, PrivateMessage.class);
// msg.setSendId(session.getUserId());
// msg.setStatus(MessageStatus.PENDING.code());
// msg.setSendTime(new Date());
// // 过滤内容中的敏感词
// if (MessageType.TEXT.code().equals(dto.getType())) {
// msg.setContent(sensitiveFilterUtil.filter(dto.getContent()));
// }
// this.save(msg);
// // 推送消息
// PrivateMessageVO msgInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class);
// IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
// sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
// sendMessage.setRecvId(msgInfo.getRecvId());
// sendMessage.setSendToSelf(true);
// sendMessage.setData(msgInfo);
// sendMessage.setSendResult(true);
// imClient.sendPrivateMessage(sendMessage);
// log.info("发送私聊消息,发送id:{},接收id:{},内容:{}", session.getUserId(), dto.getRecvId(), dto.getContent());
// return msgInfo;
// }
@Override @Override
public PrivateMessageVO sendMessage(PrivateMessageDTO dto) { public PrivateMessageVO sendMessage(PrivateMessageDTO dto) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
Long sendUserId = session.getUserId();
// 如果传了sendId就用传入的,否则用当前登录用户
Long sendUserId = dto.getSendId() != null ? dto.getSendId() : session.getUserId();
Long recvUserId = dto.getRecvId(); Long recvUserId = dto.getRecvId();
Boolean isFriends = friendService.isFriend(session.getUserId(), dto.getRecvId());
// 如果不是自己发送(模拟发送),需要验证权限
if (!sendUserId.equals(session.getUserId())) {
User sendUser = userMapper.selectById(sendUserId);
User currentUser = userMapper.selectById(session.getUserId());
// 验证是否属于同一商户
if (sendUser == null || !sendUser.getUniqueToken().equals(currentUser.getUniqueToken())) {
throw new GlobalException("无权限模拟该用户发送消息");
}
log.info("模拟发送消息 - 操作人:{}, 发送者:{}, 接收者:{}",
session.getUserId(), sendUserId, recvUserId);
}
Boolean isFriends = friendService.isFriend(sendUserId, recvUserId);
if (Boolean.FALSE.equals(isFriends)) { if (Boolean.FALSE.equals(isFriends)) {
throw new GlobalException("您已不是对方好友,无法发送消息"); throw new GlobalException("您已不是对方好友,无法发送消息");
} }
User sendUser = userMapper.selectById(sendUserId); User sendUser = userMapper.selectById(sendUserId);
User recvUser = userMapper.selectById(recvUserId); User recvUser = userMapper.selectById(recvUserId);
// 打印发送用户和接收用户信息
log.info("发送私聊消息 - 发送用户ID: {}, 接收用户ID: {}", log.info("发送私聊消息 - 发送用户ID: {}, 接收用户ID: {}", sendUserId, recvUserId);
recvUser, sendUser);
if(!recvUser.getUniqueToken().equals(sendUser.getUniqueToken())){ if(!recvUser.getUniqueToken().equals(sendUser.getUniqueToken())){
throw new GlobalException("非法客服,发送失败"); throw new GlobalException("非法客服,发送失败");
} }
// 保存消息
// 保存消息(后面的代码保持不变)
PrivateMessage msg = BeanUtils.copyProperties(dto, PrivateMessage.class); PrivateMessage msg = BeanUtils.copyProperties(dto, PrivateMessage.class);
msg.setSendId(session.getUserId()); msg.setSendId(sendUserId); // 使用处理后的发送者ID
msg.setStatus(MessageStatus.PENDING.code()); msg.setStatus(MessageStatus.PENDING.code());
msg.setSendTime(new Date()); msg.setSendTime(new Date());
// 过滤内容中的敏感词 // 过滤内容中的敏感词
if (MessageType.TEXT.code().equals(dto.getType())) { if (MessageType.TEXT.code().equals(dto.getType())) {
msg.setContent(sensitiveFilterUtil.filter(dto.getContent())); msg.setContent(sensitiveFilterUtil.filter(dto.getContent()));
} }
this.save(msg); this.save(msg);
// 推送消息 // 推送消息
PrivateMessageVO msgInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class); PrivateMessageVO msgInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>(); IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setSender(new IMUserInfo(sendUserId, session.getTerminal()));
sendMessage.setRecvId(msgInfo.getRecvId()); sendMessage.setRecvId(msgInfo.getRecvId());
sendMessage.setSendToSelf(true); // 如果是模拟发送,不发送给自己
sendMessage.setSendToSelf(sendUserId.equals(session.getUserId()));
sendMessage.setData(msgInfo); sendMessage.setData(msgInfo);
sendMessage.setSendResult(true); sendMessage.setSendResult(true);
imClient.sendPrivateMessage(sendMessage); imClient.sendPrivateMessage(sendMessage);
log.info("发送私聊消息,发送id:{},接收id:{},内容:{}", session.getUserId(), dto.getRecvId(), dto.getContent());
log.info("发送私聊消息,发送id:{},接收id:{},内容:{}", sendUserId, recvUserId, dto.getContent());
return msgInfo; return msgInfo;
} }

64
im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java

@ -95,7 +95,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
@Override @Override
public LoginVO login(LoginDTO dto) { public LoginVO login(LoginDTO dto) {
log.info("【测试】前端传的uniqueToken:{}", dto.getUniqueToken()); log.info("【测试】前端传的uniqueToken:{},kefuid:{}", dto.getUniqueToken(), dto.getKeFuId());
String uniqueToken = dto.getUniqueToken(); String uniqueToken = dto.getUniqueToken();
// 检查uniqueToken是否为空或已过期 // 检查uniqueToken是否为空或已过期
@ -122,7 +122,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
// 保存到数据库 // 保存到数据库
this.save(guestUser); this.save(guestUser);
Long customerServiceId = this.getCustomerServiceIdByUniqueToken(dto.getUniqueToken()); // Long customerServiceId = this.getCustomerServiceIdByUniqueToken(dto);
Long customerServiceId = this.getCustomerServiceId(dto);
UserSession guestSession = new UserSession(); UserSession guestSession = new UserSession();
guestSession.setUserId(guestUser.getId()); guestSession.setUserId(guestUser.getId());
@ -158,28 +159,71 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
return vo; return vo;
} }
/**
* 获取客服ID
* 优先使用前端传入的kefuid没有则通过uniqueToken查询
*/
private Long getCustomerServiceId(LoginDTO dto) {
// 1. 如果前端传了 kefuid,直接使用并验证该客服是否有效
if (StrUtil.isNotBlank(dto.getKeFuId())) {
try {
Long kefuid = Long.parseLong(dto.getKeFuId());
// 验证该客服是否存在且有效
User customer = this.getById(kefuid);
if (customer != null && customer.getIsCustomer() == 2) {
// 验证该客服的uniqueToken是否与传入的匹配
if (dto.getUniqueToken().equals(customer.getUniqueToken())) {
return kefuid;
} else {
log.warn("前端传入的客服ID {} 的uniqueToken与当前不匹配,将使用token查询", kefuid);
}
} else {
log.warn("前端传入的客服ID {} 无效或不是客服,将使用token查询", kefuid);
}
} catch (NumberFormatException e) {
log.warn("前端传入的kefuid格式错误:{}", dto.getKeFuId());
}
}
// 2. 通过uniqueToken查询客服
return getCustomerServiceIdByUniqueToken(dto.getUniqueToken());
}
/**
* 通过uniqueToken查询客服ID如果有多条匹配则随机返回一条
*/
public Long getCustomerServiceIdByUniqueToken(String uniqueToken) { public Long getCustomerServiceIdByUniqueToken(String uniqueToken) {
// 1. 构建查询条件 // 1. 构建查询条件
LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery(); LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(User::getIsCustomer, 2); // 只查客服 queryWrapper.eq(User::getIsCustomer, 2); // 只查客服
// 2. 有 token 才按 token 匹配
if (StrUtil.isNotBlank(uniqueToken)) { if (StrUtil.isNotBlank(uniqueToken)) {
queryWrapper.eq(User::getUniqueToken, uniqueToken); queryWrapper.eq(User::getUniqueToken, uniqueToken);
} }
// 3. 只查 ID
queryWrapper.select(User::getId); queryWrapper.select(User::getId);
// 4. 查一条(false=查不到不抛异常) // 2. 查询所有匹配的客服
User customer = this.getOne(queryWrapper, false); List<User> customerList = this.list(queryWrapper);
if (customer == null) {
// 3. 如果没有匹配的客服,抛出异常
if (customerList == null || customerList.isEmpty()) {
throw new GlobalException("未找到对应的客服,请检查 uniqueToken 是否正确"); throw new GlobalException("未找到对应的客服,请检查 uniqueToken 是否正确");
} }
// 5. 有客服返回ID,没有返回null
return customer == null ? null : customer.getId();
}
// 4. 如果只有一条,直接返回
if (customerList.size() == 1) {
Long customerId = customerList.get(0).getId();
return customerId;
}
// 5. 如果有多条,随机选择一条返回
int randomIndex = new Random().nextInt(customerList.size());
Long randomCustomerId = customerList.get(randomIndex).getId();
return randomCustomerId;
}
@Override @Override
public LoginVO loginCustom(LoginDTO dto) { public LoginVO loginCustom(LoginDTO dto) {

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

@ -291,57 +291,167 @@ export default {
(item) => item.replyTitle === replyTitle (item) => item.replyTitle === replyTitle
); );
if (!autoReply) return; if (!autoReply) return;
// //
if (this.isBanned) { if (this.isBanned) {
this.showBannedTip(); this.showBannedTip();
return; return;
} }
let msgInfo = { // ========== replyTitle ==========
let userMsgInfo = {
tmpId: this.generateId(), tmpId: this.generateId(),
receipt: this.isReceipt, type: this.$enums.MESSAGE_TYPE.TEXT,
content: autoReply.replyTitle,
receipt: this.isReceipt
}; };
if (autoReply.replyType === 0) { this.fillTargetId(userMsgInfo, this.chat.targetId);
//
msgInfo.type = this.$enums.MESSAGE_TYPE.TEXT;
msgInfo.content = autoReply.replyContent;
} else if (autoReply.replyType === 1) {
//
msgInfo.type = this.$enums.MESSAGE_TYPE.IMAGE;
msgInfo.content = JSON.stringify({
originUrl: autoReply.replyContent,
thumbUrl: autoReply.replyContent,
});
} else {
return;
}
this.fillTargetId(msgInfo, this.chat.targetId);
const chat = this.chat; const chat = this.chat;
if (!chat) return; if (!chat) return;
let tmpMessage = this.buildTmpMessage(msgInfo); let tmpUserMessage = this.buildTmpMessage(userMsgInfo);
this.chatStore.insertMessage(tmpMessage, chat); this.chatStore.insertMessage(tmpUserMessage, chat);
this.moveChatToTop(); this.moveChatToTop();
this.sendMessageRequest(msgInfo) //
this.sendMessageRequest(userMsgInfo)
.then((m) => { .then((m) => {
tmpMessage = JSON.parse(JSON.stringify(tmpMessage)); tmpUserMessage = JSON.parse(JSON.stringify(tmpUserMessage));
tmpMessage.id = m.id; tmpUserMessage.id = m.id;
tmpMessage.status = m.status; tmpUserMessage.status = m.status;
this.chatStore.updateMessage(tmpMessage, chat); this.chatStore.updateMessage(tmpUserMessage, chat);
// ========== ==========
this.triggerAutoReply(autoReply);
this.scrollToBottom(); this.scrollToBottom();
this.isReceipt = false; this.isReceipt = false;
}) })
.catch(() => { .catch(() => {
tmpMessage = JSON.parse(JSON.stringify(tmpMessage)); tmpUserMessage = JSON.parse(JSON.stringify(tmpUserMessage));
tmpMessage.status = this.$enums.MESSAGE_STATUS.FAILED; tmpUserMessage.status = this.$enums.MESSAGE_STATUS.FAILED;
this.chatStore.updateMessage(tmpMessage, chat); this.chatStore.updateMessage(tmpUserMessage, chat);
}); });
}, },
//
triggerAutoReply(autoReply) {
// ID
let tmpId = this.generateId();
//
let replyMsgInfo = {
tmpId: tmpId, // tmpId
sendId: this.chat.targetId, // ID
recvId: this.mine.id, //
type: autoReply.replyType, //
receipt: false
};
//
if (autoReply.replyType === 0) {
//
replyMsgInfo.content = autoReply.replyContent;
} else if (autoReply.replyType === 1) {
//
replyMsgInfo.content = JSON.stringify({
originUrl: autoReply.replyContent,
thumbUrl: autoReply.replyContent,
});
}
// ""
let typingMessage = {
id: this.generateId(),
tmpId: tmpId,
sendId: this.chat.targetId,
recvId: this.mine.id,
selfSend: false,
sendTime: new Date().getTime(),
type: this.$enums.MESSAGE_TYPE.TIP_TEXT,
content: "客服正在输入...",
status: this.$enums.MESSAGE_STATUS.SENDING
};
this.chatStore.insertMessage(typingMessage, this.chat);
//
this.$http({
url: "/message/private/send",
method: "post",
data: replyMsgInfo
}).then(res => {
console.log("自动回复发送成功", res);
// ""
this.chatStore.deleteMessage(typingMessage, this.chat);
// WebSocket
//
if (res && res.id) {
let replyMessage = {
id: res.id,
tmpId: tmpId,
sendId: this.chat.targetId,
recvId: this.mine.id,
selfSend: false,
sendTime: new Date().getTime(),
type: autoReply.replyType,
content: replyMsgInfo.content,
status: this.$enums.MESSAGE_STATUS.SENDED
};
this.chatStore.insertMessage(replyMessage, this.chat);
this.scrollToBottom();
}
}).catch(err => {
console.error("自动回复发送失败", err);
// ""
this.chatStore.deleteMessage(typingMessage, this.chat);
//
uni.showToast({
title: "自动回复失败",
icon: "none"
});
});
},
//
// triggerAutoReply(autoReply) {
// // sendId
// let replyMsgInfo = {
// sendId: this.chat.targetId, // ID
// recvId: this.mine.id, //
// type: autoReply.replyType, //
// };
// console.log(replyMsgInfo)
// //
// if (autoReply.replyType === 0) {
// //
// replyMsgInfo.content = autoReply.replyContent;
// } else if (autoReply.replyType === 1) {
// //
// replyMsgInfo.content = JSON.stringify({
// originUrl: autoReply.replyContent,
// thumbUrl: autoReply.replyContent,
// });
// }
// //
// this.$http({
// url: "/message/private/send",
// method: "post",
// data: replyMsgInfo
// }).then(res => {
// console.log("", res);
// // WebSocket
// }).catch(err => {
// console.error("", err);
// });
// },
onRecorderInput() { onRecorderInput() {
this.showRecord = true; this.showRecord = true;
this.switchChatTabBox("none"); this.switchChatTabBox("none");
@ -1070,6 +1180,7 @@ export default {
sendMessageRequest(msgInfo) { sendMessageRequest(msgInfo) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// "" // ""
this.reqQueue.push({ msgInfo, resolve, reject }); this.reqQueue.push({ msgInfo, resolve, reject });
this.processReqQueue(); this.processReqQueue();
}); });
@ -1078,6 +1189,7 @@ export default {
if (this.reqQueue.length && !this.isSending) { if (this.reqQueue.length && !this.isSending) {
this.isSending = true; this.isSending = true;
const reqData = this.reqQueue.shift(); const reqData = this.reqQueue.shift();
console.log(reqData.msgInfo);
this.$http({ this.$http({
url: this.messageAction, url: this.messageAction,
method: "post", method: "post",

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

@ -20,7 +20,8 @@ export default {
password: '', password: '',
ip: '', ip: '',
sourceUrl: '', sourceUrl: '',
uniqueToken: '' // token uniqueToken: '' ,// token
kefuid: '' // ID
} }
} }
}, },
@ -55,10 +56,9 @@ export default {
console.log("来源网址:", this.dataForm.sourceUrl); console.log("来源网址:", this.dataForm.sourceUrl);
}, },
// URLtoken // URLtokenkefuid
getTokenFromUrl() { getTokenFromUrl() {
// #ifdef H5 // #ifdef H5
// 1options
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 || {};
@ -66,16 +66,26 @@ 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); console.log("从options获取到token:", this.dataForm.uniqueToken);
return;
} }
// 2URL if (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);
const token = urlParams.get('token'); const token = urlParams.get('token');
const kefuid = urlParams.get('kefuid');
if (token) { if (token) {
this.dataForm.uniqueToken = token; this.dataForm.uniqueToken = token;
console.log("从URL解析获取到token:", this.dataForm.uniqueToken); console.log("从URL解析获取到token:", this.dataForm.uniqueToken);
} }
if (kefuid) {
this.dataForm.kefuid = kefuid;
console.log("从URL解析获取到kefuid:", this.dataForm.kefuid);
}
// #endif // #endif
// #ifdef APP-PLUS // #ifdef APP-PLUS
@ -85,7 +95,9 @@ export default {
const options = currentPage.options || {}; const options = currentPage.options || {};
if (options.token) { if (options.token) {
this.dataForm.uniqueToken = options.token; this.dataForm.uniqueToken = options.token;
console.log("App端获取到token:", this.dataForm.uniqueToken); }
if (options.kefuid) {
this.dataForm.kefuid = options.kefuid;
} }
// #endif // #endif
@ -96,7 +108,10 @@ export default {
const options = currentPage.options || {}; const options = currentPage.options || {};
if (options.token) { if (options.token) {
this.dataForm.uniqueToken = options.token; this.dataForm.uniqueToken = options.token;
console.log("小程序端获取到token:", this.dataForm.uniqueToken); }
console.log(options);
if (options.kefuid) {
this.dataForm.kefuid = options.kefuid;
} }
// #endif // #endif
}, },
@ -138,10 +153,9 @@ export default {
ip: this.dataForm.ip, ip: this.dataForm.ip,
sourceUrl: this.dataForm.sourceUrl, sourceUrl: this.dataForm.sourceUrl,
uniqueToken: this.dataForm.uniqueToken, uniqueToken: this.dataForm.uniqueToken,
keFuId: this.dataForm.kefuid, // kefuid
}; };
console.log("登录参数:", loginData);
this.$http({ this.$http({
url: '/login', url: '/login',
data: loginData, data: loginData,
@ -152,7 +166,6 @@ export default {
getApp().$vm.init() getApp().$vm.init()
getApp().$vm.unloadStore(); getApp().$vm.unloadStore();
console.log(loginInfo.customerServiceId);
this.$http({ this.$http({
url: "/friend/add?friendId=" + loginInfo.customerServiceId, url: "/friend/add?friendId=" + loginInfo.customerServiceId,
method: "POST" method: "POST"
@ -227,7 +240,10 @@ export default {
this.dataForm.uniqueToken = options.token; this.dataForm.uniqueToken = options.token;
console.log("onLoad获取到token:", this.dataForm.uniqueToken); console.log("onLoad获取到token:", this.dataForm.uniqueToken);
} }
if (options.kefuid) {
this.dataForm.kefuid = options.kefuid;
console.log("onLoad获取到kefuid:", this.dataForm.kefuid);
}
// //
setTimeout(() => { setTimeout(() => {
this.autoLogin(); this.autoLogin();

Loading…
Cancel
Save