feat(content): 新增内容管理相关功能和优化技能生成服务

- 添加了获取CmsContent内容的接口和实现方法
- 新增QueryContentDto用于内容查询参数传递
- 修改SkillGenController中上传技能接口参数类型
- 在SkillGenRequest中添加技能说明字段
- 优化SkillGenServiceImpl中的API调用异常处理
- 添加对技能上传后图标设置的逻辑处理
- 在SysUser实体和数据库映射中添加会话ID字段
- 实现用户会话创建和管理功能
- 更新数据库查询语句以包含新增的session_id字段
- 添加了canvas-design技能包示例文件
This commit is contained in:
wangzhiwei 2026-03-13 10:40:13 +08:00
parent 6398b0495e
commit ed220c9981
14 changed files with 216 additions and 23 deletions

View File

@ -6,6 +6,7 @@ import com.kexue.skills.common.CommonResult;
import com.kexue.skills.entity.CmsContent;
import com.kexue.skills.entity.base.IdDto;
import com.kexue.skills.entity.dto.CmsContentDto;
import com.kexue.skills.entity.dto.QueryContentDto;
import com.kexue.skills.service.CmsContentService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -284,4 +285,17 @@ public class CmsContentController {
return CommonResult.failed("导入失败:" + e.getMessage());
}
}
/**
* 获取CmsContent的content字段内容
*
* @param QueryContentDto 包含contentId和languageType的DTO对象
* @return content或contentEn字段的内容
*/
@PostMapping("/getContent")
@Operation(summary = "获取内容详情", description = "根据languageType获取content或contentEn字段的内容")
public CommonResult<String> getContent(@RequestBody QueryContentDto queryContentDto) {
String content = cmsContentService.getContent(queryContentDto.getContentId(), queryContentDto.getLanguageType());
return CommonResult.success(content);
}
}

View File

@ -0,0 +1,41 @@
package com.kexue.skills.controller;
import com.kexue.skills.common.CommonResult;
import com.kexue.skills.entity.dto.SessionDto;
import com.kexue.skills.service.SysUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* Session管理控制器
*
* @author 王志维
* @since 2026-03-12
*/
@RestController
@RequestMapping("api/session")
@Tag(name = "Session管理 Api")
@CrossOrigin(origins = "*")
public class SessionController {
/**
* 服务对象
*/
@Resource
private SysUserService sysUserService;
/**
* 创建或获取用户会话
*
* @param userId 包含用户ID的DTO对象
* @return 会话信息包含sessionId和isNew字段
*/
@GetMapping("/createSession")
@Operation(summary = "创建或获取会话", description = "根据用户ID创建或获取会话信息")
public CommonResult<SessionDto> createSession(@RequestParam Long userId) {
SessionDto sessionDto = sysUserService.createSession(userId);
return CommonResult.success(sessionDto);
}
}

View File

