feat(content): 添加多语言支持和Excel导入功能
- 在CmsContent实体类中增加英文标题、描述、介绍和内容字段 - 实现根据语言类型查询内容的功能,支持中英文切换 - 添加从Excel文件批量导入内容数据的功能 - 实现上传技能压缩包并解析生成技能内容的功能 - 优化分页查询逻辑,支持按标签过滤和内存分页 - 修改数据库映射配置以支持多语言字段存储 - 重构点赞功能的安全检查逻辑
This commit is contained in:
parent
1b0d102ef9
commit
6398b0495e
|
|
@ -10,8 +10,10 @@ import com.kexue.skills.service.CmsContentService;
|
|||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* (CmsContent)表控制层
|
||||
|
|
@ -261,4 +263,25 @@ public class CmsContentController {
|
|||
public CommonResult<PageInfo<CmsContent>> getUserCreated(@RequestBody CmsContentDto queryDto) {
|
||||
return CommonResult.success(cmsContentService.getPageListByUserCreated(queryDto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入Excel数据到CmsContent
|
||||
*
|
||||
* @param file Excel文件
|
||||
* @param createBy 创建人
|
||||
* @return 导入结果
|
||||
*/
|
||||
@PostMapping("/importFromExcel")
|
||||
@Operation(summary = "导入Excel数据", description = "从Excel文件导入数据到CmsContent")
|
||||
@RequireAuth
|
||||
public CommonResult<Integer> importFromExcel(@RequestParam("file") MultipartFile file, @RequestParam("createBy") String createBy) {
|
||||
try {
|
||||
byte[] fileBytes = file.getBytes();
|
||||
int successCount = cmsContentService.importFromExcel(fileBytes, createBy);
|
||||
return CommonResult.success(successCount);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return CommonResult.failed("导入失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,4 +80,16 @@ public class SkillGenController {
|
|||
public CommonResult<String> genIntroduce(@RequestBody GenIntroduceRequest request) {
|
||||
return CommonResult.success(skillGenService.genIntroduce(request.getContent()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传技能压缩包
|
||||
*
|
||||
* @param skillUrl 技能压缩包URL
|
||||
* @return 生成的技能内容
|
||||
*/
|
||||
@PostMapping("/uploadSkill")
|
||||
@Operation(summary = "上传技能压缩包", description = "上传技能压缩包并生成技能")
|
||||
public CommonResult<CmsContent> uploadSkill(@RequestBody String skillUrl) {
|
||||
return CommonResult.success(skillGenService.uploadSkill(skillUrl));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ public class CmsContent extends BaseEntity implements Serializable {
|
|||
@Schema(description ="标题")
|
||||
private String title;
|
||||
|
||||
@Schema(description ="英文标题")
|
||||
private String titleEn;
|
||||
|
||||
@Schema(description ="是否是官方:0否,1是")
|
||||
private Boolean isOfficial;
|
||||
|
||||
|
|
@ -45,12 +48,18 @@ public class CmsContent extends BaseEntity implements Serializable {
|
|||
@Schema(description ="详细描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description ="英文描述")
|
||||
private String descriptionEn;
|
||||
|
||||
@Schema(description ="需求说明")
|
||||
private String requirement;
|
||||
|
||||
@Schema(description ="介绍信息")
|
||||
private String introduce;
|
||||
|
||||
@Schema(description ="英文介绍")
|
||||
private String introduceEn;
|
||||
|
||||
@Schema(description ="分享数量")
|
||||
private Integer shareCount;
|
||||
|
||||
|
|
@ -70,6 +79,9 @@ public class CmsContent extends BaseEntity implements Serializable {
|
|||
@Schema(description ="内容详情")
|
||||
private String content;
|
||||
|
||||
@Schema(description ="英文内容")
|
||||
private String contentEn;
|
||||
|
||||
@Schema(description ="封面图片")
|
||||
private String coverImage;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,6 @@ public class CmsContentDto extends BaseQueryDto {
|
|||
|
||||
private Integer contentType;
|
||||
|
||||
private String categoryIds;
|
||||
|
||||
private Long categoryId;
|
||||
|
||||
private List<String> categoryIdList;
|
||||
|
||||
private Boolean isOfficial;
|
||||
|
||||
private Integer shareCount;
|
||||
|
|
@ -51,9 +45,14 @@ public class CmsContentDto extends BaseQueryDto {
|
|||
*/
|
||||
private List<Long> tagIdList;
|
||||
|
||||
/**
|
||||
* 语言类型:0 中文,1 英文
|
||||
*/
|
||||
private Integer languageType;
|
||||
|
||||
/**
|
||||
* 搜索关键字,同时搜索 title、description、tags
|
||||
*/
|
||||
private String keyword;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,14 @@ public interface CmsContentMapper {
|
|||
* @return 实例对象
|
||||
*/
|
||||
CmsContent queryById(Long contentId);
|
||||
|
||||
/**
|
||||
* 通过ID和语言类型查询单条数据
|
||||
*
|
||||
* @param queryDto 筛选条件
|
||||
* @return 实例对象
|
||||
*/
|
||||
CmsContent queryByIdWithLanguage(CmsContentDto queryDto);
|
||||
|
||||
/**
|
||||
* 新增数据
|
||||
|
|
@ -64,6 +72,14 @@ public interface CmsContentMapper {
|
|||
* @return 影响行数
|
||||
*/
|
||||
int insert(CmsContent cmsContent);
|
||||
|
||||
/**
|
||||
* 批量新增数据
|
||||
*
|
||||
* @param cmsContentList 实例对象列表
|
||||
* @return 影响行数
|
||||
*/
|
||||
int batchInsert(@Param("cmsContentList") List<CmsContent> cmsContentList);
|
||||
|
||||
/**
|
||||
* 更新数据
|
||||
|
|
|
|||
|
|
@ -165,4 +165,13 @@ public interface CmsContentService extends BaseService {
|
|||
* @return 查询结果
|
||||
*/
|
||||
PageInfo<CmsContent> getPageListByUserCreated(CmsContentDto queryDto);
|
||||
|
||||
/**
|
||||
* 导入Excel数据到CmsContent
|
||||
*
|
||||
* @param fileBytes Excel文件字节数组
|
||||
* @param createBy 创建人
|
||||
* @return 导入结果
|
||||
*/
|
||||
int importFromExcel(byte[] fileBytes, String createBy);
|
||||
}
|
||||
|
|
@ -43,4 +43,12 @@ public interface SkillGenService {
|
|||
* @return 技能介绍
|
||||
*/
|
||||
String genIntroduce(String content);
|
||||
|
||||
/**
|
||||
* 上传技能压缩包并生成技能
|
||||
*
|
||||
* @param skillUrl 技能压缩包URL
|
||||
* @return 生成的技能内容
|
||||
*/
|
||||
CmsContent uploadSkill(String skillUrl);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.kexue.skills.service.impl;
|
|||
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.github.pagehelper.util.StringUtil;
|
||||
import com.kexue.skills.common.LoginUserCacheUtil;
|
||||
import com.kexue.skills.entity.CmsContent;
|
||||
import com.kexue.skills.entity.CmsContentView;
|
||||
|
|
@ -12,10 +13,15 @@ import com.kexue.skills.mapper.CmsContentMapper;
|
|||
import com.kexue.skills.mapper.CmsContentViewMapper;
|
||||
import com.kexue.skills.mapper.CmsContentLikeMapper;
|
||||
import com.kexue.skills.service.CmsContentService;
|
||||
import cn.hutool.poi.excel.ExcelReader;
|
||||
import cn.hutool.poi.excel.ExcelUtil;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
@ -60,12 +66,15 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
|
||||
// 使用 PageHelper 进行分页
|
||||
PageHelper.startPage(queryDto.getPageNum(), queryDto.getPageSize());
|
||||
// 设置默认语言类型为中文
|
||||
if (queryDto.getLanguageType() == null) {
|
||||
queryDto.setLanguageType(0);
|
||||
}
|
||||
List<CmsContent> list = this.cmsContentMapper.getPageList(queryDto);
|
||||
PageInfo<CmsContent> pageInfo = new PageInfo<>(list);
|
||||
|
||||
// 如果使用了 keyword 搜索且返回结果小于等于 3 条,则根据第一个 skill 的标签查询相关 skill
|
||||
if (queryDto.getKeyword() != null && !queryDto.getKeyword().trim().isEmpty()
|
||||
&& list.size() <= 3 && !list.isEmpty()) {
|
||||
|
||||
if (queryDto.getTitle() != null && !queryDto.getTitle().trim().isEmpty() && list.size() <= 3 && !list.isEmpty()) {
|
||||
// 获取第一个 skill 的标签
|
||||
CmsContent firstSkill = list.get(0);
|
||||
String tagsStr = firstSkill.getTags();
|
||||
|
|
@ -86,6 +95,11 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
}
|
||||
}
|
||||
|
||||
//如果传入的tagId不为空,则添加到tagIdSet中
|
||||
if (queryDto.getTagId() != null) {
|
||||
tagIdSet.add(queryDto.getTagId());
|
||||
}
|
||||
|
||||
// 如果有有效的标签 ID,查询相关内容
|
||||
if (!tagIdSet.isEmpty()) {
|
||||
// 排除已返回的 contentId
|
||||
|
|
@ -100,22 +114,29 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
tagQueryDto.setTagIdList(new ArrayList<>(tagIdSet));
|
||||
tagQueryDto.setPageNum(1);
|
||||
tagQueryDto.setPageSize(1000);
|
||||
|
||||
|
||||
// 查询包含这些标签的所有 skill
|
||||
List<CmsContent> taggedContents = this.cmsContentMapper.getPageList(tagQueryDto);
|
||||
|
||||
// 过滤掉已返回的内容
|
||||
List<CmsContent> relatedContents = taggedContents.stream()
|
||||
.filter(content -> !existingIds.contains(content.getContentId()))
|
||||
.collect(Collectors.toList());
|
||||
// List<CmsContent> relatedContents = taggedContents.stream()
|
||||
// .filter(content -> !existingIds.contains(content.getContentId()))
|
||||
// .collect(Collectors.toList());
|
||||
|
||||
//如果tag传参,则根据tag再次过滤
|
||||
if (queryDto.getTagId() != null) {
|
||||
taggedContents = taggedContents.stream().filter(content -> content.getTags().contains(queryDto.getTagId().toString())).toList() ;
|
||||
}
|
||||
|
||||
List<CmsContent> finalList = new ArrayList<>(taggedContents);
|
||||
|
||||
// 如果有相关 skill,添加到结果中
|
||||
if (!relatedContents.isEmpty()) {
|
||||
if (!finalList.isEmpty()) {
|
||||
// 按 sort 和 create_time 排序
|
||||
relatedContents.sort((a, b) -> {
|
||||
finalList.sort((a, b) -> {
|
||||
int sortCompare = Integer.compare(
|
||||
a.getSort() != null ? a.getSort() : 0,
|
||||
b.getSort() != null ? b.getSort() : 0);
|
||||
a.getViewCount() != null ? a.getViewCount() : 0,
|
||||
b.getViewCount() != null ? b.getViewCount() : 0);
|
||||
if (sortCompare != 0) {
|
||||
return sortCompare;
|
||||
}
|
||||
|
|
@ -125,10 +146,10 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
return b.getCreateTime().compareTo(a.getCreateTime());
|
||||
});
|
||||
|
||||
list.addAll(relatedContents);
|
||||
// 重新构建 PageInfo
|
||||
pageInfo = new PageInfo<>(list);
|
||||
pageInfo.setTotal(list.size());
|
||||
// 计算需要添加的数量,确保总数量不超过 pageSize
|
||||
PageInfo<CmsContent> newPageInfo = new PageInfo<>(memoryPagination(finalList, queryDto.getPageNum(), queryDto.getPageSize()));
|
||||
newPageInfo.setTotal(finalList.size());
|
||||
pageInfo = newPageInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -138,6 +159,42 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
return pageInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内存分页方法
|
||||
*
|
||||
* @param list 原始数据列表
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页大小
|
||||
* @return 当前页的 list,对应的页码没有数据则返回空 list
|
||||
*/
|
||||
private List<CmsContent> memoryPagination(List<CmsContent> list, int pageNum, int pageSize) {
|
||||
// 参数校验
|
||||
if (list == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
if (pageNum < 1) {
|
||||
pageNum = 1;
|
||||
}
|
||||
if (pageSize < 1) {
|
||||
pageSize = 10;
|
||||
}
|
||||
|
||||
// 计算总记录数
|
||||
int total = list.size();
|
||||
|
||||
// 计算分页参数
|
||||
int startIndex = (pageNum - 1) * pageSize;
|
||||
int endIndex = Math.min(startIndex + pageSize, total);
|
||||
|
||||
// 截取分页数据
|
||||
List<CmsContent> pageList = new ArrayList<>();
|
||||
if (startIndex < total) {
|
||||
pageList = list.subList(startIndex, endIndex);
|
||||
}
|
||||
|
||||
return pageList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询列表
|
||||
*
|
||||
|
|
@ -146,6 +203,10 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
*/
|
||||
@Override
|
||||
public List<CmsContent> getList(CmsContentDto queryDto) {
|
||||
// 设置默认语言类型为中文
|
||||
if (queryDto.getLanguageType() == null) {
|
||||
queryDto.setLanguageType(0);
|
||||
}
|
||||
return this.cmsContentMapper.getList(queryDto);
|
||||
}
|
||||
|
||||
|
|
@ -374,7 +435,7 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
result = cmsContentLikeMapper.deleteById(existingLike.getLikeId());
|
||||
|
||||
// 减少内容的点赞数
|
||||
if (content.getLikeCount() > 0) {
|
||||
if (content != null && content.getLikeCount() > 0) {
|
||||
content.setLikeCount(content.getLikeCount() - 1);
|
||||
this.cmsContentMapper.update(content);
|
||||
}
|
||||
|
|
@ -648,4 +709,195 @@ public class CmsContentServiceImpl implements CmsContentService {
|
|||
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int importFromExcel(byte[] fileBytes, String createBy) {
|
||||
int successCount = 0;
|
||||
final int BATCH_SIZE = 100; // 批量处理大小
|
||||
|
||||
try (InputStream inputStream = new ByteArrayInputStream(fileBytes);
|
||||
ExcelReader reader = ExcelUtil.getReader(inputStream)) {
|
||||
|
||||
// 获取总行数(包括标题行)
|
||||
int totalRows = reader.getRowCount();
|
||||
if (totalRows <= 1) {
|
||||
// 只有标题行或空文件
|
||||
return 0;
|
||||
}
|
||||
|
||||
Date now = new Date();
|
||||
List<CmsContent> batchList = new ArrayList<>(BATCH_SIZE);
|
||||
|
||||
// 读取标题行
|
||||
List<Object> headerObjList = reader.readRow(0);
|
||||
if (headerObjList == null || headerObjList.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
// 转换为List<String>
|
||||
List<String> headerList = new ArrayList<>();
|
||||
for (Object obj : headerObjList) {
|
||||
headerList.add(obj != null ? obj.toString() : "");
|
||||
}
|
||||
|
||||
// 从第二行开始读取数据(第一行为标题行)
|
||||
for (int rowIndex = 1; rowIndex < totalRows; rowIndex++) {
|
||||
List<Object> rowList = reader.readRow(rowIndex);
|
||||
if (rowList == null || rowList.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 转换为Map<String, Object>
|
||||
Map<String, Object> row = new HashMap<>();
|
||||
for (int i = 0; i < headerList.size() && i < rowList.size(); i++) {
|
||||
row.put(headerList.get(i), rowList.get(i));
|
||||
}
|
||||
if (row.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CmsContent cmsContent = new CmsContent();
|
||||
|
||||
// 设置创建时间和更新时间
|
||||
cmsContent.setCreateTime(now);
|
||||
cmsContent.setUpdateTime(now);
|
||||
cmsContent.setCreateBy(createBy);
|
||||
cmsContent.setUpdateBy(createBy);
|
||||
|
||||
// 设置默认值
|
||||
cmsContent.setDeleteFlag(0);
|
||||
cmsContent.setAuditStatus(1); // 默认草稿状态
|
||||
cmsContent.setPublishStatus(1); // 默认未发布状态
|
||||
cmsContent.setViewCount(0);
|
||||
cmsContent.setLikeCount(0);
|
||||
cmsContent.setCommentCount(0);
|
||||
cmsContent.setSort(0);
|
||||
|
||||
// 读取Excel中的字段
|
||||
// content_id - 数据库自动生成,不需要读取
|
||||
cmsContent.setTitle(getStringValue(row, "title"));
|
||||
cmsContent.setTitleEn(getStringValue(row, "title_en"));
|
||||
cmsContent.setOrigin(getStringValue(row, "origin"));
|
||||
cmsContent.setTags(Objects.requireNonNull(getStringValue(row, "tags")).replaceAll(" ",""));
|
||||
cmsContent.setIcon(getStringValue(row, "icon"));
|
||||
cmsContent.setIsOfficial(getBooleanValue(row, "is_official"));
|
||||
cmsContent.setPrice(getBigDecimalValue(row, "price"));
|
||||
cmsContent.setLikeCount(getIntegerValue(row, "like_count"));
|
||||
cmsContent.setShareCount(getIntegerValue(row, "share_count"));
|
||||
cmsContent.setContentType(getIntegerValue(row, "content_type"));
|
||||
cmsContent.setContent(getStringValue(row, "content"));
|
||||
cmsContent.setContentEn(getStringValue(row, "content_en"));
|
||||
cmsContent.setAuditStatus(getIntegerValue(row, "audit_status"));
|
||||
cmsContent.setPublishStatus(getIntegerValue(row, "publish_status"));
|
||||
cmsContent.setPublishTime(getDateValue(row, "publish_time"));
|
||||
cmsContent.setViewCount(getIntegerValue(row, "view_count"));
|
||||
cmsContent.setCommentCount(getIntegerValue(row, "comment_count"));
|
||||
cmsContent.setIsPaid(getIntegerValue(row, "is_paid"));
|
||||
cmsContent.setSupportPointsPay(getIntegerValue(row, "support_points_pay"));
|
||||
cmsContent.setDescription(getStringValue(row, "description"));
|
||||
cmsContent.setDescriptionEn(getStringValue(row, "description_en"));
|
||||
cmsContent.setIntroduce(getStringValue(row, "introduce"));
|
||||
cmsContent.setIntroduceEn(getStringValue(row, "introduce_en"));
|
||||
// create_time - 由系统生成,不需要读取
|
||||
// update_time - 由系统生成,不需要读取
|
||||
cmsContent.setDeleteFlag(getIntegerValue(row, "delete_flag"));
|
||||
|
||||
batchList.add(cmsContent);
|
||||
|
||||
// 达到批量大小或最后一行时,执行批量插入
|
||||
if (batchList.size() >= BATCH_SIZE || rowIndex == totalRows - 1) {
|
||||
if (!batchList.isEmpty()) {
|
||||
// 执行批量插入
|
||||
this.cmsContentMapper.batchInsert(batchList);
|
||||
successCount += batchList.size();
|
||||
batchList.clear(); // 清空批次
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return successCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取字符串值
|
||||
*/
|
||||
private String getStringValue(Map<String, Object> row, String key) {
|
||||
Object value = row.get(key);
|
||||
return value != null ? value.toString() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取布尔值
|
||||
*/
|
||||
private Boolean getBooleanValue(Map<String, Object> row, String key) {
|
||||
Object value = row.get(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Boolean) {
|
||||
return (Boolean) value;
|
||||
}
|
||||
if (value instanceof String) {
|
||||
return Boolean.parseBoolean((String) value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取整数值
|
||||
*/
|
||||
private Integer getIntegerValue(Map<String, Object> row, String key) {
|
||||
Object value = row.get(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
}
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return Integer.parseInt((String) value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取BigDecimal值
|
||||
*/
|
||||
private BigDecimal getBigDecimalValue(Map<String, Object> row, String key) {
|
||||
Object value = row.get(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return BigDecimal.valueOf(((Number) value).doubleValue());
|
||||
}
|
||||
if (value instanceof String) {
|
||||
try {
|
||||
return new BigDecimal((String) value);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Map中获取日期值
|
||||
*/
|
||||
private Date getDateValue(Map<String, Object> row, String key) {
|
||||
Object value = row.get(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return (Date) value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -310,4 +310,91 @@ public class SkillGenServiceImpl implements SkillGenService {
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,17 @@
|
|||
<resultMap type="com.kexue.skills.entity.CmsContent" id="CmsContentMap">
|
||||
<result property="contentId" column="content_id" jdbcType="BIGINT"/>
|
||||
<result property="title" column="title" jdbcType="VARCHAR"/>
|
||||
<result property="titleEn" column="title_en" jdbcType="VARCHAR"/>
|
||||
<result property="subtitle" column="subtitle" jdbcType="VARCHAR"/>
|
||||
<result property="contentType" column="content_type" jdbcType="INTEGER"/>
|
||||
<result property="summary" column="summary" jdbcType="VARCHAR"/>
|
||||
<result property="description" column="description" jdbcType="LONGVARCHAR"/>
|
||||
<result property="descriptionEn" column="description_en" jdbcType="LONGVARCHAR"/>
|
||||
<result property="requirement" column="requirement" jdbcType="LONGVARCHAR"/>
|
||||
<result property="introduce" column="introduce" jdbcType="LONGVARCHAR"/>
|
||||
<result property="introduceEn" column="introduce_en" jdbcType="LONGVARCHAR"/>
|
||||
<result property="content" column="content" jdbcType="LONGVARCHAR"/>
|
||||
<result property="contentEn" column="content_en" jdbcType="LONGVARCHAR"/>
|
||||
<result property="coverImage" column="cover_image" jdbcType="VARCHAR"/>
|
||||
<result property="authorId" column="author_id" jdbcType="BIGINT"/>
|
||||
<result property="authorName" column="author_name" jdbcType="VARCHAR"/>
|
||||
|
|
@ -52,11 +56,37 @@
|
|||
from cms_content
|
||||
where content_id = #{contentId}
|
||||
</select>
|
||||
|
||||
<!--查询单个带语言类型-->
|
||||
<select id="queryByIdWithLanguage" resultMap="CmsContentMap">
|
||||
select
|
||||
content_id,
|
||||
case when #{languageType} = 1 then title_en else title end as title,
|
||||
subtitle,
|
||||
content_type, summary,
|
||||
case when #{languageType} = 1 then description_en else description end as description,
|
||||
requirement,
|
||||
case when #{languageType} = 1 then introduce_en else introduce end as introduce,
|
||||
case when #{languageType} = 1 then content_en else content end as content,
|
||||
cover_image, author_id, author_name,
|
||||
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, origin, tags, create_time, update_time, create_by, update_by, delete_flag
|
||||
from cms_content
|
||||
where content_id = #{contentId}
|
||||
</select>
|
||||
|
||||
<!--查询分页列表-->
|
||||
<select id="getPageList" resultMap="CmsContentMap">
|
||||
select
|
||||
content_id, title, subtitle, content_type, summary, description, requirement, introduce, content, cover_image, author_id, author_name,
|
||||
content_id,
|
||||
case when #{languageType} = 1 then title_en else title end as title,
|
||||
subtitle,
|
||||
content_type, summary,
|
||||
case when #{languageType} = 1 then description_en else description end as description,
|
||||
requirement,
|
||||
case when #{languageType} = 1 then introduce_en else introduce end as introduce,
|
||||
case when #{languageType} = 1 then content_en else content end as content,
|
||||
cover_image, author_id, author_name,
|
||||
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, origin, tags, create_time, update_time, create_by, update_by, delete_flag
|
||||
from cms_content
|
||||
|
|
@ -113,7 +143,15 @@
|
|||
<!--带分页的查询-->
|
||||
<select id="getPageListWithPagination" resultMap="CmsContentMap">
|
||||
select
|
||||
content_id, title, subtitle, content_type, summary, description, requirement, introduce, content, cover_image, author_id, author_name,
|
||||
content_id,
|
||||
case when #{queryDto.languageType} = 1 then title_en else title end as title,
|
||||
subtitle,
|
||||
content_type, summary,
|
||||
case when #{queryDto.languageType} = 1 then description_en else description end as description,
|
||||
requirement,
|
||||
case when #{queryDto.languageType} = 1 then introduce_en else introduce end as introduce,
|
||||
case when #{queryDto.languageType} = 1 then content_en else content end as content,
|
||||
cover_image, author_id, author_name,
|
||||
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, origin, tags, create_time, update_time, create_by, update_by, delete_flag
|
||||
from cms_content
|
||||
|
|
@ -217,7 +255,15 @@
|
|||
<!--查询列表-->
|
||||
<select id="getList" resultMap="CmsContentMap">
|
||||
select
|
||||
content_id, title, subtitle, content_type, summary, description, requirement, introduce, content, cover_image, author_id, author_name,
|
||||
content_id,
|
||||
case when #{languageType} = 1 then title_en else title end as title,
|
||||
subtitle,
|
||||
content_type, summary,
|
||||
case when #{languageType} = 1 then description_en else description end as description,
|
||||
requirement,
|
||||
case when #{languageType} = 1 then introduce_en else introduce end as introduce,
|
||||
case when #{languageType} = 1 then content_en else content end as content,
|
||||
cover_image, author_id, author_name,
|
||||
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, origin, tags, create_time, update_time, create_by, update_by, delete_flag
|
||||
from cms_content
|
||||
|
|
@ -267,13 +313,26 @@
|
|||
|
||||
<!--新增所有列-->
|
||||
<insert id="insert" keyProperty="contentId" useGeneratedKeys="true">
|
||||
insert into cms_content(title, subtitle, content_type, summary, description, requirement, introduce, content, cover_image, author_id, author_name,
|
||||
insert into cms_content(title, title_en, subtitle, content_type, summary, description, description_en, requirement, introduce, introduce_en, content, content_en, cover_image, author_id, author_name,
|
||||
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, origin, tags, create_time, update_time, create_by, update_by, delete_flag)
|
||||
values (#{title}, #{subtitle}, #{contentType}, #{summary}, #{description}, #{requirement}, #{introduce}, #{content}, #{coverImage}, #{authorId}, #{authorName},
|
||||
values (#{title}, #{titleEn}, #{subtitle}, #{contentType}, #{summary}, #{description}, #{descriptionEn}, #{requirement}, #{introduce}, #{introduceEn}, #{content}, #{contentEn}, #{coverImage}, #{authorId}, #{authorName},
|
||||
#{reviewerId}, #{reviewerName}, #{auditStatus}, #{auditComment}, #{publishStatus}, #{publishTime},
|
||||
#{viewCount}, #{likeCount}, #{commentCount}, #{sort}, #{isPaid}, #{price}, #{requiredPoints}, #{supportPointsPay}, #{isOfficial}, #{shareCount}, #{fileUrl}, #{icon}, #{background}, #{origin}, #{tags}, #{createTime}, #{updateTime}, #{createBy}, #{updateBy}, #{deleteFlag})
|
||||
</insert>
|
||||
|
||||
<!--批量新增数据-->
|
||||
<insert id="batchInsert" useGeneratedKeys="true" keyProperty="contentId">
|
||||
insert into cms_content(title, title_en, subtitle, content_type, summary, description, description_en, requirement, introduce, introduce_en, content, content_en, cover_image, author_id, author_name,
|
||||
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, origin, tags, create_time, update_time, create_by, update_by, delete_flag)
|
||||
values
|
||||
<foreach collection="cmsContentList" item="item" separator=",">
|
||||
(#{item.title}, #{item.titleEn}, #{item.subtitle}, #{item.contentType}, #{item.summary}, #{item.description}, #{item.descriptionEn}, #{item.requirement}, #{item.introduce}, #{item.introduceEn}, #{item.content}, #{item.contentEn}, #{item.coverImage}, #{item.authorId}, #{item.authorName},
|
||||
#{item.reviewerId}, #{item.reviewerName}, #{item.auditStatus}, #{item.auditComment}, #{item.publishStatus}, #{item.publishTime},
|
||||
#{item.viewCount}, #{item.likeCount}, #{item.commentCount}, #{item.sort}, #{item.isPaid}, #{item.price}, #{item.requiredPoints}, #{item.supportPointsPay}, #{item.isOfficial}, #{item.shareCount}, #{item.fileUrl}, #{item.icon}, #{item.background}, #{item.origin}, #{item.tags}, #{item.createTime}, #{item.updateTime}, #{item.createBy}, #{item.updateBy}, #{item.deleteFlag})
|
||||
</foreach>
|
||||
</insert>
|
||||
|
||||
<!--通过主键修改数据-->
|
||||
<update id="update">
|
||||
|
|
@ -282,6 +341,9 @@
|
|||
<if test="title != null and title != ''">
|
||||
title = #{title},
|
||||
</if>
|
||||
<if test="titleEn != null and titleEn != ''">
|
||||
title_en = #{titleEn},
|
||||
</if>
|
||||
<if test="subtitle != null">
|
||||
subtitle = #{subtitle},
|
||||
</if>
|
||||
|
|
@ -294,15 +356,24 @@
|
|||
<if test="description != null">
|
||||
description = #{description},
|
||||
</if>
|
||||
<if test="descriptionEn != null">
|
||||
description_en = #{descriptionEn},
|
||||
</if>
|
||||
<if test="requirement != null">
|
||||
requirement = #{requirement},
|
||||
</if>
|
||||
<if test="introduce != null">
|
||||
introduce = #{introduce},
|
||||
</if>
|
||||
<if test="introduceEn != null">
|
||||
introduce_en = #{introduceEn},
|
||||
</if>
|
||||
<if test="content != null">
|
||||
content = #{content},
|
||||
</if>
|
||||
<if test="contentEn != null">
|
||||
content_en = #{contentEn},
|
||||
</if>
|
||||
<if test="coverImage != null">
|
||||
cover_image = #{coverImage},
|
||||
</if>
|
||||
|
|
|
|||
Loading…
Reference in New Issue