From e3eb1afbb04640caaf5f7cdf8b61b5c6a197fc0e Mon Sep 17 00:00:00 2001 From: Peanut Date: Sun, 28 Jun 2026 22:40:13 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20listByConversation=20=E5=9C=A8=20convers?= =?UTF-8?q?ation=20=E4=B8=8D=E5=AD=98=E5=9C=A8=E6=97=B6=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E7=A9=BA=E5=88=97=E8=A1=A8=E8=80=8C=E9=9D=9E=E6=8A=9B=E5=BC=82?= =?UTF-8?q?=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 81 +++++++++++++++++++ mini-program/src/pages/main/ScriptView.vue | 6 +- .../impl/ScriptMessageServiceImpl.java | 11 ++- 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 3bff4ca..b4aa4ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -266,6 +266,37 @@ bash deploy-all.sh admin --- +## 部署强制规则(最高优先级,不可违反) + +**本地开发完成、验证通过、验收没有问题后,必须执行 `deploy.py` 部署脚本,将应用部署到服务器上。** + +### 强制流程 + +每次功能开发或 bug 修复完成后,必须按以下顺序操作: + +1. **本地验证**:在本地开发环境中完成功能开发和自测,确认无报错。 +2. **验收通过**:按"服务器端验收强制规则"完成验收,确认功能正常。 +3. **执行部署**:验收通过后,立即执行部署脚本将应用部署到服务器: + - 后端:`python deploy.py backend` + - 前端:`python deploy.py frontend` + - 管理后台:`python deploy.py admin` + - 全部:`python deploy.py all` +4. **部署验证**:部署完成后,在服务器上验证功能正常,无报错。 + +### 禁止行为 + +- ❌ 禁止本地开发完成后不部署到服务器就认为任务完成 +- ❌ 禁止跳过验收直接部署 +- ❌ 禁止部署后不验证就结束任务 +- ❌ 禁止在编译或验收有报错的情况下部署 + +### 允许的唯一例外 + +- ✅ 纯文档修改(不涉及代码变更) +- ✅ 仅在本地运行的脚本工具(不涉及线上服务) + +--- + ## 本地服务管理规则(强制) **启动、重启、停止本地前后端服务时,必须使用项目根目录下的 `dev-services.py` 脚本。** @@ -384,3 +415,53 @@ bash deploy-all.sh admin - 关键业务操作必须记录操作日志 - 异常信息要包含足够的上下文信息 - 生产环境禁止输出 debug 级别日志 + +--- + +## 日志排查规则(强制) + +**遇到"对话不存在或无权限"错误时,按以下顺序排查:** + +### 1. 检查 conversationId 是否为空 + +- **前端检查**:在浏览器 DevTools Network 面板查看请求参数 `conversationId` +- **后端检查**:查看 `UserContextHolder.getCurrentUserId()` 是否返回有效值 +- **数据库检查**:执行 `SELECT * FROM t_conversation WHERE id = ''` 确认对话是否存在 + +### 2. 检查权限匹配 + +- **前端问题**:当前登录用户的 userId 与对话的 userId 不一致 +- **后端逻辑**:`ScriptMessageServiceImpl.listByConversation` 中会校验 `conversation.getUserId().equals(currentUserId)` +- **修复方案**:确认前端传递的是正确的 conversationId,或重新创建对话 + +### 3. 常见原因 + +| 原因 | 解决方案 | +|------|----------| +| conversationId 为空字符串 | 前端增加短路保护,显示"暂无对话记录"提示 | +| 对话不存在(已删除) | 引导用户创建新对话 | +| 用户未登录 | 检查 Token 是否有效,重新登录 | +| 对话属于其他用户 | 确认前端传递的 conversationId 正确 | + +### 4. 快速定位方法 + +使用 `tools/download-server-log.py` 脚本下载服务器日志: + +```bash +# 下载完整日志 +python tools/download-server-log.py latest + +# 只看最近 50 条错误 +python tools/download-server-log.py errors 50 + +# 搜索特定关键词(如 listByConversation) +python tools/download-server-log.py grep "listByConversation" 20 +``` + +日志位置:`logs/server/` 目录 + +### 5. 防御性编程原则 + +- **Service 层**:对空参数做防御性检查,返回空列表而非抛异常 +- **Controller 层**:参数校验失败时返回友好错误提示 +- **前端**:接口调用前做空值检查,避免发送无效请求 diff --git a/mini-program/src/pages/main/ScriptView.vue b/mini-program/src/pages/main/ScriptView.vue index af390d1..340bf98 100644 --- a/mini-program/src/pages/main/ScriptView.vue +++ b/mini-program/src/pages/main/ScriptView.vue @@ -661,7 +661,11 @@ const continueCurrentVersion = () => { } const loadMessages = async () => { - if (!conversationId.value) return + // 短路保护:conversationId 为空时不请求接口,显示友好提示 + if (!conversationId.value) { + uni.showToast({ title: '暂无对话记录', icon: 'none' }) + return + } try { const res = await listMessagesByConversation({ conversationId: conversationId.value, includeVersions: false }) messages.value = res.data || [] diff --git a/server/src/main/java/com/emotion/service/impl/ScriptMessageServiceImpl.java b/server/src/main/java/com/emotion/service/impl/ScriptMessageServiceImpl.java index 9e78097..6828361 100644 --- a/server/src/main/java/com/emotion/service/impl/ScriptMessageServiceImpl.java +++ b/server/src/main/java/com/emotion/service/impl/ScriptMessageServiceImpl.java @@ -38,8 +38,15 @@ public class ScriptMessageServiceImpl implements ScriptMessageService { public List listByConversation(String conversationId, boolean includeVersions) { String currentUserId = UserContextHolder.getCurrentUserId(); Conversation conversation = conversationService.getById(conversationId); - if (conversation == null || !conversation.getUserId().equals(currentUserId)) { - throw new IllegalStateException("对话不存在或无权限"); + + // 对话不存在:视为"尚无消息",返回空列表(前端用临时 ID 查询时走这条分支) + if (conversation == null) { + return java.util.Collections.emptyList(); + } + + // 对话存在但归属他人:权限问题,必须暴露 + if (!conversation.getUserId().equals(currentUserId)) { + throw new IllegalStateException("无权限访问该对话"); } LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();