生成skill分成预生成和生成两个接口,cms_content表增加4个字段:user_upload,tag_ids,skill_path,skill_content。生成的skill既会保存到文件,也会记录到数据库
This commit is contained in:
parent
883963dbe6
commit
669c79e65d
|
|
@ -1,11 +1,19 @@
|
||||||
package com.kexue.skills.controller;
|
package com.kexue.skills.controller;
|
||||||
|
|
||||||
import com.kexue.skills.common.CommonResult;
|
import com.kexue.skills.common.CommonResult;
|
||||||
|
import com.kexue.skills.entity.request.SkillRequest;
|
||||||
import com.kexue.skills.entity.response.SkillResponse;
|
import com.kexue.skills.entity.response.SkillResponse;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 技能生成控制器
|
* 技能生成控制器
|
||||||
|
|
@ -13,6 +21,7 @@ import javax.annotation.Resource;
|
||||||
* @author 维哥
|
* @author 维哥
|
||||||
* @since 2026-01-28
|
* @since 2026-01-28
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("api/skillGen")
|
@RequestMapping("api/skillGen")
|
||||||
@Tag(name = "技能生成 Api")
|
@Tag(name = "技能生成 Api")
|
||||||
|
|
@ -23,17 +32,65 @@ public class SkillGenController {
|
||||||
private com.kexue.skills.service.SkillGenService skillGenService;
|
private com.kexue.skills.service.SkillGenService skillGenService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成技能
|
* 预生成skill,产生相关的描述信息
|
||||||
*
|
*
|
||||||
* @param request 生成请求
|
* @param request 生成请求
|
||||||
* @return 生成结果
|
* @return 生成结果
|
||||||
*/
|
*/
|
||||||
@PostMapping("/generate")
|
@PostMapping("/preGenerate")
|
||||||
@Operation(summary = "生成技能", description = "生成技能")
|
@Operation(summary = "预生成技能", description = "预生成技能")
|
||||||
public CommonResult<SkillResponse> generate(@RequestBody com.kexue.skills.entity.request.SkillGenRequest request) {
|
public CommonResult<SkillResponse> preGenerate(com.kexue.skills.entity.request.SkillGenRequest request,
|
||||||
return CommonResult.success(skillGenService.generateSkill(request));
|
@RequestPart(value = "file", required = false) MultipartFile file) {
|
||||||
|
try {
|
||||||
|
// 如果上传了文件,读取文件内容
|
||||||
|
if (file != null && !file.isEmpty()) {
|
||||||
|
String fileContent = readTextFile(file);
|
||||||
|
|
||||||
|
// 将文件内容设置到请求对象中(需要修改 SkillGenRequest)
|
||||||
|
// request.setFileContent(fileContent);
|
||||||
|
// request.setFileName(file.getOriginalFilename());
|
||||||
|
// request.setFileSize(file.getSize());
|
||||||
|
// request.setFileType(file.getContentType());
|
||||||
|
|
||||||
|
log.info("文件上传成功: {}, 大小: {} bytes, 类型: {}",
|
||||||
|
file.getOriginalFilename(), file.getSize(), file.getContentType());
|
||||||
|
return CommonResult.success(skillGenService.preGenerateSkill(request,fileContent));
|
||||||
|
}
|
||||||
|
|
||||||
|
return CommonResult.success(skillGenService.preGenerateSkill(request,null));
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("处理文件上传失败", e);
|
||||||
|
return CommonResult.failed("文件处理失败: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取文本文件内容为字符串
|
||||||
|
* @param file 上传的文件
|
||||||
|
* @return 文件内容字符串
|
||||||
|
*/
|
||||||
|
private String readTextFile(MultipartFile file) throws IOException {
|
||||||
|
// 使用 Apache Commons IO
|
||||||
|
try (InputStream inputStream = file.getInputStream()) {
|
||||||
|
return IOUtils.toString(inputStream, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成技能
|
||||||
|
*
|
||||||
|
* @param
|
||||||
|
* @return 分析结果
|
||||||
|
*/
|
||||||
|
@PostMapping("/generate")
|
||||||
|
@Operation(summary = "生成技能", description = "生成技能")
|
||||||
|
public CommonResult<SkillResponse> generate(@RequestBody SkillRequest request) {
|
||||||
|
Integer skillId=request.getSkillId();
|
||||||
|
return CommonResult.success(skillGenService.generateSkill(skillId));
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 分析技能
|
* 分析技能
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,17 @@ public class CmsContent extends BaseEntity implements Serializable {
|
||||||
@Schema(description ="父分类ID")
|
@Schema(description ="父分类ID")
|
||||||
private Long parentCategoryId;
|
private Long parentCategoryId;
|
||||||
|
|
||||||
|
@Schema(description ="用户上传的文件内容")
|
||||||
|
private String userUpload;
|
||||||
|
|
||||||
|
@Schema(description ="标签列表,逗号分隔")
|
||||||
|
private String tagIds;
|
||||||
|
|
||||||
|
@Schema(description ="技能的文件地址")
|
||||||
|
private String skillPath;
|
||||||
|
|
||||||
|
@Schema(description ="技能文件内容")
|
||||||
|
private String skillContent;
|
||||||
// 用于接收前端发送的分类ID数组
|
// 用于接收前端发送的分类ID数组
|
||||||
@JsonProperty("categoryIds")
|
@JsonProperty("categoryIds")
|
||||||
public void setCategoryIdsFromArray(List<Long> categoryIdList) {
|
public void setCategoryIdsFromArray(List<Long> categoryIdList) {
|
||||||
|
|
|
||||||
|
|
@ -44,4 +44,7 @@ public class CmsContentDto extends BaseQueryDto {
|
||||||
|
|
||||||
private Long parentCategoryId;
|
private Long parentCategoryId;
|
||||||
|
|
||||||
|
private String userLoad;
|
||||||
|
|
||||||
|
private String tagIds;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ public class SkillRequest implements Serializable {
|
||||||
private double temperature;
|
private double temperature;
|
||||||
private int max_tokens;
|
private int max_tokens;
|
||||||
private ResponseFormat response_format;
|
private ResponseFormat response_format;
|
||||||
|
private int skillId;
|
||||||
|
private int contentId;
|
||||||
|
|
||||||
public SkillRequest(boolean useDefaultSettings) {
|
public SkillRequest(boolean useDefaultSettings) {
|
||||||
if (useDefaultSettings) {
|
if (useDefaultSettings) {
|
||||||
|
|
@ -52,7 +54,7 @@ public class SkillRequest implements Serializable {
|
||||||
systemMessage.setRole("system");
|
systemMessage.setRole("system");
|
||||||
systemMessage.setContent("你是一个专业的AI技能设计助手。请严格按照指定的JSON格式输出,仅包含要求的字段,以中文形式返回。");
|
systemMessage.setContent("你是一个专业的AI技能设计助手。请严格按照指定的JSON格式输出,仅包含要求的字段,以中文形式返回。");
|
||||||
this.messages.add(systemMessage);
|
this.messages.add(systemMessage);
|
||||||
|
//获取系统存在的标签
|
||||||
Message userMessage = new Message();
|
Message userMessage = new Message();
|
||||||
userMessage.setRole("user");
|
userMessage.setRole("user");
|
||||||
userMessage.setContent("主题:"+ prompt +"。请根据agent skills撰写规范帮我生成这个skill的名称、描述,并从以下标签列表中选择一个或者多个标签:\"软件开发,系统集成,网络工程,云计算,大数据,人工智能,物联网,区块链,信息安全,运维服务,测试认证,IT 咨询,外包服务,电商技术,移动开发,前端开发,后端开发,全栈开发,数据库管理\"。输出json格式,仅输出以上所提到的名称、描述、标签,节点名称分别为name、description、tags,节点内容以中文形式返回。");
|
userMessage.setContent("主题:"+ prompt +"。请根据agent skills撰写规范帮我生成这个skill的名称、描述,并从以下标签列表中选择一个或者多个标签:\"软件开发,系统集成,网络工程,云计算,大数据,人工智能,物联网,区块链,信息安全,运维服务,测试认证,IT 咨询,外包服务,电商技术,移动开发,前端开发,后端开发,全栈开发,数据库管理\"。输出json格式,仅输出以上所提到的名称、描述、标签,节点名称分别为name、description、tags,节点内容以中文形式返回。");
|
||||||
|
|
@ -65,6 +67,53 @@ public class SkillRequest implements Serializable {
|
||||||
this.response_format.setType("json_object");
|
this.response_format.setType("json_object");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// String systemPrompt = """
|
||||||
|
// 请严格按照以下格式输出信息:
|
||||||
|
//
|
||||||
|
// ## 标题 ##
|
||||||
|
// [这里填写标题]
|
||||||
|
//
|
||||||
|
// ## 关键点 ##
|
||||||
|
// - 第一点
|
||||||
|
// - 第二点
|
||||||
|
// - 第三点
|
||||||
|
//
|
||||||
|
// ## 详细说明 ##
|
||||||
|
// [这里填写详细说明]
|
||||||
|
//
|
||||||
|
// ## 建议 ##
|
||||||
|
// [这里填写建议]
|
||||||
|
//
|
||||||
|
// 不要添加任何其他内容,严格按照上述格式输出。
|
||||||
|
// """;
|
||||||
|
|
||||||
|
public SkillRequest(boolean useDefaultSettings, String model, Double temperature, Integer maxTokens,String prompt,String description,String tags,String userUpload) {
|
||||||
|
String systemPrompt2="你是一个专业的AI技能设计助手。请基于用户提供的Skill名称、描述、标签,生成完整的skills.md文档内容,仅输出skills.md本体内容,无需其他额外说明。";
|
||||||
|
if (useDefaultSettings) {
|
||||||
|
this.model = model;
|
||||||
|
this.messages = new ArrayList<>();
|
||||||
|
|
||||||
|
Message systemMessage = new Message();
|
||||||
|
systemMessage.setRole("system");
|
||||||
|
systemMessage.setContent(systemPrompt2);
|
||||||
|
this.messages.add(systemMessage);
|
||||||
|
|
||||||
|
Message userMessage = new Message();
|
||||||
|
userMessage.setRole("user");
|
||||||
|
if(userUpload!=null&&!userUpload.equals("")){
|
||||||
|
userMessage.setContent("请根据以下Skill信息生成skills.md文档内容:Skill名称:"+prompt+"Skill描述:"+description+"Skill标签:"+tags+"。请仅输出skills.md本体内容,无需其他额外说明。内容的生成需要参考如下内容:"+userUpload);
|
||||||
|
}else{
|
||||||
|
userMessage.setContent("请根据以下Skill信息生成skills.md文档内容:Skill名称:"+prompt+"Skill描述:"+description+"Skill标签:"+tags+"。请仅输出skills.md本体内容,无需其他额外说明。");
|
||||||
|
}
|
||||||
|
this.messages.add(userMessage);
|
||||||
|
|
||||||
|
this.temperature = temperature;
|
||||||
|
this.max_tokens = maxTokens;
|
||||||
|
|
||||||
|
// this.response_format = new ResponseFormat();
|
||||||
|
// this.response_format.setType("json_object");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static SkillRequest createDefault() {
|
public static SkillRequest createDefault() {
|
||||||
return new SkillRequest(true);
|
return new SkillRequest(true);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.kexue.skills.entity.response;
|
package com.kexue.skills.entity.response;
|
||||||
|
|
||||||
|
import com.kexue.skills.entity.CmsContent;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
@ -12,4 +13,6 @@ public class SkillResponse implements Serializable {
|
||||||
private String name;
|
private String name;
|
||||||
private String description;
|
private String description;
|
||||||
private List<String> tags;
|
private List<String> tags;
|
||||||
|
private Long skillId;
|
||||||
|
private List<CmsContent> cmsContents;
|
||||||
}
|
}
|
||||||
|
|
@ -13,12 +13,16 @@ import com.kexue.skills.entity.response.SkillResponse;
|
||||||
public interface SkillGenService {
|
public interface SkillGenService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成技能
|
* 预生成技能
|
||||||
*
|
*
|
||||||
* @param request 生成请求
|
* @param request 生成请求
|
||||||
|
* @param fileContent 上传的文件内容
|
||||||
* @return 生成结果
|
* @return 生成结果
|
||||||
*/
|
*/
|
||||||
SkillResponse generateSkill(SkillGenRequest request);
|
SkillResponse preGenerateSkill(SkillGenRequest request,String fileContent);
|
||||||
|
|
||||||
|
|
||||||
|
SkillResponse generateSkill(Integer skillId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分析技能
|
* 分析技能
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,29 @@ import com.alibaba.fastjson.JSON;
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.kexue.skills.common.util.HttpUtil;
|
import com.kexue.skills.common.util.HttpUtil;
|
||||||
import com.kexue.skills.config.DeepSeekConfig;
|
import com.kexue.skills.config.DeepSeekConfig;
|
||||||
|
import com.kexue.skills.entity.CmsContent;
|
||||||
|
import com.kexue.skills.entity.CmsTag;
|
||||||
|
import com.kexue.skills.entity.dto.CmsContentDto;
|
||||||
|
import com.kexue.skills.entity.dto.CmsTagDto;
|
||||||
import com.kexue.skills.entity.request.SkillAnalyzeRequest;
|
import com.kexue.skills.entity.request.SkillAnalyzeRequest;
|
||||||
import com.kexue.skills.entity.request.SkillGenRequest;
|
import com.kexue.skills.entity.request.SkillGenRequest;
|
||||||
import com.kexue.skills.entity.request.SkillRequest;
|
import com.kexue.skills.entity.request.SkillRequest;
|
||||||
import com.kexue.skills.entity.response.SkillResponse;
|
import com.kexue.skills.entity.response.SkillResponse;
|
||||||
|
import com.kexue.skills.mapper.CmsContentMapper;
|
||||||
|
import com.kexue.skills.service.CmsContentService;
|
||||||
|
import com.kexue.skills.service.CmsTagService;
|
||||||
import com.kexue.skills.service.SkillGenService;
|
import com.kexue.skills.service.SkillGenService;
|
||||||
|
import com.kexue.skills.utils.FileManager;
|
||||||
|
import com.kexue.skills.utils.MarkdownProcessor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 技能生成服务实现
|
* 技能生成服务实现
|
||||||
|
|
@ -28,6 +41,10 @@ public class SkillGenServiceImpl implements SkillGenService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private DeepSeekConfig deepSeekConfig;
|
private DeepSeekConfig deepSeekConfig;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CmsContentService cmsContentService;
|
||||||
|
@Resource
|
||||||
|
private CmsTagService cmsTagService;
|
||||||
/**
|
/**
|
||||||
* 生成技能
|
* 生成技能
|
||||||
*
|
*
|
||||||
|
|
@ -35,8 +52,10 @@ public class SkillGenServiceImpl implements SkillGenService {
|
||||||
* @return 生成结果
|
* @return 生成结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public SkillResponse generateSkill(SkillGenRequest request) {
|
public SkillResponse preGenerateSkill(SkillGenRequest request,String fileContent) {
|
||||||
log.info("生成技能请求: {}", request);
|
log.info("预生成技能请求: {}", request);
|
||||||
|
// List<CmsTag> tags = cmsTagService.getList(new CmsTagDto());
|
||||||
|
|
||||||
String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions";
|
String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions";
|
||||||
SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(),
|
SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(),
|
||||||
deepSeekConfig.getChat().getTemperature(), deepSeekConfig.getChat().getMaxTokens(),request.getPrompt());
|
deepSeekConfig.getChat().getTemperature(), deepSeekConfig.getChat().getMaxTokens(),request.getPrompt());
|
||||||
|
|
@ -61,9 +80,57 @@ public class SkillGenServiceImpl implements SkillGenService {
|
||||||
SkillResponse skillResponse = new SkillResponse();
|
SkillResponse skillResponse = new SkillResponse();
|
||||||
skillResponse.setName(skillJson.getString("name"));
|
skillResponse.setName(skillJson.getString("name"));
|
||||||
skillResponse.setDescription(skillJson.getString("description"));
|
skillResponse.setDescription(skillJson.getString("description"));
|
||||||
skillResponse.setTags(skillJson.getJSONArray("tags").toJavaList(String.class));
|
final List<String> tags = skillJson.getJSONArray("tags").toJavaList(String.class);
|
||||||
|
//tags需要解析出id
|
||||||
|
// List<Long> targetTagIds = new ArrayList<>();
|
||||||
|
// List<CmsContent> resultContents =new ArrayList<>();
|
||||||
|
// for (String tag : tags) {
|
||||||
|
// CmsTagDto tagDto = new CmsTagDto();
|
||||||
|
// tagDto.setTagName(tag);
|
||||||
|
// List<CmsTag> list = cmsTagService.getList(tagDto);
|
||||||
|
// if (list != null && !list.isEmpty()) {
|
||||||
|
// targetTagIds.add(list.get(0).getTagId());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
skillResponse.setTags(tags);
|
||||||
log.info("解析技能响应: {}", skillResponse);
|
log.info("解析技能响应: {}", skillResponse);
|
||||||
|
//skill需要记录到数据库?
|
||||||
|
CmsContent cmsContent = new CmsContent();
|
||||||
|
cmsContent.setTitle(skillJson.getString("name"));
|
||||||
|
cmsContent.setSummary(skillJson.getString("description"));
|
||||||
|
cmsContent.setContentType(1);
|
||||||
|
cmsContent.setContent(skillJson.getString("content"));
|
||||||
|
cmsContent.setUserUpload(fileContent);
|
||||||
|
if(!tags.isEmpty()){
|
||||||
|
String result=tags.stream()
|
||||||
|
//.map(String::valueOf)
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
cmsContent.setTagIds(result);
|
||||||
|
}else{
|
||||||
|
cmsContent.setTagIds("通用技能,");
|
||||||
|
}
|
||||||
|
//记录到数据库
|
||||||
|
final CmsContent insert = cmsContentService.insert(cmsContent);
|
||||||
|
//根据tags在去匹配skill,作为推荐,推荐已经发布的skill
|
||||||
|
List<CmsContent> resultContents =new ArrayList<>();
|
||||||
|
CmsContentDto cmsContentDto = new CmsContentDto();
|
||||||
|
cmsContentDto.setPublishStatus(2);
|
||||||
|
final List<CmsContent> list = cmsContentService.getList(cmsContentDto);
|
||||||
|
for (CmsContent c:list) {
|
||||||
|
List<String> currentTags = Arrays.stream(c.getTagIds().split(","))
|
||||||
|
//.map(Long::valueOf)
|
||||||
|
.toList();
|
||||||
|
if(tags.stream().anyMatch(currentTags::contains)) {
|
||||||
|
//存在交集,需要被推荐
|
||||||
|
resultContents.add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(resultContents.isEmpty()&& !list.isEmpty()) {
|
||||||
|
//如果没匹配到,就先给第一个
|
||||||
|
resultContents.add(list.get(0));
|
||||||
|
}
|
||||||
|
skillResponse.setSkillId(insert.getContentId());
|
||||||
|
skillResponse.setCmsContents(resultContents);
|
||||||
return skillResponse;
|
return skillResponse;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|
@ -73,6 +140,63 @@ public class SkillGenServiceImpl implements SkillGenService {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SkillResponse generateSkill(Integer skillId) {
|
||||||
|
//根据skillId获取用户预创建时的信息,以此来生成skill文档
|
||||||
|
final CmsContent cmsContent = cmsContentService.queryById(Long.valueOf(skillId));
|
||||||
|
String name = cmsContent.getTitle();
|
||||||
|
String description = cmsContent.getSummary();
|
||||||
|
String userUpload = cmsContent.getUserUpload();
|
||||||
|
String tags=cmsContent.getTagIds();
|
||||||
|
|
||||||
|
|
||||||
|
log.info("生成技能请求: {}", skillId);
|
||||||
|
String url = deepSeekConfig.getBaseUrl() + "/v1/chat/completions";
|
||||||
|
SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(),
|
||||||
|
deepSeekConfig.getChat().getTemperature(), deepSeekConfig.getChat().getMaxTokens(),name,description,tags,userUpload);
|
||||||
|
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");
|
||||||
|
log.info("输出结果:{}",content);
|
||||||
|
FileManager manager = new FileManager();
|
||||||
|
FileManager.FileInfo fileInfo = manager.processAndWrite(content, name);
|
||||||
|
String storedFilePath = "";
|
||||||
|
if (fileInfo != null) {
|
||||||
|
log.info("文件信息: " + fileInfo);
|
||||||
|
// 可以将文件路径存储到数据库或其他地方
|
||||||
|
storedFilePath = fileInfo.getFilePath();
|
||||||
|
log.info("存储的文件路径: " + storedFilePath);
|
||||||
|
}
|
||||||
|
CmsContent c = new CmsContent();
|
||||||
|
c.setSkillPath(storedFilePath);
|
||||||
|
c.setSkillContent(content);
|
||||||
|
c.setContentId(Long.valueOf(skillId));
|
||||||
|
cmsContentService.update(c);
|
||||||
|
// 解析content中的JSON
|
||||||
|
SkillResponse skillResponse = new SkillResponse();
|
||||||
|
skillResponse.setDescription(MarkdownProcessor.unescapeString(content));
|
||||||
|
skillResponse.setSkillId(Long.valueOf(skillId));
|
||||||
|
return skillResponse;
|
||||||
|
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("调用Deepseek API失败: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
package com.kexue.skills.utils;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class FileManager {
|
||||||
|
private String outputDirectory;
|
||||||
|
private String fileExtension;
|
||||||
|
|
||||||
|
public FileManager() {
|
||||||
|
this.outputDirectory = "skill_output";
|
||||||
|
this.fileExtension = ".md";
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileManager(String outputDirectory, String fileExtension) {
|
||||||
|
this.outputDirectory = outputDirectory;
|
||||||
|
this.fileExtension = fileExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理内容并写入文件
|
||||||
|
public FileInfo processAndWrite(String content, String customFileName) {
|
||||||
|
try {
|
||||||
|
// 生成文件名
|
||||||
|
String fileName = generateFileName(customFileName);
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
String filePath = writeToFile(content, fileName);
|
||||||
|
|
||||||
|
// 返回文件信息
|
||||||
|
return new FileInfo(filePath, fileName);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("写入文件失败: " + e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> parseTagIds(String tagIds) {
|
||||||
|
if (tagIds == null || tagIds.trim().isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arrays.stream(tagIds.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.filter(tag -> !tag.isEmpty())
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateFileName(String customName) {
|
||||||
|
if (customName != null && !customName.trim().isEmpty()) {
|
||||||
|
return customName.endsWith(fileExtension) ? customName : customName + fileExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
|
||||||
|
String timestamp = LocalDateTime.now().format(formatter);
|
||||||
|
return "skills_" + timestamp + fileExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private String writeToFile(String content, String fileName) throws IOException {
|
||||||
|
// 创建输出目录
|
||||||
|
Path dirPath = Paths.get(outputDirectory);
|
||||||
|
if (!Files.exists(dirPath)) {
|
||||||
|
Files.createDirectories(dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建完整文件路径
|
||||||
|
Path filePath = dirPath.resolve(fileName);
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
Files.writeString(
|
||||||
|
filePath,
|
||||||
|
content,
|
||||||
|
StandardOpenOption.CREATE,
|
||||||
|
StandardOpenOption.TRUNCATE_EXISTING,
|
||||||
|
StandardOpenOption.WRITE
|
||||||
|
);
|
||||||
|
|
||||||
|
return filePath.toAbsolutePath().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件信息类
|
||||||
|
public static class FileInfo {
|
||||||
|
private final String filePath;
|
||||||
|
private final String fileName;
|
||||||
|
|
||||||
|
public FileInfo(String filePath, String fileName) {
|
||||||
|
this.filePath = filePath;
|
||||||
|
this.fileName = fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// getters
|
||||||
|
public String getFilePath() { return filePath; }
|
||||||
|
public String getFileName() { return fileName; }
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("文件: %s, 路径: %s",
|
||||||
|
fileName, filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.kexue.skills.utils;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringEscapeUtils;
|
||||||
|
|
||||||
|
public class MarkdownProcessor {
|
||||||
|
|
||||||
|
public static String unescapeString(String escapedString) {
|
||||||
|
// 将 \n 转换为真正的换行符
|
||||||
|
return escapedString.replace("\\n", "\n")
|
||||||
|
.replace("\\t", "\t")
|
||||||
|
.replace("\\\"", "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String convertToMarkdown(String escapedString) {
|
||||||
|
// 使用Apache Commons Text库
|
||||||
|
return StringEscapeUtils.unescapeJava(escapedString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<configuration scan="true" scanPeriod="60 seconds">
|
<configuration scan="true" scanPeriod="60 seconds">
|
||||||
|
|
||||||
<!-- 定义日志文件的存储路径 -->
|
<!-- 定义日志文件的存储路径 -->
|
||||||
<property name="LOG_HOME" value="/data/service/logs/yuelong-portal" />
|
<property name="LOG_HOME" value="./logs" />
|
||||||
|
|
||||||
<!-- 控制台输出 -->
|
<!-- 控制台输出 -->
|
||||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
|
@ -13,10 +13,10 @@
|
||||||
|
|
||||||
<!-- 每日滚动文件输出 -->
|
<!-- 每日滚动文件输出 -->
|
||||||
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
<file>${LOG_HOME}/hyxp-portal.log</file>
|
<file>${LOG_HOME}/skill.log</file>
|
||||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||||
<!-- 每天生成一个新的日志文件 -->
|
<!-- 每天生成一个新的日志文件 -->
|
||||||
<fileNamePattern>${LOG_HOME}/hyxp-portal.%d{yyyy-MM-dd}.log</fileNamePattern>
|
<fileNamePattern>${LOG_HOME}/skill.%d{yyyy-MM-dd}.log</fileNamePattern>
|
||||||
<!-- 保留 30 天的历史日志文件 -->
|
<!-- 保留 30 天的历史日志文件 -->
|
||||||
<maxHistory>30</maxHistory>
|
<maxHistory>30</maxHistory>
|
||||||
</rollingPolicy>
|
</rollingPolicy>
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@
|
||||||
select
|
select
|
||||||
content_id, title, subtitle, parent_category_id, content_type, category_ids, summary, content, cover_image, author_id, author_name,
|
content_id, title, subtitle, parent_category_id, content_type, category_ids, summary, content, cover_image, author_id, author_name,
|
||||||
reviewer_id, reviewer_name, audit_status, audit_comment, publish_status, publish_time,
|
reviewer_id, reviewer_name, audit_status, audit_comment, publish_status, publish_time,
|
||||||
view_count, like_count, comment_count, sort, is_paid, price, required_points, support_points_pay, is_official, share_count, file_url, icon, background, create_time, update_time, create_by, update_by, delete_flag
|
view_count, like_count, comment_count, sort, is_paid, price, required_points, support_points_pay, is_official, share_count, file_url, icon, background, create_time, update_time, create_by, update_by, delete_flag,user_upload,tag_ids
|
||||||
from cms_content
|
from cms_content
|
||||||
<where>
|
<where>
|
||||||
<if test="contentId != null">
|
<if test="contentId != null">
|
||||||
|
|
@ -161,6 +161,12 @@
|
||||||
<if test="parentCategoryId != null">
|
<if test="parentCategoryId != null">
|
||||||
and parent_category_id = #{parentCategoryId}
|
and parent_category_id = #{parentCategoryId}
|
||||||
</if>
|
</if>
|
||||||
|
<if test="userLoad != null">
|
||||||
|
and userLoad = #{userLoad}
|
||||||
|
</if>
|
||||||
|
<if test="tagIds != null">
|
||||||
|
and tagIds = #{tagIds}
|
||||||
|
</if>
|
||||||
</where>
|
</where>
|
||||||
order by sort asc, create_time desc
|
order by sort asc, create_time desc
|
||||||
</select>
|
</select>
|
||||||
|
|
@ -168,11 +174,13 @@
|
||||||
<!--新增所有列-->
|
<!--新增所有列-->
|
||||||
<insert id="insert" keyProperty="contentId" useGeneratedKeys="true">
|
<insert id="insert" keyProperty="contentId" useGeneratedKeys="true">
|
||||||
insert into cms_content(title, subtitle, parent_category_id, content_type, category_ids, summary, content, cover_image, author_id, author_name,
|
insert into cms_content(title, subtitle, parent_category_id, content_type, category_ids, summary, content, cover_image, author_id, author_name,
|
||||||
reviewer_id, reviewer_name, audit_status, audit_comment, publish_status, publish_time,
|
reviewer_id, reviewer_name, audit_status, audit_comment, publish_status, publish_time,
|
||||||
view_count, like_count, comment_count, sort, is_paid, price, required_points, support_points_pay, is_official, share_count, file_url, icon, background, create_time, update_time, create_by, update_by, delete_flag)
|
view_count, like_count, comment_count, sort, is_paid, price, required_points, support_points_pay, is_official, share_count, file_url, icon, background, create_time, update_time, create_by, update_by, delete_flag,user_upload,tag_ids)
|
||||||
values (#{title}, #{subtitle}, #{parentCategoryId}, #{contentType}, #{categoryIds}, #{summary}, #{content}, #{coverImage}, #{authorId}, #{authorName},
|
values (#{title}, #{subtitle}, #{parentCategoryId}, #{contentType}, #{categoryIds}, #{summary}, #{content}, #{coverImage}, #{authorId}, #{authorName},
|
||||||
#{reviewerId}, #{reviewerName}, #{auditStatus}, #{auditComment}, #{publishStatus}, #{publishTime},
|
#{reviewerId}, #{reviewerName}, #{auditStatus}, #{auditComment}, #{publishStatus}, #{publishTime},
|
||||||
#{viewCount}, #{likeCount}, #{commentCount}, #{sort}, #{isPaid}, #{price}, #{requiredPoints}, #{supportPointsPay}, #{isOfficial}, #{shareCount}, #{fileUrl}, #{icon}, #{background}, #{createTime}, #{updateTime}, #{createBy}, #{updateBy}, #{deleteFlag})
|
#{viewCount}, #{likeCount}, #{commentCount}, #{sort}, #{isPaid}, #{price}, #{requiredPoints}, #{supportPointsPay}, #{isOfficial}, #{shareCount}, #{fileUrl}, #{icon}, #{background}, #{createTime}, #{updateTime}, #{createBy}, #{updateBy}, #{deleteFlag},
|
||||||
|
#{userUpload},#{tagIds} )
|
||||||
|
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
<!--通过主键修改数据-->
|
<!--通过主键修改数据-->
|
||||||
|
|
@ -272,6 +280,12 @@
|
||||||
<if test="updateBy != null">
|
<if test="updateBy != null">
|
||||||
update_by = #{updateBy},
|
update_by = #{updateBy},
|
||||||
</if>
|
</if>
|
||||||
|
<if test="skillPath != null">
|
||||||
|
skill_path = #{skillPath},
|
||||||
|
</if>
|
||||||
|
<if test="skillContent != null">
|
||||||
|
skill_content = #{skillContent},
|
||||||
|
</if>
|
||||||
</set>
|
</set>
|
||||||
where content_id = #{contentId}
|
where content_id = #{contentId}
|
||||||
</update>
|
</update>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue