2 Commits

Author SHA1 Message Date
a8aa2a60fe feat(ai): 添加样本文件后缀支持和标签分组功能
- 在AiSampleDO中新增suffix字段存储文件后缀
- 在AiSampleRespVO中添加suffix响应字段
- 修改文件上传逻辑分离文件名和后缀并分别存储
- 新增根据标签ID获取分组列表的API接口
- 在标签服务中实现分组关联查询功能
- 修复标签分组关联关系创建和更新逻辑
- 优化标签分页查询添加软删除过滤条件
2026-03-04 12:50:10 +08:00
56656693c3 feat(ai-sample): 添加样本批量下载功能
- 在AiSampleController中新增/download接口支持批量下载
- 实现downloadSamples方法处理单个和多个样本下载逻辑
- 集成HttpUtil下载文件并使用ZipOutputStream打包多文件
- 添加文件服务器地址配置注入
2026-02-28 11:48:10 +08:00
10 changed files with 117 additions and 11 deletions

View File

@@ -11,10 +11,12 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static com.tashow.cloud.common.pojo.CommonResult.success;
@@ -91,4 +93,13 @@ public class AiSampleController {
PageResult<AiSampleDO> pageResult = aiSampleService.getAiSamplePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, AiSampleRespVO.class));
}
@GetMapping("/download")
@Operation(summary = "批量下载样本")
@Parameter(name = "ids", description = "样本ID列表逗号分隔", required = true, example = "1,2,3")
@PermitAll
public void downloadSamples(@RequestParam("ids") String ids, HttpServletResponse response) throws IOException {
aiSampleService.downloadSamples(ids, response);
}
}

View File

