second commit

This commit is contained in:
litianxiang
2025-10-22 14:56:53 +08:00
parent 16980a566d
commit 56096e8d23
25 changed files with 988 additions and 203 deletions

View File

@@ -0,0 +1,15 @@
package com.aida.lanecarford.common;
public class CommonConstant {
// 单位 秒 10分钟过期
// public static final Long TASK_EXPIRE_TIME = 24 * 60 * 60L;
public static final Long TASK_EXPIRE_TIME = 10 * 60L;
// 单位 秒 两天过期
public static final Long CREDITS_EXPIRE_TIME = 2 * 24 * 60 * 60L;
// 单位 分钟
public static final Integer MINIO_IMAGE_EXPIRE_TIME = 24 * 60;
// 单位 秒 一天过期 in redis
public static final Long GENERATE_RESULT_EXPIRE_TIME = 24 * 60 * 60L;
// 单位 秒 7天过期
public static final Long REDIS_SET_EXPIRE_TIME = 24 * 60 * 60 * 7L;
}

View File

@@ -1,7 +1,12 @@
package com.aida.lanecarford.controller;
import com.aida.lanecarford.common.ApiResponse;
import com.aida.lanecarford.dto.CustomerPhotoDto;
import com.aida.lanecarford.entity.CustomerPhoto;
import com.aida.lanecarford.service.CustomerPhotoService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -18,4 +23,10 @@ public class CustomerPhotoController {
private final CustomerPhotoService customerPhotoService;
@PostMapping("/upload")
public ApiResponse<CustomerPhoto> upload(@RequestBody CustomerPhotoDto customerPhotoDto) {
CustomerPhoto customerPhoto = customerPhotoService.upload(customerPhotoDto);
return ApiResponse.success(customerPhoto);
}
}

View File

@@ -3,15 +3,15 @@ package com.aida.lanecarford.controller;
import com.aida.lanecarford.common.ApiResponse;
import com.aida.lanecarford.entity.TryOnEffect;
import com.aida.lanecarford.service.TryOnEffectService;
import com.aida.lanecarford.vo.TryOnResultVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 试穿效果控制器
@@ -36,4 +36,45 @@ public class TryOnEffectController {
return ApiResponse.success(taskId);
}
@Operation(summary = "获取收藏的试穿效果", description = "获取指定进店记录的收藏的试穿效果")
@GetMapping("/favorites/{visitRecordId}")
public ApiResponse<List<TryOnResultVo>> getFavoriteTryOnEffects(
@Parameter(description = "进店记录ID", required = true)
@PathVariable Long visitRecordId) {
List<TryOnResultVo> tryOnResultVos = tryOnEffectService.getFavoriteTryOnEffects(visitRecordId);
return ApiResponse.success(tryOnResultVos);
}
@GetMapping("/style/{styleId}")
@Operation(summary = "获取某套服装的所有生成结果", description = "获取某套服装的所有生成结果")
public ApiResponse<List<TryOnResultVo>> getTryOnEffectsByStyleId(
@Parameter(description = "服装ID", required = true)
@PathVariable Long styleId) {
List<TryOnResultVo> tryOnResultVos = tryOnEffectService.getTryOnEffectsByStyleId(styleId);
return ApiResponse.success(tryOnResultVos);
}
/**
* 设置喜欢的试穿效果
*/
@Operation(summary = "设置喜欢的试穿效果", description = "将指定试穿效果设置为收藏")
@PostMapping("/set-favorite/{tryOnId}")
public ApiResponse<Void> setFavoriteTryOnEffect(
@Parameter(description = "试穿效果ID", required = true)
@PathVariable Long tryOnId) {
tryOnEffectService.setFavoriteTryOnEffect(tryOnId);
return ApiResponse.success();
}
/**
* 取消喜欢的试穿效果
*/
@Operation(summary = "取消喜欢的试穿效果", description = "取消指定试穿效果的收藏")
@PostMapping("/cancel-favorite/{tryOnId}")
public ApiResponse<Void> cancelFavoriteTryOnEffect(
@Parameter(description = "试穿效果ID", required = true)
@PathVariable Long tryOnId) {
tryOnEffectService.cancelFavoriteTryOnEffect(tryOnId);
return ApiResponse.success();
}
}

View File

@@ -1,9 +1,15 @@
package com.aida.lanecarford.controller;
import com.aida.lanecarford.common.ApiResponse;
import com.aida.lanecarford.entity.VisitRecord;
import com.aida.lanecarford.service.VisitRecordService;
import com.aida.lanecarford.vo.LibraryVo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 进店记录控制器
@@ -11,6 +17,7 @@ import org.springframework.web.bind.annotation.RestController;
* @author AI Assistant
* @since 2024-01-01
*/
@Slf4j
@RestController
@RequestMapping("/api/visit-records")
@RequiredArgsConstructor
@@ -18,4 +25,40 @@ public class VisitRecordController {
private final VisitRecordService visitRecordService;
/**
* 根据ID删除进店记录逻辑删除
*
* @param id 进店记录ID
* @return 删除结果
*/
@DeleteMapping("/{id}")
public ApiResponse<Void> deleteById(@PathVariable Long id) {
log.info("开始删除进店记录ID: {}", id);
Boolean result = visitRecordService.delete(id);
if (result) {
log.info("进店记录删除成功ID: {}", id);
return ApiResponse.success("Visit record deleted successfully");
} else {
log.warn("进店记录删除失败ID: {}", id);
return ApiResponse.error("Failed to delete visit record");
}
}
//根据顾客ID查询所有进店记录
@GetMapping("/customer/{customerId}")
public ApiResponse<List<LibraryVo>> getByCustomerId(@PathVariable Long customerId) {
log.info("开始查询顾客ID为{}的进店记录", customerId);
List<LibraryVo> result = visitRecordService.getByCustomerId(customerId);
if (result != null && !result.isEmpty()) {
log.info("查询成功顾客ID为{}的进店记录为:{}", customerId, result);
return ApiResponse.success(result);
} else {
log.warn("没有找到顾客ID为{}的进店记录", customerId);
return ApiResponse.error("No visit records found for customer ID: " + customerId);
}
}
}

