diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/config/ThreadPoolConfig.java b/src/main/java/com/supervision/livedigitalavatarmanage/config/ThreadPoolConfig.java new file mode 100644 index 0000000..a8d6030 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/config/ThreadPoolConfig.java @@ -0,0 +1,20 @@ +package com.supervision.livedigitalavatarmanage.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +@Configuration +public class ThreadPoolConfig { + + @Bean("taskExecutor") + public ThreadPoolTaskExecutor taskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(1); + executor.setMaxPoolSize(1); + executor.setQueueCapacity(1000); + executor.setThreadNamePrefix("Async-Task-"); + executor.initialize(); + return executor; + } +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/constant/PromptTemplate.java b/src/main/java/com/supervision/livedigitalavatarmanage/constant/PromptTemplate.java index e927513..7b7598d 100644 --- a/src/main/java/com/supervision/livedigitalavatarmanage/constant/PromptTemplate.java +++ b/src/main/java/com/supervision/livedigitalavatarmanage/constant/PromptTemplate.java @@ -1,5 +1,12 @@ package com.supervision.livedigitalavatarmanage.constant; +import cn.hutool.core.util.StrUtil; +import com.supervision.livedigitalavatarmanage.dto.ScriptKeyPointsDTO; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + /** * 提示词缓存 */ @@ -27,4 +34,35 @@ public class PromptTemplate { 3. 依次生成{num}组销售话术,每组话术之间**禁止**重复。 4. 响应结果必须是jsonArray。格式["话术1","话术2"]/no_think """; + + + public static final String GENERATE_SALESPITCH_TEMPLATE_V2 = """ + 你是一个`{productCategory}`行业专业的直播文案编辑专家。请根据提供的`{productCategory}`信息和`关键点示例`,生成一段自然流畅的直播`{scriptType}`。 + + `{productCategory}`信息: + ```text + {productInfo} + ``` + + **规则(非常重要,请务必遵守):** + - **规则一:** 禁止虚构或添加`{productCategory}`信息中不存在的内容。 + - **规则二:** 禁止使用`关键点示例信息`中未明确列出的`关键点`。你只能使用示例中提供的{keyPointTitles1}这三个关键点。 + - **规则三:** 你的任务是**模仿**示例的语句结构和语气特征来创作新内容,而不是创造新的关键点结构。 + - **规则四:** 最终输出中**禁止出现{keyPointTitles2}等关键点标题**,而是要将这些内容自然地融合成一段连贯的口播文案。 + + `关键点示例信息`: + ```text + {keyPointExamples} + ``` + + **任务要求:** + + 请严格按照以下步骤执行: + + 1. **分析提取:** 仔细分析`{productCategory}`信息,提取出与`关键点示例`({keyPointTitles3})相匹配的核心卖点。 + 2. **模仿创作:** 针对每一个`关键点`,严格模仿其对应`示例`的句式、口吻和长度,生成一条新的内容。 + 3. **组合成文:** 将你为三个关键点新生成的内容,按照“{keyPointTitles4}”的逻辑顺序,流畅地组合成一个完整的、可用于口播的`{scriptType}`。语句之间可以稍作润色以确保连贯自然,但不得添加额外信息,且**确保不出现关键点标题**。 + + **最终,请直接输出开场文案,不需要输出思考过程。** + """; } diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/constant/TaskStautsEnum.java b/src/main/java/com/supervision/livedigitalavatarmanage/constant/TaskStautsEnum.java new file mode 100644 index 0000000..ee17a82 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/constant/TaskStautsEnum.java @@ -0,0 +1,20 @@ +package com.supervision.livedigitalavatarmanage.constant; + +import lombok.Getter; + +@Getter +public enum TaskStautsEnum { + + WAITING(0, "等待中"), + RUNNING(1, "进行中"), + SUCCESS(2, "成功"), + FAILED(3, "失败"); + + private final int code; + private final String description; + + TaskStautsEnum(int code, String description) { + this.code = code; + this.description = description; + } +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/controller/LiveDigitalController.java b/src/main/java/com/supervision/livedigitalavatarmanage/controller/LiveDigitalController.java index 3abd112..b7838ee 100644 --- a/src/main/java/com/supervision/livedigitalavatarmanage/controller/LiveDigitalController.java +++ b/src/main/java/com/supervision/livedigitalavatarmanage/controller/LiveDigitalController.java @@ -1,8 +1,10 @@ package com.supervision.livedigitalavatarmanage.controller; +import com.supervision.livedigitalavatarmanage.dto.AsyncTaskResultDTO; import com.supervision.livedigitalavatarmanage.dto.R; import com.supervision.livedigitalavatarmanage.service.LiveDigitalService; import com.supervision.livedigitalavatarmanage.vo.SalesPitchReqVo; +import com.supervision.livedigitalavatarmanage.vo.SalesPitchResVo; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -23,9 +25,34 @@ public class LiveDigitalController { * @return */ @PostMapping("/generate/salespitch") - public R> generateSalesPitch(@RequestBody SalesPitchReqVo salespitchReqVo) { + public R> generateSalesPitch(@RequestBody SalesPitchReqVo salespitchReqVo) { - List salesPitchResVos = liveDigitalService.generateSalesPitch(salespitchReqVo); + List salesPitchResVos = liveDigitalService.generateSalesPitch(salespitchReqVo); return R.ok(salesPitchResVos); } + + /** + * 提交数字人销售话术任务 + * @param salespitchReqVo + * @return + */ + @PostMapping("/submit/salespitch/task") + public R submitSalesPitchTask(@RequestBody SalesPitchReqVo salespitchReqVo) { + + String taskId = liveDigitalService.submitSalesPitchTask(salespitchReqVo); + return R.ok(taskId); + } + + /** + * 查询数字人销售话术任务结果 + * @param taskId + * @return + */ + @PostMapping("/query/salespitch/task") + public R>> querySalespitchTaskResult(String taskId) { + + AsyncTaskResultDTO> result = liveDigitalService.querySalespitchTaskResult(taskId); + return R.ok(result); + } + } diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/domain/AsyncTaskResult.java b/src/main/java/com/supervision/livedigitalavatarmanage/domain/AsyncTaskResult.java new file mode 100644 index 0000000..b60e32e --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/domain/AsyncTaskResult.java @@ -0,0 +1,67 @@ +package com.supervision.livedigitalavatarmanage.domain; + +import com.baomidou.mybatisplus.annotation.*; + +import java.io.Serializable; +import java.time.LocalDateTime; +import lombok.Data; + +/** + * 异步任务结果表 + * @TableName async_task_result + */ +@TableName(value ="async_task_result") +@Data +public class AsyncTaskResult implements Serializable { + /** + * 任务ID + */ + @TableId + private String id; + + /** + * 任务类型 0:生成产品营销话术 + */ + private String taskType; + + /** + * 状态:0-处理中,1-成功,2-失败 + */ + private Integer status; + + /** + * 请求参数 + */ + private String requestData; + + /** + * 处理结果 + */ + private String resultData; + + /** + * 错误信息 + */ + private String errorMessage; + + /** + * 过期时间 + */ + private LocalDateTime expireTime; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/domain/ScriptKeypoints.java b/src/main/java/com/supervision/livedigitalavatarmanage/domain/ScriptKeypoints.java new file mode 100644 index 0000000..364fed6 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/domain/ScriptKeypoints.java @@ -0,0 +1,66 @@ +package com.supervision.livedigitalavatarmanage.domain; + +import com.baomidou.mybatisplus.annotation.*; + +import java.io.Serializable; +import java.time.LocalDateTime; +import lombok.Data; + +/** + * 话术重要点表 + * @TableName script_keypoints + */ +@TableName(value ="script_keypoints") +@Data +public class ScriptKeypoints implements Serializable { + /** + * 主键 + */ + @TableId + private String id; + + /** + * 话术类型 + */ + private String scriptType; + + /** + * 话术组id + */ + private String scriptId; + + /** + * 关键点 + */ + private String keyPoint; + + /** + * 关键点示例 + */ + private String keyPointExample; + + /** + * 话术类型序号 数值越小优先级越高 + */ + private String scriptTypeOrderNum; + + /** + * 关键点序号 数值越小优先级越高 + */ + private Integer keyPointOrderNum; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/domain/ScriptUser.java b/src/main/java/com/supervision/livedigitalavatarmanage/domain/ScriptUser.java new file mode 100644 index 0000000..67c4594 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/domain/ScriptUser.java @@ -0,0 +1,51 @@ +package com.supervision.livedigitalavatarmanage.domain; + +import com.baomidou.mybatisplus.annotation.*; + +import java.io.Serializable; +import java.time.LocalDateTime; +import lombok.Data; + +/** + * 话术组信息 + * @TableName script_user + */ +@TableName(value ="script_user") +@Data +public class ScriptUser implements Serializable { + /** + * 主键 + */ + @TableId + private String id; + + /** + * 话术名称 + */ + private String scriptName; + + /** + * 产品分类 + */ + private String productCategory; + + /** + * 用户id + */ + private String userId; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/dto/AsyncTaskResultDTO.java b/src/main/java/com/supervision/livedigitalavatarmanage/dto/AsyncTaskResultDTO.java new file mode 100644 index 0000000..7d96ec8 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/dto/AsyncTaskResultDTO.java @@ -0,0 +1,61 @@ +package com.supervision.livedigitalavatarmanage.dto; + +import cn.hutool.core.util.StrUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.supervision.livedigitalavatarmanage.constant.TaskStautsEnum; +import com.supervision.livedigitalavatarmanage.domain.AsyncTaskResult; +import lombok.Data; + +@Data +public class AsyncTaskResultDTO { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private String id; + + /** + * 任务类型 0:生成产品营销话术 + */ + private String taskType; + + /** + * 状态:0-处理中,1-成功,2-失败 + */ + private Integer status; + + /** + * 请求参数 + */ + private String requestData; + + /** + * 处理结果 + */ + private T resultData; + + /** + * 错误信息 + */ + private String errorMessage; + + + public AsyncTaskResultDTO() { + } + + public AsyncTaskResultDTO(AsyncTaskResult asyncTaskResult, TypeReference typeReference) { + this.id = asyncTaskResult.getId(); + this.taskType = asyncTaskResult.getTaskType(); + this.status = asyncTaskResult.getStatus(); + this.requestData = asyncTaskResult.getRequestData(); + this.errorMessage = asyncTaskResult.getErrorMessage(); + if (TaskStautsEnum.SUCCESS.getCode() == this.status && StrUtil.isNotEmpty(asyncTaskResult.getResultData())) { + try { + this.resultData = objectMapper.readValue(asyncTaskResult.getResultData(), typeReference); + } catch (JsonProcessingException e) { + this.status = TaskStautsEnum.FAILED.getCode(); + } + } + } +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/dto/ScriptKeyPointsDTO.java b/src/main/java/com/supervision/livedigitalavatarmanage/dto/ScriptKeyPointsDTO.java new file mode 100644 index 0000000..9c5acbf --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/dto/ScriptKeyPointsDTO.java @@ -0,0 +1,70 @@ +package com.supervision.livedigitalavatarmanage.dto; + +import lombok.Data; + +/** + * 销售话术要点DTO + */ +@Data +public class ScriptKeyPointsDTO { + + /** + * 话术组id + */ + private String scriptGroupId; + + /** + * 话术名称 + */ + private String scriptName; + + /** + * 产品分类 + */ + private String productCategory; + + /** + * 用户id + */ + private String userId; + + /** + * 话术类型 + */ + private String scriptType; + + /** + * 话术组id + */ + private String scriptId; + + /** + * 关键点 + */ + private String keyPoint; + + /** + * 关键点示例 + */ + private String keyPointExample; + + /** + * 话术类型序号 数值越小优先级越高 + */ + private Integer scriptTypeOrderNum; + + /** + * 关键点序号 数值越小优先级越高 + */ + private Integer keyPointOrderNum; + + + public ScriptKeyPointsDTO() { + } + + public ScriptKeyPointsDTO(String scriptType, String keyPoint, String keyPointExample) { + this.scriptType = scriptType; + this.keyPoint = keyPoint; + this.keyPointExample = keyPointExample; + } +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/mapper/AsyncTaskResultMapper.java b/src/main/java/com/supervision/livedigitalavatarmanage/mapper/AsyncTaskResultMapper.java new file mode 100644 index 0000000..13f5a63 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/mapper/AsyncTaskResultMapper.java @@ -0,0 +1,18 @@ +package com.supervision.livedigitalavatarmanage.mapper; + +import com.supervision.livedigitalavatarmanage.domain.AsyncTaskResult; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author Administrator +* @description 针对表【async_task_result(异步任务结果表)】的数据库操作Mapper +* @createDate 2025-09-17 15:35:10 +* @Entity com.supervision.livedigitalavatarmanage.domain.AsyncTaskResult +*/ +public interface AsyncTaskResultMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/mapper/ScriptKeypointsMapper.java b/src/main/java/com/supervision/livedigitalavatarmanage/mapper/ScriptKeypointsMapper.java new file mode 100644 index 0000000..bcd905f --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/mapper/ScriptKeypointsMapper.java @@ -0,0 +1,23 @@ +package com.supervision.livedigitalavatarmanage.mapper; + +import com.supervision.livedigitalavatarmanage.domain.ScriptKeypoints; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.supervision.livedigitalavatarmanage.dto.ScriptKeyPointsDTO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** +* @author Administrator +* @description 针对表【script_keypoints(话术重要点表)】的数据库操作Mapper +* @createDate 2025-09-17 10:57:12 +* @Entity com.supervision.livedigitalavatarmanage.domain.ScriptKeypoints +*/ +public interface ScriptKeypointsMapper extends BaseMapper { + + List listByUserIdAndProductCategory(@Param("userId") String userId,@Param("productCategory") String productCategory); +} + + + + diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/mapper/ScriptUserMapper.java b/src/main/java/com/supervision/livedigitalavatarmanage/mapper/ScriptUserMapper.java new file mode 100644 index 0000000..6e03a15 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/mapper/ScriptUserMapper.java @@ -0,0 +1,18 @@ +package com.supervision.livedigitalavatarmanage.mapper; + +import com.supervision.livedigitalavatarmanage.domain.ScriptUser; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author Administrator +* @description 针对表【script_user(话术组信息)】的数据库操作Mapper +* @createDate 2025-09-17 10:57:12 +* @Entity com.supervision.livedigitalavatarmanage.domain.ScriptUser +*/ +public interface ScriptUserMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/AsyncTaskResultService.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/AsyncTaskResultService.java new file mode 100644 index 0000000..77bbb81 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/AsyncTaskResultService.java @@ -0,0 +1,19 @@ +package com.supervision.livedigitalavatarmanage.service; + +import com.supervision.livedigitalavatarmanage.domain.AsyncTaskResult; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.concurrent.Callable; + +/** +* @author Administrator +* @description 针对表【async_task_result(异步任务结果表)】的数据库操作Service +* @createDate 2025-09-17 15:35:10 +*/ +public interface AsyncTaskResultService extends IService { + + String submitAsyncTask(String taskType, Object requestData, Callable callable); + + + int cleanExpiredTasks(); +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/ExpiredTaskCleaner.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/ExpiredTaskCleaner.java new file mode 100644 index 0000000..89f233d --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/ExpiredTaskCleaner.java @@ -0,0 +1,19 @@ +package com.supervision.livedigitalavatarmanage.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; + +@Slf4j +@RequiredArgsConstructor +public class ExpiredTaskCleaner { + + private final AsyncTaskResultService asyncTaskResultService; + + @Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行 + public void cleanExpiredTasks() { + + int count = asyncTaskResultService.cleanExpiredTasks(); + log.info("清理了 {} 条过期任务记录", count); + } +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/LiveDigitalService.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/LiveDigitalService.java index b7a30c3..dcfecf1 100644 --- a/src/main/java/com/supervision/livedigitalavatarmanage/service/LiveDigitalService.java +++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/LiveDigitalService.java @@ -1,10 +1,14 @@ package com.supervision.livedigitalavatarmanage.service; +import com.supervision.livedigitalavatarmanage.dto.AsyncTaskResultDTO; import com.supervision.livedigitalavatarmanage.vo.SalesPitchReqVo; - +import com.supervision.livedigitalavatarmanage.vo.SalesPitchResVo; import java.util.List; public interface LiveDigitalService { - List generateSalesPitch(SalesPitchReqVo salespitchReqVo); + List generateSalesPitch(SalesPitchReqVo salespitchReqVo); + + String submitSalesPitchTask(SalesPitchReqVo salespitchReqVo); + AsyncTaskResultDTO> querySalespitchTaskResult(String taskId); } diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/OllamaAgentService.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/OllamaAgentService.java index ae33fb7..7f65291 100644 --- a/src/main/java/com/supervision/livedigitalavatarmanage/service/OllamaAgentService.java +++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/OllamaAgentService.java @@ -8,7 +8,7 @@ public interface OllamaAgentService { /** * 流式智能体问答 - * @param agentChatReqDTO agentChatReqDTO + * @param ollamaDTO agentChatReqDTO * @return */ Flux streamChat(OllamaDTO ollamaDTO); diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/ScriptKeypointsService.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/ScriptKeypointsService.java new file mode 100644 index 0000000..311d41f --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/ScriptKeypointsService.java @@ -0,0 +1,23 @@ +package com.supervision.livedigitalavatarmanage.service; + +import com.supervision.livedigitalavatarmanage.domain.ScriptKeypoints; +import com.baomidou.mybatisplus.extension.service.IService; +import com.supervision.livedigitalavatarmanage.dto.ScriptKeyPointsDTO; + +import java.util.List; + +/** +* @author Administrator +* @description 针对表【script_keypoints(话术重要点表)】的数据库操作Service +* @createDate 2025-09-17 10:57:12 +*/ +public interface ScriptKeypointsService extends IService { + + /** + * 根据用户ID和产品类别查询话术重点 + * @param userId 用户ID + * @param productCategory 产品类别 + * @return 话术重点列表 + */ + List listByUserIdAndProductCategory(String userId, String productCategory); +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/ScriptUserService.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/ScriptUserService.java new file mode 100644 index 0000000..b767ac7 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/ScriptUserService.java @@ -0,0 +1,13 @@ +package com.supervision.livedigitalavatarmanage.service; + +import com.supervision.livedigitalavatarmanage.domain.ScriptUser; +import com.baomidou.mybatisplus.extension.service.IService; + +/** +* @author Administrator +* @description 针对表【script_user(话术组信息)】的数据库操作Service +* @createDate 2025-09-17 10:57:12 +*/ +public interface ScriptUserService extends IService { + +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/AsyncTaskResultServiceImpl.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/AsyncTaskResultServiceImpl.java new file mode 100644 index 0000000..27f6778 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/AsyncTaskResultServiceImpl.java @@ -0,0 +1,99 @@ +package com.supervision.livedigitalavatarmanage.service.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateField; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.lang.UUID; +import cn.hutool.json.JSONUtil; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.supervision.livedigitalavatarmanage.constant.TaskStautsEnum; +import com.supervision.livedigitalavatarmanage.domain.AsyncTaskResult; +import com.supervision.livedigitalavatarmanage.service.AsyncTaskResultService; +import com.supervision.livedigitalavatarmanage.mapper.AsyncTaskResultMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; +import java.util.List; +import java.util.concurrent.Callable; + +/** +* @author Administrator +* @description 针对表【async_task_result(异步任务结果表)】的数据库操作Service实现 +* @createDate 2025-09-17 15:35:10 +*/ +@Slf4j +@Service +@RequiredArgsConstructor +public class AsyncTaskResultServiceImpl extends ServiceImpl + implements AsyncTaskResultService{ + + @Autowired + @Qualifier("taskExecutor") + private ThreadPoolTaskExecutor taskExecutor; + + @Override + public String submitAsyncTask(String taskType, Object requestData, Callable callable) { + String taskId = UUID.fastUUID().toString(); + + AsyncTaskResult result = new AsyncTaskResult(); + result.setId(taskId); + result.setTaskType(taskType); + result.setStatus(TaskStautsEnum.RUNNING.getCode()); + result.setRequestData(JSONUtil.toJsonStr(requestData)); + result.setExpireTime(DateUtil.offset(DateUtil.date(), DateField.DAY_OF_YEAR,7).toLocalDateTime()); // 7天后过期 + super.save(result); + + // 异步执行任务 + taskExecutor.execute(() -> executeTask(taskId, callable)); + + return taskId; + } + + /** + * 异步执行任务 + */ + private void executeTask(String taskId, Callable callable) { + try { + // 执行您的耗时操作 + T result = callable.call(); + // 更新任务结果 + super.lambdaUpdate().eq(AsyncTaskResult::getId, taskId) + .set(AsyncTaskResult::getStatus, TaskStautsEnum.SUCCESS.getCode()) + .set(AsyncTaskResult::getResultData, JSONUtil.toJsonStr(result)) + .update(); + + log.info("异步任务执行成功,任务ID: {}", taskId); + + } catch (Exception e) { + log.error("异步任务执行失败,任务ID: {}", taskId, e); + + super.lambdaUpdate().eq(AsyncTaskResult::getId, taskId) + .set(AsyncTaskResult::getStatus, TaskStautsEnum.SUCCESS.getCode()) + .set(AsyncTaskResult::getErrorMessage, e.getMessage()) + .update(); + } + } + + + + @Override + public int cleanExpiredTasks() { + + List list = super.lambdaQuery() + .lt(AsyncTaskResult::getExpireTime, DateUtil.date().toLocalDateTime()) + .list(); + if (CollUtil.isEmpty(list)){ + return 0; + } + List ids = list.stream().map(AsyncTaskResult::getId).toList(); + super.lambdaUpdate().in(AsyncTaskResult::getId, ids).remove(); + return ids.size(); + } +} + + + + diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/LiveDigitalServiceImpl.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/LiveDigitalServiceImpl.java index a56142b..37c1451 100644 --- a/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/LiveDigitalServiceImpl.java +++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/LiveDigitalServiceImpl.java @@ -4,9 +4,16 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; +import com.fasterxml.jackson.core.type.TypeReference; import com.supervision.livedigitalavatarmanage.constant.PromptTemplate; +import com.supervision.livedigitalavatarmanage.domain.AsyncTaskResult; +import com.supervision.livedigitalavatarmanage.dto.AsyncTaskResultDTO; +import com.supervision.livedigitalavatarmanage.dto.ScriptKeyPointsDTO; +import com.supervision.livedigitalavatarmanage.service.AsyncTaskResultService; import com.supervision.livedigitalavatarmanage.service.LiveDigitalService; +import com.supervision.livedigitalavatarmanage.service.ScriptKeypointsService; import com.supervision.livedigitalavatarmanage.vo.SalesPitchReqVo; +import com.supervision.livedigitalavatarmanage.vo.SalesPitchResVo; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.messages.SystemMessage; @@ -16,6 +23,8 @@ import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.ollama.OllamaChatModel; import org.springframework.stereotype.Service; import java.util.*; +import java.util.concurrent.Callable; +import java.util.stream.Collectors; @Slf4j @Service @@ -23,41 +32,134 @@ import java.util.*; public class LiveDigitalServiceImpl implements LiveDigitalService { private final OllamaChatModel ollamaChatModel; + + private final ScriptKeypointsService scriptKeypointsService; + + private final AsyncTaskResultService asyncTaskResultService; @Override - public List generateSalesPitch(SalesPitchReqVo salespitchReqVo) { + public List generateSalesPitch(SalesPitchReqVo salespitchReqVo) { Assert.notEmpty(salespitchReqVo.getProductName(), "产品名称不能为空"); Assert.notEmpty(salespitchReqVo.getSpecifications(), "产品规格不能为空"); Assert.notEmpty(salespitchReqVo.getDetail(), "产品详情不能为空"); + List salesPitchResVos = new ArrayList<>(); + // 根据用户id和产品品类查询话术要点 + List scriptKeyPointsDTOS = scriptKeypointsService.listByUserIdAndProductCategory(null, salespitchReqVo.getProductCategory()); + + // 对模板数据进行分组 (话术组ID -> 话术类型 -> 话术要点列表(按升序顺序排序)) + Map>> scriptKeyPointsGroup = scriptKeyPointsDTOS.stream() + .collect(Collectors.groupingBy( + ScriptKeyPointsDTO::getScriptGroupId, + Collectors.groupingBy( + ScriptKeyPointsDTO::getScriptType, + Collectors.collectingAndThen( + Collectors.toList(), + list -> list.stream() + .sorted(Comparator.comparing(ScriptKeyPointsDTO::getKeyPointOrderNum)) + .collect(Collectors.toList()) + ) + ) + )); + + if (CollUtil.isEmpty(scriptKeyPointsGroup)){ + return salesPitchResVos; + } - List points = generateCopywritingPoints(salespitchReqVo); - Assert.notEmpty(points, "生成话术失败!请稍后重试"); - List prompts = generatePrompts(points, salespitchReqVo); - List result = new ArrayList<>(); - for (Prompt prompt : prompts) { - for (int i = 0; i < 2; i++) { + // 生成话术 + for (Map.Entry>> entry : scriptKeyPointsGroup.entrySet()) { + // 话术类型 -> 话术要点列表 + Map> scriptTypeMapPoints = entry.getValue(); + for (Map.Entry> scriptTypeMapPointsEntry : scriptTypeMapPoints.entrySet()) { + // 话术要点列表 + List scriptKeyPointsDTOList = scriptTypeMapPointsEntry.getValue(); + if (CollUtil.isEmpty(scriptKeyPointsDTOList)){ + continue; + } + // 生成销售话术 + String productCategory = CollUtil.getFirst(scriptKeyPointsDTOList).getProductCategory(); + String scriptType = CollUtil.getFirst(scriptKeyPointsDTOList).getScriptType(); + String salesPitch = null; try { - log.info("======================>>>>"); - log.info("开始生成销售话术,请求内容:{}", prompt.getContents()); - ChatResponse call = ollamaChatModel.call(prompt); - String text = call.getResult().getOutput().getText(); - // 去除think标签 - text = removeThinkTag(text); - log.info("生成销售话术成功,结果:{}", text); - log.info("<<<<======================"); - result.addAll(JSONUtil.toList(text, String.class)); - break; - }catch (Exception e) { - log.error("生成销售话术失败,尝试第 {} 次。请求内容:{}", i + 1, salespitchReqVo.buildTemplate(),e); + salesPitch = generateSalesPitchByPoints(productCategory, scriptType, + salespitchReqVo.buildProductInfo(), scriptKeyPointsDTOList); + } catch (Exception e) { + log.error("generateSalesPitch:生成销售话术失败", e); } + salesPitchResVos.add(new SalesPitchResVo(scriptType, salesPitch, CollUtil.getFirst(scriptKeyPointsDTOList).getScriptName())); } } - if (result.size() > 10){ - result = result.subList(0, 10); + return salesPitchResVos; + } + + @Override + public String submitSalesPitchTask(SalesPitchReqVo salespitchReqVo) { + SalesPitchTask salesPitchTask = new SalesPitchTask(salespitchReqVo); + return asyncTaskResultService.submitAsyncTask("0", salespitchReqVo, salesPitchTask); + } + + @Override + public AsyncTaskResultDTO> querySalespitchTaskResult(String taskId) { + AsyncTaskResult asyncTaskResult = asyncTaskResultService.getById(taskId); + return new AsyncTaskResultDTO<>(asyncTaskResult, new TypeReference<>() {}); + } + + class SalesPitchTask implements Callable> { + private final SalesPitchReqVo salespitchReqVo; + + public SalesPitchTask(SalesPitchReqVo salespitchReqVo) { + this.salespitchReqVo = salespitchReqVo; } - return result; + + @Override + public List call() { + return generateSalesPitch(salespitchReqVo); + } + } + + + /** + * 生成销售话术 + * @param scriptKeyPointsDTOS 话术要点 + * @return + */ + private String generateSalesPitchByPoints(String productCategory,String scriptType,String productInfo,List scriptKeyPointsDTOS) { + List sps = scriptKeyPointsDTOS.stream().sorted(Comparator.comparing(ScriptKeyPointsDTO::getScriptTypeOrderNum)) + .map(skp -> new ScriptKeyPointsDTO(skp.getScriptType(), skp.getKeyPoint(), skp.getKeyPointExample())).toList(); + // “品牌故事”、“生产工艺”、“市场口碑” + String keyPointTitles1 = sps.stream().map(sp -> "“" +sp.getKeyPoint() + ":”").reduce((a, b) -> a + "、" + b).orElse(""); + //“品牌故事:”、“生产工艺:”、“市场口碑:” + String keyPointTitles2 = sps.stream().map(sp -> "“" +sp.getKeyPoint() + ":”").reduce((a, b) -> a + "、" + b).orElse(""); + //品牌故事、生产工艺、市场口碑 + String keyPointTitles3 = sps.stream().map(ScriptKeyPointsDTO::getKeyPoint).reduce((a, b) -> a + "、" + b).orElse(""); + // 品牌故事 -> 生产工艺 -> 市场口碑 + String keyPointTitles4 = sps.stream().map(ScriptKeyPointsDTO::getKeyPoint).reduce((a, b) -> a + " -> " + b).orElse(""); + //关键点:品牌故事 示例:衡恩老白干有着几十年的酿酒历史啦,一直坚持传统工艺,就为了给大家带来地道的老白干风味~ + String keyPointExamples = sps.stream().map(sp -> "关键点:" + sp.getKeyPoint() + " 示例:" + sp.getKeyPointExample()).reduce((a, b) -> a + "\n" + b).orElse(""); + + Map map = Map.of( + "productCategory", productCategory, + "scriptType", scriptType, + "productInfo", productInfo, + "keyPointTitles1", keyPointTitles1, + "keyPointTitles2", keyPointTitles2, + "keyPointTitles3", keyPointTitles3, + "keyPointTitles4", keyPointTitles4, + "keyPointExamples", keyPointExamples + ); + + String format = StrUtil.format(PromptTemplate.GENERATE_SALESPITCH_TEMPLATE_V2, map); + log.info("generateSalesPitchByPoints:提示词:{}",format); + UserMessage userMessage = new UserMessage(format); + Prompt prompt = new Prompt(userMessage); + ChatResponse call = ollamaChatModel.call(prompt); + String text = call.getResult().getOutput().getText(); + // 去除think标签 + text = removeThinkTag(text); + log.info("generateSalesPitchByPoints:模型生成的话术:{}",text); + return text; } + private List generatePrompts(List points, SalesPitchReqVo salespitchReqVo) { int promptNum = 10; // 需要生成的销售话术数量 int bestNum = 5; diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/ScriptKeypointsServiceImpl.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/ScriptKeypointsServiceImpl.java new file mode 100644 index 0000000..89197f9 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/ScriptKeypointsServiceImpl.java @@ -0,0 +1,29 @@ +package com.supervision.livedigitalavatarmanage.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.supervision.livedigitalavatarmanage.domain.ScriptKeypoints; +import com.supervision.livedigitalavatarmanage.dto.ScriptKeyPointsDTO; +import com.supervision.livedigitalavatarmanage.service.ScriptKeypointsService; +import com.supervision.livedigitalavatarmanage.mapper.ScriptKeypointsMapper; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** +* @author Administrator +* @description 针对表【script_keypoints(话术重要点表)】的数据库操作Service实现 +* @createDate 2025-09-17 10:57:12 +*/ +@Service +public class ScriptKeypointsServiceImpl extends ServiceImpl + implements ScriptKeypointsService{ + + @Override + public List listByUserIdAndProductCategory(String userId, String productCategory) { + return super.getBaseMapper().listByUserIdAndProductCategory(userId,productCategory); + } +} + + + + diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/ScriptUserServiceImpl.java b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/ScriptUserServiceImpl.java new file mode 100644 index 0000000..ef4ef40 --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/service/impl/ScriptUserServiceImpl.java @@ -0,0 +1,22 @@ +package com.supervision.livedigitalavatarmanage.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.supervision.livedigitalavatarmanage.domain.ScriptUser; +import com.supervision.livedigitalavatarmanage.service.ScriptUserService; +import com.supervision.livedigitalavatarmanage.mapper.ScriptUserMapper; +import org.springframework.stereotype.Service; + +/** +* @author Administrator +* @description 针对表【script_user(话术组信息)】的数据库操作Service实现 +* @createDate 2025-09-17 10:57:12 +*/ +@Service +public class ScriptUserServiceImpl extends ServiceImpl + implements ScriptUserService{ + +} + + + + diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchReqVo.java b/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchReqVo.java index 5daf230..b0d36aa 100644 --- a/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchReqVo.java +++ b/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchReqVo.java @@ -27,6 +27,11 @@ public class SalesPitchReqVo { */ private String detail; + /** + * 产品类别 + */ + private String productCategory; + public String buildTemplate() { return "请根据产品信息生成销售话术:" + diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchResVo.java b/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchResVo.java new file mode 100644 index 0000000..1543d4c --- /dev/null +++ b/src/main/java/com/supervision/livedigitalavatarmanage/vo/SalesPitchResVo.java @@ -0,0 +1,30 @@ +package com.supervision.livedigitalavatarmanage.vo; + +import lombok.Data; + +@Data +public class SalesPitchResVo { + + /** + * 销售话术 + */ + private String salesPitch; + + /** + * 话术类型 + */ + private String scriptType; + + /** + * 话术名称 + */ + private String scriptName; + + public SalesPitchResVo() { + } + public SalesPitchResVo(String salesPitch, String scriptType, String scriptName) { + this.salesPitch = salesPitch; + this.scriptType = scriptType; + this.scriptName = scriptName; + } +} diff --git a/src/main/java/com/supervision/livedigitalavatarmanage/vo/ollama/OllamaDTO.java b/src/main/java/com/supervision/livedigitalavatarmanage/vo/ollama/OllamaDTO.java index c00f3cc..28b0847 100644 --- a/src/main/java/com/supervision/livedigitalavatarmanage/vo/ollama/OllamaDTO.java +++ b/src/main/java/com/supervision/livedigitalavatarmanage/vo/ollama/OllamaDTO.java @@ -1,13 +1,10 @@ package com.supervision.livedigitalavatarmanage.vo.ollama; -import cn.hutool.core.lang.Assert; import lombok.Data; import org.springframework.ai.chat.messages.*; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.ollama.api.OllamaOptions; - import java.util.List; -import java.util.stream.Collectors; @Data public class OllamaDTO { @@ -36,7 +33,6 @@ public class OllamaDTO { }).toList(); OllamaOptions.Builder optionsBuilder = OllamaOptions.builder(); optionsBuilder.model(model); - optionsBuilder.model(model); return new Prompt(list,optionsBuilder.build()); } } diff --git a/src/main/resources/mapper/AsyncTaskResultMapper.xml b/src/main/resources/mapper/AsyncTaskResultMapper.xml new file mode 100644 index 0000000..26c2870 --- /dev/null +++ b/src/main/resources/mapper/AsyncTaskResultMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + id,task_type,status, + request_data,result_data,error_message, + create_time,update_time,expire_time + + diff --git a/src/main/resources/mapper/ScriptKeypointsMapper.xml b/src/main/resources/mapper/ScriptKeypointsMapper.xml new file mode 100644 index 0000000..0f25e47 --- /dev/null +++ b/src/main/resources/mapper/ScriptKeypointsMapper.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + id,script_type,script_id, + key_point ,key_point_example,script_type_order_num,keypoint_order_num, + create_time,update_time + + + diff --git a/src/main/resources/mapper/ScriptUserMapper.xml b/src/main/resources/mapper/ScriptUserMapper.xml new file mode 100644 index 0000000..4485c33 --- /dev/null +++ b/src/main/resources/mapper/ScriptUserMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + id,script_name,product_category, + user_id,create_time,update_time + +