fix: 收紧智能体聊天历史访问范围
- 普通账号仅可访问本人 Agent 会话 - 超级管理员保留全量查询并限制筛选入口
This commit is contained in:
@@ -10,19 +10,54 @@ import tech.easyflow.chatlog.domain.query.ChatSessionFilterQuery;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Agent 聊天历史管理服务。
|
||||
*/
|
||||
public interface ChatHistoryManageService {
|
||||
|
||||
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 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 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);
|
||||
|
||||
@@ -32,7 +67,33 @@ public interface ChatHistoryManageService {
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Agent 聊天历史管理服务实现。
|
||||
*/
|
||||
@Service
|
||||
public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
||||
|
||||
@@ -29,6 +32,15 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
||||
private final ChatRoundOperateService chatRoundOperateService;
|
||||
private final ChatAnalyticalDBRepository chatAnalyticalDBRepository;
|
||||
|
||||
/**
|
||||
* 创建 Agent 聊天历史管理服务。
|
||||
*
|
||||
* @param chatSessionQueryService 会话查询服务
|
||||
* @param chatSessionCommandService 会话命令服务
|
||||
* @param chatHistoryQueryService 历史消息查询服务
|
||||
* @param chatRoundOperateService 对话轮次操作服务
|
||||
* @param chatAnalyticalDBRepository 聊天分析库仓储
|
||||
*/
|
||||
public ChatHistoryManageServiceImpl(ChatSessionQueryService chatSessionQueryService,
|
||||
ChatSessionCommandService chatSessionCommandService,
|
||||
ChatHistoryQueryService chatHistoryQueryService,
|
||||
@@ -46,11 +58,22 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
||||
return chatSessionQueryService.pageSessions(userId, assistantId, AGENT_ASSISTANT_CODE, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public ChatSessionPage queryAdminSessions(ChatSessionFilterQuery query) {
|
||||
public ChatSessionPage queryAdminSessions(BigInteger requesterId,
|
||||
boolean superAdmin,
|
||||
ChatSessionFilterQuery query) {
|
||||
ChatSessionFilterQuery effectiveQuery = query == null ? new ChatSessionFilterQuery() : query;
|
||||
// 管理端聊天历史已经切换为 Agent 专属入口,类型由服务端固定,避免客户端绕过。
|
||||
effectiveQuery.setAssistantCode(AGENT_ASSISTANT_CODE);
|
||||
if (!superAdmin) {
|
||||
requireRequesterId(requesterId);
|
||||
// 普通账号的数据范围由服务端覆盖,客户端无法通过筛选参数扩大范围。
|
||||
effectiveQuery.setUserId(requesterId);
|
||||
effectiveQuery.setUserAccount(null);
|
||||
}
|
||||
return chatAnalyticalDBRepository.pageSessions(effectiveQuery);
|
||||
}
|
||||
|
||||
@@ -68,14 +91,25 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
||||
return summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public ChatSessionSummary getAdminSession(BigInteger sessionId) {
|
||||
public ChatSessionSummary getAdminSession(BigInteger requesterId,
|
||||
boolean superAdmin,
|
||||
BigInteger sessionId) {
|
||||
ChatSessionSummary summary = chatAnalyticalDBRepository.getSession(sessionId);
|
||||
if (summary == null
|
||||
|| Integer.valueOf(1).equals(summary.getIsDeleted())
|
||||
|| !AGENT_ASSISTANT_CODE.equals(summary.getAssistantCode())) {
|
||||
throw new BusinessException("Agent 会话不存在");
|
||||
}
|
||||
if (!superAdmin) {
|
||||
requireRequesterId(requesterId);
|
||||
if (summary.getUserId() == null || !summary.getUserId().equals(requesterId)) {
|
||||
throw new BusinessException(403, 403, "无权访问该会话");
|
||||
}
|
||||
}
|
||||
return summary;
|
||||
}
|
||||
|
||||
@@ -89,9 +123,15 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
||||
return chatHistoryQueryService.queryHistoryMessages(sessionId, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public ChatHistoryPage queryAdminMessages(BigInteger sessionId, ChatPageQuery query) {
|
||||
ChatSessionSummary summary = getAdminSession(sessionId);
|
||||
public ChatHistoryPage queryAdminMessages(BigInteger requesterId,
|
||||
boolean superAdmin,
|
||||
BigInteger sessionId,
|
||||
ChatPageQuery query) {
|
||||
ChatSessionSummary summary = getAdminSession(requesterId, superAdmin, sessionId);
|
||||
ChatHistoryPage firstPage = restoreRecentMessages(summary, query);
|
||||
if (firstPage != null) {
|
||||
return firstPage;
|
||||
@@ -126,16 +166,29 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
||||
return chatRoundOperateService.selectVariant(sessionId, roundId, variantIndex, operatorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public java.util.List<ChatMessageRecord> listAdminRoundVariants(BigInteger sessionId, BigInteger roundId) {
|
||||
getAdminSession(sessionId);
|
||||
public java.util.List<ChatMessageRecord> listAdminRoundVariants(BigInteger requesterId,
|
||||
boolean superAdmin,
|
||||
BigInteger sessionId,
|
||||
BigInteger roundId) {
|
||||
getAdminSession(requesterId, superAdmin, sessionId);
|
||||
return chatRoundOperateService.listVariants(sessionId, roundId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public ChatMessageRecord selectAdminRoundVariant(BigInteger sessionId, BigInteger roundId, Integer variantIndex, BigInteger operatorId) {
|
||||
getAdminSession(sessionId);
|
||||
return chatRoundOperateService.selectVariant(sessionId, roundId, variantIndex, operatorId);
|
||||
public ChatMessageRecord selectAdminRoundVariant(BigInteger requesterId,
|
||||
boolean superAdmin,
|
||||
BigInteger sessionId,
|
||||
BigInteger roundId,
|
||||
Integer variantIndex) {
|
||||
getAdminSession(requesterId, superAdmin, sessionId);
|
||||
return chatRoundOperateService.selectVariant(sessionId, roundId, variantIndex, requesterId);
|
||||
}
|
||||
|
||||
private ChatHistoryPage restoreRecentMessages(ChatSessionSummary summary, ChatPageQuery query) {
|
||||
@@ -157,4 +210,15 @@ public class ChatHistoryManageServiceImpl implements ChatHistoryManageService {
|
||||
page.setTotal(Math.max(total, records.size()));
|
||||
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();
|
||||
query.setAssistantCode("BOT");
|
||||
|
||||
service.queryAdminSessions(query);
|
||||
service.queryAdminSessions(BigInteger.ONE, true, query);
|
||||
|
||||
Assert.assertEquals("AGENT", query.getAssistantCode());
|
||||
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 会话。
|
||||
*/
|
||||
@@ -125,11 +142,31 @@ public class ChatHistoryManageServiceImplTest {
|
||||
ChatSessionSummary summary = session(sessionId, "AGENT", 0);
|
||||
chatAnalyticalDBRepository.sessionResult = summary;
|
||||
|
||||
ChatSessionSummary result = service.getAdminSession(sessionId);
|
||||
ChatSessionSummary result = service.getAdminSession(BigInteger.ONE, true, sessionId);
|
||||
|
||||
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 会话可以继续读取历史消息。
|
||||
*/
|
||||
@@ -148,7 +185,7 @@ public class ChatHistoryManageServiceImplTest {
|
||||
ChatPageQuery query = new ChatPageQuery();
|
||||
query.setPageNumber(2);
|
||||
|
||||
ChatHistoryPage result = service.queryAdminMessages(sessionId, query);
|
||||
ChatHistoryPage result = service.queryAdminMessages(BigInteger.ONE, true, sessionId, query);
|
||||
|
||||
Assert.assertSame(expectedPage, result);
|
||||
}
|
||||
@@ -163,7 +200,7 @@ public class ChatHistoryManageServiceImplTest {
|
||||
|
||||
BusinessException exception = Assert.assertThrows(
|
||||
BusinessException.class,
|
||||
() -> service.getAdminSession(sessionId)
|
||||
() -> service.getAdminSession(BigInteger.ONE, true, sessionId)
|
||||
);
|
||||
|
||||
Assert.assertEquals("Agent 会话不存在", exception.getMessage());
|
||||
@@ -179,7 +216,12 @@ public class ChatHistoryManageServiceImplTest {
|
||||
|
||||
Assert.assertThrows(
|
||||
BusinessException.class,
|
||||
() -> service.queryAdminMessages(sessionId, new ChatPageQuery())
|
||||
() -> service.queryAdminMessages(
|
||||
BigInteger.ONE,
|
||||
true,
|
||||
sessionId,
|
||||
new ChatPageQuery()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -193,7 +235,12 @@ public class ChatHistoryManageServiceImplTest {
|
||||
|
||||
Assert.assertThrows(
|
||||
BusinessException.class,
|
||||
() -> service.listAdminRoundVariants(sessionId, BigInteger.ONE)
|
||||
() -> service.listAdminRoundVariants(
|
||||
BigInteger.ONE,
|
||||
true,
|
||||
sessionId,
|
||||
BigInteger.ONE
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -208,10 +255,11 @@ public class ChatHistoryManageServiceImplTest {
|
||||
Assert.assertThrows(
|
||||
BusinessException.class,
|
||||
() -> service.selectAdminRoundVariant(
|
||||
BigInteger.TEN,
|
||||
true,
|
||||
sessionId,
|
||||
BigInteger.ONE,
|
||||
1,
|
||||
BigInteger.TEN
|
||||
1
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user