@ -6,6 +6,7 @@ import com.kexue.skills.entity.CmsContent;
import com.kexue.skills.entity.request.GenIntroduceRequest;
import com.kexue.skills.entity.request.SkillGenRequest;
import com.kexue.skills.entity.request.SkillPreGenRequest;
import com.kexue.skills.entity.request.SkillUploadDto;
import com.kexue.skills.entity.response.SkillResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -84,12 +85,12 @@ public class SkillGenController {
/**
* 上传技能压缩包
*
* @param skillUrl 技能压缩包URL
* @param skillUploadDto 技能压缩包URL
* @return 生成的技能内容
*/
@PostMapping("/uploadSkill")
@Operation(summary = "上传技能压缩包", description = "上传技能压缩包并生成技能")
public CommonResult<CmsContent> uploadSkill(@RequestBody String skillUrl) {
return CommonResult.success(skillGenService.uploadSkill(skillUrl));
public CommonResult<CmsContent> uploadSkill(@RequestBody SkillUploadDto skillUploadDto) {
return CommonResult.success(skillGenService.uploadSkill(skillUploadDto.getUrl()));
}
}

View File

@ -63,4 +63,7 @@ public class SysUser extends BaseEntity implements Serializable {
@Schema(description ="更新人")
private String updateBy;
@Schema(description ="会话ID")
private String sessionId;
}

View File

@ -0,0 +1,18 @@
package com.kexue.skills.entity.dto;
import com.kexue.skills.entity.base.BaseQueryDto;
import lombok.Data;
import java.util.List;
/**
* (CmsContent)查询DTO类
*
* @author 王志维
* @since 2025-02-21 23:01:48
*/
@Data
public class QueryContentDto extends BaseQueryDto {
private Long contentId;
private Integer languageType;
}

View File

@ -0,0 +1,19 @@
package com.kexue.skills.entity.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 会话DTO类
*
* @author 王志维
* @since 2026-03-12
*/
@Data
public class SessionDto {
@Schema(description = "会话ID")
private String sessionId;
@Schema(description = "是否为新会话")
private boolean isNew;
}

View File

@ -16,6 +16,8 @@ public class SkillGenRequest implements Serializable {
private String description;
@Schema(description = "技能标签")
private List<String> tags;
@Schema(description = "技能说明")
private String introduce;
@Schema(description = "需求说明")
private String requirement;
}

View File

@ -0,0 +1,13 @@
package com.kexue.skills.entity.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serializable;
@Data
public class SkillUploadDto implements Serializable {
@Schema(description = "技能包地址", required = true)
private String url;
}

View File

@ -174,4 +174,13 @@ public interface CmsContentService extends BaseService {
* @return 导入结果
*/
int importFromExcel(byte[] fileBytes, String createBy);
/**
* 获取CmsContent的content字段内容
*
* @param contentId 内容ID
* @param languageType 语言类型0 中文1 英文
* @return content或contentEn字段的内容
*/
String getContent(Long contentId, Integer languageType);
}

View File

@ -118,4 +118,12 @@ public interface SysUserService extends BaseService {
* @return 用户对象
*/
SysUser getUserByUsernameOrPhone(String usernameOrPhone);
/**
* 创建或获取用户会话
*
* @param userId 用户ID
* @return 会话信息
*/
com.kexue.skills.entity.dto.SessionDto createSession(Long userId);
}

View File

@ -900,4 +900,19 @@ public class CmsContentServiceImpl implements CmsContentService {
}
return null;
}
@Override
public String getContent(Long contentId, Integer languageType) {
CmsContent cmsContent = this.cmsContentMapper.queryById(contentId);
if (cmsContent == null) {
return null;
}
// 当languageType为1时返回contentEn否则返回content
if (languageType != null && languageType == 1) {
return cmsContent.getContentEn();
} else {
return cmsContent.getContent();
}
}
}

View File

