微服务改造

This commit is contained in:
litianxiang
2026-04-27 11:47:15 +08:00
parent 7c8f1bee6a
commit d36baf747f
10 changed files with 3923 additions and 3592 deletions

View File

@@ -102,7 +102,7 @@ jobs:
- ./temp:/temp - ./temp:/temp
- ./uploads:/temp/uploads - ./uploads:/temp/uploads
ports: ports:
- '10090:5567' - '10090:5566'
restart: always restart: always
EOF EOF
# 验证docker-compose.yml生成 # 验证docker-compose.yml生成

View File

@@ -10,7 +10,7 @@ public class CommonConstant {
// 单位 秒 两天过期 // 单位 秒 两天过期
public static final Long CREDITS_EXPIRE_TIME = 2 * 24 * 60 * 60L; public static final Long CREDITS_EXPIRE_TIME = 2 * 24 * 60 * 60L;
// 单位 分钟 // 单位 分钟
public static final Integer MINIO_IMAGE_EXPIRE_TIME = 24 * 60; public static final Integer MINIO_IMAGE_EXPIRE_TIME = 24 * 60 * 7;
// 单位 秒 一天过期 in redis // 单位 秒 一天过期 in redis
public static final Long GENERATE_RESULT_EXPIRE_TIME = 24 * 60 * 60L; public static final Long GENERATE_RESULT_EXPIRE_TIME = 24 * 60 * 60L;
// 单位 秒 7天过期 // 单位 秒 7天过期

View File

@@ -14,6 +14,7 @@ import io.netty.util.internal.StringUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@@ -41,6 +42,9 @@ public class MinioUtil {
@Autowired @Autowired
private MinioClient minioClient; private MinioClient minioClient;
@Value("${minio.endpoint}")
private String endpoint;
/** /**
* 获取MinIO客户端实例 * 获取MinIO客户端实例
*/ */
@@ -958,6 +962,166 @@ public class MinioUtil {
} }
} }
/**
* 检测字符串是否为预签名URL
* 通过检查URL中是否包含minio endpoint来判断
*
* @param str 待检测的字符串
* @return true表示是预签名URLfalse表示不是
*/
public boolean isPresignedUrl(String str) {
if (str == null || str.isEmpty()) {
return false;
}
try {
// 检查字符串是否是一个有效的URL
URL url = new URL(str);
String host = url.getHost();
// 获取endpoint中的主机部分去掉http://或https://
String endpointHost = endpoint;
if (endpointHost.startsWith("http://")) {
endpointHost = endpointHost.substring(7);
} else if (endpointHost.startsWith("https://")) {
endpointHost = endpointHost.substring(8);
}
// 去掉端口号
if (endpointHost.contains(":")) {
endpointHost = endpointHost.substring(0, endpointHost.indexOf(":"));
}
// 检查URL的host是否与endpoint的host匹配
return host.equals(endpointHost);
} catch (Exception e) {
// 不是有效的URL
return false;
}
}
/**
* 检测字符串是否为MinIO逻辑路径bucketName/objectName格式
* 逻辑路径特点:
* 1. 包含 "/"(桶名和对象名之间的分隔符)
* 2. 不是完整的URL不以http://或https://开头)
* 3. 路径中没有查询参数
*
* @param str 待检测的字符串
* @return true表示是MinIO逻辑路径false表示不是
*/
public boolean isMinioLogicalPath(String str) {
if (str == null || str.isEmpty()) {
return false;
}
// 必须是字符串
if (!(str instanceof String)) {
return false;
}
String trimStr = str.trim();
// 不应该以http://或https://开头
if (trimStr.startsWith("http://") || trimStr.startsWith("https://")) {
return false;
}
// 应该包含 "/"bucket/object格式
if (!trimStr.contains("/")) {
return false;
}
// 不应该包含空格或特殊字符
if (trimStr.contains(" ") || trimStr.contains("\n") || trimStr.contains("\t")) {
return false;
}
return true;
}
/**
* 将预签名URL转换为逻辑路径
*
* @param presignedUrl 预签名URL
* @return 逻辑路径格式bucketName/objectName
*/
public String getLogicalPathFromPresignedUrl(String presignedUrl) {
try {
// 解析URL
URL url = new URL(presignedUrl);
// 获取路径部分(去掉开头的/
String path = url.getPath();
if (path.startsWith("/")) {
path = path.substring(1);
}
// 路径格式为 bucketName/objectName
// Minio路径中可能包含多个/,需要正确分割
int firstSlashIndex = path.indexOf("/");
if (firstSlashIndex <= 0) {
throw new MinioException("预签名URL路径格式无效应包含桶名和对象名: " + presignedUrl);
}
String bucketName = path.substring(0, firstSlashIndex);
String objectName = path.substring(firstSlashIndex + 1);
// log.info("预签名URL转换成功桶名: {}, 对象名: {}", bucketName, objectName);
return bucketName + "/" + objectName;
} catch (Exception e) {
log.error("预签名URL解析失败: {}", e.getMessage(), e);
throw new BusinessException("system.error");
}
}
/**
* 处理MinIO资源预签名URL或逻辑路径统一生成预签名URL
*
* @param resource 预签名URL或逻辑路径
* @param expires 过期时间(秒)
* @return 新的预签名URL
*/
public String processMinioResource(String resource, int expires) {
try {
String logicalPath;
if (isPresignedUrl(resource)) {
// 是预签名URL解析为逻辑路径
logicalPath = getLogicalPathFromPresignedUrl(resource);
} else if (isMinioLogicalPath(resource)) {
// 本身就是逻辑路径
logicalPath = resource.trim();
} else {
// 不认识的内容,直接返回原始值
log.warn("未识别的MinIO资源格式: {}", resource);
return resource;
}
// 统一生成预签名URL
return getPreSignedUrl(logicalPath, expires);
} catch (Exception e) {
log.error("处理MinIO资源失败: {}, error: {}", resource, e.getMessage(), e);
// 如果失败,返回原始内容
return resource;
}
}
/**
* 将任意MinIO URL转换为逻辑路径
* 检测URL类型并转换为逻辑路径返回
*
* @param url 预签名URL或逻辑路径
* @return 逻辑路径格式bucketName/objectName
* @throws MinioException 如果不是有效的MinIO资源
*/
public String convertToLogicalPath(String url) {
if (url == null || url.isEmpty()) {
throw new BusinessException("url.cannot.be.empty");
}
if (isMinioLogicalPath(url)) {
// 本身就是逻辑路径,直接返回
return url.trim();
} else if (isPresignedUrl(url)) {
// 是预签名URL转换为逻辑路径
return getLogicalPathFromPresignedUrl(url);
} else {
// 不认识的内容,抛出异常
throw new BusinessException("无法识别的MinIO资源格式: " + url + "请提供有效的预签名URL或逻辑路径");
}
}
} }

