Browse Source

修改切换账号功能类似飞机

master
[yxf] 4 weeks ago
parent
commit
efac214d69
  1. 19
      im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java
  2. 111
      im-platform/src/main/java/com/bx/implatform/controller/UserController.java
  3. 3
      im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java
  4. 7
      im-platform/src/main/java/com/bx/implatform/entity/User.java
  5. 9
      im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java
  6. 9
      im-platform/src/main/java/com/bx/implatform/service/UserService.java
  7. 37
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  8. 76
      im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java
  9. 3
      im-platform/src/main/java/com/bx/implatform/vo/LoginVO.java
  10. 2
      im-web/src/components/setting/Setting.vue
  11. 1337
      im-web/src/view/Home.vue

19
im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java

@ -12,7 +12,9 @@ import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
@Tag(name = "私聊消息") @Tag(name = "私聊消息")
@RestController @RestController
@ -48,6 +50,7 @@ public class PrivateMessageController {
return ResultUtils.success(); return ResultUtils.success();
} }
@GetMapping("/maxReadedId") @GetMapping("/maxReadedId")
@Operation(summary = "获取最大已读消息的id", description = "获取某个会话中已读消息的最大id") @Operation(summary = "获取最大已读消息的id", description = "获取某个会话中已读消息的最大id")
public Result<Long> getMaxReadedId(@RequestParam Long friendId) { public Result<Long> getMaxReadedId(@RequestParam Long friendId) {
@ -63,5 +66,21 @@ public class PrivateMessageController {
return ResultUtils.success(privateMessageService.findHistoryMessage(friendId, page, size)); return ResultUtils.success(privateMessageService.findHistoryMessage(friendId, page, size));
} }
/**
* 批量获取指定用户的未读消息数
*/
@PostMapping("/unreadCounts")
@Operation(summary = "批量获取指定用户的未读消息数", description = "批量获取多个指定用户的未读消息数")
public Result<Map<Long, Integer>> getUnreadCounts(@RequestBody Map<String, List<Long>> params) {
List<Long> userIds = params.get("userIds");
if (userIds == null || userIds.isEmpty()) {
return ResultUtils.success(new HashMap<>());
}
Map<Long, Integer> result = privateMessageService.getUnreadCountsByUserIds(userIds);
return ResultUtils.success(result);
}
} }

111
im-platform/src/main/java/com/bx/implatform/controller/UserController.java