@@ -2,6 +2,7 @@ package com.tashow.cloud.ai.controller.admin.aisample;
import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagListRespVO;
import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagPageReqVO;
import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagGroupRespVO;
import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagSaveReqVO;
import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagDO;
import com.tashow.cloud.ai.service.aisample.AiSampleTagService;
@@ -66,6 +67,14 @@ public class AiSampleTagController {
return success(BeanUtils.toBean(aiSampleTagService.getAiSampleTagList(), AiSampleTagListRespVO.class));
}
@GetMapping("/group-list-by-tag-id")
@Operation(summary = "根据标签 id 获取分组")
@Parameter(name = "tagId", description = "标签 id", required = true)
@PermitAll
public CommonResult<List<AiSampleTagGroupRespVO>> getGroupListByTagId(@RequestParam("tagId") Long tagId) {
return success(BeanUtils.toBean(aiSampleTagService.getGroupListByTagId(tagId), AiSampleTagGroupRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得样本标签库分页")
// @PreAuthorize("@ss.hasPermission('ai:sampleTag:query')")
@@ -75,4 +84,4 @@ public class AiSampleTagController {
return success(pageResult);
}
}
}

View File

@@ -47,4 +47,10 @@ public class AiSampleRespVO {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateTime;
@Schema(description = "文件后缀")
private String suffix;
}

View File

@@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.tashow.cloud.common.serializer.ImgJsonSerializer;
import com.tashow.cloud.mybatis.mybatis.core.dataobject.BaseDO;
import lombok.*;
@@ -57,5 +59,9 @@ public class AiSampleDO extends BaseDO {
* 样本注释
*/
private String remark;
/**
*后缀)
*/
private String suffix;
}

View File

@@ -32,7 +32,7 @@ public interface AiSampleMapper extends BaseMapperX<AiSampleDO> {
"<where>" +
"AND t.deleted =0"+
" <if test=\"pageReqVO.tagIds != null and pageReqVO.tagIds != ''\">" +
" AND FIND_IN_SET(r.sample_tag_id,${pageReqVO.tagIds}) " +
" AND FIND_IN_SET(r.sample_tag_id, '${pageReqVO.tagIds}') " +
" </if>" +
" <if test=\"pageReqVO.sampleName != null and pageReqVO.sampleName != ''\">" +
" AND t.sample_name LIKE CONCAT('%',#{pageReqVO.sampleName},'%')" +

View File

@@ -28,6 +28,7 @@ public interface AiSampleTagMapper extends BaseMapperX<AiSampleTagDO> {
"FROM tz_ai_sample_tag t " +
"INNER JOIN tz_ai_sample_tag_group_relate r ON t.id = r.sample_tag_id " +
"<where>" +
"AND t.deleted =0"+
" <if test=\"pageReqVO.groupId != null\">" +
" AND r.sample_tag_group_id = #{pageReqVO.groupId}" +
" </if>" +

View File

@@ -3,8 +3,10 @@ package com.tashow.cloud.ai.service.aisample;
import com.tashow.cloud.ai.controller.admin.aisample.vo.*;
import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleDO;
import com.tashow.cloud.common.pojo.PageResult;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import java.io.IOException;
import java.util.List;
/**
@@ -69,5 +71,12 @@ public interface AiSampleService {
*/
PageResult<AiSampleDO> getAiSamplePage(AiSamplePageReqVO pageReqVO);
/**
* 批量下载样本
*
* @param ids 样本ID列表逗号分隔
* @param response HTTP响应
*/
void downloadSamples(String ids, HttpServletResponse response) throws IOException;
}

View File

@@ -3,6 +3,7 @@ package com.tashow.cloud.ai.service.aisample;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.tashow.cloud.ai.controller.admin.aisample.vo.*;
@@ -17,15 +18,20 @@ import com.tashow.cloud.common.util.object.BeanUtils;
import com.tashow.cloud.fileapi.api.file.FileApi;
import com.tashow.cloud.mybatis.mybatis.core.util.MyBatisUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 样本库 Service 实现类
@@ -44,7 +50,8 @@ public class AiSampleServiceImpl implements AiSampleService {
private AiSampleTagRelateMapper aiSampleTagRelateMapper;
@Resource
private FileApi fileApi;
@Value("${file-server}")
private String fileServer;
@Override
@SneakyThrows
@@ -58,11 +65,14 @@ public class AiSampleServiceImpl implements AiSampleService {
//保存样本信息
AiSampleDO aiSampleDO = new AiSampleDO();
aiSampleDO.setSampleFilePath(filePath);
aiSampleDO.setSampleName(file.getOriginalFilename());
String filename = file.getOriginalFilename();
String displayName = filename.substring(0, filename.lastIndexOf('.'));
aiSampleDO.setSampleName(displayName);
aiSampleDO.setSuffix(filename.substring(filename.lastIndexOf('.') + 1));
aiSampleDO.setSampleMineType(file.getContentType());
aiSampleDO.setSampleSize(file.getSize());
aiSampleMapper.insert(aiSampleDO);
urls.add(new AiSampleFileRespVO().setFileUrl(filePath).setFileName(file.getOriginalFilename()));
urls.add(new AiSampleFileRespVO().setFileUrl(filePath).setFileName(filename).setDisplayName(displayName));
}
// 返回
return urls;
@@ -175,4 +185,30 @@ public class AiSampleServiceImpl implements AiSampleService {
return new PageResult<>(aiSampleDOPageResult.getRecords(), aiSampleDOPageResult.getTotal());
}
@Override
public void downloadSamples(String ids, HttpServletResponse response) throws IOException {
List<AiSampleDO> samples = aiSampleMapper.selectBatchIds(Arrays.asList(ids.split(StrUtil.COMMA)));
if (samples.size() == 1) {
// 单个文件直接下载
AiSampleDO sample = samples.get(0);
byte[] bytes = HttpUtil.downloadBytes(fileServer + sample.getSampleFilePath());
response.setContentType(sample.getSampleMineType());
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(sample.getSampleName(), "UTF-8"));
response.getOutputStream().write(bytes);
} else {
// 多个文件打包成ZIP
response.setContentType("application/zip");
response.setHeader("Content-Disposition", "attachment; filename=samples.zip");
try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
for (AiSampleDO sample : samples) {
byte[] bytes = HttpUtil.downloadBytes(fileServer + sample.getSampleFilePath());
zos.putNextEntry(new ZipEntry(sample.getSampleName()));
zos.write(bytes);
zos.closeEntry();
}
}
}
}
}

View File

@@ -3,6 +3,7 @@ package com.tashow.cloud.ai.service.aisample;
import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagPageReqVO;
import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagSaveReqVO;
import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagDO;
import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagGroupDO;
import com.tashow.cloud.common.pojo.PageResult;
import jakarta.validation.Valid;
@@ -58,4 +59,12 @@ public interface AiSampleTagService {
* @return
*/
List<AiSampleTagDO> getAiSampleTagList();
}
/**
* 根据标签 id 获取关联分组列表
*
* @param tagId 标签 id
* @return 分组列表
*/
List<AiSampleTagGroupDO> getGroupListByTagId(Long tagId);
}

