发布 v1.10 #5
@@ -16,6 +16,7 @@ import tech.easyflow.common.domain.Result;
|
|||||||
import tech.easyflow.common.entity.LoginAccount;
|
import tech.easyflow.common.entity.LoginAccount;
|
||||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||||
import tech.easyflow.common.web.jsonbody.JsonBody;
|
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||||
|
import tech.easyflow.system.service.CategoryPermissionService;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -25,37 +26,108 @@ import java.util.List;
|
|||||||
public class ChatHistoryController {
|
public class ChatHistoryController {
|
||||||
|
|
||||||
private final ChatHistoryManageService chatHistoryManageService;
|
private final ChatHistoryManageService chatHistoryManageService;
|
||||||
|
private final CategoryPermissionService categoryPermissionService;
|
||||||
|
|
||||||
public ChatHistoryController(ChatHistoryManageService chatHistoryManageService) {
|
/**
|
||||||
|
* 创建聊天历史控制器。
|
||||||
|
*
|
||||||
|
* @param chatHistoryManageService 聊天历史管理服务
|
||||||
|
* @param categoryPermissionService 账号权限服务
|
||||||
|
*/
|
||||||
|
public ChatHistoryController(ChatHistoryManageService chatHistoryManageService,
|
||||||
|
CategoryPermissionService categoryPermissionService) {
|
||||||
this.chatHistoryManageService = chatHistoryManageService;
|
this.chatHistoryManageService = chatHistoryManageService;
|
||||||
|
this.categoryPermissionService = categoryPermissionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询当前账号可见的 Agent 会话。
|
||||||
|
*
|
||||||
|
* @param query 会话筛选条件
|
||||||
|
* @return 会话分页结果
|
||||||
|
*/
|
||||||
@GetMapping("/sessions")
|
@GetMapping("/sessions")
|
||||||
public Result<ChatSessionPage> listSessions(ChatSessionFilterQuery query) {
|
public Result<ChatSessionPage> listSessions(ChatSessionFilterQuery query) {
|
||||||
return Result.ok(chatHistoryManageService.queryAdminSessions(query));
|
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||||
|
return Result.ok(chatHistoryManageService.queryAdminSessions(
|
||||||
|
account.getId(),
|
||||||
|
categoryPermissionService.isSuperAdmin(account),
|
||||||
|
query
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前账号可见的 Agent 会话详情。
|
||||||
|
*
|
||||||
|
* @param sessionId 会话 ID
|
||||||
|
* @return 会话详情
|
||||||
|
*/
|
||||||
@GetMapping("/sessions/{sessionId}")
|
@GetMapping("/sessions/{sessionId}")
|
||||||
public Result<ChatSessionSummary> getSession(@PathVariable BigInteger sessionId) {
|
public Result<ChatSessionSummary> getSession(@PathVariable BigInteger sessionId) {
|
||||||
return Result.ok(chatHistoryManageService.getAdminSession(sessionId));
|
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||||
|
return Result.ok(chatHistoryManageService.getAdminSession(
|
||||||
|
account.getId(),
|
||||||
|
categoryPermissionService.isSuperAdmin(account),
|
||||||
|
sessionId
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询当前账号可见会话的消息。
|
||||||
|
*
|
||||||
|
* @param sessionId 会话 ID
|
||||||
|
* @param query 消息分页条件
|
||||||
|
* @return 消息分页结果
|
||||||
|
*/
|
||||||
@GetMapping("/sessions/{sessionId}/messages")
|
@GetMapping("/sessions/{sessionId}/messages")
|
||||||
public Result<ChatHistoryPage> queryMessages(@PathVariable BigInteger sessionId, ChatPageQuery query) {
|
public Result<ChatHistoryPage> queryMessages(@PathVariable BigInteger sessionId, ChatPageQuery query) {
|
||||||
return Result.ok(chatHistoryManageService.queryAdminMessages(sessionId, query));
|
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||||
|
return Result.ok(chatHistoryManageService.queryAdminMessages(
|
||||||
|
account.getId(),
|
||||||
|
categoryPermissionService.isSuperAdmin(account),
|
||||||
|
sessionId,
|
||||||
|
query
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前账号可见会话的答案版本。
|
||||||
|
*
|
||||||
|
* @param sessionId 会话 ID
|
||||||
|
* @param roundId 对话轮次 ID
|
||||||
|
* @return 答案版本列表
|
||||||
|
*/
|
||||||
@GetMapping("/sessions/{sessionId}/rounds/{roundId}/variants")
|
@GetMapping("/sessions/{sessionId}/rounds/{roundId}/variants")
|
||||||
public Result<List<ChatMessageRecord>> listRoundVariants(@PathVariable BigInteger sessionId,
|
public Result<List<ChatMessageRecord>> listRoundVariants(@PathVariable BigInteger sessionId,
|
||||||
@PathVariable BigInteger roundId) {
|
@PathVariable BigInteger roundId) {
|
||||||
return Result.ok(chatHistoryManageService.listAdminRoundVariants(sessionId, roundId));
|
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||||
|
return Result.ok(chatHistoryManageService.listAdminRoundVariants(
|
||||||
|
account.getId(),
|
||||||
|
categoryPermissionService.isSuperAdmin(account),
|
||||||
|
sessionId,
|
||||||
|
roundId
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择当前账号可见会话的答案版本。
|
||||||
|
*
|
||||||
|
* @param sessionId 会话 ID
|
||||||
|
* @param roundId 对话轮次 ID
|
||||||
|
* @param variantIndex 目标版本索引
|
||||||
|
* @return 选中的答案记录
|
||||||
|
*/
|
||||||
@PostMapping("/sessions/{sessionId}/rounds/{roundId}/selectVariant")
|
@PostMapping("/sessions/{sessionId}/rounds/{roundId}/selectVariant")
|
||||||
public Result<ChatMessageRecord> selectRoundVariant(@PathVariable BigInteger sessionId,
|
public Result<ChatMessageRecord> selectRoundVariant(@PathVariable BigInteger sessionId,
|
||||||
@PathVariable BigInteger roundId,
|
@PathVariable BigInteger roundId,
|
||||||
@JsonBody(value = "variantIndex", required = true) Integer variantIndex) {
|
@JsonBody(value = "variantIndex", required = true) Integer variantIndex) {
|
||||||
LoginAccount account = SaTokenUtil.getLoginAccount();
|
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||||
return Result.ok(chatHistoryManageService.selectAdminRoundVariant(sessionId, roundId, variantIndex, account.getId()));
|
return Result.ok(chatHistoryManageService.selectAdminRoundVariant(
|
||||||
|
account.getId(),
|
||||||
|
categoryPermissionService.isSuperAdmin(account),
|
||||||
|
sessionId,
|
||||||
|
roundId,
|
||||||
|
variantIndex
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package tech.easyflow.admin.controller.ai;
|
||||||
|
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
import tech.easyflow.chatlog.domain.dto.ChatSessionPage;
|
||||||
|
import tech.easyflow.chatlog.domain.dto.ChatSessionSummary;
|
||||||
|
import tech.easyflow.chatlog.domain.query.ChatSessionFilterQuery;
|
||||||
|
import tech.easyflow.chatlog.service.ChatHistoryManageService;
|
||||||
|
import tech.easyflow.common.entity.LoginAccount;
|
||||||
|
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||||
|
import tech.easyflow.system.service.CategoryPermissionService;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.mockStatic;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ChatHistoryController} 数据范围测试。
|
||||||
|
*/
|
||||||
|
public class ChatHistoryControllerTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证普通账号查询时将本人范围传给服务层。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void listSessionsShouldUseCurrentUserScopeForRegularAccount() {
|
||||||
|
BigInteger accountId = BigInteger.valueOf(20);
|
||||||
|
ChatHistoryManageService service = mock(ChatHistoryManageService.class);
|
||||||
|
CategoryPermissionService permissionService = mock(CategoryPermissionService.class);
|
||||||
|
ChatHistoryController controller = new ChatHistoryController(service, permissionService);
|
||||||
|
ChatSessionFilterQuery query = new ChatSessionFilterQuery();
|
||||||
|
LoginAccount account = loginAccount(accountId);
|
||||||
|
when(permissionService.isSuperAdmin(account)).thenReturn(false);
|
||||||
|
when(service.queryAdminSessions(accountId, false, query)).thenReturn(new ChatSessionPage());
|
||||||
|
|
||||||
|
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
|
||||||
|
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
|
||||||
|
|
||||||
|
controller.listSessions(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(service).queryAdminSessions(accountId, false, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证超级管理员查询详情时保留全量范围。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getSessionShouldUseAllScopeForSuperAdmin() {
|
||||||
|
BigInteger accountId = BigInteger.ONE;
|
||||||
|
BigInteger sessionId = BigInteger.valueOf(30);
|
||||||
|
ChatHistoryManageService service = mock(ChatHistoryManageService.class);
|
||||||
|
CategoryPermissionService permissionService = mock(CategoryPermissionService.class);
|
||||||
|
ChatHistoryController controller = new ChatHistoryController(service, permissionService);
|
||||||
|
LoginAccount account = loginAccount(accountId);
|
||||||
|
when(permissionService.isSuperAdmin(account)).thenReturn(true);
|
||||||
|
when(service.getAdminSession(accountId, true, sessionId)).thenReturn(new ChatSessionSummary());
|
||||||
|
|
||||||
|
try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
|
||||||
|
saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
|
||||||
|
|
||||||
|
controller.getSession(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
verify(service).getAdminSession(accountId, true, sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造登录账号。
|
||||||
|
*
|
||||||
|
* @param accountId 账号 ID
|
||||||
|
* @return 登录账号
|
||||||
|
*/
|
||||||
|
private LoginAccount loginAccount(BigInteger accountId) {
|
||||||
|
LoginAccount account = new LoginAccount();
|
||||||
|
account.setId(accountId);
|
||||||
|
return account;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,19 +10,54 @@ import tech.easyflow.chatlog.domain.query.ChatSessionFilterQuery;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent 聊天历史管理服务。
|
||||||
|
*/
|
||||||
public interface ChatHistoryManageService {
|
public interface ChatHistoryManageService {
|
||||||
|
|
||||||
ChatSessionPage queryUserSessions(BigInteger userId, BigInteger assistantId, ChatPageQuery query);
|
ChatSessionPage queryUserSessions(BigInteger userId, BigInteger assistantId, ChatPageQuery query);
|
||||||
|
|
||||||
ChatSessionPage queryAdminSessions(ChatSessionFilterQuery query);
|
/**
|
||||||
|
* 按当前管理端账号的数据范围查询 Agent 会话。
|
||||||
|
*
|
||||||
|
* @param requesterId 当前账号 ID
|
||||||
|
* @param superAdmin 当前账号是否为超级管理员
|
||||||
|
* @param query 会话筛选条件
|
||||||
|
* @return 会话分页结果
|
||||||
|
*/
|
||||||
|
ChatSessionPage queryAdminSessions(BigInteger requesterId,
|
||||||
|
boolean superAdmin,
|
||||||
|
ChatSessionFilterQuery query);
|
||||||
|
|
||||||
ChatSessionSummary getUserSession(BigInteger userId, BigInteger sessionId);
|
ChatSessionSummary getUserSession(BigInteger userId, BigInteger sessionId);
|
||||||
|
|
||||||
ChatSessionSummary getAdminSession(BigInteger sessionId);
|
/**
|
||||||
|
* 获取当前管理端账号可见的 Agent 会话。
|
||||||
|
*
|
||||||
|
* @param requesterId 当前账号 ID
|
||||||
|
* @param superAdmin 当前账号是否为超级管理员
|
||||||
|
* @param sessionId 会话 ID
|
||||||
|
* @return 会话摘要
|
||||||
|
*/
|
||||||
|
ChatSessionSummary getAdminSession(BigInteger requesterId,
|
||||||
|
boolean superAdmin,
|
||||||
|
BigInteger sessionId);
|
||||||
|
|
||||||
ChatHistoryPage queryUserMessages(BigInteger userId, BigInteger sessionId, ChatPageQuery query);
|
ChatHistoryPage queryUserMessages(BigInteger userId, BigInteger sessionId, ChatPageQuery query);
|
||||||
|
|
||||||
ChatHistoryPage queryAdminMessages(BigInteger sessionId, ChatPageQuery query);
|
/**
|
||||||
|
* 查询当前管理端账号可见会话的消息。
|
||||||
|
*
|
||||||
|
* @param requesterId 当前账号 ID
|
||||||
|
* @param superAdmin 当前账号是否为超级管理员
|
||||||
|
* @param sessionId 会话 ID
|
||||||
|
* @param query 消息分页条件
|
||||||
|
* @return 消息分页结果
|
||||||
|
*/
|
||||||
|
ChatHistoryPage queryAdminMessages(BigInteger requesterId,
|
||||||
|
boolean superAdmin,
|
||||||
|
BigInteger sessionId,
|
||||||
|
ChatPageQuery query);
|
||||||
|
|
||||||
void renameUserSession(BigInteger userId, BigInteger sessionId, String title, BigInteger operatorId);
|
void renameUserSession(BigInteger userId, BigInteger sessionId, String title, BigInteger operatorId);
|
||||||
|
|
||||||
@@ -32,7 +67,33 @@ public interface ChatHistoryManageService {
|
|||||||
|
|
||||||
ChatMessageRecord selectUserRoundVariant(BigInteger userId, BigInteger sessionId, BigInteger roundId, Integer variantIndex, BigInteger operatorId);
|
ChatMessageRecord selectUserRoundVariant(BigInteger userId, BigInteger sessionId, BigInteger roundId, Integer variantIndex, BigInteger operatorId);
|
||||||
|
|
||||||
List<ChatMessageRecord> listAdminRoundVariants(BigInteger sessionId, BigInteger roundId);
|
/**
|
||||||
|
* 查询当前管理端账号可见会话的答案版本。
|
||||||
|
*
|
||||||
|
* @param requesterId 当前账号 ID
|
||||||
|
* @param superAdmin 当前账号是否为超级管理员
|
||||||
|
* @param sessionId 会话 ID
|
||||||
|
* @param roundId 对话轮次 ID
|
||||||
|
* @return 答案版本列表
|
||||||
|
*/
|
||||||
|
List<ChatMessageRecord> listAdminRoundVariants(BigInteger requesterId,
|
||||||
|
boolean superAdmin,
|
||||||
|
BigInteger sessionId,
|
||||||
|
BigInteger roundId);
|
||||||
|
|
||||||
ChatMessageRecord selectAdminRoundVariant(BigInteger sessionId, BigInteger roundId, Integer variantIndex, BigInteger operatorId);
|
/**
|
||||||
|
* 选择当前管理端账号可见会话的答案版本。
|
||||||
|
*
|
||||||
|
* @param requesterId 当前账号 ID,同时作为操作人 ID
|
||||||
|
* @param superAdmin 当前账号是否为超级管理员
|
||||||
|
* @param sessionId 会话 ID
|
||||||
|
* @param roundId 对话轮次 ID
|
||||||
|
* @param variantIndex 目标版本索引
|
||||||
|
* @return 选中的答案记录
|
||||||
|
*/
|
||||||
|
ChatMessageRecord selectAdminRoundVariant(BigInteger requesterId,
|
||||||
|
boolean superAdmin,
|
||||||
|
BigInteger sessionId,
|
||||||
|
BigInteger roundId,
|
||||||
|
Integer variantIndex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ import tech.easyflow.common.web.exceptions.BusinessException;
|
|||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agent 聊天历史管理服务实现。
|
||||||
|
*/
|
||||||
@Service
|
@Service
|
||||||
public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
||||||
|
|
||||||
@@ -29,6 +32,15 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
|||||||
private final ChatRoundOperateService chatRoundOperateService;
|
private final ChatRoundOperateService chatRoundOperateService;
|
||||||
private final ChatAnalyticalDBRepository chatAnalyticalDBRepository;
|
private final ChatAnalyticalDBRepository chatAnalyticalDBRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建 Agent 聊天历史管理服务。
|
||||||
|
*
|
||||||
|
* @param chatSessionQueryService 会话查询服务
|
||||||
|
* @param chatSessionCommandService 会话命令服务
|
||||||
|
* @param chatHistoryQueryService 历史消息查询服务
|
||||||
|
* @param chatRoundOperateService 对话轮次操作服务
|
||||||
|
* @param chatAnalyticalDBRepository 聊天分析库仓储
|
||||||
|
*/
|
||||||
public ChatHistoryManageServiceImpl(ChatSessionQueryService chatSessionQueryService,
|
public ChatHistoryManageServiceImpl(ChatSessionQueryService chatSessionQueryService,
|
||||||
ChatSessionCommandService chatSessionCommandService,
|
ChatSessionCommandService chatSessionCommandService,
|
||||||
ChatHistoryQueryService chatHistoryQueryService,
|
ChatHistoryQueryService chatHistoryQueryService,
|
||||||
@@ -46,11 +58,22 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
|||||||
return chatSessionQueryService.pageSessions(userId, assistantId, AGENT_ASSISTANT_CODE, query);
|
return chatSessionQueryService.pageSessions(userId, assistantId, AGENT_ASSISTANT_CODE, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ChatSessionPage queryAdminSessions(ChatSessionFilterQuery query) {
|
public ChatSessionPage queryAdminSessions(BigInteger requesterId,
|
||||||
|
boolean superAdmin,
|
||||||
|
ChatSessionFilterQuery query) {
|
||||||
ChatSessionFilterQuery effectiveQuery = query == null ? new ChatSessionFilterQuery() : query;
|
ChatSessionFilterQuery effectiveQuery = query == null ? new ChatSessionFilterQuery() : query;
|
||||||
// 管理端聊天历史已经切换为 Agent 专属入口,类型由服务端固定,避免客户端绕过。
|
// 管理端聊天历史已经切换为 Agent 专属入口,类型由服务端固定,避免客户端绕过。
|
||||||
effectiveQuery.setAssistantCode(AGENT_ASSISTANT_CODE);
|
effectiveQuery.setAssistantCode(AGENT_ASSISTANT_CODE);
|
||||||
|
if (!superAdmin) {
|
||||||
|
requireRequesterId(requesterId);
|
||||||
|
// 普通账号的数据范围由服务端覆盖,客户端无法通过筛选参数扩大范围。
|
||||||
|
effectiveQuery.setUserId(requesterId);
|
||||||
|
effectiveQuery.setUserAccount(null);
|
||||||
|
}
|
||||||
return chatAnalyticalDBRepository.pageSessions(effectiveQuery);
|
return chatAnalyticalDBRepository.pageSessions(effectiveQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,14 +91,25 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
|||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ChatSessionSummary getAdminSession(BigInteger sessionId) {
|
public ChatSessionSummary getAdminSession(BigInteger requesterId,
|
||||||
|
boolean superAdmin,
|
||||||
|
BigInteger sessionId) {
|
||||||
ChatSessionSummary summary = chatAnalyticalDBRepository.getSession(sessionId);
|
ChatSessionSummary summary = chatAnalyticalDBRepository.getSession(sessionId);
|
||||||
if (summary == null
|
if (summary == null
|
||||||
|| Integer.valueOf(1).equals(summary.getIsDeleted())
|
|| Integer.valueOf(1).equals(summary.getIsDeleted())
|
||||||
|| !AGENT_ASSISTANT_CODE.equals(summary.getAssistantCode())) {
|
|| !AGENT_ASSISTANT_CODE.equals(summary.getAssistantCode())) {
|
||||||
throw new BusinessException("Agent 会话不存在");
|
throw new BusinessException("Agent 会话不存在");
|
||||||
}
|
}
|
||||||
|
if (!superAdmin) {
|
||||||
|
requireRequesterId(requesterId);
|
||||||
|
if (summary.getUserId() == null || !summary.getUserId().equals(requesterId)) {
|
||||||
|
throw new BusinessException(403, 403, "无权访问该会话");
|
||||||
|
}
|
||||||
|
}
|
||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,9 +123,15 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
|||||||
return chatHistoryQueryService.queryHistoryMessages(sessionId, query);
|
return chatHistoryQueryService.queryHistoryMessages(sessionId, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ChatHistoryPage queryAdminMessages(BigInteger sessionId, ChatPageQuery query) {
|
public ChatHistoryPage queryAdminMessages(BigInteger requesterId,
|
||||||
ChatSessionSummary summary = getAdminSession(sessionId);
|
boolean superAdmin,
|
||||||
|
BigInteger sessionId,
|
||||||
|
ChatPageQuery query) {
|
||||||
|
ChatSessionSummary summary = getAdminSession(requesterId, superAdmin, sessionId);
|
||||||
ChatHistoryPage firstPage = restoreRecentMessages(summary, query);
|
ChatHistoryPage firstPage = restoreRecentMessages(summary, query);
|
||||||
if (firstPage != null) {
|
if (firstPage != null) {
|
||||||
return firstPage;
|
return firstPage;
|
||||||
@@ -126,16 +166,29 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
|||||||
return chatRoundOperateService.selectVariant(sessionId, roundId, variantIndex, operatorId);
|
return chatRoundOperateService.selectVariant(sessionId, roundId, variantIndex, operatorId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public java.util.List<ChatMessageRecord> listAdminRoundVariants(BigInteger sessionId, BigInteger roundId) {
|
public java.util.List<ChatMessageRecord> listAdminRoundVariants(BigInteger requesterId,
|
||||||
getAdminSession(sessionId);
|
boolean superAdmin,
|
||||||
|
BigInteger sessionId,
|
||||||
|
BigInteger roundId) {
|
||||||
|
getAdminSession(requesterId, superAdmin, sessionId);
|
||||||
return chatRoundOperateService.listVariants(sessionId, roundId);
|
return chatRoundOperateService.listVariants(sessionId, roundId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ChatMessageRecord selectAdminRoundVariant(BigInteger sessionId, BigInteger roundId, Integer variantIndex, BigInteger operatorId) {
|
public ChatMessageRecord selectAdminRoundVariant(BigInteger requesterId,
|
||||||
getAdminSession(sessionId);
|
boolean superAdmin,
|
||||||
return chatRoundOperateService.selectVariant(sessionId, roundId, variantIndex, operatorId);
|
BigInteger sessionId,
|
||||||
|
BigInteger roundId,
|
||||||
|
Integer variantIndex) {
|
||||||
|
getAdminSession(requesterId, superAdmin, sessionId);
|
||||||
|
return chatRoundOperateService.selectVariant(sessionId, roundId, variantIndex, requesterId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChatHistoryPage restoreRecentMessages(ChatSessionSummary summary, ChatPageQuery query) {
|
private ChatHistoryPage restoreRecentMessages(ChatSessionSummary summary, ChatPageQuery query) {
|
||||||
@@ -157,4 +210,15 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
|||||||
page.setTotal(Math.max(total, records.size()));
|
page.setTotal(Math.max(total, records.size()));
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验受限查询必须携带当前账号 ID。
|
||||||
|
*
|
||||||
|
* @param requesterId 当前账号 ID
|
||||||
|
*/
|
||||||
|
private void requireRequesterId(BigInteger requesterId) {
|
||||||
|
if (requesterId == null) {
|
||||||
|
throw new BusinessException(403, 403, "无权访问聊天记录");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,12 +110,29 @@ public class ChatHistoryManageServiceImplTest {
|
|||||||
ChatSessionFilterQuery query = new ChatSessionFilterQuery();
|
ChatSessionFilterQuery query = new ChatSessionFilterQuery();
|
||||||
query.setAssistantCode("BOT");
|
query.setAssistantCode("BOT");
|
||||||
|
|
||||||
service.queryAdminSessions(query);
|
service.queryAdminSessions(BigInteger.ONE, true, query);
|
||||||
|
|
||||||
Assert.assertEquals("AGENT", query.getAssistantCode());
|
Assert.assertEquals("AGENT", query.getAssistantCode());
|
||||||
Assert.assertSame(query, chatAnalyticalDBRepository.lastPageQuery);
|
Assert.assertSame(query, chatAnalyticalDBRepository.lastPageQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证普通账号列表由服务端强制限定为本人,并忽略用户账号筛选。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void queryAdminSessionsShouldForceCurrentUserForRegularAccount() {
|
||||||
|
BigInteger requesterId = BigInteger.valueOf(2004);
|
||||||
|
ChatSessionFilterQuery query = new ChatSessionFilterQuery();
|
||||||
|
query.setUserId(BigInteger.valueOf(9999));
|
||||||
|
query.setUserAccount("other-user");
|
||||||
|
|
||||||
|
service.queryAdminSessions(requesterId, false, query);
|
||||||
|
|
||||||
|
Assert.assertEquals(requesterId, query.getUserId());
|
||||||
|
Assert.assertNull(query.getUserAccount());
|
||||||
|
Assert.assertEquals("AGENT", query.getAssistantCode());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证管理端可以读取正式 Agent 会话。
|
* 验证管理端可以读取正式 Agent 会话。
|
||||||
*/
|
*/
|
||||||
@@ -125,11 +142,31 @@ public class ChatHistoryManageServiceImplTest {
|
|||||||
ChatSessionSummary summary = session(sessionId, "AGENT", 0);
|
ChatSessionSummary summary = session(sessionId, "AGENT", 0);
|
||||||
chatAnalyticalDBRepository.sessionResult = summary;
|
chatAnalyticalDBRepository.sessionResult = summary;
|
||||||
|
|
||||||
ChatSessionSummary result = service.getAdminSession(sessionId);
|
ChatSessionSummary result = service.getAdminSession(BigInteger.ONE, true, sessionId);
|
||||||
|
|
||||||
Assert.assertSame(summary, result);
|
Assert.assertSame(summary, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证普通账号只能读取归属于本人的 Agent 会话。
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void getAdminSessionShouldRejectForeignSessionForRegularAccount() {
|
||||||
|
BigInteger requesterId = BigInteger.valueOf(2005);
|
||||||
|
BigInteger sessionId = BigInteger.valueOf(3005);
|
||||||
|
ChatSessionSummary summary = session(sessionId, "AGENT", 0);
|
||||||
|
summary.setUserId(BigInteger.valueOf(9999));
|
||||||
|
chatAnalyticalDBRepository.sessionResult = summary;
|
||||||
|
|
||||||
|
BusinessException exception = Assert.assertThrows(
|
||||||
|
BusinessException.class,
|
||||||
|
() -> service.getAdminSession(requesterId, false, sessionId)
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.assertEquals(403, exception.getHttpStatus());
|
||||||
|
Assert.assertEquals("无权访问该会话", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证正式 Agent 会话可以继续读取历史消息。
|
* 验证正式 Agent 会话可以继续读取历史消息。
|
||||||
*/
|
*/
|
||||||
@@ -148,7 +185,7 @@ public class ChatHistoryManageServiceImplTest {
|
|||||||
ChatPageQuery query = new ChatPageQuery();
|
ChatPageQuery query = new ChatPageQuery();
|
||||||
query.setPageNumber(2);
|
query.setPageNumber(2);
|
||||||
|
|
||||||
ChatHistoryPage result = service.queryAdminMessages(sessionId, query);
|
ChatHistoryPage result = service.queryAdminMessages(BigInteger.ONE, true, sessionId, query);
|
||||||
|
|
||||||
Assert.assertSame(expectedPage, result);
|
Assert.assertSame(expectedPage, result);
|
||||||
}
|
}
|
||||||
@@ -163,7 +200,7 @@ public class ChatHistoryManageServiceImplTest {
|
|||||||
|
|
||||||
BusinessException exception = Assert.assertThrows(
|
BusinessException exception = Assert.assertThrows(
|
||||||
BusinessException.class,
|
BusinessException.class,
|
||||||
() -> service.getAdminSession(sessionId)
|
() -> service.getAdminSession(BigInteger.ONE, true, sessionId)
|
||||||
);
|
);
|
||||||
|
|
||||||
Assert.assertEquals("Agent 会话不存在", exception.getMessage());
|
Assert.assertEquals("Agent 会话不存在", exception.getMessage());
|
||||||
@@ -179,7 +216,12 @@ public class ChatHistoryManageServiceImplTest {
|
|||||||
|
|
||||||
Assert.assertThrows(
|
Assert.assertThrows(
|
||||||
BusinessException.class,
|
BusinessException.class,
|
||||||
() -> service.queryAdminMessages(sessionId, new ChatPageQuery())
|
() -> service.queryAdminMessages(
|
||||||
|
BigInteger.ONE,
|
||||||
|
true,
|
||||||
|
sessionId,
|
||||||
|
new ChatPageQuery()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +235,12 @@ public class ChatHistoryManageServiceImplTest {
|
|||||||
|
|
||||||
Assert.assertThrows(
|
Assert.assertThrows(
|
||||||
BusinessException.class,
|
BusinessException.class,
|
||||||
() -> service.listAdminRoundVariants(sessionId, BigInteger.ONE)
|
() -> service.listAdminRoundVariants(
|
||||||
|
BigInteger.ONE,
|
||||||
|
true,
|
||||||
|
sessionId,
|
||||||
|
BigInteger.ONE
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,10 +255,11 @@ public class ChatHistoryManageServiceImplTest {
|
|||||||
Assert.assertThrows(
|
Assert.assertThrows(
|
||||||
BusinessException.class,
|
BusinessException.class,
|
||||||
() -> service.selectAdminRoundVariant(
|
() -> service.selectAdminRoundVariant(
|
||||||
|
BigInteger.TEN,
|
||||||
|
true,
|
||||||
sessionId,
|
sessionId,
|
||||||
BigInteger.ONE,
|
BigInteger.ONE,
|
||||||
1,
|
1
|
||||||
BigInteger.TEN
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { ChatTimeTimelineItem } from '@easyflow/types';
|
|||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { useEasyFlowDrawer } from '@easyflow/common-ui';
|
import { useEasyFlowDrawer } from '@easyflow/common-ui';
|
||||||
|
import { useUserStore } from '@easyflow/stores';
|
||||||
import {
|
import {
|
||||||
ChatTimeHistoryMapper,
|
ChatTimeHistoryMapper,
|
||||||
ChatTimeTimelineBuilder,
|
ChatTimeTimelineBuilder,
|
||||||
@@ -28,6 +29,7 @@ import { api } from '#/api/request';
|
|||||||
import ChatHistoryDetailDrawer from '#/components/chat-history/ChatHistoryDetailDrawer.vue';
|
import ChatHistoryDetailDrawer from '#/components/chat-history/ChatHistoryDetailDrawer.vue';
|
||||||
import ListPageShell from '#/components/page/ListPageShell.vue';
|
import ListPageShell from '#/components/page/ListPageShell.vue';
|
||||||
|
|
||||||
|
const userStore = useUserStore();
|
||||||
const agentOptions = ref<any[]>([]);
|
const agentOptions = ref<any[]>([]);
|
||||||
const agentLoading = ref(false);
|
const agentLoading = ref(false);
|
||||||
const sessions = ref<any[]>([]);
|
const sessions = ref<any[]>([]);
|
||||||
@@ -91,6 +93,9 @@ const hasMoreMessages = computed(
|
|||||||
const selectedSessionId = computed(() =>
|
const selectedSessionId = computed(() =>
|
||||||
String(currentSession.value?.id || ''),
|
String(currentSession.value?.id || ''),
|
||||||
);
|
);
|
||||||
|
const isSuperAdmin = computed(() =>
|
||||||
|
userStore.userRoles.includes('super_admin'),
|
||||||
|
);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await Promise.all([fetchAgents(), fetchSessions()]);
|
await Promise.all([fetchAgents(), fetchSessions()]);
|
||||||
@@ -119,7 +124,9 @@ async function fetchSessions() {
|
|||||||
const [, res] = await tryit(api.get)('/api/v1/chatHistory/sessions', {
|
const [, res] = await tryit(api.get)('/api/v1/chatHistory/sessions', {
|
||||||
params: {
|
params: {
|
||||||
assistantId: query.value.assistantId,
|
assistantId: query.value.assistantId,
|
||||||
userAccount: query.value.userAccount || undefined,
|
userAccount: isSuperAdmin.value
|
||||||
|
? query.value.userAccount || undefined
|
||||||
|
: undefined,
|
||||||
startTime: query.value.timeRange?.[0],
|
startTime: query.value.timeRange?.[0],
|
||||||
endTime: query.value.timeRange?.[1],
|
endTime: query.value.timeRange?.[1],
|
||||||
pageNumber: query.value.pageNumber,
|
pageNumber: query.value.pageNumber,
|
||||||
@@ -394,6 +401,7 @@ function closeDetail() {
|
|||||||
@change="handleSearch"
|
@change="handleSearch"
|
||||||
/>
|
/>
|
||||||
<ElInput
|
<ElInput
|
||||||
|
v-if="isSuperAdmin"
|
||||||
v-model="query.userAccount"
|
v-model="query.userAccount"
|
||||||
class="chat-history-page__filter-control is-input"
|
class="chat-history-page__filter-control is-input"
|
||||||
placeholder="搜索聊天用户"
|
placeholder="搜索聊天用户"
|
||||||
@@ -472,7 +480,12 @@ function closeDetail() {
|
|||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
|
|
||||||
<ElTableColumn prop="userAccount" label="聊天用户" min-width="160">
|
<ElTableColumn
|
||||||
|
v-if="isSuperAdmin"
|
||||||
|
prop="userAccount"
|
||||||
|
label="聊天用户"
|
||||||
|
min-width="160"
|
||||||
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<span class="chat-history-page__user-cell">
|
<span class="chat-history-page__user-cell">
|
||||||
{{ row.userAccount || '-' }}
|
{{ row.userAccount || '-' }}
|
||||||
|
|||||||
Reference in New Issue
Block a user