@ -1,7 +1,10 @@
package com.bx.implatform.controller; package com.bx.implatform.controller;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bx.implatform.dto.LoginDTO;
import com.bx.implatform.dto.RegisterDTO; import com.bx.implatform.dto.RegisterDTO;
import com.bx.implatform.entity.User; import com.bx.implatform.entity.User;
import com.bx.implatform.result.Result; import com.bx.implatform.result.Result;
@ -24,9 +27,8 @@ import com.alibaba.fastjson.JSON;
import com.bx.imcommon.util.JwtUtil; import com.bx.imcommon.util.JwtUtil;
import com.bx.implatform.config.props.JwtProperties; import com.bx.implatform.config.props.JwtProperties;
import java.util.HashMap; import java.util.*;
import java.util.List; import java.util.stream.Collectors;
import java.util.Map;
import static com.bx.implatform.enums.ResultCode.XSS_PARAM_ERROR; import static com.bx.implatform.enums.ResultCode.XSS_PARAM_ERROR;
@ -133,6 +135,109 @@ public class UserController {
return ResultUtils.success(result); return ResultUtils.success(result);
} }
@PostMapping("/addAccounts")
@Operation(summary = "客服登录", description = "客服登录")
public Result<LoginVO> addAccounts(@Valid @RequestBody LoginDTO dto) {
LoginVO vo = userService.addAccounts(dto);
return ResultUtils.success(vo);
}
@PostMapping("/getSwitchableAccounts")
@Operation(summary = "获取可切换的账号列表", description = "获取当前客服可切换的账号列表")
public Result<Map<String, Object>> getSwitchableAccounts() {
UserSession session = SessionContext.getSession();
Long userId = session.getUserId();
if (ObjectUtil.isNull(userId)) {
return ResultUtils.error(XSS_PARAM_ERROR);
}
// 获取当前用户信息
User currentUser = userService.getById(userId);
if (currentUser == null) {
return ResultUtils.error(XSS_PARAM_ERROR);
}
Map<String, Object> result = new HashMap<>();
// 获取可切换的账号ID列表(逗号分隔的字符串,如 "13,14")
String switchableIdsStr = currentUser.getSwitchableAccountIds();
List<Map<String, Object>> switchableUsers = new ArrayList<>();
if (StrUtil.isNotBlank(switchableIdsStr)) {
String[] idArray = switchableIdsStr.split(",");
List<Long> ids = Arrays.stream(idArray)
.filter(StrUtil::isNotBlank)
.map(Long::parseLong)
.collect(Collectors.toList());
if (!ids.isEmpty()) {
List<User> users = userService.listByIds(ids);
// 过滤掉被封禁的账号
users = users.stream()
.filter(u -> !Boolean.TRUE.equals(u.getIsBanned()))
.collect(Collectors.toList());
switchableUsers = users.stream().map(user -> {
Map<String, Object> map = new HashMap<>();
map.put("id", user.getId());
map.put("userName", user.getUserName());
map.put("nickName", user.getNickName());
map.put("headImage", user.getHeadImage());
map.put("headImageThumb", user.getHeadImageThumb());
return map;
}).collect(Collectors.toList());
}
}
result.put("switchableUsers", switchableUsers);
// 获取当前用户的 unique_token
String currentUserUniqueToken = currentUser.getUniqueToken();
// 构建查询条件
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<User>()
.eq(User::getIsCustomer, 2)
.ne(User::getId, userId)
.eq(User::getIsBanned, 0);
// 添加 unique_token 条件
if (StrUtil.isNotBlank(currentUserUniqueToken)) {
// 当前用户有 unique_token,只查询相同 unique_token 的客服
queryWrapper.eq(User::getUniqueToken, currentUserUniqueToken);
} else {
// 当前用户没有 unique_token,只查询也没有 unique_token 的客服
queryWrapper.isNull(User::getUniqueToken).or().eq(User::getUniqueToken, "");
}
List<User> availableUsers = userService.list(queryWrapper);
// 获取已添加的ID集合
Set<Long> existingIds = new HashSet<>();
if (StrUtil.isNotBlank(switchableIdsStr)) {
Arrays.stream(switchableIdsStr.split(","))
.filter(StrUtil::isNotBlank)
.map(Long::parseLong)
.forEach(existingIds::add);
}
// 标记是否已添加
List<Map<String, Object>> availableUsersList = availableUsers.stream().map(user -> {
Map<String, Object> map = new HashMap<>();
map.put("id", user.getId());
map.put("userName", user.getUserName());
map.put("nickName", user.getNickName());
map.put("headImage", user.getHeadImage());
// map.put("headImageThumb", user.getHeadImageThumb());
map.put("isAdded", existingIds.contains(user.getId()));
return map;
}).collect(Collectors.toList());
result.put("availableUsers", availableUsersList);
return ResultUtils.success(result);
}
@PostMapping("/changeCustomer") @PostMapping("/changeCustomer")
@Operation(summary = "转接客服", description = "转接客服") @Operation(summary = "转接客服", description = "转接客服")
public Result register(@RequestBody JSONObject jsonObject) { public Result register(@RequestBody JSONObject jsonObject) {

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

@ -37,4 +37,7 @@ public class LoginDTO {
@Schema(description = "客服ID") @Schema(description = "客服ID")
private String keFuId; private String keFuId;
@Schema(description = "可切换客服账号")
private String switchableAccountIds;
} }

7
im-platform/src/main/java/com/bx/implatform/entity/User.java

@ -129,4 +129,11 @@ public class User {
*/ */
private String welcomeMsg; private String welcomeMsg;
/**
* 可切换的账号IDs列表
*/
private String switchableAccountIds;
} }

9
im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java

@ -6,6 +6,7 @@ import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.vo.PrivateMessageVO; import com.bx.implatform.vo.PrivateMessageVO;
import java.util.List; import java.util.List;
import java.util.Map;
public interface PrivateMessageService extends IService<PrivateMessage> { public interface PrivateMessageService extends IService<PrivateMessage> {
@ -63,4 +64,12 @@ public interface PrivateMessageService extends IService<PrivateMessage> {
* @param customerId 客服id * @param customerId 客服id
*/ */
void changeMessageRecord(Long customerId,Long targetId, Long userId); void changeMessageRecord(Long customerId,Long targetId, Long userId);
/**
* 批量获取指定用户的未读消息数
* @param userIds 要查询的用户ID列表
* @return key: 用户ID, value: 该用户的未读消息数
*/
Map<Long, Integer> getUnreadCountsByUserIds(List<Long> userIds);
} }

9
im-platform/src/main/java/com/bx/implatform/service/UserService.java

@ -29,6 +29,15 @@ public interface UserService extends IService<User> {
*/ */
LoginVO loginCustom(LoginDTO dto); LoginVO loginCustom(LoginDTO dto);
/**
* 客服登录
*
* @param dto 登录dto
* @return 登录token
*/
LoginVO addAccounts(LoginDTO dto);
/** /**
* 修改用户密码 * 修改用户密码
* *

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

@ -32,10 +32,7 @@ import org.apache.commons.lang3.time.DateUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList; import java.util.*;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -242,6 +239,36 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
return vos; return vos;
} }
@Override
public Map<Long, Integer> getUnreadCountsByUserIds(List<Long> userIds) {
if (userIds == null || userIds.isEmpty()) {
return new HashMap<>();
}
Map<Long, Integer> result = new HashMap<>();
// 初始化所有用户为0
for (Long userId : userIds) {
result.put(userId, 0);
}
// 批量查询未读消息(status = 0 表示未读)
LambdaQueryWrapper<PrivateMessage> wrapper = Wrappers.lambdaQuery();
wrapper.in(PrivateMessage::getRecvId, userIds)
.eq(PrivateMessage::getStatus, 0) // 0 表示未读
.select(PrivateMessage::getRecvId);
List<PrivateMessage> messages = this.list(wrapper);
// 统计每个用户的未读消息数
for (PrivateMessage msg : messages) {
Long recvId = msg.getRecvId();
result.put(recvId, result.getOrDefault(recvId, 0) + 1);
}
return result;
}
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public void readedMessage(Long friendId) { public void readedMessage(Long friendId) {
@ -307,7 +334,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
} }
@Override // @Override
public void changeMessageRecord(Long customerId, Long targetId, Long userId) { public void changeMessageRecord(Long customerId, Long targetId, Long userId) {
List<PrivateMessage> list = new ArrayList<>(); List<PrivateMessage> list = new ArrayList<>();

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

@ -225,6 +225,82 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
return randomCustomerId; return randomCustomerId;
} }
@Override
@Transactional(rollbackFor = Exception.class)
public LoginVO addAccounts(LoginDTO dto) {
// 获取当前登录用户
UserSession session = SessionContext.getSession();
Long currentUserId = session.getUserId();
if (ObjectUtil.isNull(currentUserId)) {
throw new GlobalException("用户未登录");
}
// 获取当前用户信息
User currentUser = this.getById(currentUserId);
if (ObjectUtil.isNull(currentUser)) {
throw new GlobalException("当前用户不存在");
}
// 验证要添加的账号
User targetUser = this.findUserByUserName(dto.getUserName());
if (Objects.isNull(targetUser)) {
throw new GlobalException("用户不存在");
}
if (targetUser.getIsCustomer() == 1) {
throw new GlobalException("只能添加客服账号");
}
if (targetUser.getIsBanned()) {
String tip = String.format("该账号因'%s'已被管理员封禁", targetUser.getReason());
throw new GlobalException(tip);
}
if (!passwordEncoder.matches(dto.getPassword(), targetUser.getPassword())) {
throw new GlobalException(ResultCode.PASSWOR_ERROR);
}
// 未设置套餐或套餐过期
if (imAgentService.isPackageExpire(targetUser.getUniqueToken())) {
throw new GlobalException("套餐已过期");
}
// 验证是否属于同一个代理(unique_token 相同)
if (!Objects.equals(currentUser.getUniqueToken(), targetUser.getUniqueToken())) {
throw new GlobalException("只能添加同属一个代理的客服账号");
}
// 不能添加自己
if (currentUserId.equals(targetUser.getId())) {
throw new GlobalException("不能添加当前登录的账号");
}
// 获取当前用户的可切换账号ID列表
String switchableIdsStr = currentUser.getSwitchableAccountIds();
Set<String> idSet = new HashSet<>();
if (StrUtil.isNotBlank(switchableIdsStr)) {
idSet.addAll(Arrays.asList(switchableIdsStr.split(",")));
}
// 添加新ID
String newId = String.valueOf(targetUser.getId());
if (idSet.contains(newId)) {
throw new GlobalException("该账号已在切换列表中");
}
idSet.add(newId);
// 更新当前用户的 switchable_account_ids
String newIdsStr = String.join(",", idSet);
currentUser.setSwitchableAccountIds(newIdsStr);
this.updateById(currentUser);
log.info("用户 {} 添加了可切换账号 {},当前可切换账号列表: {}",
currentUserId, targetUser.getId(), newIdsStr);
LoginVO vo = new LoginVO();
return vo;
}
@Override @Override
public LoginVO loginCustom(LoginDTO dto) { public LoginVO loginCustom(LoginDTO dto) {
User user = this.findUserByUserName(dto.getUserName()); User user = this.findUserByUserName(dto.getUserName());

3
im-platform/src/main/java/com/bx/implatform/vo/LoginVO.java

@ -26,4 +26,7 @@ public class LoginVO {
@Schema(description = "当前登录用户信息") @Schema(description = "当前登录用户信息")
private User user; private User user;
@Schema(description = "可切换客服账号")
private String switchableAccountIds;
} }

2
im-web/src/components/setting/Setting.vue

@ -30,7 +30,7 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="onSwitchAccount" style="float: left;">切换账号</el-button> <!-- <el-button @click="onSwitchAccount" style="float: left;">切换账号</el-button> -->
<el-button @click="onClose()"> </el-button> <el-button @click="onClose()"> </el-button>
<el-button type="primary" @click="onSubmit()"> </el-button> <el-button type="primary" @click="onSubmit()"> </el-button>
</span> </span>

1337
im-web/src/view/Home.vue

File diff suppressed because it is too large
Loading…
Cancel
Save