View File

@@ -5,7 +5,9 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagPageReqVO;
import com.tashow.cloud.ai.controller.admin.aisample.vo.AiSampleTagSaveReqVO;
import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagDO;
import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagGroupDO;
import com.tashow.cloud.ai.dal.dataobject.aisample.AiSampleTagGroupRelateDO;
import com.tashow.cloud.ai.dal.mysql.aisample.AiSampleTagGroupMapper;
import com.tashow.cloud.ai.dal.mysql.aisample.AiSampleTagGroupRelateMapper;
import com.tashow.cloud.ai.dal.mysql.aisample.AiSampleTagMapper;
import com.tashow.cloud.common.pojo.PageResult;
@@ -17,6 +19,7 @@ import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 样本标签库 Service 实现类
@@ -30,6 +33,8 @@ public class AiSampleTagServiceImpl implements AiSampleTagService {
@Resource
private AiSampleTagMapper aiSampleTagMapper;
@Resource
private AiSampleTagGroupMapper aiSampleTagGroupMapper;
@Resource
private AiSampleTagGroupRelateMapper tagGroupRelateMapper;
@Override
@@ -41,7 +46,6 @@ public class AiSampleTagServiceImpl implements AiSampleTagService {
List<AiSampleTagGroupRelateDO> tagGroupRelateDOS = new ArrayList<>();
for (Long groupId : createReqVO.getGroupIds()) {
AiSampleTagGroupRelateDO tagGroupRelateDO = new AiSampleTagGroupRelateDO();
tagGroupRelateDO.setSampleTagId(createReqVO.getId());
tagGroupRelateDO.setSampleTagGroupId(groupId);
tagGroupRelateDO.setSampleTagId(aiSampleTag.getId());
tagGroupRelateDOS.add(tagGroupRelateDO);
@@ -58,11 +62,13 @@ public class AiSampleTagServiceImpl implements AiSampleTagService {
// 更新
AiSampleTagDO updateObj = BeanUtils.toBean(updateReqVO, AiSampleTagDO.class);
aiSampleTagMapper.updateById(updateObj);
//删除所有关联
tagGroupRelateMapper.delete(new LambdaQueryWrapper<AiSampleTagGroupRelateDO>().eq(AiSampleTagGroupRelateDO::getSampleTagId, updateReqVO.getId()));
List<Long> groupIds = updateReqVO.getGroupIds();
// 删除该标签的所有旧关联
tagGroupRelateMapper.delete(new LambdaQueryWrapper<AiSampleTagGroupRelateDO>()
.eq(AiSampleTagGroupRelateDO::getSampleTagId, updateReqVO.getId()));
//插入关联
List<AiSampleTagGroupRelateDO> tagGroupRelateDOS = new ArrayList<>();
for (Long groupId : updateReqVO.getGroupIds()) {
for (Long groupId : groupIds) {
AiSampleTagGroupRelateDO tagGroupRelateDO = new AiSampleTagGroupRelateDO();
tagGroupRelateDO.setSampleTagId(updateReqVO.getId());
tagGroupRelateDO.setSampleTagGroupId(groupId);
@@ -100,4 +106,17 @@ public class AiSampleTagServiceImpl implements AiSampleTagService {
public List<AiSampleTagDO> getAiSampleTagList() {
return aiSampleTagMapper.selectList(new LambdaQueryWrapper<AiSampleTagDO>().orderByDesc(AiSampleTagDO::getId));
}
}
@Override
public List<AiSampleTagGroupDO> getGroupListByTagId(Long tagId) {
List<AiSampleTagGroupRelateDO> relateList = tagGroupRelateMapper.selectList(
new LambdaQueryWrapper<AiSampleTagGroupRelateDO>()
.eq(AiSampleTagGroupRelateDO::getSampleTagId, tagId));
List<Long> groupIds = relateList.stream()
.map(AiSampleTagGroupRelateDO::getSampleTagGroupId)
.distinct()
.collect(Collectors.toList());
return aiSampleTagGroupMapper.selectList(
new LambdaQueryWrapper<AiSampleTagGroupDO>().in(AiSampleTagGroupDO::getId, groupIds));
}
}