diff --git a/.gitignore b/.gitignore
index 667aaef..cce3969 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,4 @@ build/
### VS Code ###
.vscode/
+/logs/
diff --git a/pom.xml b/pom.xml
index 98f4870..e8faec2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,154 +1,247 @@
- 4.0.0
-
- org.springframework.boot
- spring-boot-starter-parent
- 3.1.6
-
-
- com.aida
- lanecarford
- 0.0.1-SNAPSHOT
- lanecarford
- Demo project for Spring Boot
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 21
-
-
-
-
- org.springframework.boot
- spring-boot-starter-web
-
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.1.6
+
+
+ com.aida
+ lanecarford
+ 0.0.1-SNAPSHOT
+ lanecarford
+ Demo project for Spring Boot
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 21
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
-
-
- org.springframework.boot
- spring-boot-starter-security
-
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+
+
+ io.minio
+ minio
+ 8.0.3
+
-
-
-
- com.baomidou
- mybatis-plus-boot-starter
- 3.5.5
-
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ 3.5.5
+
+
+
+ com.mysql
+ mysql-connector-j
+ 8.2.0
+
-
-
- com.mysql
- mysql-connector-j
- 8.2.0
-
+
+
+ org.projectlombok
+ lombok
+ true
+
-
-
- org.projectlombok
- lombok
- true
-
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
-
-
- org.springframework.boot
- spring-boot-starter-validation
-
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
-
-
- org.springframework.boot
- spring-boot-starter-actuator
-
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
-
-
- com.fasterxml.jackson.core
- jackson-databind
-
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.6.0
+
-
-
- org.springdoc
- springdoc-openapi-starter-webmvc-ui
- 2.6.0
-
+
+
+ com.google.auth
+ google-auth-library-oauth2-http
+ 1.38.0
+
+
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.12.0
+
+
+
+
+ cn.hutool
+ hutool-all
+ 5.8.25
+
+
+
+
+ com.alibaba
+ fastjson
+ 2.0.43
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-aop
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.12.3
+
+
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.12.3
+ runtime
+
+
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.12.3
+ runtime
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+
+
+ com.tencentcloudapi
+ tencentcloud-sdk-java-ses
+ 3.1.572
+
+
-
-
- org.springframework.boot
- spring-boot-starter-aop
-
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ 21
+ 21
+ UTF-8
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+
+
+
+
+
+
+ dev
+
+ dev
+
+
+ true
+
+
+
+
+ prod
+
+ prod
+
+
+
-
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
-
- org.springframework.security
- spring-security-test
- test
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.12.1
-
- 21
- 21
- 21
- UTF-8
-
- --enable-preview
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
- org.projectlombok
- lombok
-
-
-
-
-
-
-
diff --git a/src/main/java/com/aida/lanecarford/aspect/LoggingAspect.java b/src/main/java/com/aida/lanecarford/aspect/LoggingAspect.java
index 8c7e272..cc1f866 100644
--- a/src/main/java/com/aida/lanecarford/aspect/LoggingAspect.java
+++ b/src/main/java/com/aida/lanecarford/aspect/LoggingAspect.java
@@ -10,11 +10,12 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
+
import java.util.Arrays;
/**
* 日志切面
- *
+ *
* @author AI Assistant
* @since 2024-01-01
*/
@@ -28,13 +29,15 @@ public class LoggingAspect {
* 定义切点:所有Controller方法
*/
@Pointcut("execution(* com.aida.lanecarford.controller..*(..))")
- public void controllerMethods() {}
+ public void controllerMethods() {
+ }
/**
* 定义切点:所有Service方法
*/
@Pointcut("execution(* com.aida.lanecarford.service..*(..))")
- public void serviceMethods() {}
+ public void serviceMethods() {
+ }
/**
* Controller方法执行前记录日志
@@ -44,12 +47,13 @@ public class LoggingAspect {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
-
+
logger.info("=== 请求开始 ===");
logger.info("请求URL: {}", request.getRequestURL().toString());
logger.info("请求方法: {}", request.getMethod());
logger.info("请求IP: {}", getClientIpAddress(request));
- logger.info("调用方法: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
+// logger.info("调用方法: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
+ logger.info("调用方法: {}.{}", joinPoint.getSignature().getDeclaringType().getSimpleName(), joinPoint.getSignature().getName());
logger.info("请求参数: {}", Arrays.toString(joinPoint.getArgs()));
}
}
@@ -59,7 +63,7 @@ public class LoggingAspect {
*/
@AfterReturning(pointcut = "controllerMethods()", returning = "result")
public void logControllerAfterReturning(JoinPoint joinPoint, Object result) {
- logger.info("方法执行成功: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
+ logger.info("方法执行成功: {}.{}", joinPoint.getSignature().getDeclaringType().getSimpleName(), joinPoint.getSignature().getName());
logger.info("返回结果: {}", result);
logger.info("=== 请求结束 ===");
}
@@ -69,7 +73,7 @@ public class LoggingAspect {
*/
@AfterThrowing(pointcut = "controllerMethods()", throwing = "exception")
public void logControllerAfterThrowing(JoinPoint joinPoint, Throwable exception) {
- logger.error("方法执行异常: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
+ logger.error("方法执行异常: {}.{}", joinPoint.getSignature().getDeclaringType().getSimpleName(), joinPoint.getSignature().getName());
logger.error("异常信息: ", exception);
logger.info("=== 请求异常结束 ===");
}
@@ -80,15 +84,15 @@ public class LoggingAspect {
@Around("serviceMethods()")
public Object logServiceAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
- String methodName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
-
+// String methodName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
+ String methodName = joinPoint.getSignature().getDeclaringType().getSimpleName() + "." + joinPoint.getSignature().getName();
try {
logger.debug("Service方法开始执行: {}", methodName);
Object result = joinPoint.proceed();
-
+
long endTime = System.currentTimeMillis();
logger.debug("Service方法执行成功: {}, 耗时: {}ms", methodName, (endTime - startTime));
-
+
return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
@@ -106,22 +110,22 @@ public class LoggingAspect {
if (xForwardedFor != null && !xForwardedFor.isEmpty() && !"unknown".equalsIgnoreCase(xForwardedFor)) {
return xForwardedFor.split(",")[0];
}
-
+
String xRealIp = request.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty() && !"unknown".equalsIgnoreCase(xRealIp)) {
return xRealIp;
}
-
+
String proxyClientIp = request.getHeader("Proxy-Client-IP");
if (proxyClientIp != null && !proxyClientIp.isEmpty() && !"unknown".equalsIgnoreCase(proxyClientIp)) {
return proxyClientIp;
}
-
+
String wlProxyClientIp = request.getHeader("WL-Proxy-Client-IP");
if (wlProxyClientIp != null && !wlProxyClientIp.isEmpty() && !"unknown".equalsIgnoreCase(wlProxyClientIp)) {
return wlProxyClientIp;
}
-
+
return request.getRemoteAddr();
}
}
\ No newline at end of file
diff --git a/src/main/java/com/aida/lanecarford/aspect/PerformanceAspect.java b/src/main/java/com/aida/lanecarford/aspect/PerformanceAspect.java
index be3394d..a52a443 100644
--- a/src/main/java/com/aida/lanecarford/aspect/PerformanceAspect.java
+++ b/src/main/java/com/aida/lanecarford/aspect/PerformanceAspect.java
@@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
/**
* 性能监控切面
- *
+ *
* @author AI Assistant
* @since 2024-01-01
*/
@@ -22,7 +22,7 @@ public class PerformanceAspect {
/**
* 监控Controller方法性能
*/
- @Around("execution(* com.aida.lanecarford.controller..*(..))")
+// @Around("execution(* com.aida.lanecarford.controller..*(..))")
public Object monitorControllerPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
return monitorMethodPerformance(joinPoint, "Controller");
}
@@ -30,7 +30,7 @@ public class PerformanceAspect {
/**
* 监控Service方法性能
*/
- @Around("execution(* com.aida.lanecarford.service..*(..))")
+// @Around("execution(* com.aida.lanecarford.service..*(..))")
public Object monitorServicePerformance(ProceedingJoinPoint joinPoint) throws Throwable {
return monitorMethodPerformance(joinPoint, "Service");
}
@@ -38,7 +38,7 @@ public class PerformanceAspect {
/**
* 监控数据库操作性能
*/
- @Around("execution(* com.aida.lanecarford.mapper..*(..))")
+// @Around("execution(* com.aida.lanecarford.mapper..*(..))")
public Object monitorMapperPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
return monitorMethodPerformance(joinPoint, "Mapper");
}
@@ -49,28 +49,28 @@ public class PerformanceAspect {
private Object monitorMethodPerformance(ProceedingJoinPoint joinPoint, String layer) throws Throwable {
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
-
+
try {
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
-
+
// 记录性能日志
logPerformance(layer, methodName, executionTime, true);
-
+
// 如果执行时间过长,记录警告
if (executionTime > getWarningThreshold(layer)) {
logger.warn("{}方法执行时间过长: {} - {}ms", layer, methodName, executionTime);
}
-
+
return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
-
+
// 记录异常性能日志
logPerformance(layer, methodName, executionTime, false);
-
+
throw e;
}
}
@@ -80,10 +80,10 @@ public class PerformanceAspect {
*/
private void logPerformance(String layer, String methodName, long executionTime, boolean success) {
if (logger.isDebugEnabled()) {
- logger.debug("性能监控 - {}: {} - {}ms - {}",
- layer, methodName, executionTime, success ? "成功" : "失败");
+ logger.debug("性能监控 - {}: {} - {}ms - {}",
+ layer, methodName, executionTime, success ? "成功" : "失败");
}
-
+
// 可以在这里添加性能数据收集逻辑,比如发送到监控系统
collectPerformanceMetrics(layer, methodName, executionTime, success);
}
@@ -114,7 +114,7 @@ public class PerformanceAspect {
// - 发送到时序数据库
// - 更新内存中的统计信息
// - 发送到监控系统
-
+
// 示例:简单的内存统计
PerformanceMetrics.recordExecution(layer, methodName, executionTime, success);
}
@@ -123,7 +123,7 @@ public class PerformanceAspect {
* 简单的性能指标收集器
*/
private static class PerformanceMetrics {
-
+
public static void recordExecution(String layer, String methodName, long executionTime, boolean success) {
// 简单的日志记录,实际项目中可以替换为更复杂的指标收集
if (executionTime > 1000) { // 超过1秒的操作
diff --git a/src/main/java/com/aida/lanecarford/common/ApiResponse.java b/src/main/java/com/aida/lanecarford/common/ApiResponse.java
index 491ce98..2d8135d 100644
--- a/src/main/java/com/aida/lanecarford/common/ApiResponse.java
+++ b/src/main/java/com/aida/lanecarford/common/ApiResponse.java
@@ -1,5 +1,6 @@
package com.aida.lanecarford.common;
+import com.aida.lanecarford.common.response.ResultEnum;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@@ -20,7 +21,7 @@ public class ApiResponse implements Serializable {
/**
* 响应状态码
*/
- private String code;
+ private Integer code;
/**
* 响应消息
@@ -46,7 +47,7 @@ public class ApiResponse implements Serializable {
this.timestamp = System.currentTimeMillis();
}
- public ApiResponse(boolean success, String code, String message, T data) {
+ public ApiResponse(boolean success, Integer code, String message, T data) {
this();
this.success = success;
this.code = code;
@@ -58,34 +59,34 @@ public class ApiResponse implements Serializable {
* 成功响应(无数据)
*/
public static ApiResponse success() {
- return new ApiResponse<>(true, "SUCCESS", "操作成功", null);
+ return new ApiResponse<>(true, ResultEnum.SUCCESS.getCode(), "操作成功", null);
}
/**
* 成功响应(带数据)
*/
public static ApiResponse success(T data) {
- return new ApiResponse<>(true, "SUCCESS", "操作成功", data);
+ return new ApiResponse<>(true, ResultEnum.SUCCESS.getCode(), "操作成功", data);
}
/**
* 成功响应(自定义消息,无数据)
*/
public static ApiResponse success(String message) {
- return new ApiResponse<>(true, "SUCCESS", message, null);
+ return new ApiResponse<>(true, ResultEnum.SUCCESS.getCode(), message, null);
}
/**
* 成功响应(自定义消息)
*/
public static ApiResponse success(String message, T data) {
- return new ApiResponse<>(true, "SUCCESS", message, data);
+ return new ApiResponse<>(true, ResultEnum.SUCCESS.getCode(), message, data);
}
/**
* 失败响应
*/
- public static ApiResponse error(String code, String message) {
+ public static ApiResponse error(Integer code, String message) {
return new ApiResponse<>(false, code, message, null);
}
@@ -93,76 +94,76 @@ public class ApiResponse implements Serializable {
* 失败响应(默认错误码)
*/
public static ApiResponse error(String message) {
- return new ApiResponse<>(false, "ERROR", message, null);
+ return new ApiResponse<>(false, ResultEnum.ERROR.getCode(), message, null);
}
- /**
- * 参数错误响应
- */
- public static ApiResponse paramError(String message) {
- return new ApiResponse<>(false, "PARAM_ERROR", message, null);
- }
-
- /**
- * 数据不存在响应
- */
- public static ApiResponse notFound(String message) {
- return new ApiResponse<>(false, "NOT_FOUND", message, null);
- }
-
- /**
- * 权限不足响应
- */
- public static ApiResponse forbidden(String message) {
- return new ApiResponse<>(false, "FORBIDDEN", message, null);
- }
-
- /**
- * 服务器内部错误响应
- */
- public static ApiResponse serverError(String message) {
- return new ApiResponse<>(false, "SERVER_ERROR", message, null);
- }
-
- /**
- * 业务异常响应
- */
- public static ApiResponse businessError(String code, String message) {
- return new ApiResponse<>(false, code, message, null);
- }
-
- /**
- * 外部服务错误响应
- */
- public static ApiResponse externalServiceError(String message) {
- return new ApiResponse<>(false, "EXTERNAL_SERVICE_ERROR", message, null);
- }
-
- /**
- * 文件上传错误响应
- */
- public static ApiResponse fileUploadError(String message) {
- return new ApiResponse<>(false, "FILE_UPLOAD_ERROR", message, null);
- }
-
- /**
- * 验证失败响应
- */
- public static ApiResponse validationError(String message) {
- return new ApiResponse<>(false, "VALIDATION_ERROR", message, null);
- }
-
- /**
- * 重复数据响应
- */
- public static ApiResponse duplicateError(String message) {
- return new ApiResponse<>(false, "DUPLICATE_ERROR", message, null);
- }
-
- /**
- * 操作超时响应
- */
- public static ApiResponse timeoutError(String message) {
- return new ApiResponse<>(false, "TIMEOUT_ERROR", message, null);
- }
+// /**
+// * 参数错误响应
+// */
+// public static ApiResponse paramError(String message) {
+// return new ApiResponse<>(false, "PARAM_ERROR", message, null);
+// }
+//
+// /**
+// * 数据不存在响应
+// */
+// public static ApiResponse notFound(String message) {
+// return new ApiResponse<>(false, "NOT_FOUND", message, null);
+// }
+//
+// /**
+// * 权限不足响应
+// */
+// public static ApiResponse forbidden(String message) {
+// return new ApiResponse<>(false, "FORBIDDEN", message, null);
+// }
+//
+// /**
+// * 服务器内部错误响应
+// */
+// public static ApiResponse serverError(String message) {
+// return new ApiResponse<>(false, "SERVER_ERROR", message, null);
+// }
+//
+// /**
+// * 业务异常响应
+// */
+// public static ApiResponse businessError(String code, String message) {
+// return new ApiResponse<>(false, code, message, null);
+// }
+//
+// /**
+// * 外部服务错误响应
+// */
+// public static ApiResponse externalServiceError(String message) {
+// return new ApiResponse<>(false, "EXTERNAL_SERVICE_ERROR", message, null);
+// }
+//
+// /**
+// * 文件上传错误响应
+// */
+// public static ApiResponse fileUploadError(String message) {
+// return new ApiResponse<>(false, "FILE_UPLOAD_ERROR", message, null);
+// }
+//
+// /**
+// * 验证失败响应
+// */
+// public static ApiResponse validationError(String message) {
+// return new ApiResponse<>(false, "VALIDATION_ERROR", message, null);
+// }
+//
+// /**
+// * 重复数据响应
+// */
+// public static ApiResponse duplicateError(String message) {
+// return new ApiResponse<>(false, "DUPLICATE_ERROR", message, null);
+// }
+//
+// /**
+// * 操作超时响应
+// */
+// public static ApiResponse timeoutError(String message) {
+// return new ApiResponse<>(false, "TIMEOUT_ERROR", message, null);
+// }
}
\ No newline at end of file
diff --git a/src/main/java/com/aida/lanecarford/common/CommonConstant.java b/src/main/java/com/aida/lanecarford/common/CommonConstant.java
new file mode 100644
index 0000000..34f2d79
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/common/CommonConstant.java
@@ -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;
+}
\ No newline at end of file
diff --git a/src/main/java/com/aida/lanecarford/common/constant/CommonConstants.java b/src/main/java/com/aida/lanecarford/common/constant/CommonConstants.java
new file mode 100644
index 0000000..ad084ad
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/common/constant/CommonConstants.java
@@ -0,0 +1,14 @@
+package com.aida.lanecarford.common.constant;
+
+public class CommonConstants {
+
+ public static final String REQUEST_OUTFIT = "http://18.167.251.121:10004/api/v1/agent";
+
+ public static final String CHAT = "http://18.167.251.121:10004/api/v1/chatbot";
+
+ public static final int MINIO_PATH_TIMEOUT = 7 * 24 * 60 * 60; // minio图片临时访问地址 7 天过期(second)
+
+ public static final int CONN_TIMEOUT = 30000; // (milliseconds)
+
+
+}
diff --git a/src/main/java/com/aida/lanecarford/common/constant/MinioFileConstants.java b/src/main/java/com/aida/lanecarford/common/constant/MinioFileConstants.java
new file mode 100644
index 0000000..aaad702
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/common/constant/MinioFileConstants.java
@@ -0,0 +1,264 @@
+package com.aida.lanecarford.common.constant;
+
+import java.util.UUID;
+
+/**
+ * MinIO文件命名常量类
+ * 统一管理不同类型图片的命名规范
+ *
+ * @author AI Assistant
+ * @since 2024-01-01
+ */
+public class MinioFileConstants {
+
+ /**
+ * 文件路径分隔符
+ */
+ public static final String PATH_SEPARATOR = "/";
+
+ /**
+ * 图片文件扩展名
+ */
+ public static final String PNG_EXTENSION = ".png";
+ public static final String JPG_EXTENSION = ".jpg";
+ public static final String JPEG_EXTENSION = ".jpeg";
+
+ /**
+ * 顾客照片目录
+ */
+ public static final String CUSTOMER_PHOTO_DIR = "customer_photo";
+
+ /**
+ * 模特照片目录
+ */
+ public static final String MODEL_PHOTO_DIR = "model_photo";
+
+ /**
+ * 风格图片目录
+ */
+ public static final String STYLE_IMAGE_DIR = "style_image";
+
+ /**
+ * 试穿结果图片目录
+ */
+ public static final String TRY_ON_RESULT_DIR = "try_on_result";
+
+ /**
+ * 合成图片目录
+ */
+ public static final String COMPOSED_IMAGE_DIR = "composed_image";
+
+ /**
+ * 生成顾客照片文件名
+ * 格式: 桶名/customer_photo/UUID.png
+ *
+ * @param bucketName 桶名
+ * @return 完整的文件路径
+ */
+ public static String generateCustomerPhotoPath(String bucketName) {
+ return bucketName + PATH_SEPARATOR + CUSTOMER_PHOTO_DIR + PATH_SEPARATOR + UUID.randomUUID() + PNG_EXTENSION;
+ }
+
+ /**
+ * 生成顾客照片文件名(仅路径部分)
+ * 格式: customer_photo/UUID.png
+ *
+ * @return 文件路径(不含桶名)
+ */
+ public static String generateCustomerPhotoObjectName() {
+ return CUSTOMER_PHOTO_DIR + PATH_SEPARATOR + UUID.randomUUID() + PNG_EXTENSION;
+ }
+
+ /**
+ * 生成模特照片文件名
+ * 格式: 桶名/model_photo/UUID.png
+ *
+ * @param bucketName 桶名
+ * @return 完整的文件路径
+ */
+ public static String generateModelPhotoPath(String bucketName) {
+ return bucketName + PATH_SEPARATOR + MODEL_PHOTO_DIR + PATH_SEPARATOR + UUID.randomUUID() + PNG_EXTENSION;
+ }
+
+ /**
+ * 生成模特照片文件名(仅路径部分)
+ * 格式: model_photo/UUID.png
+ *
+ * @return 文件路径(不含桶名)
+ */
+ public static String generateModelPhotoObjectName() {
+ return MODEL_PHOTO_DIR + PATH_SEPARATOR + UUID.randomUUID() + PNG_EXTENSION;
+ }
+
+ /**
+ * 生成风格图片文件名
+ * 格式: 桶名/style_image/UUID.png
+ *
+ * @param bucketName 桶名
+ * @return 完整的文件路径
+ */
+ public static String generateStyleImagePath(String bucketName) {
+ return bucketName + PATH_SEPARATOR + STYLE_IMAGE_DIR + PATH_SEPARATOR + UUID.randomUUID() + PNG_EXTENSION;
+ }
+
+ /**
+ * 生成风格图片文件名(仅路径部分)
+ * 格式: style_image/UUID.png
+ *
+ * @return 文件路径(不含桶名)
+ */
+ public static String generateStyleImageObjectName() {
+ return STYLE_IMAGE_DIR + PATH_SEPARATOR + UUID.randomUUID() + PNG_EXTENSION;
+ }
+
+ /**
+ * 生成试穿结果图片文件名
+ * 格式: 桶名/try_on_result/UUID.png
+ *
+ * @param bucketName 桶名
+ * @return 完整的文件路径
+ */
+ public static String generateTryOnResultPath(String bucketName) {
+ return bucketName + PATH_SEPARATOR + TRY_ON_RESULT_DIR + PATH_SEPARATOR + UUID.randomUUID() + PNG_EXTENSION;
+ }
+
+ /**
+ * 生成试穿结果图片文件名(仅路径部分)
+ * 格式: try_on_result/UUID.png
+ *
+ * @return 文件路径(不含桶名)
+ */
+ public static String generateTryOnResultObjectName() {
+ return TRY_ON_RESULT_DIR + PATH_SEPARATOR + UUID.randomUUID() + PNG_EXTENSION;
+ }
+
+ /**
+ * 生成合成图片文件名
+ * 格式: 桶名/composed_image/UUID.jpg
+ *
+ * @param bucketName 桶名
+ * @return 完整的文件路径
+ */
+ public static String generateComposedImagePath(String bucketName) {
+ return bucketName + PATH_SEPARATOR + COMPOSED_IMAGE_DIR + PATH_SEPARATOR + UUID.randomUUID() + JPG_EXTENSION;
+ }
+
+ /**
+ * 生成合成图片文件名(仅路径部分)
+ * 格式: composed_image/UUID.jpg
+ *
+ * @return 文件路径(不含桶名)
+ */
+ public static String generateComposedImageObjectName() {
+ return COMPOSED_IMAGE_DIR + PATH_SEPARATOR + UUID.randomUUID() + JPG_EXTENSION;
+ }
+
+ /**
+ * 生成带前缀的试穿结果图片文件名
+ * 格式: 桶名/try_on_result/tryon_result_UUID.png
+ *
+ * @param bucketName 桶名
+ * @return 完整的文件路径
+ */
+ public static String generateTryOnResultWithPrefixPath(String bucketName) {
+ return bucketName + PATH_SEPARATOR + TRY_ON_RESULT_DIR + PATH_SEPARATOR + "tryon_result_" + UUID.randomUUID() + PNG_EXTENSION;
+ }
+
+ /**
+ * 生成带前缀的试穿结果图片文件名(仅路径部分)
+ * 格式: try_on_result/tryon_result_UUID.png
+ *
+ * @return 文件路径(不含桶名)
+ */
+ public static String generateTryOnResultWithPrefixObjectName() {
+ return TRY_ON_RESULT_DIR + PATH_SEPARATOR + "tryon_result_" + UUID.randomUUID() + PNG_EXTENSION;
+ }
+
+ /**
+ * 生成带时间戳的合成图片文件名
+ * 格式: 桶名/composed_image/composed_[图片数量]_images_composed_[时间戳].jpg
+ *
+ * @param bucketName 桶名
+ * @param imageCount 图片数量
+ * @return 完整的文件路径
+ */
+ public static String generateComposedImageWithTimestampPath(String bucketName, int imageCount) {
+ String timestamp = String.valueOf(System.currentTimeMillis());
+ String suffix = imageCount + "_images_composed";
+ String fileName = "composed_" + suffix + "_" + timestamp + JPG_EXTENSION;
+ return bucketName + PATH_SEPARATOR + COMPOSED_IMAGE_DIR + PATH_SEPARATOR + fileName;
+ }
+
+ /**
+ * 生成带时间戳的合成图片文件名(仅路径部分)
+ * 格式: composed_image/composed_[图片数量]_images_composed_[时间戳].jpg
+ *
+ * @param imageCount 图片数量
+ * @return 文件路径(不含桶名)
+ */
+ public static String generateComposedImageWithTimestampObjectName(int imageCount) {
+ String timestamp = String.valueOf(System.currentTimeMillis());
+ String suffix = imageCount + "_images_composed";
+ String fileName = "composed_" + suffix + "_" + timestamp + JPG_EXTENSION;
+ return COMPOSED_IMAGE_DIR + PATH_SEPARATOR + fileName;
+ }
+
+ /**
+ * 根据文件类型生成对应的对象名称
+ *
+ * @param fileType 文件类型
+ * @return 对象名称
+ */
+ public static String generateObjectNameByType(FileType fileType) {
+ return switch (fileType) {
+ case CUSTOMER_PHOTO -> generateCustomerPhotoObjectName();
+ case MODEL_PHOTO -> generateModelPhotoObjectName();
+ case STYLE_IMAGE -> generateStyleImageObjectName();
+ case TRY_ON_RESULT -> generateTryOnResultObjectName();
+ case COMPOSED_IMAGE -> generateComposedImageObjectName();
+ };
+ }
+
+ /**
+ * 根据文件类型生成对应的完整路径
+ *
+ * @param bucketName 桶名
+ * @param fileType 文件类型
+ * @return 完整路径
+ */
+ public static String generatePathByType(String bucketName, FileType fileType) {
+ return switch (fileType) {
+ case CUSTOMER_PHOTO -> generateCustomerPhotoPath(bucketName);
+ case MODEL_PHOTO -> generateModelPhotoPath(bucketName);
+ case STYLE_IMAGE -> generateStyleImagePath(bucketName);
+ case TRY_ON_RESULT -> generateTryOnResultPath(bucketName);
+ case COMPOSED_IMAGE -> generateComposedImagePath(bucketName);
+ };
+ }
+
+ /**
+ * 文件类型枚举
+ */
+ public enum FileType {
+ /**
+ * 顾客照片
+ */
+ CUSTOMER_PHOTO,
+ /**
+ * 模特照片
+ */
+ MODEL_PHOTO,
+ /**
+ * 风格图片
+ */
+ STYLE_IMAGE,
+ /**
+ * 试穿结果图片
+ */
+ TRY_ON_RESULT,
+ /**
+ * 合成图片
+ */
+ COMPOSED_IMAGE
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/aida/lanecarford/common/constant/RedisURIConstants.java b/src/main/java/com/aida/lanecarford/common/constant/RedisURIConstants.java
new file mode 100644
index 0000000..6a95f2c
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/common/constant/RedisURIConstants.java
@@ -0,0 +1,18 @@
+package com.aida.lanecarford.common.constant;
+
+public class RedisURIConstants {
+
+ public static final String tokenCache = "TokenCache:";
+
+ public static final String verifyCodeCache = "VerifyCodeCache:";
+ // 验证码 10分钟过期
+ public static final Long verifyCodeTimeout = 10 * 60L;
+ // outfit result 结果30分钟过期
+ public static final Long outfitResultTimeout = 30 * 60L;
+
+ public static final String outfitResultCache = "OutfitResultCache:";
+
+ public static final String minioPathCache = "MinioPathCache:";
+
+
+}
diff --git a/src/main/java/com/aida/lanecarford/common/enums/AuthenticationOperationTypeEnum.java b/src/main/java/com/aida/lanecarford/common/enums/AuthenticationOperationTypeEnum.java
new file mode 100644
index 0000000..f34ba1d
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/common/enums/AuthenticationOperationTypeEnum.java
@@ -0,0 +1,37 @@
+package com.aida.lanecarford.common.enums;
+
+import java.util.stream.Stream;
+
+/**
+ * @description: 操作类型 登入 忘记密码
+ **/
+public enum AuthenticationOperationTypeEnum {
+ /**
+ * 登入
+ */
+ LOGIN,
+ /**
+ * 异常ip
+ */
+ EXCEPTION_IP,
+ /**
+ * 绑定邮箱
+ */
+ BIND_MAILBOX,
+ /**
+ * 忘记密码
+ */
+ FORGET_PWD,
+ /**
+ * 更改邮箱
+ */
+ CHANGE_MAILBOX,
+ /**
+ * 注册
+ */
+ REGISTER;
+
+ public static AuthenticationOperationTypeEnum of(String name) {
+ return Stream.of(AuthenticationOperationTypeEnum.values()).filter(v -> v.name().equals(name)).findFirst().orElse(null);
+ }
+}
diff --git a/src/main/java/com/aida/lanecarford/common/enums/LanguageEnum.java b/src/main/java/com/aida/lanecarford/common/enums/LanguageEnum.java
new file mode 100644
index 0000000..2a11c96
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/common/enums/LanguageEnum.java
@@ -0,0 +1,8 @@
+package com.aida.lanecarford.common.enums;
+
+public enum LanguageEnum {
+
+ CHINESE,
+
+ ENGLISH;
+}
diff --git a/src/main/java/com/aida/lanecarford/common/enums/StatusEnum.java b/src/main/java/com/aida/lanecarford/common/enums/StatusEnum.java
new file mode 100644
index 0000000..2582866
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/common/enums/StatusEnum.java
@@ -0,0 +1,33 @@
+package com.aida.lanecarford.common.enums;
+
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.stream.Stream;
+
+@Getter
+@AllArgsConstructor
+@Schema(description = "生成状态枚举")
+public enum StatusEnum {
+ @Schema(description = "等待中")
+ PENDING(0),
+
+ @Schema(description = "成功")
+ SUCCEEDED(1),
+
+ @Schema(description = "失败")
+ FAILED(2),
+
+ @Schema(description = "运行中")
+ RUNNING(3);
+
+ private int code;
+
+ public static StatusEnum of(int code) {
+ return Stream.of(StatusEnum.values()).filter(v -> v.getCode() == code).findFirst().orElse(null);
+ }
+
+
+}
diff --git a/src/main/java/com/aida/lanecarford/common/enums/StylistPathEnum.java b/src/main/java/com/aida/lanecarford/common/enums/StylistPathEnum.java
new file mode 100644
index 0000000..4038c25
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/common/enums/StylistPathEnum.java
@@ -0,0 +1,25 @@
+package com.aida.lanecarford.common.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.stream.Stream;
+
+@Getter
+@AllArgsConstructor
+public enum StylistPathEnum {
+
+ STYLIST_ONE("crystal", "lanecarford/stylist_guide/latest/crystal_en.md"),
+
+ STYLIST_TWO("mini", "lanecarford/stylist_guide/latest/mini_en.md");
+
+ private String name;
+
+ private String path;
+
+ public static StylistPathEnum of(String name) {
+ return Stream.of(StylistPathEnum.values()).filter(v -> v.getName().equals(name)).findFirst().orElse(null);
+ }
+
+
+}
diff --git a/src/main/java/com/aida/lanecarford/common/response/ResultEnum.java b/src/main/java/com/aida/lanecarford/common/response/ResultEnum.java
new file mode 100644
index 0000000..d1df838
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/common/response/ResultEnum.java
@@ -0,0 +1,67 @@
+package com.aida.lanecarford.common.response;
+
+/**
+ * @ClassName ResultEnum
+ * @Description 响应结果枚举
+ * @Author dwjian
+ * @Date 2019/9/8 21:58
+ */
+public enum ResultEnum {
+
+ SUCCESS(true, 0, "SUCCESS", "操作成功"),
+ FAIL(false, -1, "FAIL", "操作失败"),
+ ERROR(false, -1, "System error", "系统错误"),
+ PARAMETER_ERROR(false, -2, "Parameter error", "参数错误"),
+
+ NO_LOGIN(false, -100, "User not logged in", "用户未登录"),
+ NO_PERMISSION(false, -200, "No permission", "无权限访问"),
+ ACCOUNT_LOCK(false, -300, "Account locked", "账户已锁定"),
+
+ PROMPT(false, 1, "Prompt", "提示"),
+ WARNING(false, 2, "Warning", "警告"),
+
+ ;
+ private int code;
+ private String msg; // 英文消息,返回给前端
+ private String msgCn; // 中文消息,用于日志
+ private boolean isOK;
+
+ ResultEnum(boolean isOK, int code, String msg, String msgCn) {
+ this.isOK = isOK;
+ this.code = code;
+ this.msg = msg;
+ this.msgCn = msgCn;
+ }
+
+ public int getCode() {
+ return code;
+ }
+
+ public void setCode(int code) {
+ this.code = code;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public void setMsg(String msg) {
+ this.msg = msg;
+ }
+
+ public String getMsgCn() {
+ return msgCn;
+ }
+
+ public void setMsgCn(String msgCn) {
+ this.msgCn = msgCn;
+ }
+
+ public boolean isOK() {
+ return isOK;
+ }
+
+ public void setOK(boolean OK) {
+ isOK = OK;
+ }
+}
diff --git a/src/main/java/com/aida/lanecarford/common/security/JwtInterceptor.java b/src/main/java/com/aida/lanecarford/common/security/JwtInterceptor.java
new file mode 100644
index 0000000..fac1e28
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/common/security/JwtInterceptor.java
@@ -0,0 +1,73 @@
+package com.aida.lanecarford.common.security;
+
+import com.aida.lanecarford.common.security.config.JwtProperties;
+import com.aida.lanecarford.common.security.context.UserContext;
+import com.aida.lanecarford.exception.BusinessException;
+import com.aida.lanecarford.util.CacheUtil;
+import com.aida.lanecarford.vo.AuthPrincipalVO;
+import com.alibaba.fastjson.JSONObject;
+import io.netty.util.internal.StringUtil;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import java.util.Objects;
+
+@Component
+@Slf4j
+public class JwtInterceptor implements HandlerInterceptor {
+
+ @Resource
+ private CacheUtil cacheUtil;
+
+ @Autowired
+ private JwtUtil jwtUtil;
+
+ @Resource
+ private JwtProperties jwtProperties;
+
+ @Override
+ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
+ if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
+ return false;
+ }
+
+ String jwtToken = request.getHeader(jwtProperties.getJwtTokenHeader());
+ if (jwtToken == null || !jwtToken.startsWith(jwtProperties.getJwtTokenPrefix())) {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ return false;
+ }
+
+ boolean validated = jwtUtil.validateToken(jwtToken);
+ if (validated) {
+ String extracted = jwtUtil.extractUserinfo(jwtToken);
+ if (StringUtil.isNullOrEmpty(extracted)) {
+ log.warn("TOKEN已过期,请重新登录!(token without userInfo)");
+ throw new BusinessException("Token has expired, please log in again.");
+ }
+
+ AuthPrincipalVO authPrincipalVO = JSONObject.parseObject(extracted, AuthPrincipalVO.class);
+ // 先清空当前线程变量,防止上一个线程遗留
+ UserContext.delete();
+ // 存取用户信息到缓存
+ UserContext.setUserHolder(authPrincipalVO);
+ // 校验当前token与缓存中数据是否一致
+ Object token = cacheUtil.getToken(authPrincipalVO.getId());
+
+ if (Objects.isNull(token)) {
+ log.warn("TOKEN已过期,请重新登录!(local cache empty)");
+ throw new BusinessException("Token has expired, please log in again.");
+ } else if (!token.toString().equals(jwtToken)) {
+ log.warn("TOKEN已过期,请重新登录!(token not match local cache)");
+ throw new BusinessException("Token has expired, please log in again.");
+ }
+ return true;
+ }
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ return false;
+ }
+}
diff --git a/src/main/java/com/aida/lanecarford/common/security/JwtUtil.java b/src/main/java/com/aida/lanecarford/common/security/JwtUtil.java
new file mode 100644
index 0000000..b2e7eb0
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/common/security/JwtUtil.java
@@ -0,0 +1,124 @@
+package com.aida.lanecarford.common.security;
+
+import com.aida.lanecarford.common.security.config.JwtProperties;
+import com.aida.lanecarford.vo.AuthPrincipalVO;
+import com.alibaba.fastjson.JSONObject;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.MalformedJwtException;
+import io.jsonwebtoken.security.Keys;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.SecretKey;
+import java.util.Date;
+
+import io.jsonwebtoken.*;
+
+@Slf4j
+@Component
+public class JwtUtil {
+
+ @Resource
+ private JwtProperties jwtProperties;
+
+ private SecretKey getSigningKey() {
+ return Keys.hmacShaKeyFor(jwtProperties.getJwtSecret().getBytes());
+ }
+
+ // 生成JWT token
+ public String generateToken(AuthPrincipalVO principal) {
+ String token = Jwts.builder()
+ .subject(JSONObject.toJSONString(principal))
+ .issuedAt(new Date())
+ .expiration(new Date(System.currentTimeMillis() + jwtProperties.getJwtExpiration()))
+ .signWith(getSigningKey())
+ .compact();
+ return jwtProperties.getJwtTokenPrefix() + token;
+ }
+
+ // 从token中提取用户信息
+ public String extractUserinfo(String token) {
+ return parser(token).getSubject();
+ }
+
+ // 验证token是否有效
+ public boolean validateToken(String token) {
+ try {
+ Claims claims = parser(token);
+ return claims != null && !claims.isEmpty() && !isTokenExpired(claims);
+ } catch (Exception e) {
+ log.debug("Token验证失败: {}", e.getMessage());
+ return false;
+ }
+ }
+
+ // 解析token - 适配 JJWT 0.12.x
+ public Claims parser(String token) {
+ try {
+ // 移除前缀
+ if (token.startsWith(jwtProperties.getJwtTokenPrefix())) {
+ token = token.substring(jwtProperties.getJwtTokenPrefix().length()).trim();
+ }
+
+ return parseClaims(token);
+
+ } catch (ExpiredJwtException e) {
+ log.error("Token已过期: {}", e.getMessage());
+ throw e;
+ } catch (SecurityException | MalformedJwtException e) {
+ log.error("Token格式错误: {}", e.getMessage());
+ return null;
+ } catch (Exception e) {
+ log.error("解析Token失败: {}", e.getMessage());
+ return null;
+ }
+ }
+
+ // 检查token是否过期
+ private boolean isTokenExpired(Claims claims) {
+ return claims.getExpiration().before(new Date());
+ }
+
+ // 解析Claims(共用方法)- 适配 JJWT 0.12.x
+ private Claims parseClaims(String token) {
+ return Jwts.parser()
+ .verifyWith(getSigningKey())
+ .build()
+ .parseSignedClaims(token)
+ .getPayload();
+ }
+
+ // 新增:刷新token
+ public String refreshToken(String token) {
+ Claims claims = parser(token);
+ if (claims == null || isTokenExpired(claims)) {
+ throw new JwtException("无法刷新过期的token");
+ }
+
+ // 使用原始主题创建新token
+ return Jwts.builder()
+ .subject(claims.getSubject())
+ .issuedAt(new Date())
+ .expiration(new Date(System.currentTimeMillis() + jwtProperties.getJwtExpiration()))
+ .signWith(getSigningKey())
+ .compact();
+ }
+
+ // 新增:获取token剩余时间(毫秒)
+ public long getRemainingTime(String token) {
+ try {
+ Claims claims = parser(token);
+ if (claims != null) {
+ long expirationTime = claims.getExpiration().getTime();
+ long currentTime = System.currentTimeMillis();
+ return Math.max(0, expirationTime - currentTime);
+ }
+ } catch (Exception e) {
+ log.debug("获取token剩余时间失败: {}", e.getMessage());
+ }
+ return 0;
+ }
+}
diff --git a/src/main/java/com/aida/lanecarford/common/security/config/JwtProperties.java b/src/main/java/com/aida/lanecarford/common/security/config/JwtProperties.java
new file mode 100644
index 0000000..8b6f40e
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/common/security/config/JwtProperties.java
@@ -0,0 +1,22 @@
+package com.aida.lanecarford.common.security.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * JWT配置类
+ */
+@Data
+@ConfigurationProperties(prefix = "spring.security")
+@Configuration
+public class JwtProperties {
+
+ private String jwtSecret;
+
+ private String jwtTokenHeader;
+
+ private String jwtTokenPrefix;
+
+ private long jwtExpiration;
+}
diff --git a/src/main/java/com/aida/lanecarford/common/security/context/UserContext.java b/src/main/java/com/aida/lanecarford/common/security/context/UserContext.java
new file mode 100644
index 0000000..47458ae
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/common/security/context/UserContext.java
@@ -0,0 +1,19 @@
+package com.aida.lanecarford.common.security.context;
+
+import com.aida.lanecarford.vo.AuthPrincipalVO;
+
+public class UserContext {
+ private static ThreadLocal userHolder = new ThreadLocal();
+
+ public static AuthPrincipalVO getUserHolder() {
+ return userHolder.get();
+ }
+
+ public static void delete() {
+ userHolder.remove();
+ }
+
+ public static void setUserHolder(AuthPrincipalVO authPrincipalVo) {
+ userHolder.set(authPrincipalVo);
+ }
+}
diff --git a/src/main/java/com/aida/lanecarford/config/MinioConfig.java b/src/main/java/com/aida/lanecarford/config/MinioConfig.java
new file mode 100644
index 0000000..c451c3a
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/config/MinioConfig.java
@@ -0,0 +1,58 @@
+package com.aida.lanecarford.config;
+
+import io.minio.MinioClient;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * MinIO 配置类
+ * 用于配置 MinIO 客户端连接参数
+ *
+ * @author Aida
+ * @since 2024-01-01
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "minio")
+public class MinioConfig {
+
+ /**
+ * MinIO 服务端点
+ */
+ private String endpoint;
+
+ /**
+ * 访问密钥
+ */
+ private String accessKey;
+
+ /**
+ * 秘密密钥
+ */
+ private String secretKey;
+
+ /**
+ * 默认存储桶名称
+ */
+ private String bucketName;
+
+ /**
+ * 文件访问URL前缀
+ */
+ private String urlPrefix;
+
+ /**
+ * 创建 MinIO 客户端 Bean
+ *
+ * @return MinioClient 实例
+ */
+ @Bean
+ public MinioClient minioClient() {
+ return MinioClient.builder()
+ .endpoint(endpoint)
+ .credentials(accessKey, secretKey)
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/aida/lanecarford/config/RedisConfig.java b/src/main/java/com/aida/lanecarford/config/RedisConfig.java
new file mode 100644
index 0000000..9d80380
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/config/RedisConfig.java
@@ -0,0 +1,30 @@
+package com.aida.lanecarford.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration
+public class RedisConfig {
+
+ @Bean
+ public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
+ RedisTemplate template = new RedisTemplate<>();
+ template.setConnectionFactory(factory);
+
+ // 使用Spring提供的GenericJackson2JsonRedisSerializer
+ GenericJackson2JsonRedisSerializer serializer =
+ new GenericJackson2JsonRedisSerializer();
+
+ template.setKeySerializer(new StringRedisSerializer());
+ template.setValueSerializer(serializer);
+ template.setHashKeySerializer(new StringRedisSerializer());
+ template.setHashValueSerializer(serializer);
+ template.afterPropertiesSet();
+
+ return template;
+ }
+}
diff --git a/src/main/java/com/aida/lanecarford/config/SwaggerConfig.java b/src/main/java/com/aida/lanecarford/config/SwaggerConfig.java
index cc6acc0..3363158 100644
--- a/src/main/java/com/aida/lanecarford/config/SwaggerConfig.java
+++ b/src/main/java/com/aida/lanecarford/config/SwaggerConfig.java
@@ -20,7 +20,7 @@ import java.util.List;
/**
* Swagger配置类
* 提供完整的API文档配置,包括安全认证、服务器信息和标签分类
- *
+ *
* @author AI Assistant
* @since 2024-01-01
*/
@@ -116,9 +116,10 @@ public class SwaggerConfig {
.in(SecurityScheme.In.COOKIE)
.name("JSESSIONID")
.description("Session认证,通过登录接口获取"))
- .addSecuritySchemes("basicAuth", new SecurityScheme()
- .type(SecurityScheme.Type.HTTP)
- .scheme("basic")
+ .addSecuritySchemes("CustomAuth", new SecurityScheme()
+ .type(SecurityScheme.Type.APIKEY)
+ .name("Authorization")
+ .in(SecurityScheme.In.HEADER)
.description("基础认证(仅用于开发测试)"));
}
@@ -128,7 +129,7 @@ public class SwaggerConfig {
private List createSecurityRequirements() {
return Arrays.asList(
new SecurityRequirement().addList("sessionAuth"),
- new SecurityRequirement().addList("basicAuth")
+ new SecurityRequirement().addList("CustomAuth")
);
}
diff --git a/src/main/java/com/aida/lanecarford/config/WebConfig.java b/src/main/java/com/aida/lanecarford/config/WebConfig.java
index 5fe4125..c034a95 100644
--- a/src/main/java/com/aida/lanecarford/config/WebConfig.java
+++ b/src/main/java/com/aida/lanecarford/config/WebConfig.java
@@ -1,13 +1,18 @@
package com.aida.lanecarford.config;
+import com.aida.lanecarford.common.security.JwtInterceptor;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import java.util.Arrays;
+
/**
* Web配置类 - 纯API后端服务
- *
+ *
* @author AI Assistant
* @since 2024-01-01
*/
@@ -27,6 +32,17 @@ public class WebConfig implements WebMvcConfigurer {
.maxAge(3600);
}
+ @Autowired
+ private JwtInterceptor jwtInterceptor;
+
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(jwtInterceptor)
+ .addPathPatterns("/api/**/**") // 保护这些路径
+ .excludePathPatterns(Arrays.asList("/api/auth/precheckEmail", "/api/auth/registerOrLogin",
+ "/api/auth/forgotPwd", "/api/style/callback")); // 排除登录接口
+ }
+
/**
* 配置资源处理 - 仅保留API文档和文件上传
*/
@@ -35,11 +51,11 @@ public class WebConfig implements WebMvcConfigurer {
// 配置上传文件的访问路径
registry.addResourceHandler("/uploads/**")
.addResourceLocations("file:uploads/");
-
+
// 配置Swagger UI资源
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/");
-
+
registry.addResourceHandler("/v3/api-docs/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
diff --git a/src/main/java/com/aida/lanecarford/controller/ChatController.java b/src/main/java/com/aida/lanecarford/controller/ChatController.java
new file mode 100644
index 0000000..c5ff0c2
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/controller/ChatController.java
@@ -0,0 +1,40 @@
+package com.aida.lanecarford.controller;
+
+import com.aida.lanecarford.service.ChatService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
+
+
+@Slf4j
+@RestController
+@RequestMapping("/api/llm")
+@Tag(name = "LLM对话管理", description = "大语言模型流式对话相关API接口")
+public class ChatController {
+
+ @Resource
+ private ChatService chatService;
+
+ @CrossOrigin
+ @Operation(
+ summary = "流式对话",
+ description = "与大语言模型进行流式对话,返回Server-Sent Events数据流"
+ )
+ @GetMapping(value = "/streamChat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
+ public SseEmitter streamChat(
+ @Parameter(description = "用户输入的消息内容", example = "你好,请介绍一下自己")
+ @RequestParam(required = false) String message,
+
+ @Parameter(description = "会话ID", example = "123456", required = true)
+ @RequestParam Long sessionId,
+
+ @Parameter(description = "性别", example = "male | female", required = true)
+ @RequestParam String gender) {
+ return chatService.streamChat(message, sessionId, gender);
+ }
+}
diff --git a/src/main/java/com/aida/lanecarford/controller/CustomerController.java b/src/main/java/com/aida/lanecarford/controller/CustomerController.java
index e2d2435..b524432 100644
--- a/src/main/java/com/aida/lanecarford/controller/CustomerController.java
+++ b/src/main/java/com/aida/lanecarford/controller/CustomerController.java
@@ -1,21 +1,40 @@
package com.aida.lanecarford.controller;
+import com.aida.lanecarford.common.ApiResponse;
+import com.aida.lanecarford.dto.BaseRequest;
import com.aida.lanecarford.service.CustomerService;
+import com.aida.lanecarford.vo.CustomerCheckInVO;
+import com.aida.lanecarford.vo.CustomerVO;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
/**
* 顾客控制器
- *
- * @author AI Assistant
- * @since 2024-01-01
*/
@RestController
@RequestMapping("/api/customers")
@RequiredArgsConstructor
+@Tag(name = "顾客管理", description = "顾客入店登记、信息查询等相关API接口")
public class CustomerController {
private final CustomerService customerService;
+ @Operation(
+ summary = "顾客入店登记",
+ description = "验证顾客身份并创建入店记录,如果是新顾客则自动注册到系统中。"
+ )
+ @GetMapping("/checkIn")
+ public ApiResponse customerCheckIn(@RequestParam String name, @RequestParam String email) {
+ return ApiResponse.success(customerService.customerCheckIn(name, email));
+ }
+
+ @PostMapping("/getAllCustomer")
+ public ApiResponse> getAllCustomer(@Valid @RequestBody BaseRequest request) {
+ return ApiResponse.success(customerService.getAllCustomer(request));
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/com/aida/lanecarford/controller/CustomerPhotoController.java b/src/main/java/com/aida/lanecarford/controller/CustomerPhotoController.java
index a8bcf2e..c15cd48 100644
--- a/src/main/java/com/aida/lanecarford/controller/CustomerPhotoController.java
+++ b/src/main/java/com/aida/lanecarford/controller/CustomerPhotoController.java
@@ -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.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
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 upload(@ModelAttribute CustomerPhotoDto customerPhotoDto) {
+ CustomerPhoto customerPhoto = customerPhotoService.upload(customerPhotoDto);
+ return ApiResponse.success(customerPhoto);
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/com/aida/lanecarford/controller/LoginController.java b/src/main/java/com/aida/lanecarford/controller/LoginController.java
new file mode 100644
index 0000000..d856d41
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/controller/LoginController.java
@@ -0,0 +1,99 @@
+package com.aida.lanecarford.controller;
+
+import com.aida.lanecarford.common.ApiResponse;
+import com.aida.lanecarford.dto.LoginRequest;
+import com.aida.lanecarford.entity.User;
+import com.aida.lanecarford.service.LoginService;
+import com.aida.lanecarford.vo.LoginVO;
+import io.swagger.v3.oas.annotations.Hidden;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * 用户认证控制器
+ */
+@RestController
+@RequestMapping("/api/auth")
+@RequiredArgsConstructor
+@Tag(name = "用户认证管理", description = "用户注册、登录、登出等认证相关API接口")
+public class LoginController {
+
+ private final LoginService loginService;
+
+ @Operation(
+ summary = "预检查并发送邮箱验证码",
+ description = "根据操作类型验证邮箱有效性并发送验证码。支持注册、登录、忘记密码三种操作类型。"
+ )
+ @PostMapping("/precheckAndSendEmail")
+ @Hidden
+ public ApiResponse preCheckAndSendEmail(@Valid @RequestBody LoginRequest loginRequest) {
+ loginService.preCheckAndSendEmail(loginRequest);
+ return ApiResponse.success("验证码已发送到您的邮箱");
+ }
+
+ @Operation(
+ summary = "检查邮箱",
+ description = "根据操作类型验证邮箱有效性并发送验证码。仅支持忘记密码。"
+ )
+ @GetMapping("/precheckEmail")
+ public ApiResponse precheckForgotPwdAndSendEmail(@Valid @RequestParam String email) {
+ loginService.precheckForgotPwdAndSendEmail(email);
+ return ApiResponse.success("验证码已发送到您的邮箱");
+ }
+
+ @Operation(
+ summary = "用户注册或登录",
+ description = "通过验证码完成用户注册或登录,返回JWT令牌和用户信息。"
+ )
+ @PostMapping("/registerOrLogin")
+ public ApiResponse registerOrLogin(@Valid @RequestBody LoginRequest loginRequest) {
+ return ApiResponse.success(loginService.registerOrLogin(loginRequest));
+ }
+
+ @Operation(
+ summary = "用户登出",
+ description = "清除用户登录状态,使当前JWT令牌失效。"
+ )
+ @GetMapping("/logout")
+ public ApiResponse logout() {
+ loginService.logout();
+ return ApiResponse.success("登出成功");
+ }
+
+ @Operation(
+ summary = "忘记密码",
+ description = "通过邮箱验证码重置用户密码。需要先获取验证码,然后提供新密码。"
+ )
+ @PostMapping("/forgotPwd")
+ public ApiResponse forgotPwd(@Valid @RequestBody LoginRequest loginRequest) {
+ loginService.forgotPwd(loginRequest);
+ return ApiResponse.success("密码重置成功");
+ }
+
+ @Operation(
+ summary = "检查登录状态",
+ description = "验证当前用户的登录状态是否有效,检查JWT令牌是否过期。"
+ )
+ @GetMapping("/checkLoginStatus")
+ public ApiResponse checkLoginStatus() {
+ boolean isLogin = loginService.checkLoginStatus();
+ if (isLogin) {
+ return ApiResponse.success("用户已登录");
+ } else {
+ return ApiResponse.error("Please log in again.");
+ }
+ }
+
+ @Operation(
+ summary = "获取用户信息",
+ description = "通过token获取当前用户信息"
+ )
+ @GetMapping("/getUserInfo")
+ public ApiResponse getUserInfo() {
+ return ApiResponse.success(loginService.getUserInfo());
+ }
+
+}
diff --git a/src/main/java/com/aida/lanecarford/controller/StyleController.java b/src/main/java/com/aida/lanecarford/controller/StyleController.java
index ec08b9c..d11a982 100644
--- a/src/main/java/com/aida/lanecarford/controller/StyleController.java
+++ b/src/main/java/com/aida/lanecarford/controller/StyleController.java
@@ -1,9 +1,20 @@
package com.aida.lanecarford.controller;
+import com.aida.lanecarford.common.ApiResponse;
+import com.aida.lanecarford.dto.OutfitCallbackDTO;
+import com.aida.lanecarford.dto.RequestOutfitDTO;
import com.aida.lanecarford.service.StyleService;
+import com.aida.lanecarford.vo.OutfitResultVO;
+import io.swagger.v3.oas.annotations.Hidden;
+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.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
/**
* 风格配置控制器
@@ -11,11 +22,60 @@ import org.springframework.web.bind.annotation.RestController;
* @author AI Assistant
* @since 2024-01-01
*/
+@Slf4j
@RestController
-@RequestMapping("/api/styles")
+@RequestMapping("/api/style")
@RequiredArgsConstructor
+@Tag(name = "穿搭风格管理", description = "AI穿搭推荐和结果查询相关API接口")
public class StyleController {
private final StyleService styleService;
+ /**
+ * 请求AI穿搭推荐
+ * 提交穿搭需求给AI模型,异步生成穿搭方案
+ *
+ * @param requestOutfitDTO 穿搭请求参数DTO
+ * @return 包含请求ID列表的响应结果
+ */
+ @Operation(
+ summary = "请求AI穿搭推荐",
+ description = "提交用户的穿搭需求给AI模型进行异步处理,返回请求ID用于后续结果查询"
+ )
+ @PostMapping("/requestOutfit")
+ public ApiResponse> requestOutfit(@Valid @RequestBody RequestOutfitDTO requestOutfitDTO) {
+ return ApiResponse.success(styleService.requestOutfit(requestOutfitDTO));
+ }
+
+ /**
+ * AI服务回调接口
+ * 接收AI服务处理完成后的回调通知,更新穿搭结果状态
+ * 注意:此接口为内部接口,供AI服务调用,不对外暴露文档
+ *
+ * @param callbackDTO AI回调数据DTO
+ */
+ @Hidden
+ @PostMapping("/callback")
+ public void callback(@RequestBody OutfitCallbackDTO callbackDTO) {
+ styleService.callback(callbackDTO);
+ }
+
+ /**
+ * 获取穿搭结果
+ * 根据请求ID列表查询AI生成的穿搭方案结果
+ *
+ * @param requestIDs 请求ID列表,通过requestOutfit接口获取
+ * @return 穿搭结果视图对象列表
+ */
+ @Operation(
+ summary = "获取穿搭结果",
+ description = "根据请求ID列表查询AI生成的穿搭方案结果,支持批量查询"
+ )
+ @GetMapping("/getOutfitResult")
+ public ApiResponse> getOutfitResult(
+ @Parameter(description = "请求ID列表", required = true, example = "[a22019ac-9db2-4076-9953-42d65e8120ec, 20217a09-435d-4b34-962a-3af59881c6d9]")
+ @RequestParam List requestIDs) {
+ return ApiResponse.success(styleService.getOutfitResult(requestIDs));
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/com/aida/lanecarford/controller/TryOnEffectController.java b/src/main/java/com/aida/lanecarford/controller/TryOnEffectController.java
index 128a9be..702650a 100644
--- a/src/main/java/com/aida/lanecarford/controller/TryOnEffectController.java
+++ b/src/main/java/com/aida/lanecarford/controller/TryOnEffectController.java
@@ -1,9 +1,17 @@
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.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
/**
* 试穿效果控制器
@@ -14,8 +22,59 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/try-on-effects")
@RequiredArgsConstructor
+@Tag(name = "试穿效果管理", description = "试穿效果生成和管理相关API")
public class TryOnEffectController {
private final TryOnEffectService tryOnEffectService;
+ @Operation(summary = "生成试穿效果", description = "根据服装,模特照片生成试穿效果,其中styleId是必选,当二次生成时,要带上相关参数,比如顾客照片")
+ @PostMapping("/generate")
+ public ApiResponse generateTryOnEffect(
+ @Parameter(description = "试穿效果请求参数", required = true)
+ @Valid @RequestBody TryOnEffect tryOnEffectDto) {
+ TryOnResultVo tryOnResultVo = tryOnEffectService.generateTryOnEffect(tryOnEffectDto);
+ return ApiResponse.success(tryOnResultVo);
+ }
+
+ @Operation(summary = "获取收藏的试穿效果", description = "对应library页面点击details后的显示,参数为进店记录id")
+ @GetMapping("/favorites/{visitRecordId}")
+ public ApiResponse> getFavoriteTryOnEffects(
+ @Parameter(description = "进店记录ID", required = true)
+ @PathVariable Long visitRecordId) {
+ List tryOnResultVos = tryOnEffectService.getFavoriteTryOnEffects(visitRecordId);
+ return ApiResponse.success(tryOnResultVos);
+ }
+
+ @GetMapping("/style/{styleId}")
+ @Operation(summary = "获取某套服装的所有生成结果", description = "对应customize your look页面点击finish后的显示")
+ public ApiResponse> getTryOnEffectsByStyleId(
+ @Parameter(description = "服装ID", required = true)
+ @PathVariable Long styleId) {
+ List tryOnResultVos = tryOnEffectService.getTryOnEffectsByStyleId(styleId);
+ return ApiResponse.success(tryOnResultVos);
+ }
+
+ /**
+ * 设置喜欢的试穿效果
+ */
+ @Operation(summary = "设置喜欢的试穿效果", description = "将指定试穿效果设置为收藏")
+ @PostMapping("/set-favorite/{tryOnId}")
+ public ApiResponse setFavoriteTryOnEffect(
+ @Parameter(description = "试穿效果ID", required = true)
+ @PathVariable Long tryOnId) {
+ tryOnEffectService.setFavoriteTryOnEffect(tryOnId);
+ return ApiResponse.success();
+ }
+
+ /**
+ * 取消喜欢的试穿效果
+ */
+ @Operation(summary = "取消喜欢的试穿效果", description = "取消指定试穿效果的收藏")
+ @PostMapping("/cancel-favorite/{tryOnId}")
+ public ApiResponse cancelFavoriteTryOnEffect(
+ @Parameter(description = "试穿效果ID", required = true)
+ @PathVariable Long tryOnId) {
+ tryOnEffectService.cancelFavoriteTryOnEffect(tryOnId);
+ return ApiResponse.success();
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/aida/lanecarford/controller/VisitRecordController.java b/src/main/java/com/aida/lanecarford/controller/VisitRecordController.java
index 14be7d4..e056662 100644
--- a/src/main/java/com/aida/lanecarford/controller/VisitRecordController.java
+++ b/src/main/java/com/aida/lanecarford/controller/VisitRecordController.java
@@ -1,9 +1,16 @@
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 io.swagger.v3.oas.annotations.Operation;
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 +18,7 @@ import org.springframework.web.bind.annotation.RestController;
* @author AI Assistant
* @since 2024-01-01
*/
+@Slf4j
@RestController
@RequestMapping("/api/visit-records")
@RequiredArgsConstructor
@@ -18,4 +26,41 @@ public class VisitRecordController {
private final VisitRecordService visitRecordService;
+ /**
+ * 根据ID删除进店记录(逻辑删除)
+ *
+ * @param id 进店记录ID
+ * @return 删除结果
+ */
+ @DeleteMapping("/{id}")
+ public ApiResponse 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}")
+ @Operation(summary = "根据顾客ID查询所有进店记录",description = "打开libiary页面调用这个接口,参数为当前顾客的id")
+ public ApiResponse> getByCustomerId(@PathVariable Long customerId) {
+ log.info("开始查询顾客ID为{}的进店记录", customerId);
+
+ List 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);
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/com/aida/lanecarford/dto/BaseRequest.java b/src/main/java/com/aida/lanecarford/dto/BaseRequest.java
index 6d8e6df..dd527ba 100644
--- a/src/main/java/com/aida/lanecarford/dto/BaseRequest.java
+++ b/src/main/java/com/aida/lanecarford/dto/BaseRequest.java
@@ -5,17 +5,15 @@ import lombok.Data;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
+
import java.io.Serializable;
/**
* 请求参数基类
- *
- * @author AI Assistant
- * @since 2024-01-01
*/
@Data
@Schema(description = "请求参数基类")
-public abstract class BaseRequest implements Serializable {
+public class BaseRequest implements Serializable {
private static final long serialVersionUID = 1L;
@@ -71,8 +69,8 @@ public abstract class BaseRequest implements Serializable {
* 是否有时间范围筛选
*/
public boolean hasTimeRange() {
- return startTime != null && !startTime.trim().isEmpty()
- && endTime != null && !endTime.trim().isEmpty();
+ return startTime != null && !startTime.trim().isEmpty()
+ && endTime != null && !endTime.trim().isEmpty();
}
/**
diff --git a/src/main/java/com/aida/lanecarford/dto/CustomerPhotoDto.java b/src/main/java/com/aida/lanecarford/dto/CustomerPhotoDto.java
new file mode 100644
index 0000000..f7d136b
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/dto/CustomerPhotoDto.java
@@ -0,0 +1,15 @@
+package com.aida.lanecarford.dto;
+
+import com.aida.lanecarford.entity.CustomerPhoto;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+@Data
+@Schema(description = "顾客照片数据传输对象")
+public class CustomerPhotoDto extends CustomerPhoto {
+ @NotNull(message = "file.cannot.be.empty")
+ @Schema(description = "上传的照片文件", required = true, type = "string", format = "binary")
+ private MultipartFile file;
+}
\ No newline at end of file
diff --git a/src/main/java/com/aida/lanecarford/dto/LoginRequest.java b/src/main/java/com/aida/lanecarford/dto/LoginRequest.java
new file mode 100644
index 0000000..04e2fea
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/dto/LoginRequest.java
@@ -0,0 +1,63 @@
+package com.aida.lanecarford.dto;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Data
+@Schema(description = "登录请求数据传输对象")
+public class LoginRequest {
+
+
+ @Schema(
+ description = "用户名(仅注册时需要)",
+ example = "张三",
+ maxLength = 50
+ )
+ private String name;
+
+
+ @NotBlank(message = "email cannot be empty")
+ @Email(message = "Email format is incorrect.")
+ @Schema(
+ description = "邮箱地址",
+ example = "user@example.com",
+ required = true,
+ format = "email"
+ )
+ private String email;
+
+
+ @NotBlank(message = "password cannot be empty")
+ @Size(min = 6, message = "Password must be at least 6 characters.")
+ @Schema(
+ description = "密码(至少6位字符)",
+ example = "password123",
+ required = true,
+ minLength = 6,
+ maxLength = 100
+ )
+ private String password;
+
+
+ @NotBlank(message = "operation type cannot be empty")
+ @Schema(
+ description = "操作类型",
+ example = "REGISTER",
+ required = true,
+ allowableValues = {"REGISTER", "LOGIN", "FORGET_PWD"/*,"CHANGE_MAILBOX","EXCEPTION_IP","BIND_MAILBOX"*/}
+ )
+ private String operationType;
+
+
+ @Schema(
+ description = "邮箱验证码(6位数字)",
+ example = "123456",
+ pattern = "\\d{6}",
+ minLength = 6,
+ maxLength = 6
+ )
+ private String verifyCode;
+}
\ No newline at end of file
diff --git a/src/main/java/com/aida/lanecarford/dto/OutfitCallbackDTO.java b/src/main/java/com/aida/lanecarford/dto/OutfitCallbackDTO.java
new file mode 100644
index 0000000..275bc67
--- /dev/null
+++ b/src/main/java/com/aida/lanecarford/dto/OutfitCallbackDTO.java
@@ -0,0 +1,19 @@
+package com.aida.lanecarford.dto;
+
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Data
+public class OutfitCallbackDTO {
+
+ private String outfit_id;
+
+ // 取值范围:ok || failed || stop
+ private String status;
+
+ private String path;
+
+ private List