@ -1,6 +1,7 @@
package com.kexue.skills.service.impl;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.kexue.skills.common.util.HttpUtil;
@ -14,6 +15,7 @@ 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.exception.BizException;
import com.kexue.skills.mapper.CmsContentMapper;
import com.kexue.skills.service.CmsTagService;
import com.kexue.skills.service.SkillGenService;
@ -83,9 +85,10 @@ public class SkillGenServiceImpl implements SkillGenService {
deepSeekConfig.getChat().getTemperature(), deepSeekConfig.getChat().getMaxTokens(),
request.getPrompt(), tagsList.toString());
String deepseekResponse = "";
try {
// 发送HTTP请求到deepseek API
String deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null);
deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null);
log.info("Deepseek API响应: {}", deepseekResponse);
// 解析deepseek返回结果
@ -110,6 +113,7 @@ public class SkillGenServiceImpl implements SkillGenService {
}
} catch (Exception e) {
log.error("调用Deepseek API失败: {}", e.getMessage(), e);
throw new BizException("调用Deepseek API失败:"+ e.getMessage()+" Deepseek API响应:"+ deepseekResponse);
}
return null;
@ -155,9 +159,10 @@ public class SkillGenServiceImpl implements SkillGenService {
// 创建技能请求
SkillRequest skillRequest = new SkillRequest(model, systemContent, request.getPrompt(), fileUrls, temperature, maxTokens);
String response = "";
try {
// 发送HTTP请求到API
String response = HttpUtil.sendPostRequest(url, skillRequest, apiKey, null);
response = HttpUtil.sendPostRequest(url, skillRequest, apiKey, null);
log.info("API响应: {}", response);
// 解析返回结果
@ -183,8 +188,8 @@ public class SkillGenServiceImpl implements SkillGenService {
}
} catch (Exception e) {
log.error("调用API失败: {}", e.getMessage(), e);
throw new BizException("调用Deepseek API失败:"+ e.getMessage()+" Deepseek API响应:"+ response);
}
return null;
}
@ -220,21 +225,26 @@ public class SkillGenServiceImpl implements SkillGenService {
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");
String deepseekResponse = "";
try {
// 发送HTTP请求到deepseek API
String deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null);
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);
List<CmsTag> list = tags.stream().filter(tag -> tag.getTagId() == Long.parseLong(cmsContent.getTags().split(",")[0])).toList();
if (CollectionUtil.isNotEmpty( list)){
cmsContent.setIcon(list.get(0).getIcon());
}
// 保存到数据库
cmsContentMapper.insert(cmsContent);
return cmsContent;
} catch (Exception e) {
log.error("调用Deepseek API失败: {}", e.getMessage(), e);
throw new BizException("调用Deepseek API失败:"+ e.getMessage()+" Deepseek API响应:"+ deepseekResponse);
}
return null;
}
private CmsContent getCmsContent(SkillGenRequest request, String CmsContent,Long userId,String defaultIcon){
@ -264,6 +274,7 @@ public class SkillGenServiceImpl implements SkillGenService {
cmsContent.setDeleteFlag(0);
cmsContent.setIcon(defaultIcon);
cmsContent.setRequirement(request.getRequirement());
cmsContent.setIntroduce(request.getIntroduce());
return cmsContent;
}
@ -289,10 +300,10 @@ public class SkillGenServiceImpl implements SkillGenService {
String systemContent = "你是一个专业的AI技能设计助手。我会给你提供一个完整的skill的内容请你帮我总结出skill的作用能够解决的问题输出一段描述文本";
SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(), systemContent, content, 0.3, 500, "text");
String deepseekResponse = "";
try {
// 发送HTTP请求到deepseek API
String deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null);
deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null);
log.info("Deepseek API响应: {}", deepseekResponse);
// 解析返回结果
@ -306,6 +317,7 @@ public class SkillGenServiceImpl implements SkillGenService {
}
} catch (Exception e) {
log.error("调用Deepseek API失败: {}", e.getMessage(), e);
throw new BizException("调用Deepseek API失败:"+ e.getMessage()+" Deepseek API响应:"+ deepseekResponse);
}
return null;
@ -337,6 +349,7 @@ public class SkillGenServiceImpl implements SkillGenService {
你是一位专业的AI技能包解析助手需完成以下任务
1. 我会提供一个技能包压缩包格式包含zip/rar的URL你需解析该压缩包根目录下的SKILL.md文件; 或者提供一个在线的SKILL.md文件的url
2. 基于SKILL.md内容提炼出
- skill的核心标题name
- skill的核心描述description
- 详细功能介绍introduce
- 从指定标签列表中选取至少3个适配标签tagList标签范围TAG_LIST
@ -349,7 +362,7 @@ public class SkillGenServiceImpl implements SkillGenService {
- 文件内容需具备实际使用价值
- YAML文档需完整核心概要包含nameversiondescriptionauthorcreatedtags等属性
- 核心节点为structure用于描述技能包文件目录结构structure下每个节点需包含基础属性nametypepathformatdescriptioncontent和children为互选属性type为file时content字段填写文件内容type为directory时children数组填写子目录/文件节点
请将最终结果以JSON格式输出JSON结构必须包含description技能描述introduce功能介绍tagList标签列表content完整YAML格式技能包内容无需额外说明仅输出符合要求的JSON内容
请将最终结果以JSON格式输出JSON结构必须包含name(技能名称),description技能描述introduce功能介绍tagList标签列表content完整YAML格式技能包内容无需额外说明仅输出符合要求的JSON内容
""";
systemContent = systemContent.replace("TAG_LIST", tagsList.toString());
@ -359,9 +372,10 @@ public class SkillGenServiceImpl implements SkillGenService {
// 创建技能请求
SkillRequest skillRequest = new SkillRequest(true, deepSeekConfig.getChat().getModel(), systemContent, userContent, 0.5, 8192, "json_object");
String deepseekResponse = "";
try {
// 发送HTTP请求到DeepSeek API
String deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null);
deepseekResponse = HttpUtil.sendPostRequest(url, skillRequest, deepSeekConfig.getApiKey(), null);
log.info("Deepseek API响应: {}", deepseekResponse);
// 解析返回结果
@ -381,18 +395,22 @@ public class SkillGenServiceImpl implements SkillGenService {
SkillGenRequest request = new SkillGenRequest();
request.setName(skillJson.getString("name"));
request.setDescription(skillJson.getString("description"));
request.setRequirement(skillJson.getString("introduce"));
request.setIntroduce(skillJson.getString("introduce"));
request.setTags(skillJson.getJSONArray("tagList").toJavaList(String.class));
// 生成CmsContent对象
CmsContent cmsContent = getCmsContent(request, skillJson.getString("content"), StpUtil.getLoginIdAsLong(), "");
List<CmsTag> list = tags.stream().filter(tag -> tag.getTagId() == Long.parseLong(cmsContent.getTags().split(",")[0])).toList();
if (CollectionUtil.isNotEmpty( list)){
cmsContent.setIcon(list.get(0).getIcon());
}
// 保存到数据库
cmsContentMapper.insert(cmsContent);
// cmsContentMapper.insert(cmsContent);
return cmsContent;
}
} catch (Exception e) {
log.error("调用Deepseek API失败: {}", e.getMessage(), e);
throw new BizException("调用Deepseek API失败:"+ e.getMessage()+" Deepseek API响应:"+ deepseekResponse);
}
return null;

View File

@ -13,6 +13,7 @@ import com.kexue.skills.common.LoginUserCacheUtil;
import com.kexue.skills.config.CaptchaConfig;
import com.kexue.skills.entity.*;
import com.kexue.skills.entity.dto.ContentPurchaseDto;
import com.kexue.skills.entity.dto.SessionDto;
import com.kexue.skills.entity.dto.SysUserDto;
import com.kexue.skills.entity.request.*;
import com.kexue.skills.mapper.*;
@ -961,4 +962,31 @@ public class SysUserServiceImpl implements SysUserService {
}
return sb.toString();
}
@Override
public SessionDto createSession(Long userId) {
SessionDto sessionDto = new SessionDto();
// 查询用户信息
SysUser sysUser = sysUserMapper.queryById(userId);
if (sysUser == null) {
throw new RuntimeException("用户不存在");
}
String sessionId = sysUser.getSessionId();
if (sessionId != null && !sessionId.isEmpty()) {
// 已有会话直接返回
sessionDto.setSessionId(sessionId);
sessionDto.setNew(false);
} else {
// 没有会话创建新的会话ID
sessionId = java.util.UUID.randomUUID().toString().replaceAll("-","");
sysUser.setSessionId(sessionId);
sysUserMapper.update(sysUser);
sessionDto.setSessionId(sessionId);
sessionDto.setNew(true);
}
return sessionDto;
}
}

View File

@ -14,12 +14,13 @@
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="enable" column="enable" jdbcType="OTHER"/>
<result property="deleteFlag" column="delete_flag" jdbcType="OTHER"/>
<result property="sessionId" column="session_id" jdbcType="VARCHAR"/>
</resultMap>
<!--查询单个-->
<select id="queryById" resultMap="SysUserMap">
select
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
from sys_user
where user_id = #{userId}
</select>
@ -27,7 +28,7 @@
<!--查询指定行数据-->
<select id="getPageList" resultMap="SysUserMap">
select
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
from sys_user
<where>
<if test="userId != null">
@ -69,7 +70,7 @@
<!--查询指定行数据-->
<select id="queryAllByLimit" resultMap="SysUserMap">
select
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
from sys_user
limit #{offset}, #{limit}
</select>
@ -77,7 +78,7 @@
<!--通过实体作为筛选条件查询-->
<select id="queryAll" resultMap="SysUserMap">
select
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
from sys_user
<where>
<if test="userId != null">
@ -118,8 +119,8 @@
<!--新增所有列-->
<insert id="insert" keyProperty="userId" useGeneratedKeys="true">
insert into sys_user(user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag)
values (#{userName}, #{pwd}, #{realName}, #{tel}, #{email}, #{salt}, #{remark}, #{createTime}, #{enable}, #{deleteFlag})
insert into sys_user(user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id)
values (#{userName}, #{pwd}, #{realName}, #{tel}, #{email}, #{salt}, #{remark}, #{createTime}, #{enable}, #{deleteFlag}, #{sessionId})
</insert>
@ -178,6 +179,9 @@
<if test="deleteFlag != null">
delete_flag = #{deleteFlag},
</if>
<if test="sessionId != null and sessionId != ''">
session_id = #{sessionId},
</if>
</set>
where user_id = #{userId}
</update>
@ -189,7 +193,7 @@
<select id="getByUsername" resultMap="SysUserMap">
select
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
from sys_user
where user_name = #{userName}
and delete_flag = 0
@ -198,7 +202,7 @@
<select id="getByTel" resultMap="SysUserMap">
select
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag
user_id, user_name, pwd, real_name, tel, email, salt, remark, create_time, enable, delete_flag, session_id
from sys_user
where tel = #{tel}
and delete_flag = 0