fix: 移除损坏的 Claude gitlink 并同步业务与文档更新

- 从索引移除误记录的 .claude/worktrees gitlink(旧绝对路径会导致 git 命令失败)
- 新增根目录 .gitignore 忽略 .claude/worktrees 与 .DS_Store
- 后端:Coze/知识库、ResultAdvice、应用配置
- 前端 uniapp:AI 营养、食物百科等页面与 API
- 更新 README、测试文档与 shop-msh.sql

Made-with: Cursor
This commit is contained in:
panchengyong
2026-03-30 12:46:24 +08:00
parent 3329a2b296
commit 3023115bb0
19 changed files with 671 additions and 166 deletions

View File

@@ -39,7 +39,12 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
@@ -145,15 +150,30 @@ public class ToolCozeServiceImpl implements ToolCozeService {
}
}
private static final ScheduledExecutorService heartbeatScheduler =
Executors.newSingleThreadScheduledExecutor(r -> {
Thread t = new Thread(r, "sse-heartbeat");
t.setDaemon(true);
return t;
});
@Override
public SseEmitter chatStream(CozeChatRequest request) {
SseEmitter emitter = new SseEmitter(60000L); // 60 秒超时
SseEmitter emitter = new SseEmitter(120000L);
ScheduledFuture<?> heartbeat = heartbeatScheduler.scheduleAtFixedRate(() -> {
try {
emitter.send(SseEmitter.event().comment("heartbeat"));
} catch (Exception ignored) {
}
}, 15, 15, TimeUnit.SECONDS);
try {
CozeAPI client = getClient();
List<Message> messages = buildMessages(request);
if (messages == null || messages.isEmpty()) {
logger.warn("Coze chat stream: no user message in request");
heartbeat.cancel(false);
emitter.completeWithError(new RuntimeException("请提供对话内容"));
return emitter;
}
@@ -163,7 +183,6 @@ public class ToolCozeServiceImpl implements ToolCozeService {
.userID(request.getUserId())
.messages(messages);
// 传入 conversationId 以支持多轮对话上下文
if (request.getConversationId() != null && !request.getConversationId().isEmpty()) {
builder.conversationID(request.getConversationId());
}
@@ -172,26 +191,59 @@ public class ToolCozeServiceImpl implements ToolCozeService {
Disposable disposable = client.chat().stream(req)
.subscribe(
chatEvent -> SseEmitterUtil.send(emitter, chatEvent),
chatEvent -> {
Map<String, Object> simplified = new HashMap<>();
simplified.put("event", chatEvent.getEvent() != null
? chatEvent.getEvent().getValue() : null);
if (chatEvent.getChat() != null) {
simplified.put("conversation_id",
chatEvent.getChat().getConversationID());
simplified.put("chat_id", chatEvent.getChat().getID());
if (chatEvent.getChat().getStatus() != null) {
simplified.put("status",
chatEvent.getChat().getStatus().getValue());
}
}
if (chatEvent.getMessage() != null) {
simplified.put("content",
chatEvent.getMessage().getContent());
if (chatEvent.getMessage().getRole() != null) {
simplified.put("role",
chatEvent.getMessage().getRole().getValue());
}
if (chatEvent.getMessage().getType() != null) {
simplified.put("type",
chatEvent.getMessage().getType().getValue());
}
}
SseEmitterUtil.send(emitter, simplified);
},
error -> {
logger.error("Coze chat stream error", error);
heartbeat.cancel(false);
emitter.completeWithError(error);
},
() -> SseEmitterUtil.complete(emitter)
() -> {
heartbeat.cancel(false);
SseEmitterUtil.complete(emitter);
}
);
emitter.onCompletion(() -> {
heartbeat.cancel(false);
if (!disposable.isDisposed()) {
disposable.dispose();
}
});
emitter.onTimeout(() -> {
heartbeat.cancel(false);
if (!disposable.isDisposed()) {
disposable.dispose();
}
});
} catch (Exception e) {
logger.error("Coze chat stream error", e);
heartbeat.cancel(false);
emitter.completeWithError(e);
}

View File

@@ -202,7 +202,7 @@ public class ToolKnowledgeServiceImpl implements ToolKnowledgeService {
existQuery.eq(V2Knowledge::getType, "nutrients")
.eq(V2Knowledge::getNutrientName, nutrient)
.eq(V2Knowledge::getStatus, "published");
Long count = v2KnowledgeDao.selectCount(existQuery);
Long count = Long.valueOf(v2KnowledgeDao.selectCount(existQuery));
if (count > 0) {
log.info("[generateNutrient] 营养素 {} 已存在,跳过", nutrient);
continue;
@@ -218,7 +218,8 @@ public class ToolKnowledgeServiceImpl implements ToolKnowledgeService {
msg.setRole("user");
msg.setContent(prompt);
msg.setContentType("text");
req.setAdditionalMessages(java.util.Collections.singletonList(msg));
// 修复后
req.setChatHistory(java.util.Collections.singletonList(msg));
CozeBaseResponse<Object> resp = toolCozeService.chat(req);
String content = extractCozeContent(resp);