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:
@@ -1 +1 @@
|
||||
404: Not Found
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip
|
||||
|
||||
@@ -8,17 +8,19 @@ server:
|
||||
|
||||
spring:
|
||||
datasource:
|
||||
name: shop_msh
|
||||
name: shop-msh
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driver-class-name: com.mysql.jdbc.Driver
|
||||
url: jdbc:mysql://118.89.113.119:3306/${spring.datasource.name}?useUnicode=true&serverTimezone=GMT%2B8&characterEncoding=utf8
|
||||
username: baisui
|
||||
password: fFmTJhBEFSnYGYW7
|
||||
url: jdbc:mysql://49.235.131.69:3306/${spring.datasource.name}?useUnicode=true&serverTimezone=GMT%2B8&characterEncoding=utf8
|
||||
username: root
|
||||
password: mogu2018
|
||||
|
||||
redis:
|
||||
host: 118.89.113.119 #地址
|
||||
host: 49.235.131.69 #地址 118.89.113.119 49.235.131.69
|
||||
port: 6379 #端口
|
||||
password: 'UthinkCloud2017'
|
||||
password: 'mogu2018'
|
||||
timeout: 10000 # 连接超时时间(毫秒)
|
||||
database: 26 #默认数据库
|
||||
database: 3 #默认数据库
|
||||
jedis:
|
||||
pool:
|
||||
max-active: 200 # 连接池最大连接数(使用负值表示没有限制)
|
||||
@@ -35,7 +37,7 @@ logging:
|
||||
org.springframework.boot.autoconfigure: ERROR
|
||||
config: classpath:logback-spring.xml
|
||||
file:
|
||||
path: ./crmeb_log
|
||||
path: ./logs
|
||||
|
||||
# mybatis 配置
|
||||
mybatis-plus:
|
||||
|
||||
@@ -38,7 +38,7 @@ server:
|
||||
|
||||
spring:
|
||||
profiles:
|
||||
active: jxz
|
||||
active: sophia
|
||||
servlet:
|
||||
multipart:
|
||||
max-file-size: 50MB #设置单个文件大小
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Objects;
|
||||
@@ -39,6 +40,10 @@ public class ResultAdvice implements ResponseBodyAdvice<Object> {
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
|
||||
// SseEmitter 由 Spring 内部直接处理,不能经过统一响应包装
|
||||
if (SseEmitter.class.isAssignableFrom(returnType.getParameterType())) {
|
||||
return false;
|
||||
}
|
||||
ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
HttpServletRequest request = Objects.requireNonNull(sra).getRequest();
|
||||
CustomResponseAnnotation customResponseAnnotation = (CustomResponseAnnotation) request.getAttribute(CUSTOM_RESPONSE_RESULT_ANNOTATION);
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -56,7 +57,9 @@ public class CozeController {
|
||||
*/
|
||||
@ApiOperation(value = "流式对话", notes = "与 Coze Bot 进行流式对话,使用 SSE 实时推送响应")
|
||||
@PostMapping(value = "/chat/stream", produces = "text/event-stream")
|
||||
public SseEmitter chatStream(@RequestBody CozeChatRequest request) {
|
||||
public SseEmitter chatStream(@RequestBody CozeChatRequest request, HttpServletResponse response) {
|
||||
response.setHeader("X-Accel-Buffering", "no");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
request.setStream(true);
|
||||
return toolCozeService.chatStream(request);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ coze:
|
||||
api:
|
||||
base-url: https://api.coze.cn
|
||||
auth-type: pat # pat 或 jwt
|
||||
token: pat_fGSD3Jax9VNWOJ7yrjke8R1XjeLWQCT2amc2gk4xBI68OPrnlFGwkOAMS2xk5XuY # 有效期30天
|
||||
token: pat_ehJTZT6rpqgllqiTmoeOZVRmvsLX9TMq7eVrE3E0q0HcyYQmSCqPNII8vwoaU4EW # 有效期30天
|
||||
# JWT 模式配置(当 auth-type=jwt 时使用)
|
||||
client-id: 1180790412263
|
||||
private-key-file: classpath:coze-1180790412263-private_key.pem
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# CRMEB 相关配置
|
||||
crmeb:
|
||||
version: JAVA-SY-v2.2 # 当前代码版本
|
||||
version: SY-v2.2 # 当前代码版本
|
||||
imagePath: /usr/local/crmeb/crmebimage/ # 服务器图片路径配置 斜杠结尾
|
||||
asyncConfig: true #是否同步config表数据到redis
|
||||
activityStyleCachedTime: 10 #活动边框缓存周期 秒为单位,生产环境适当5-10分钟即可
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user