View File

@@ -0,0 +1,12 @@
package com.aida.lanecarford.dto;
import com.aida.lanecarford.entity.CustomerPhoto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
@Data
public class CustomerPhotoDto extends CustomerPhoto {
@NotNull(message = "file.cannot.be.empty")
private MultipartFile file;
}

View File

@@ -19,15 +19,9 @@ import java.time.LocalDateTime;
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@EqualsAndHashCode(callSuper = true)
@TableName("customers")
public class Customer {
/**
* 顾客ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
public class Customer extends BaseEntity {
/**
* 顾客姓名
@@ -58,16 +52,4 @@ public class Customer {
*/
@TableField("age_range")
private String ageRange;
/**
* 创建时间
*/
@TableField(value = "created_time", fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 更新时间
*/
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
}

View File

@@ -19,15 +19,9 @@ import java.time.LocalDateTime;
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@EqualsAndHashCode(callSuper = true)
@TableName("customer_photos")
public class CustomerPhoto {
/**
* 顾客照片ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
public class CustomerPhoto extends BaseEntity {
/**
* 顾客ID
@@ -53,21 +47,5 @@ public class CustomerPhoto {
@TableField("is_primary")
private Integer isPrimary;
/**
* 上传时间
*/
@TableField("upload_time")
private LocalDateTime uploadTime;
/**
* 创建时间
*/
@TableField(value = "created_time", fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 更新时间
*/
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
// 注意uploadTime、createdTime、updatedTime 字段已在 BaseEntity 中定义
}

View File

@@ -19,15 +19,9 @@ import java.time.LocalDateTime;
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@EqualsAndHashCode(callSuper = true)
@TableName("model_photos")
public class ModelPhoto {
/**
* 模特照片ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
public class ModelPhoto extends BaseEntity {
/**
* 模特照片URL
@@ -59,15 +53,5 @@ public class ModelPhoto {
@TableField("sort_order")
private Integer sortOrder;
/**
* 创建时间
*/
@TableField(value = "created_time", fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 更新时间
*/
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
// 注意createdTime、updatedTime 字段已在 BaseEntity 中定义
}

View File

@@ -19,15 +19,9 @@ import java.time.LocalDateTime;
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@EqualsAndHashCode(callSuper = true)
@TableName("sales")
public class Sales {
/**
* 导购ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
public class Sales extends BaseEntity {
/**
* 用户名
@@ -83,15 +77,5 @@ public class Sales {
@TableField("is_active")
private Integer isActive;
/**
* 创建时间
*/
@TableField(value = "created_time", fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 更新时间
*/
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
// 注意createdTime、updatedTime 字段已在 BaseEntity 中定义
}

View File

@@ -19,15 +19,9 @@ import java.time.LocalDateTime;
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@EqualsAndHashCode(callSuper = true)
@TableName("styles")
public class Style {
/**
* 风格配置ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
public class Style extends BaseEntity {
/**
* 顾客ID
@@ -71,15 +65,5 @@ public class Style {
@TableField("error_message")
private String errorMessage;
/**
* 创建时间
*/
@TableField(value = "created_time", fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 更新时间
*/
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
// 注意createdTime、updatedTime 字段已在 BaseEntity 中定义
}

View File

@@ -19,15 +19,9 @@ import java.time.LocalDateTime;
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@EqualsAndHashCode(callSuper = true)
@TableName("try_on_effects")
public class TryOnEffect {
/**
* 试穿效果ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
public class TryOnEffect extends BaseEntity {
/**
* 顾客ID
@@ -60,7 +54,7 @@ public class TryOnEffect {
private Long modelPhotoId;
/**
* 提示词
* 提示词,当is_regenerated为1时才会有值
*/
@TableField("prompt")
private String prompt;
@@ -107,15 +101,5 @@ public class TryOnEffect {
@TableField("is_favorite")
private Integer isFavorite;
/**
* 创建时间
*/
@TableField(value = "created_time", fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 更新时间
*/
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
// 注意createdTime、updatedTime 字段已在 BaseEntity 中定义
}

View File

@@ -20,15 +20,9 @@ import java.time.LocalDateTime;
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
@EqualsAndHashCode(callSuper = true)
@TableName("visit_records")
public class VisitRecord {
/**
* 进店记录ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
public class VisitRecord extends BaseEntity {
/**
* 顾客ID
@@ -54,17 +48,6 @@ public class VisitRecord {
@TableField("visit_time")
private LocalDateTime visitTime;
/**
* 会话ID
*/
@TableField("session_id")
private String sessionId;
/**
* 状态(0-已结束,1-进行中)
*/
@TableField("status")
private Integer status;
/**
* 备注
@@ -72,15 +55,5 @@ public class VisitRecord {
@TableField("notes")
private String notes;
/**
* 创建时间
*/
@TableField(value = "created_time", fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 更新时间
*/
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
// 注意createdTime、updatedTime 字段已在 BaseEntity 中定义
}

View File

@@ -1,5 +1,6 @@
package com.aida.lanecarford.service;
import com.aida.lanecarford.dto.CustomerPhotoDto;
import com.aida.lanecarford.entity.CustomerPhoto;
import com.baomidou.mybatisplus.extension.service.IService;
@@ -11,4 +12,5 @@ import com.baomidou.mybatisplus.extension.service.IService;
*/
public interface CustomerPhotoService extends IService<CustomerPhoto> {
CustomerPhoto upload(CustomerPhotoDto customerPhotoDto);
}

View File

@@ -1,9 +1,12 @@
package com.aida.lanecarford.service;
import com.aida.lanecarford.entity.TryOnEffect;
import com.aida.lanecarford.vo.TryOnResultVo;
import com.baomidou.mybatisplus.extension.service.IService;
import jakarta.validation.Valid;
import java.util.List;
/**
* 试穿效果服务接口
*
@@ -13,4 +16,20 @@ import jakarta.validation.Valid;
public interface TryOnEffectService extends IService<TryOnEffect> {
String generateTryOnEffect(@Valid TryOnEffect tryOnEffectDto);
List<TryOnResultVo> getFavoriteTryOnEffects(Long visitRecordId);
/**
* 设置试穿效果为收藏
* @param tryOnId 试穿效果ID
*/
void setFavoriteTryOnEffect(Long tryOnId);
/**
* 取消试穿效果的收藏
* @param tryOnId 试穿效果ID
*/
void cancelFavoriteTryOnEffect(Long tryOnId);
List<TryOnResultVo> getTryOnEffectsByStyleId(Long styleId);
}

View File

@@ -1,8 +1,11 @@
package com.aida.lanecarford.service;
import com.aida.lanecarford.entity.VisitRecord;
import com.aida.lanecarford.vo.LibraryVo;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* 进店记录服务接口
*
@@ -11,4 +14,7 @@ import com.baomidou.mybatisplus.extension.service.IService;
*/
public interface VisitRecordService extends IService<VisitRecord> {
Boolean delete(Long id);
List<LibraryVo> getByCustomerId(Long customerId);
}

View File

@@ -1,8 +1,11 @@
package com.aida.lanecarford.service.impl;
import com.aida.lanecarford.dto.CustomerPhotoDto;
import com.aida.lanecarford.entity.CustomerPhoto;
import com.aida.lanecarford.mapper.CustomerPhotoMapper;
import com.aida.lanecarford.service.CustomerPhotoService;
import com.aida.lanecarford.util.CopyUtil;
import com.aida.lanecarford.util.MinioUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -16,5 +19,18 @@ import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CustomerPhotoServiceImpl extends ServiceImpl<CustomerPhotoMapper, CustomerPhoto> implements CustomerPhotoService {
private final MinioUtil minioUtil;
@Override
public CustomerPhoto upload(CustomerPhotoDto customerPhotoDto) {
//TODO:设置桶名
String url = minioUtil.uploadFile(customerPhotoDto.getFile());
CustomerPhoto customerPhoto = CopyUtil.copyObject(customerPhotoDto, CustomerPhoto.class);
customerPhoto.setPhotoUrl(url);
this.save(customerPhoto);
return customerPhoto;
}
}

View File

@@ -1,22 +1,39 @@
package com.aida.lanecarford.service.impl;
import cn.hutool.json.JSONObject;
import com.aida.lanecarford.common.CommonConstant;
import com.aida.lanecarford.common.response.ResultEnum;
import com.aida.lanecarford.entity.CustomerPhoto;
import com.aida.lanecarford.entity.ModelPhoto;
import com.aida.lanecarford.entity.Style;
import com.aida.lanecarford.entity.TryOnEffect;
import com.aida.lanecarford.exception.BusinessException;
import com.aida.lanecarford.mapper.TryOnEffectMapper;
import com.aida.lanecarford.service.CustomerPhotoService;
import com.aida.lanecarford.service.ImageCompositionService;
import com.aida.lanecarford.service.ModelPhotoService;
import com.aida.lanecarford.service.StyleService;
import com.aida.lanecarford.service.TryOnEffectService;
import com.aida.lanecarford.util.MinioUtil;
import com.aida.lanecarford.vo.TryOnResultVo;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.auth.oauth2.GoogleCredentials;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 试穿效果服务实现类
@@ -31,34 +48,43 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
private final StyleService styleService;
private final ModelPhotoService modelPhotoService;
private final CustomerPhotoService customerPhotoService;
private final TryOnEffectService tryOnEffectService;
private final ImageCompositionService imageCompositionService;
private final MinioUtil minioUtil;
@Override
public String generateTryOnEffect(TryOnEffect tryOnEffectDto) {
Integer isRegenerated = tryOnEffectDto.getIsRegenerated();
String toAIUrl = null;
String prompt = null;
// 收集图片URL
List<String> imageUrls = new ArrayList<>();
if (isRegenerated == 1) {
String prompt = tryOnEffectDto.getPrompt();
prompt = tryOnEffectDto.getPrompt();
Long originalTryOnId = tryOnEffectDto.getOriginalTryOnId();
// 验证originalTryOnId不能为空
if (originalTryOnId == null) {
throw new RuntimeException("originalTryOnId cannot be null");
}
TryOnEffect originalTryOn = tryOnEffectService.getById(originalTryOnId);
TryOnEffect originalTryOn = this.getById(originalTryOnId);
String resultImageUrl = originalTryOn.getResultImageUrl();
imageUrls.add(resultImageUrl);
Long customerPhotoId = tryOnEffectDto.getCustomerPhotoId();
if (customerPhotoId != null) {
//根据id查到对应customerurl
CustomerPhoto customerPhoto = customerPhotoService.getById(customerPhotoId);
String customerPhotoUrl = customerPhoto.getPhotoUrl();
if (customerPhotoUrl != null && !customerPhotoUrl.trim().isEmpty()) {
imageUrls.add(customerPhotoUrl);
}
}
} else {
Long styleId = tryOnEffectDto.getStyleId();
// 验证styleId不能为空
if (styleId == null) {
throw new RuntimeException("styleId cannot be null");
}
//根据id查到对应styleurl
Style style = styleService.getById(styleId);
String styleImageUrl = style.getStyleImageUrl();
@@ -75,17 +101,7 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
imageUrls.add(modelPhotoUrl);
}
}
Long customerPhotoId = tryOnEffectDto.getCustomerPhotoId();
if (customerPhotoId != null) {
//根据id查到对应customerurl
CustomerPhoto customerPhoto = customerPhotoService.getById(customerPhotoId);
String customerPhotoUrl = customerPhoto.getPhotoUrl();
if (customerPhotoUrl != null && !customerPhotoUrl.trim().isEmpty()) {
imageUrls.add(customerPhotoUrl);
}
}
// 合成图片
if (!imageUrls.isEmpty()) {
log.info("开始合成图片,图片数量: {}", imageUrls.size());
@@ -104,15 +120,372 @@ public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOn
//调用模型生成试穿效果
log.info("准备调用第三方AI服务输入图片URL: {}", toAIUrl);
String AIRreultUrl = AITryOnEffect(tryOnEffectDto.getPrompt(), toAIUrl);
String AIRreultUrl = AITryOnEffect(prompt, toAIUrl);
tryOnEffectDto.setResultImageUrl(AIRreultUrl);
this.saveOrUpdate(tryOnEffectDto);
return "taskId";
}
public String AITryOnEffect(String prompt,String url) {
@Override
public List<TryOnResultVo> getFavoriteTryOnEffects(Long visitRecordId) {
List<TryOnEffect> tryOnEffects = this.list(new LambdaQueryWrapper<TryOnEffect>()
.eq(TryOnEffect::getVisitRecordId, visitRecordId)
.eq(TryOnEffect::getIsFavorite, 1));
List<TryOnResultVo> tryOnResultVos = new ArrayList<>();
for (TryOnEffect tryOnEffect : tryOnEffects) {
TryOnResultVo tryOnResultVo = new TryOnResultVo();
tryOnResultVo.setTryOnId(tryOnEffect.getId());
tryOnResultVo.setTryOnUrl(minioUtil.getPresignedUrl(
tryOnEffect.getResultImageUrl(),
CommonConstant.MINIO_IMAGE_EXPIRE_TIME
));
// 如果是原始效果则获取对应的style图片
if (tryOnEffect.getIsRegenerated()==0){
LambdaQueryWrapper<Style> styleLambdaQueryWrapper = new LambdaQueryWrapper<>();
styleLambdaQueryWrapper.eq(Style::getId, tryOnEffect.getStyleId()).select(Style::getStyleImageUrl);
Style style = styleService.getOne(styleLambdaQueryWrapper);
tryOnResultVo.setStyleUrl(minioUtil.getPresignedUrl(
style.getStyleImageUrl(),
CommonConstant.MINIO_IMAGE_EXPIRE_TIME
));
}
return "url";
//调用模型生成试穿效果
tryOnResultVo.setIsRegenerated(tryOnEffect.getIsRegenerated());
tryOnResultVo.setIsFavorite(tryOnEffect.getIsFavorite());
tryOnResultVos.add(tryOnResultVo);
}
return tryOnResultVos;
}
@Override
public List<TryOnResultVo> getTryOnEffectsByStyleId(Long styleId) {
List<TryOnEffect> tryOnEffects = this.list(new LambdaQueryWrapper<TryOnEffect>()
.eq(TryOnEffect::getStyleId, styleId));
List<TryOnResultVo> tryOnResultVos = new ArrayList<>();
for (TryOnEffect tryOnEffect : tryOnEffects) {
TryOnResultVo tryOnResultVo = new TryOnResultVo();
tryOnResultVo.setTryOnId(tryOnEffect.getId());
tryOnResultVo.setTryOnUrl(minioUtil.getPresignedUrl(
tryOnEffect.getResultImageUrl(),
CommonConstant.MINIO_IMAGE_EXPIRE_TIME
));
// 如果是原始效果则获取对应的style图片
if (tryOnEffect.getIsRegenerated()==0){
LambdaQueryWrapper<Style> styleLambdaQueryWrapper = new LambdaQueryWrapper<>();
styleLambdaQueryWrapper.eq(Style::getId, tryOnEffect.getStyleId()).select(Style::getStyleImageUrl);
Style style = styleService.getOne(styleLambdaQueryWrapper);
tryOnResultVo.setStyleUrl(minioUtil.getPresignedUrl(
style.getStyleImageUrl(),
CommonConstant.MINIO_IMAGE_EXPIRE_TIME
));
}
tryOnResultVo.setIsRegenerated(tryOnEffect.getIsRegenerated());
tryOnResultVo.setIsFavorite(tryOnEffect.getIsFavorite());
tryOnResultVos.add(tryOnResultVo);
}
return tryOnResultVos;
}
@Override
public void setFavoriteTryOnEffect(Long tryOnId) {
if (tryOnId == null) {
throw new BusinessException("TryOn ID is required", "试穿效果ID不能为空", ResultEnum.PARAMETER_ERROR.getCode());
}
TryOnEffect tryOnEffect = this.getById(tryOnId);
if (tryOnEffect == null) {
throw new BusinessException("TryOn effect not found", "试穿效果不存在", ResultEnum.FAIL.getCode());
}
// 设置为收藏
tryOnEffect.setIsFavorite(1);
this.updateById(tryOnEffect);
log.info("试穿效果ID: {} 已设置为收藏", tryOnId);
}
@Override
public void cancelFavoriteTryOnEffect(Long tryOnId) {
if (tryOnId == null) {
throw new BusinessException("TryOn ID is required", "试穿效果ID不能为空", ResultEnum.PARAMETER_ERROR.getCode());
}
TryOnEffect tryOnEffect = this.getById(tryOnId);
if (tryOnEffect == null) {
throw new BusinessException("TryOn effect not found", "试穿效果不存在", ResultEnum.FAIL.getCode());
}
// 取消收藏
tryOnEffect.setIsFavorite(0);
this.updateById(tryOnEffect);
log.info("试穿效果ID: {} 已取消收藏", tryOnId);
}
public String AITryOnEffect(String prompt, String url) {
log.info("开始执行AITryOnEffect - prompt: {}, url: {}", prompt, url);
// 参数验证
if (prompt == null || prompt.trim().isEmpty()) {
log.error("参数验证失败 - prompt不能为空");
throw new BusinessException("Prompt is required", "prompt不能为空", ResultEnum.PARAMETER_ERROR.getCode());
}
if (url == null || url.trim().isEmpty()) {
log.error("参数验证失败 - url不能为空");
throw new BusinessException("URL is required", "url不能为空", ResultEnum.PARAMETER_ERROR.getCode());
}
// 获取图片的base64编码
String base64Image = getImageAsBase64(url);
// 调用谷歌API进行图生图
String resultImageUrl = callGoogleImageGenerationAPI(prompt, base64Image);
log.info("AITryOnEffect执行成功结果URL: {}", resultImageUrl);
return resultImageUrl;
}
/**
* 获取图片的base64编码
*/
private String getImageAsBase64(String imageUrl) {
try {
// 从MinIO下载图片并转换为Base64
InputStream inputStream = minioUtil.downloadFile(imageUrl);
byte[] imageBytes = inputStream.readAllBytes();
inputStream.close();
// 转换为Base64编码
String base64 = java.util.Base64.getEncoder().encodeToString(imageBytes);
return base64;
} catch (Exception e) {
log.error("获取图片base64编码失败: {}", e.getMessage(), e);
throw new BusinessException("Image processing failed", "图片处理失败", ResultEnum.ERROR.getCode());
}
}
/**
* 调用谷歌API进行图像生成
*/
private String callGoogleImageGenerationAPI(String prompt, String base64Image) {
try {
System.setProperty("https.proxyHost", "127.0.0.1");
System.setProperty("https.proxyPort", "10809");
// 谷歌API配置
String projectId = "aida-461108";
String location = "global";
String model = "gemini-2.0-flash-exp"; // 使用适合的模型
String endpoint = String.format(
"https://aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:generateContent",
projectId, location, model
);
// 构建请求体
JSONObject requestBody = buildRequestBody(prompt, base64Image);
// 获取访问令牌
String accessToken = getGoogleAccessToken();
// 发送HTTP请求
String response = sendHttpRequest(endpoint, requestBody.toString(), accessToken);
// 解析响应并提取图片
return processGoogleAPIResponse(response);
} catch (Exception e) {
log.error("调用Google API失败: {}", e.getMessage(), e);
throw new BusinessException("Google API call failed", "Google API调用失败", ResultEnum.ERROR.getCode());
}
}
/**
* 构建谷歌API请求体
*/
private JSONObject buildRequestBody(String prompt, String base64Image) {
JSONObject requestBody = new JSONObject();
// 创建图片部分
JSONObject imagePart = new JSONObject();
JSONObject inlineData = new JSONObject();
inlineData.set("mimeType", "image/png");
inlineData.set("data", base64Image.replace("data:image/png;base64,", ""));
imagePart.set("inlineData", inlineData);
// 创建文本部分
JSONObject textPart = new JSONObject();
textPart.set("text", prompt);
// 创建内容对象
JSONObject content = new JSONObject();
content.set("role", "user");
content.set("parts", Arrays.asList(imagePart, textPart));
// 设置contents数组
requestBody.set("contents", Arrays.asList(content));
// 设置生成配置
JSONObject generationConfig = new JSONObject();
generationConfig.set("maxOutputTokens", 8192);
generationConfig.set("responseModalities", Arrays.asList("IMAGE"));
JSONObject imageConfig = new JSONObject();
imageConfig.set("aspectRatio", "9:16");
generationConfig.set("imageConfig", imageConfig);
requestBody.set("generationConfig", generationConfig);
return requestBody;
}
/**
* 获取谷歌访问令牌
*/
private String getGoogleAccessToken() throws IOException {
try (InputStream inputStream = TryOnEffectServiceImpl.class.getClassLoader()
.getResourceAsStream("aida-461108-b4afaabebb84.json")) {
if (inputStream == null) {
throw new IOException("Google credentials file not found");
}
GoogleCredentials credentials = GoogleCredentials
.fromStream(inputStream)
.createScoped(Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"));
credentials.refreshIfExpired();
return credentials.getAccessToken().getTokenValue();
}
}
/**
* 发送HTTP请求
*/
private String sendHttpRequest(String endpoint, String jsonBody, String accessToken) throws IOException {
ConnectionPool connectionPool = new ConnectionPool(10, 5, TimeUnit.MINUTES);
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(45, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.callTimeout(180, TimeUnit.SECONDS)
.connectionPool(connectionPool)
.retryOnConnectionFailure(true)
.build();
Request request = new Request.Builder()
.url(endpoint)
.addHeader("Authorization", "Bearer " + accessToken)
.addHeader("Content-Type", "application/json")
.addHeader("User-Agent", "LaneCarford-Client/1.0")
.addHeader("Accept", "application/json")
.post(RequestBody.create(MediaType.parse("application/json"), jsonBody))
.build();
// 实现重试逻辑
int maxRetries = 3;
int retryDelay = 2000; // 2秒
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
log.info("发起HTTP请求 - 尝试次数: {}/{}, URL: {}", attempt, maxRetries, request.url());
try (Response response = client.newCall(request).execute()) {
if (!response.isSuccessful()) {
log.warn("Google API响应失败状态码: {}", response.code());
if (attempt < maxRetries) {
Thread.sleep(retryDelay * attempt);
continue;
} else {
throw new IOException("HTTP error code: " + response.code());
}
}
String result = response.body().string();
log.info("Google API调用成功");
return result;
}
} catch (IOException e) {
log.warn("网络连接问题 - 尝试: {}/{}, 错误: {}", attempt, maxRetries, e.getMessage());
if (attempt < maxRetries) {
try {
Thread.sleep(retryDelay * attempt);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("重试被中断", ie);
}
} else {
throw e;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("请求被中断", e);
}
}
throw new IOException("所有重试都失败了");
}
/**
* 处理谷歌API响应
*/
private String processGoogleAPIResponse(String response) {
try {
com.alibaba.fastjson.JSONObject jsonResponse = JSON.parseObject(response);
JSONArray candidates = jsonResponse.getJSONArray("candidates");
if (candidates == null || candidates.isEmpty()) {
log.error("Google API响应中没有候选结果");
throw new BusinessException("Google API response invalid", "Google API响应无效", ResultEnum.ERROR.getCode());
}
com.alibaba.fastjson.JSONObject candidate = candidates.getJSONObject(0);
String finishReason = candidate.getString("finishReason");
if (!"STOP".equals(finishReason)) {
String finishMessage = candidate.getString("finishMessage");
if ("IMAGE_SAFETY".equals(finishReason)) {
log.error("图片安全检查失败,请尝试修改提示词或图片");
throw new BusinessException("Image safety check failed", "图片安全检查失败", ResultEnum.ERROR.getCode());
}
log.error("生成失败: {}", finishMessage);
throw new BusinessException("Image generation failed", "图片生成失败", ResultEnum.ERROR.getCode());
}
com.alibaba.fastjson.JSONObject contentResult = candidate.getJSONObject("content");
JSONArray parts = contentResult.getJSONArray("parts");
// 查找包含图片数据的部分
for (int i = 0; i < parts.size(); i++) {
com.alibaba.fastjson.JSONObject part = parts.getJSONObject(i);
if (part.containsKey("inlineData")) {
com.alibaba.fastjson.JSONObject inlineDataResult = part.getJSONObject("inlineData");
String base64Data = inlineDataResult.getString("data");
if (base64Data != null && !base64Data.isEmpty()) {
// 上传生成的图片到MinIO
String fileName = "tryon_result_" + UUID.randomUUID().toString() + ".png";
String uploadedUrl = minioUtil.uploadBytes(
java.util.Base64.getDecoder().decode(base64Data),
fileName,
"image/png"
);
log.info("生成的图片已上传到MinIO: {}", uploadedUrl);
return uploadedUrl;
}
}
}
log.error("响应中没有找到有效的图片数据");
throw new BusinessException("Image data not found", "未找到图片数据", ResultEnum.ERROR.getCode());
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
log.error("处理Google API响应失败: {}", e.getMessage(), e);
throw new BusinessException("Response processing failed", "响应处理失败", ResultEnum.ERROR.getCode());
}
}
}

View File

@@ -1,12 +1,23 @@
package com.aida.lanecarford.service.impl;
import com.aida.lanecarford.common.CommonConstant;
import com.aida.lanecarford.entity.TryOnEffect;
import com.aida.lanecarford.entity.VisitRecord;
import com.aida.lanecarford.exception.BusinessException;
import com.aida.lanecarford.mapper.TryOnEffectMapper;
import com.aida.lanecarford.mapper.VisitRecordMapper;
import com.aida.lanecarford.service.TryOnEffectService;
import com.aida.lanecarford.service.VisitRecordService;
import com.aida.lanecarford.util.MinioUtil;
import com.aida.lanecarford.vo.LibraryVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* 进店记录服务实现类
*
@@ -17,4 +28,63 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor
public class VisitRecordServiceImpl extends ServiceImpl<VisitRecordMapper, VisitRecord> implements VisitRecordService {
private final TryOnEffectService tryOnEffectService;
private final MinioUtil minioUtil;
@Override
public Boolean delete(Long id) {
if (id == null) {
throw BusinessException.parameterRequired("Visit record ID");
}
// 先检查记录是否存在
VisitRecord visitRecord = this.getById(id);
if (visitRecord == null) {
throw BusinessException.resourceNotFound("Visit record");
}
//removeById会自动进行逻辑删除
return this.removeById(id);
}
@Override
public List<LibraryVo> getByCustomerId(Long customerId) {
if (customerId == null) {
throw BusinessException.parameterRequired("Customer ID");
}
LambdaQueryWrapper<VisitRecord> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(VisitRecord::getCustomerId, customerId);
List<VisitRecord> visitRecords = this.list(queryWrapper);
List<LibraryVo> libraryVos = new ArrayList<>();
//根据VisitRecordId查询第一条tryon记录
for (VisitRecord visitRecord : visitRecords) {
// 查询该访问记录的试穿效果,只获取收藏的效果
LambdaQueryWrapper<TryOnEffect> effectWrapper = new LambdaQueryWrapper<>();
effectWrapper.eq(TryOnEffect::getVisitRecordId, visitRecord.getId())
.eq(TryOnEffect::getIsFavorite, 1)
.eq(TryOnEffect::getIsRegenerated, 0)
.orderByDesc(TryOnEffect::getCreatedTime)
.last("LIMIT 1");
TryOnEffect favoriteEffect = tryOnEffectService.getOne(effectWrapper);
// 创建LibraryVo对象
LibraryVo libraryVo = new LibraryVo();
// 如果有收藏的试穿效果设置其图片URL为默认图片
if (favoriteEffect != null && favoriteEffect.getResultImageUrl() != null) {
libraryVo.setDefaultImageUrl(minioUtil.getPresignedUrl(
favoriteEffect.getResultImageUrl(),
CommonConstant.MINIO_IMAGE_EXPIRE_TIME
));
} else {
//如果仅进店未进行任何喜欢收藏结果,不做展示
continue;
}
libraryVo.setVisitTime(visitRecord.getVisitTime());
libraryVo.setVisitRecordId(visitRecord.getId());
libraryVos.add(libraryVo);
}
return libraryVos;
}
}

View File

@@ -0,0 +1,127 @@
package com.aida.lanecarford.util;
import org.apache.logging.log4j.util.BiConsumer;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CopyUtil {
public static <T> T copyObject(Object source, Class<T> tClass) throws BeansException {
return entityToModel(source, tClass);
}
public static <F, T> List<T> copyList(List<F> source, Class<T> tClass) {
if (source == null || source.isEmpty()) {
return new ArrayList<>();
}
List<T> tList = new ArrayList<>();
for (F f : source) {
T t = entityToModel(f, tClass);
tList.add(t);
}
return tList;
}
public static <F, T> List<T> copyList(List<F> source, Class<T> tClass, BiConsumer<F, T> consumer) {
if (source == null || source.isEmpty()) {
return new ArrayList<>();
}
List<T> tList = new ArrayList<>();
for (F f : source) {
T t = entityToModel(f, tClass);
consumer.accept(f, t);
tList.add(t);
}
return tList;
}
public static List<String> copyListToString(List source, String fieldName) {
List<String> list = new ArrayList<>();
if (null == source || source.isEmpty()) {
return list;
}
for (int i = 0; i < source.size(); i++) {
try {
Class c = source.get(i).getClass();
if (null != c) {
Method methodGetKey = c.getMethod(fieldName);
String key = "" + methodGetKey.invoke(source.get(i));
list.add(key);
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
return list;
}
/**
* 复制对象
*
* @param entity
* @param modelClass
* @param <F>
* @param <T>
* @return
*/
private static <F, T> T entityToModel(F entity, Class<T> modelClass) {
Object model = null;
if (entity == null || modelClass == null) {
return null;
}
try {
model = modelClass.newInstance();
} catch (Exception e) {
//忽略
}
BeanUtils.copyProperties(entity, model);
return (T) model;
}
public static <K, V, F> Map<K, V> listToMap(List<F> list, Class<V> c) {
List<V> vList = CopyUtil.copyList(list, c);
return list2Map(vList, c);
}
public static <K, V, F> Map<K, V> listToMap(List<F> list, Class<V> c, String fieldName) {
List<V> vList = CopyUtil.copyList(list, c);
return list2Map(vList, c, fieldName);
}
public static <K, V> Map<K, V> list2Map(List<V> list, Class<V> c) {
return list2Map(list, c, "getId");
}
public static <K, V> Map<K, V> list2Map(List<V> list, Class<V> c, String fieldName) {
Map<K, V> map = new HashMap<>();
if (list != null) {
try {
Method methodGetKey = c.getMethod(fieldName);
for (int i = 0; i < list.size(); i++) {
V value = list.get(i);
@SuppressWarnings("unchecked")
K key = (K) methodGetKey.invoke(list.get(i));
map.put(key, value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return map;
}
}

View File

@@ -3,6 +3,7 @@ package com.aida.lanecarford.util;
import com.aida.lanecarford.config.MinioConfig;
import com.aida.lanecarford.exception.MinioException;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
@@ -12,11 +13,19 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@@ -33,9 +42,9 @@ import java.util.concurrent.TimeUnit;
@RequiredArgsConstructor
public class MinioUtil {
private MinioClient minioClient;
private final MinioClient minioClient;
private MinioConfig minioConfig;
private final MinioConfig minioConfig;
/**
* 检查存储桶是否存在
@@ -175,7 +184,7 @@ public class MinioUtil {
// 确保存储桶存在
createBucket(bucketName);
// 生成文件名
// 生成文件名:TODO 确定好桶的位置后
String objectName = generateFileName(fileName);
// 上传文件
@@ -215,6 +224,14 @@ public class MinioUtil {
*/
public InputStream downloadFile(String bucketName, String fileName) {
try {
// 确保存储桶存在,如果不存在则创建
createBucket(bucketName);
// 检查文件是否存在
if (!doesObjectExist(bucketName, fileName)) {
throw new IOException("File " + fileName + " does not exist in bucket " + bucketName);
}
return minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
@@ -390,12 +407,105 @@ public class MinioUtil {
extension = originalFilename.substring(lastDotIndex);
}
// 生成时间戳路径
String datePath = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
// 生成唯一文件名
String uuid = UUID.randomUUID().toString().replace("-", "");
return datePath + "/" + uuid + extension;
return uuid + extension;
}
/**
* 获取压缩后的图片Base64编码
* @param path 图片的minio路径
* @param targetWidth 目标宽度
* @param targetHeight 目标高度
* @return 压缩后的图片Base64编码
* @throws IOException
*/
public String getCompressedImageAsBase64(String path, int targetWidth, int targetHeight) throws IOException {
int index = path.indexOf("/");
String bucketName = path.substring(0, index);
String fileName = path.substring(index + 1);
// 检查桶是否存在
boolean found = doesObjectExist(bucketName, fileName);
if (!found) {
throw new IOException("Bucket " + bucketName + " does not exist");
}
try (InputStream stream = minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.build())) {
// 读取原始图片
BufferedImage originalImage = ImageIO.read(stream);
if (originalImage == null) {
throw new IOException("无法读取图片: " + path);
}
// 计算压缩比例,保持宽高比
int originalWidth = originalImage.getWidth();
int originalHeight = originalImage.getHeight();
double scaleX = (double) targetWidth / originalWidth;
double scaleY = (double) targetHeight / originalHeight;
double scale = Math.min(scaleX, scaleY); // 选择较小的缩放比例以保持宽高比
int newWidth = (int) (originalWidth * scale);
int newHeight = (int) (originalHeight * scale);
// 创建压缩后的图片
BufferedImage compressedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = compressedImage.createGraphics();
// 设置高质量渲染
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// 绘制压缩后的图片
g2d.drawImage(originalImage, 0, 0, newWidth, newHeight, null);
g2d.dispose();
// 转换为Base64
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(compressedImage, "JPEG", baos); // 使用JPEG格式以减小文件大小
byte[] imageBytes = baos.toByteArray();
log.info("图片压缩完成: {} -> {}x{} (原始: {}x{})", path, newWidth, newHeight, originalWidth, originalHeight);
return Base64.getEncoder().encodeToString(imageBytes);
} catch (ServerException e) {
throw new RuntimeException(e);
} catch (InsufficientDataException e) {
throw new RuntimeException(e);
} catch (ErrorResponseException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
} catch (InvalidResponseException e) {
throw new RuntimeException(e);
} catch (XmlParserException e) {
throw new RuntimeException(e);
} catch (InternalException e) {
throw new RuntimeException(e);
}
}
public boolean doesObjectExist(String bucketName, String objectName) {
try {
minioClient.statObject(
StatObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
return true;
} catch (Exception e) {
// 如果发生异常,说明文件不存在或者出现了其他错误
return false;
}
}
}

View File

@@ -0,0 +1,17 @@
package com.aida.lanecarford.vo;
import com.aida.lanecarford.entity.VisitRecord;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class LibraryVo {
//进店时间
private LocalDateTime visitTime;
//默认显示图片url(第一个喜欢的try on图片)
private String defaultImageUrl;
private Long visitRecordId;
}

View File

@@ -0,0 +1,17 @@
package com.aida.lanecarford.vo;
import lombok.Data;
@Data
public class TryOnResultVo {
private Long tryOnId;
private String tryOnUrl;
private String styleUrl;
private Integer isRegenerated;
private Integer isFavorite;
}