View File

@@ -0,0 +1,31 @@
package com.ai.da.seller;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
/**
* 设计URLs DTO
*/
@Data
@Schema(description = "设计URLs数据传输对象")
public class DesignUrlsDTO {
/**
* 设计项ID
*/
@Schema(description = "设计项ID", example = "1")
private Long designItemId;
/**
* TO_PRODUCT_IMAGE类型的URL列表
*/
@Schema(description = "TO_PRODUCT_IMAGE类型的URL列表")
private List<String> toProductImageUrls;
/**
* DesignItemDetail的path列表
*/
@Schema(description = "DesignItemDetail的path列表")
private List<String> clothes;
}

View File

@@ -0,0 +1,44 @@
package com.ai.da.seller;
import com.ai.da.common.response.Response;
import com.ai.da.service.UserLikeGroupService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
/**
* Seller Controller
*/
@RestController
@RequestMapping("/api/seller")
@RequiredArgsConstructor
@Tag(name = "Seller", description = "Seller相关接口")
public class SellerController {
private final UserLikeGroupService userLikeGroupService;
/**
* 根据designItemId列表获取设计相关的URL列表
* @param designItemIds designItemId列表
* @return 设计URLs DTO列表
*/
@GetMapping("/sketchDetail")
@Operation(summary = "获取设计相关URL列表", description = "根据designItemId列表获取设计相关的URL列表包括TO_PRODUCT_IMAGE类型的URL和DesignItemDetail的path列表")
public Response<List<DesignUrlsDTO>> getDesignUrlsByDesignItemIds(
@Parameter(description = "设计项ID列表", required = true, example = "1,2,3")
@RequestParam List<Long> designItemIds) {
List<DesignUrlsDTO> designUrlsDTOList = new ArrayList<>();
for (Long designItemId : designItemIds) {
DesignUrlsDTO designUrlsDTO = userLikeGroupService.getToProductImageUrlsByDesignItemId(designItemId);
designUrlsDTOList.add(designUrlsDTO);
}
return Response.success(designUrlsDTOList);
}
}

View File

