- 在CmsContent实体类中增加英文标题、描述、介绍和内容字段 - 实现根据语言类型查询内容的功能,支持中英文切换 - 添加从Excel文件批量导入内容数据的功能 - 实现上传技能压缩包并解析生成技能内容的功能 - 优化分页查询逻辑,支持按标签过滤和内存分页 - 修改数据库映射配置以支持多语言字段存储 - 重构点赞功能的安全检查逻辑
401 lines
20 KiB
Java
401 lines
20 KiB
Java
package com.kexue.skills.service.impl;
|
||
|
||
import cn.dev33.satoken.stp.StpUtil;
|
||
import com.alibaba.fastjson.JSON;
|
||
import com.alibaba.fastjson.JSONObject;
|
||
import com.kexue.skills.common.util.HttpUtil;
|
||
import com.kexue.skills.config.DeepSeekConfig;
|
||
import com.kexue.skills.config.GlmConfig;
|
||
import com.kexue.skills.entity.CmsContent;
|
||
import com.kexue.skills.entity.CmsTag;
|
||
import com.kexue.skills.entity.dto.CmsTagDto;
|
||
import com.kexue.skills.entity.request.SkillAnalyzeRequest;
|
||
import com.kexue.skills.entity.request.SkillGenRequest;
|
||
import com.kexue.skills.entity.request.SkillPreGenRequest;
|
||
import com.kexue.skills.entity.request.SkillRequest;
|
||
import com.kexue.skills.entity.response.SkillResponse;
|
||
import com.kexue.skills.mapper.CmsContentMapper;
|
||
import com.kexue.skills.service.CmsTagService;
|
||
import com.kexue.skills.service.SkillGenService;
|
||
import com.kexue.skills.utils.EscapeCharacterUtils;
|
||
import jodd.util.StringUtil;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.stereotype.Service;
|
||
|
||
import javax.annotation.Resource;
|
||
import java.math.BigDecimal;
|
||
import java.util.ArrayList;
|
||
import java.util.Date;
|
||
import java.util.List;
|
||
import java.util.stream.Collectors;
|
||
|
||
/**
|
||
* 技能生成服务实现
|
||
*
|
||
* @author 维哥
|
||
* @since 2026-01-28
|
||
*/
|
||
@Slf4j
|
||
@Service
|
||
public class SkillGenServiceImpl implements SkillGenService {
|
||
|
||
@Autowired
|
||
private DeepSeekConfig deepSeekConfig;
|
||
|
||
@Autowired
|
||
private GlmConfig glmConfig;
|
||
|
||
@Autowired
|
||
private CmsTagService cmsTagService;
|
||
|
||
@Resource
|
||
private CmsContentMapper cmsContentMapper;
|
||
|
||
/**
|
||
* 生成技能
|
||
*
|
||
* @param request 生成请求
|
||
* @return 生成结果
|
||
*/
|
||
@Override
|
||
public SkillResponse preGenerate(SkillPreGenRequest request) {
|
||
log.info("生成技能请求: {}", request);
|
||
String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions";
|
||
|
||
// 从数据库中读取cms_tag表的标签信息
|
||
CmsTagDto tagDto = new CmsTagDto();
|
||
tagDto.setDeleteFlag(0);
|
||
tagDto.setStatus(1);
|
||
List<CmsTag> tags = cmsTagService.getList(tagDto);
|
||
|
||
// 将标签名称拼接成逗号分隔的字符串
|
||
StringBuilder tagsList = new StringBuilder();
|
||
for (int i = 0; i < tags.size(); i++) {
|
||
CmsTag tag = tags.get(i);
|
||
tagsList.append(tag.getTagId()+"."+tag.getTagName());
|
||
if (i < tags.size() - 1) {
|
||
tagsList.append(",");
|
||
}
|
||
}
|
||
|
||
SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(),
|
||
deepSeekConfig.getChat().getTemperature(), deepSeekConfig.getChat().getMaxTokens(),
|
||
request.getPrompt(), tagsList.toString());
|
||
|
||
try {
|
||
// 发送HTTP请求到deepseek API
|
||
String deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null);
|
||
log.info("Deepseek API响应: {}", deepseekResponse);
|
||
|
||
// 解析deepseek返回结果
|
||
JSONObject responseJson = JSON.parseObject(deepseekResponse);
|
||
List<JSONObject> choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class);
|
||
|
||
if (choices != null && !choices.isEmpty()) {
|
||
// 获取最新的choice(这里是第一个,因为只有一个)
|
||
JSONObject latestChoice = choices.get(0);
|
||
JSONObject message = latestChoice.getJSONObject("message");
|
||
String content = message.getString("content");
|
||
|
||
// 解析content中的JSON
|
||
JSONObject skillJson = JSON.parseObject(content);
|
||
SkillResponse skillResponse = new SkillResponse();
|
||
skillResponse.setName(skillJson.getString("name"));
|
||
skillResponse.setDescription(skillJson.getString("description"));
|
||
skillResponse.setTags(skillJson.getJSONArray("tags").toJavaList(String.class));
|
||
|
||
log.info("解析技能响应: {}", skillResponse);
|
||
return skillResponse;
|
||
}
|
||
} catch (Exception e) {
|
||
log.error("调用Deepseek API失败: {}", e.getMessage(), e);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
@Override
|
||
public SkillResponse preGenerateV2(SkillPreGenRequest request) {
|
||
log.info("生成技能请求V2: {}", request);
|
||
String url = glmConfig.getBaseUrl() + "/chat/completions";
|
||
String apiKey = glmConfig.getApiKey();
|
||
String model = glmConfig.getChat().getModel();
|
||
double temperature = glmConfig.getChat().getTemperature();
|
||
int maxTokens = glmConfig.getChat().getMaxTokens();
|
||
|
||
// 从数据库中读取cms_tag表的标签信息
|
||
CmsTagDto tagDto = new CmsTagDto();
|
||
tagDto.setDeleteFlag(0);
|
||
tagDto.setStatus(1);
|
||
List<CmsTag> tags = cmsTagService.getList(tagDto);
|
||
|
||
// 将标签名称拼接成逗号分隔的字符串
|
||
StringBuilder tagsList = new StringBuilder();
|
||
for (int i = 0; i < tags.size(); i++) {
|
||
CmsTag tag = tags.get(i);
|
||
tagsList.append(tag.getTagName());
|
||
if (i < tags.size() - 1) {
|
||
tagsList.append(",");
|
||
}
|
||
}
|
||
|
||
// 构建系统消息内容
|
||
String systemContent = "你是一个专业的AI技能设计助手。请根据agent skills撰写规范,按照用户提出的主题描述及参考文件,生成这个skill的名称、描述,并从以下标签列表中选择一个或者多个标签:\"" + tagsList.toString() + "\",并简述这个skill的具体价值点。输出json格式,仅输出以上所提到的名称、描述、标签、价值点,节点名称分别为name、description、tags、value_point,节点内容以中文形式返回。请严格按照指定的JSON格式输出,仅包含要求的字段,以中文形式返回。";
|
||
|
||
// 准备文件URL列表
|
||
List<String> fileUrls = new ArrayList<>();
|
||
if (request.getFileUrl() != null && !request.getFileUrl().isEmpty()) {
|
||
fileUrls.add(request.getFileUrl());
|
||
}
|
||
if (request.getFileUrls() != null && !request.getFileUrls().isEmpty()) {
|
||
fileUrls.addAll(request.getFileUrls());
|
||
}
|
||
|
||
// 创建技能请求
|
||
SkillRequest skillRequest = new SkillRequest(model, systemContent, request.getPrompt(), fileUrls, temperature, maxTokens);
|
||
|
||
try {
|
||
// 发送HTTP请求到API
|
||
String response = HttpUtil.sendPostRequest(url, skillRequest, apiKey, null);
|
||
log.info("API响应: {}", response);
|
||
|
||
// 解析返回结果
|
||
JSONObject responseJson = JSON.parseObject(response);
|
||
List<JSONObject> choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class);
|
||
|
||
if (choices != null && !choices.isEmpty()) {
|
||
// 获取最新的choice(这里是第一个,因为只有一个)
|
||
JSONObject latestChoice = choices.get(0);
|
||
JSONObject message = latestChoice.getJSONObject("message");
|
||
String content = message.getString("content");
|
||
|
||
// 解析content中的JSON
|
||
JSONObject skillJson = JSON.parseObject(content);
|
||
SkillResponse skillResponse = new SkillResponse();
|
||
skillResponse.setName(skillJson.getString("name"));
|
||
skillResponse.setDescription(skillJson.getString("description"));
|
||
skillResponse.setTags(skillJson.getJSONArray("tags").toJavaList(String.class));
|
||
skillResponse.setSummary(skillJson.getString("value_point"));
|
||
|
||
log.info("解析技能响应: {}", skillResponse);
|
||
return skillResponse;
|
||
}
|
||
} catch (Exception e) {
|
||
log.error("调用API失败: {}", e.getMessage(), e);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
@Override
|
||
public CmsContent generate(SkillGenRequest request) {
|
||
log.info("生成技能请求: {}", request);
|
||
String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions";
|
||
|
||
// 从数据库中读取cms_tag表的标签信息
|
||
CmsTagDto tagDto = new CmsTagDto();
|
||
tagDto.setDeleteFlag(0);
|
||
tagDto.setStatus(1);
|
||
List<CmsTag> tags = cmsTagService.getList(tagDto);
|
||
|
||
List<String> tags1 = request.getTags();
|
||
|
||
// 将标签名称拼接成逗号分隔的字符串
|
||
StringBuilder tagsList = new StringBuilder();
|
||
String defaultIcon = "";
|
||
for (int i = 0; i < tags.size(); i++) {
|
||
CmsTag tag = tags.get(i);
|
||
if (tags1.contains(tag.getTagId()+"")) {
|
||
if (StringUtil.isEmpty(defaultIcon)){
|
||
defaultIcon = tag.getIcon();
|
||
}
|
||
tagsList.append(tag.getTagName());
|
||
if (i < tags.size() - 1) {
|
||
tagsList.append(",");
|
||
}
|
||
}
|
||
}
|
||
String systemContent = "你是一个专业的AI技能设计助手。请基于用户提供的Skill名称、描述、标签,按照skills目录结构输出完整的skills内容,包括skills.md本体内容、scripts目录中的脚本等,并打包成一个YAML文件技能包。请严格遵循以下规范:1. 包含必需的文件和目录;2. 多行内容使用 | 字面块;3. 内容从行首开始;4. 空目录用 children: [] 表示;5. 文件内容要实际有用;6.输出一个完整的YAML文档,概要含核心属性:name、version、description、author、created、tags 等,其中 structure 为核心节点,用于描述 skill 包的文件目录结构;structure 下的每个节点均含基础属性:name、type、path、format、description,content 和 children 为互选属性,由 type 决定:type 为 file 时,展示文件具体内容在 content 字段;type 为 directory 时,展示子目录 / 文件节点在 children [] 数组中,无需其他额外说明。";
|
||
String userContent = "请根据以下Skill信息生成skills.md文档内容:Skill名称:SKILL_NAME,Skill描述:DESCRIPTION,Skill标签:TAGS ,摘要:SUMMARY。";
|
||
userContent = userContent.replace("SKILL_NAME", request.getName()).replace("DESCRIPTION", request.getDescription()).replace("TAGS", tagsList.toString()).replace("SUMMARY", request.getRequirement());
|
||
SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(),systemContent,userContent,deepSeekConfig.getChat().getTemperature(), 8192,"text");
|
||
try {
|
||
// 发送HTTP请求到deepseek API
|
||
String deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null);
|
||
log.info("Deepseek API响应: {}", deepseekResponse);
|
||
JSONObject responseJson = JSON.parseObject(deepseekResponse);
|
||
String content = responseJson.getJSONArray("choices").toJavaList(JSONObject.class).get(0).getJSONObject("message").getString("content");
|
||
content = EscapeCharacterUtils.removeEscapeCharacters( content);//去除转义字符
|
||
CmsContent cmsContent = getCmsContent(request, content, StpUtil.getLoginIdAsLong(),defaultIcon);
|
||
// 保存到数据库
|
||
cmsContentMapper.insert(cmsContent);
|
||
return cmsContent;
|
||
} catch (Exception e) {
|
||
log.error("调用Deepseek API失败: {}", e.getMessage(), e);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private CmsContent getCmsContent(SkillGenRequest request, String CmsContent,Long userId,String defaultIcon){
|
||
CmsContent cmsContent = new CmsContent();
|
||
cmsContent.setTitle(request.getName());
|
||
cmsContent.setDescription(request.getDescription());
|
||
cmsContent.setContent(CmsContent);
|
||
cmsContent.setTags(request.getTags().stream().map(String::valueOf).collect(Collectors.joining(",")));
|
||
cmsContent.setIsPaid(0);//免费
|
||
cmsContent.setIsOfficial(false);
|
||
cmsContent.setPublishStatus(1);
|
||
cmsContent.setAuthorId(userId);
|
||
cmsContent.setAuthorName(null);
|
||
cmsContent.setAuditStatus(1);
|
||
cmsContent.setViewCount(0);
|
||
cmsContent.setCommentCount(0);
|
||
cmsContent.setRequiredPoints(0);
|
||
cmsContent.setSupportPointsPay(0);
|
||
cmsContent.setPrice(new BigDecimal(0));
|
||
cmsContent.setLikeCount(0);
|
||
cmsContent.setShareCount(0);
|
||
cmsContent.setCreateTime(new Date());
|
||
cmsContent.setUpdateTime(new Date());
|
||
cmsContent.setContentType(1);
|
||
cmsContent.setCreateBy(userId+"");
|
||
cmsContent.setUpdateBy(userId+"");
|
||
cmsContent.setDeleteFlag(0);
|
||
cmsContent.setIcon(defaultIcon);
|
||
cmsContent.setRequirement(request.getRequirement());
|
||
return cmsContent;
|
||
}
|
||
|
||
|
||
/**
|
||
* 分析技能
|
||
*
|
||
* @param request 分析请求
|
||
* @return 分析结果
|
||
*/
|
||
@Override
|
||
public String analyzeSkill(SkillAnalyzeRequest request) {
|
||
log.info("分析技能请求: {}", request);
|
||
// 这里可以实现技能分析逻辑
|
||
// 不需要数据库操作,直接返回结果
|
||
return "技能分析成功: " + request.getSkillId();
|
||
}
|
||
|
||
@Override
|
||
public String genIntroduce(String content) {
|
||
log.info("生成技能介绍请求: {}", content);
|
||
String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions";
|
||
|
||
String systemContent = "你是一个专业的AI技能设计助手。我会给你提供一个完整的skill的内容,请你帮我总结出skill的作用,能够解决的问题,输出一段描述文本";
|
||
SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(), systemContent, content, 0.3, 500, "text");
|
||
|
||
try {
|
||
// 发送HTTP请求到deepseek API
|
||
String deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null);
|
||
log.info("Deepseek API响应: {}", deepseekResponse);
|
||
|
||
// 解析返回结果
|
||
JSONObject responseJson = JSON.parseObject(deepseekResponse);
|
||
List<JSONObject> choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class);
|
||
|
||
if (choices != null && !choices.isEmpty()) {
|
||
JSONObject latestChoice = choices.get(0);
|
||
JSONObject message = latestChoice.getJSONObject("message");
|
||
return EscapeCharacterUtils.removeEscapeCharacters( message.getString("content"));//去除转义字符
|
||
}
|
||
} catch (Exception e) {
|
||
log.error("调用Deepseek API失败: {}", e.getMessage(), e);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
@Override
|
||
public CmsContent uploadSkill(String skillUrl) {
|
||
log.info("上传技能压缩包请求: {}", skillUrl);
|
||
String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions";
|
||
|
||
// 从数据库中读取cms_tag表的标签信息
|
||
CmsTagDto tagDto = new CmsTagDto();
|
||
tagDto.setDeleteFlag(0);
|
||
tagDto.setStatus(1);
|
||
List<CmsTag> tags = cmsTagService.getList(tagDto);
|
||
|
||
// 将标签名称拼接成逗号分隔的字符串
|
||
StringBuilder tagsList = new StringBuilder();
|
||
for (int i = 0; i < tags.size(); i++) {
|
||
CmsTag tag = tags.get(i);
|
||
tagsList.append(tag.getTagId()+"."+tag.getTagName());
|
||
if (i < tags.size() - 1) {
|
||
tagsList.append(",");
|
||
}
|
||
}
|
||
|
||
// 构建系统消息内容
|
||
String systemContent = """
|
||
你是一位专业的AI技能包解析助手,需完成以下任务:
|
||
1. 我会提供一个技能包压缩包(格式包含zip/rar)的URL,你需解析该压缩包根目录下的SKILL.md文件; 或者提供一个在线的SKILL.md文件的url;
|
||
2. 基于SKILL.md内容,提炼出:
|
||
- skill的核心描述(description);
|
||
- 详细功能介绍(introduce);
|
||
- 从指定标签列表中选取至少3个适配标签(tagList),标签范围:TAG_LIST;
|
||
- 选择标签的时候只需要输出对应的标签编号
|
||
3. 生成content字段:基于技能包的名称、描述、标签,按照skills目录结构输出完整的技能包YAML内容(包含skills.md本体、scripts目录脚本等),需遵循以下严格规范:
|
||
- 包含技能包必需的文件和目录;
|
||
- 多行内容使用「|」字面块表示;
|
||
- 所有内容从行首开始,无前置空格;
|
||
- 空目录标注为「children: []」;
|
||
- 文件内容需具备实际使用价值;
|
||
- YAML文档需完整,核心概要包含name、version、description、author、created、tags等属性;
|
||
- 核心节点为structure,用于描述技能包文件目录结构;structure下每个节点需包含基础属性:name、type、path、format、description;content和children为互选属性(type为file时,content字段填写文件内容;type为directory时,children数组填写子目录/文件节点)。
|
||
请将最终结果以JSON格式输出,JSON结构必须包含:description(技能描述)、introduce(功能介绍)、tagList(标签列表)、content(完整YAML格式技能包内容),无需额外说明,仅输出符合要求的JSON内容。
|
||
""";
|
||
systemContent = systemContent.replace("TAG_LIST", tagsList.toString());
|
||
|
||
// 构建用户消息内容
|
||
String userContent = skillUrl;
|
||
|
||
// 创建技能请求
|
||
SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(), systemContent, userContent, 0.5, 8192, "json_object");
|
||
|
||
try {
|
||
// 发送HTTP请求到DeepSeek API
|
||
String deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null);
|
||
log.info("Deepseek API响应: {}", deepseekResponse);
|
||
|
||
// 解析返回结果
|
||
JSONObject responseJson = JSON.parseObject(deepseekResponse);
|
||
List<JSONObject> choices = responseJson.getJSONArray("choices").toJavaList(JSONObject.class);
|
||
|
||
if (choices != null && !choices.isEmpty()) {
|
||
// 获取最新的choice
|
||
JSONObject latestChoice = choices.get(0);
|
||
JSONObject message = latestChoice.getJSONObject("message");
|
||
String content = message.getString("content");
|
||
|
||
// 解析content中的JSON
|
||
JSONObject skillJson = JSON.parseObject(content);
|
||
|
||
// 构建技能生成请求
|
||
SkillGenRequest request = new SkillGenRequest();
|
||
request.setName(skillJson.getString("name"));
|
||
request.setDescription(skillJson.getString("description"));
|
||
request.setRequirement(skillJson.getString("introduce"));
|
||
request.setTags(skillJson.getJSONArray("tagList").toJavaList(String.class));
|
||
|
||
// 生成CmsContent对象
|
||
CmsContent cmsContent = getCmsContent(request, skillJson.getString("content"), StpUtil.getLoginIdAsLong(), "");
|
||
|
||
// 保存到数据库
|
||
cmsContentMapper.insert(cmsContent);
|
||
return cmsContent;
|
||
}
|
||
} catch (Exception e) {
|
||
log.error("调用Deepseek API失败: {}", e.getMessage(), e);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
}
|