second commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
12
src/main/java/com/aida/lanecarford/dto/CustomerPhotoDto.java
Normal file
12
src/main/java/com/aida/lanecarford/dto/CustomerPhotoDto.java
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 中定义
|
||||
}
|
||||
@@ -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 中定义
|
||||
}
|
||||
@@ -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 中定义
|
||||
}
|
||||
@@ -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 中定义
|
||||
}
|
||||
@@ -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 中定义
|
||||
}
|
||||
@@ -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 中定义
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
127
src/main/java/com/aida/lanecarford/util/CopyUtil.java
Normal file
127
src/main/java/com/aida/lanecarford/util/CopyUtil.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/main/java/com/aida/lanecarford/vo/LibraryVo.java
Normal file
17
src/main/java/com/aida/lanecarford/vo/LibraryVo.java
Normal 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;
|
||||
}
|
||||
17
src/main/java/com/aida/lanecarford/vo/TryOnResultVo.java
Normal file
17
src/main/java/com/aida/lanecarford/vo/TryOnResultVo.java
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user