@@ -4,6 +4,7 @@ import com.ai.da.common.response.PageBaseResponse;
import com.ai.da.mapper.primary.entity.*; import com.ai.da.mapper.primary.entity.*;
import com.ai.da.model.dto.*; import com.ai.da.model.dto.*;
import com.ai.da.model.vo.*; import com.ai.da.model.vo.*;
import com.ai.da.seller.DesignUrlsDTO;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
@@ -121,4 +122,11 @@ public interface UserLikeGroupService extends IService<UserLikeGroup> {
Boolean toProductImageElementDelete(Long id); Boolean toProductImageElementDelete(Long id);
ToProductElementVO convertRelightElement(Long id); ToProductElementVO convertRelightElement(Long id);
/**
* 根据designItemId获取TO_PRODUCT_IMAGE类型的URL列表和DesignItemDetail的path列表
* @param designItemId designItemId
* @return 包含TO_PRODUCT_IMAGE类型的URL列表和DesignItemDetail的path列表的对象
*/
DesignUrlsDTO getToProductImageUrlsByDesignItemId(Long designItemId);
} }

View File

@@ -23,6 +23,7 @@ import com.ai.da.model.enums.Module;
import com.ai.da.model.vo.*; import com.ai.da.model.vo.*;
import com.ai.da.python.PythonService; import com.ai.da.python.PythonService;
import com.ai.da.service.*; import com.ai.da.service.*;
import com.ai.da.seller.DesignUrlsDTO;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
@@ -31,6 +32,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import java.util.Comparator; import java.util.Comparator;
import java.util.ArrayList;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -61,6 +63,8 @@ import java.time.ZoneId;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static com.ai.da.common.enums.LayersPriorityEnum.BODY;
/** /**
* 服务实现类 * 服务实现类
* *
@@ -71,6 +75,83 @@ import java.util.stream.Collectors;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, UserLikeGroup> implements UserLikeGroupService { public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, UserLikeGroup> implements UserLikeGroupService {
/**
* 根据CollectionSort ID获取TO_PRODUCT_IMAGE类型的URL列表
* @param collectionSortId CollectionSort ID
* @return TO_PRODUCT_IMAGE类型的URL列表
*/
private List<String> getToProductImageUrlsByCollectionSortId(Long collectionSortId) {
List<String> toProductImageUrls = new ArrayList<>();
// 查询子记录中的TO_PRODUCT_IMAGE类型
QueryWrapper<CollectionSort> childCollectionQw = new QueryWrapper<>();
childCollectionQw.lambda().eq(CollectionSort::getParentId, collectionSortId);
childCollectionQw.lambda().eq(CollectionSort::getRelationType, CollectionType.TO_PRODUCT_IMAGE.getValue());
childCollectionQw.lambda().orderByAsc(CollectionSort::getSort);
List<CollectionSort> childSortList = collectionSortMapper.selectList(childCollectionQw);
for (CollectionSort userLikeSort : childSortList) {
ToProductImageResult toProductImageResult = toProductImageResultMapper.selectById(userLikeSort.getRelationId());
if (toProductImageResult != null && !isGenerateTaskFailed(toProductImageResult.getStatus(), toProductImageResult.getCreateTime())) {
String url = toProductImageResult.getUrl();
toProductImageUrls.add(minioUtil.processMinioResource(url, CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
}
}
return toProductImageUrls;
}
/**
* 根据designItemId获取TO_PRODUCT_IMAGE类型的URL列表和DesignItemDetail的path列表
* @param designItemId designItemId
* @return 包含TO_PRODUCT_IMAGE类型的URL列表和DesignItemDetail的path列表的对象
*/
public DesignUrlsDTO getToProductImageUrlsByDesignItemId(Long designItemId) {
DesignUrlsDTO designUrlsDTO = new DesignUrlsDTO();
designUrlsDTO.setDesignItemId(designItemId);
List<String> toProductImageUrls = new ArrayList<>();
List<String> designItemDetailPaths = new ArrayList<>();
// 根据designItemId查询UserLike
QueryWrapper<UserLike> userLikeQueryWrapper = new QueryWrapper<>();
userLikeQueryWrapper.lambda().eq(UserLike::getDesignItemId, designItemId);
UserLike userLike = userLikeMapper.selectOne(userLikeQueryWrapper);
if (userLike != null) {
// 根据UserLike的ID查询CollectionSort
QueryWrapper<CollectionSort> collectionSortQueryWrapper = new QueryWrapper<>();
collectionSortQueryWrapper.lambda().eq(CollectionSort::getRelationId, userLike.getId());
collectionSortQueryWrapper.lambda().eq(CollectionSort::getRelationType, CollectionType.DESIGN.getValue());
CollectionSort collectionSort = collectionSortMapper.selectOne(collectionSortQueryWrapper);
if (collectionSort != null) {
// 获取TO_PRODUCT_IMAGE类型的URL列表
toProductImageUrls = getToProductImageUrlsByCollectionSortId(collectionSort.getId());
}
}
// 查询DesignItemDetail表排除type为"Body"的数据
QueryWrapper<DesignItemDetail> designItemDetailQueryWrapper = new QueryWrapper<>();
designItemDetailQueryWrapper.lambda().eq(DesignItemDetail::getDesignItemId, designItemId);
designItemDetailQueryWrapper.lambda().ne(DesignItemDetail::getType, BODY.getType());
List<DesignItemDetail> designItemDetails = designItemDetailMapper.selectList(designItemDetailQueryWrapper);
for (DesignItemDetail designItemDetail : designItemDetails) {
// 判断当前用户是否是DesignItemDetail的创建者
if (!Objects.equals(designItemDetail.getAccountId(), UserContext.getUserHolder().getId())){
throw new BusinessException("unknown.authentication.operation.type");
}
if (designItemDetail.getPath() != null) {
designItemDetailPaths.add(minioUtil.processMinioResource(designItemDetail.getPath(), CommonConstant.MINIO_IMAGE_EXPIRE_TIME));
}
}
designUrlsDTO.setToProductImageUrls(toProductImageUrls);
designUrlsDTO.setClothes(designItemDetailPaths);
return designUrlsDTO;
}
private final UserLikeGroupMapper userLikeGroupMapper; private final UserLikeGroupMapper userLikeGroupMapper;
private final AccountMapper accountMapper; private final AccountMapper accountMapper;
private final CollectionService collectionService; private final CollectionService collectionService;
@@ -87,6 +168,7 @@ public class UserLikeGroupServiceImpl extends ServiceImpl<UserLikeGroupMapper, U
private final PortfolioMapper portfolioMapper; private final PortfolioMapper portfolioMapper;
private final TagsMapper tagsMapper; private final TagsMapper tagsMapper;
private final DesignItemDetailService designItemDetailService; private final DesignItemDetailService designItemDetailService;
private final DesignItemDetailMapper designItemDetailMapper;
private final CollectionElementMapper collectionElementMapper; private final CollectionElementMapper collectionElementMapper;
private final AttributeRetrievalMapper attributeRetrievalMapper; private final AttributeRetrievalMapper attributeRetrievalMapper;
private final ProductImageAttributeMapper productImageAttributeMapper; private final ProductImageAttributeMapper productImageAttributeMapper;

View File

@@ -5,7 +5,7 @@
# ============================================================ # ============================================================
server: server:
port: 5567 port: 5566
spring: spring:
application: application:

View File

@@ -111,6 +111,7 @@ waistbandRight.cannot.be.empty=waistbandRight cannot be empty.
handLeft.cannot.be.empty=handLeft cannot be empty. handLeft.cannot.be.empty=handLeft cannot be empty.
handRight.cannot.be.empty=handRight cannot be empty. handRight.cannot.be.empty=handRight cannot be empty.
id.cannot.be.empty=id cannot be empty. id.cannot.be.empty=id cannot be empty.
url.cannot.be.empty=url cannot be empty.
type.cannot.be.empty=type cannot be empty. type.cannot.be.empty=type cannot be empty.
color.cannot.be.empty=color cannot be empty. color.cannot.be.empty=color cannot be empty.
generateDetailId.cannot.be.empty=generateDetailId cannot be empty. generateDetailId.cannot.be.empty=generateDetailId cannot be empty.

View File

@@ -110,6 +110,7 @@ waistbandRight.cannot.be.empty=waistbandRight不能为空。
handLeft.cannot.be.empty=handLeft不能为空。 handLeft.cannot.be.empty=handLeft不能为空。
handRight.cannot.be.empty=handRight不能为空。 handRight.cannot.be.empty=handRight不能为空。
id.cannot.be.empty=id不能为空。 id.cannot.be.empty=id不能为空。
url.cannot.be.empty=url不能为空。
type.cannot.be.empty=type不能为空。 type.cannot.be.empty=type不能为空。
color.cannot.be.empty=color不能为空。 color.cannot.be.empty=color不能为空。
generateDetailId.cannot.be.empty=generateDetailId不能为空。 generateDetailId.cannot.be.empty=generateDetailId不能为空。