sxwz2.0/src/main/java/com/kexue/skills/service/impl/SkillGenServiceImpl.java
wangzhiwei 6398b0495e feat(content): 添加多语言支持和Excel导入功能
- 在CmsContent实体类中增加英文标题、描述、介绍和内容字段
- 实现根据语言类型查询内容的功能,支持中英文切换
- 添加从Excel文件批量导入内容数据的功能
- 实现上传技能压缩包并解析生成技能内容的功能
- 优化分页查询逻辑,支持按标签过滤和内存分页
- 修改数据库映射配置以支持多语言字段存储
- 重构点赞功能的安全检查逻辑
2026-03-11 15:36:48 +08:00

401 lines
20 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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、descriptioncontent 和 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、descriptioncontent和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;
}
}