first commit
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
package com.aida.lanecarford;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
@MapperScan("com.aida.lanecarford.mapper")
|
||||
public class LanecarfordApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(LanecarfordApplication.class, args);
|
||||
System.out.println("LaneCarfordApplication started successfully.");
|
||||
}
|
||||
|
||||
}
|
||||
127
src/main/java/com/aida/lanecarford/aspect/LoggingAspect.java
Normal file
127
src/main/java/com/aida/lanecarford/aspect/LoggingAspect.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package com.aida.lanecarford.aspect;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
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
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class LoggingAspect {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
|
||||
|
||||
/**
|
||||
* 定义切点:所有Controller方法
|
||||
*/
|
||||
@Pointcut("execution(* com.aida.lanecarford.controller..*(..))")
|
||||
public void controllerMethods() {}
|
||||
|
||||
/**
|
||||
* 定义切点:所有Service方法
|
||||
*/
|
||||
@Pointcut("execution(* com.aida.lanecarford.service..*(..))")
|
||||
public void serviceMethods() {}
|
||||
|
||||
/**
|
||||
* Controller方法执行前记录日志
|
||||
*/
|
||||
@Before("controllerMethods()")
|
||||
public void logControllerBefore(JoinPoint joinPoint) {
|
||||
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("请求参数: {}", Arrays.toString(joinPoint.getArgs()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller方法执行后记录日志
|
||||
*/
|
||||
@AfterReturning(pointcut = "controllerMethods()", returning = "result")
|
||||
public void logControllerAfterReturning(JoinPoint joinPoint, Object result) {
|
||||
logger.info("方法执行成功: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
|
||||
logger.info("返回结果: {}", result);
|
||||
logger.info("=== 请求结束 ===");
|
||||
}
|
||||
|
||||
/**
|
||||
* Controller方法抛出异常时记录日志
|
||||
*/
|
||||
@AfterThrowing(pointcut = "controllerMethods()", throwing = "exception")
|
||||
public void logControllerAfterThrowing(JoinPoint joinPoint, Throwable exception) {
|
||||
logger.error("方法执行异常: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
|
||||
logger.error("异常信息: ", exception);
|
||||
logger.info("=== 请求异常结束 ===");
|
||||
}
|
||||
|
||||
/**
|
||||
* Service方法环绕通知,记录执行时间
|
||||
*/
|
||||
@Around("serviceMethods()")
|
||||
public Object logServiceAround(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
long startTime = System.currentTimeMillis();
|
||||
String methodName = joinPoint.getSignature().getDeclaringTypeName() + "." + 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();
|
||||
logger.error("Service方法执行异常: {}, 耗时: {}ms", methodName, (endTime - startTime));
|
||||
logger.error("异常详情: ", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端真实IP地址
|
||||
*/
|
||||
private String getClientIpAddress(HttpServletRequest request) {
|
||||
String xForwardedFor = request.getHeader("X-Forwarded-For");
|
||||
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();
|
||||
}
|
||||
}
|
||||
134
src/main/java/com/aida/lanecarford/aspect/PerformanceAspect.java
Normal file
134
src/main/java/com/aida/lanecarford/aspect/PerformanceAspect.java
Normal file
@@ -0,0 +1,134 @@
|
||||
package com.aida.lanecarford.aspect;
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 性能监控切面
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Aspect
|
||||
@Component
|
||||
public class PerformanceAspect {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PerformanceAspect.class);
|
||||
|
||||
/**
|
||||
* 监控Controller方法性能
|
||||
*/
|
||||
@Around("execution(* com.aida.lanecarford.controller..*(..))")
|
||||
public Object monitorControllerPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
return monitorMethodPerformance(joinPoint, "Controller");
|
||||
}
|
||||
|
||||
/**
|
||||
* 监控Service方法性能
|
||||
*/
|
||||
@Around("execution(* com.aida.lanecarford.service..*(..))")
|
||||
public Object monitorServicePerformance(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
return monitorMethodPerformance(joinPoint, "Service");
|
||||
}
|
||||
|
||||
/**
|
||||
* 监控数据库操作性能
|
||||
*/
|
||||
@Around("execution(* com.aida.lanecarford.mapper..*(..))")
|
||||
public Object monitorMapperPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
return monitorMethodPerformance(joinPoint, "Mapper");
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用性能监控方法
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录性能日志
|
||||
*/
|
||||
private void logPerformance(String layer, String methodName, long executionTime, boolean success) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("性能监控 - {}: {} - {}ms - {}",
|
||||
layer, methodName, executionTime, success ? "成功" : "失败");
|
||||
}
|
||||
|
||||
// 可以在这里添加性能数据收集逻辑,比如发送到监控系统
|
||||
collectPerformanceMetrics(layer, methodName, executionTime, success);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取警告阈值(毫秒)
|
||||
*/
|
||||
private long getWarningThreshold(String layer) {
|
||||
switch (layer) {
|
||||
case "Controller":
|
||||
return 5000; // 5秒
|
||||
case "Service":
|
||||
return 3000; // 3秒
|
||||
case "Mapper":
|
||||
return 1000; // 1秒
|
||||
default:
|
||||
return 2000; // 2秒
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集性能指标
|
||||
* TODO: 可以集成到监控系统(如Micrometer、Prometheus等)
|
||||
*/
|
||||
private void collectPerformanceMetrics(String layer, String methodName, long executionTime, boolean success) {
|
||||
// 这里可以添加性能指标收集逻辑
|
||||
// 例如:
|
||||
// - 发送到时序数据库
|
||||
// - 更新内存中的统计信息
|
||||
// - 发送到监控系统
|
||||
|
||||
// 示例:简单的内存统计
|
||||
PerformanceMetrics.recordExecution(layer, methodName, executionTime, success);
|
||||
}
|
||||
|
||||
/**
|
||||
* 简单的性能指标收集器
|
||||
*/
|
||||
private static class PerformanceMetrics {
|
||||
|
||||
public static void recordExecution(String layer, String methodName, long executionTime, boolean success) {
|
||||
// 简单的日志记录,实际项目中可以替换为更复杂的指标收集
|
||||
if (executionTime > 1000) { // 超过1秒的操作
|
||||
logger.info("慢操作记录 - {}.{}: {}ms, 成功: {}", layer, methodName, executionTime, success);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
168
src/main/java/com/aida/lanecarford/common/ApiResponse.java
Normal file
168
src/main/java/com/aida/lanecarford/common/ApiResponse.java
Normal file
@@ -0,0 +1,168 @@
|
||||
package com.aida.lanecarford.common;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 统一API响应格式
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class ApiResponse<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 响应状态码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 响应数据
|
||||
*/
|
||||
private T data;
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* 时间戳
|
||||
*/
|
||||
private long timestamp;
|
||||
|
||||
public ApiResponse() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public ApiResponse(boolean success, String code, String message, T data) {
|
||||
this();
|
||||
this.success = success;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应(无数据)
|
||||
*/
|
||||
public static <T> ApiResponse<T> success() {
|
||||
return new ApiResponse<>(true, "SUCCESS", "操作成功", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应(带数据)
|
||||
*/
|
||||
public static <T> ApiResponse<T> success(T data) {
|
||||
return new ApiResponse<>(true, "SUCCESS", "操作成功", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应(自定义消息,无数据)
|
||||
*/
|
||||
public static <T> ApiResponse<T> success(String message) {
|
||||
return new ApiResponse<>(true, "SUCCESS", message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功响应(自定义消息)
|
||||
*/
|
||||
public static <T> ApiResponse<T> success(String message, T data) {
|
||||
return new ApiResponse<>(true, "SUCCESS", message, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应
|
||||
*/
|
||||
public static <T> ApiResponse<T> error(String code, String message) {
|
||||
return new ApiResponse<>(false, code, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败响应(默认错误码)
|
||||
*/
|
||||
public static <T> ApiResponse<T> error(String message) {
|
||||
return new ApiResponse<>(false, "ERROR", message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 参数错误响应
|
||||
*/
|
||||
public static <T> ApiResponse<T> paramError(String message) {
|
||||
return new ApiResponse<>(false, "PARAM_ERROR", message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据不存在响应
|
||||
*/
|
||||
public static <T> ApiResponse<T> notFound(String message) {
|
||||
return new ApiResponse<>(false, "NOT_FOUND", message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限不足响应
|
||||
*/
|
||||
public static <T> ApiResponse<T> forbidden(String message) {
|
||||
return new ApiResponse<>(false, "FORBIDDEN", message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务器内部错误响应
|
||||
*/
|
||||
public static <T> ApiResponse<T> serverError(String message) {
|
||||
return new ApiResponse<>(false, "SERVER_ERROR", message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务异常响应
|
||||
*/
|
||||
public static <T> ApiResponse<T> businessError(String code, String message) {
|
||||
return new ApiResponse<>(false, code, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 外部服务错误响应
|
||||
*/
|
||||
public static <T> ApiResponse<T> externalServiceError(String message) {
|
||||
return new ApiResponse<>(false, "EXTERNAL_SERVICE_ERROR", message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传错误响应
|
||||
*/
|
||||
public static <T> ApiResponse<T> fileUploadError(String message) {
|
||||
return new ApiResponse<>(false, "FILE_UPLOAD_ERROR", message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证失败响应
|
||||
*/
|
||||
public static <T> ApiResponse<T> validationError(String message) {
|
||||
return new ApiResponse<>(false, "VALIDATION_ERROR", message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重复数据响应
|
||||
*/
|
||||
public static <T> ApiResponse<T> duplicateError(String message) {
|
||||
return new ApiResponse<>(false, "DUPLICATE_ERROR", message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作超时响应
|
||||
*/
|
||||
public static <T> ApiResponse<T> timeoutError(String message) {
|
||||
return new ApiResponse<>(false, "TIMEOUT_ERROR", message, null);
|
||||
}
|
||||
}
|
||||
136
src/main/java/com/aida/lanecarford/common/PageResult.java
Normal file
136
src/main/java/com/aida/lanecarford/common/PageResult.java
Normal file
@@ -0,0 +1,136 @@
|
||||
package com.aida.lanecarford.common;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 分页结果类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
public class PageResult<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 数据列表
|
||||
*/
|
||||
private List<T> records;
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
private long total;
|
||||
|
||||
/**
|
||||
* 当前页码
|
||||
*/
|
||||
private long current;
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
private long size;
|
||||
|
||||
/**
|
||||
* 总页数
|
||||
*/
|
||||
private long pages;
|
||||
|
||||
/**
|
||||
* 是否有上一页
|
||||
*/
|
||||
private boolean hasPrevious;
|
||||
|
||||
/**
|
||||
* 是否有下一页
|
||||
*/
|
||||
private boolean hasNext;
|
||||
|
||||
public PageResult() {
|
||||
}
|
||||
|
||||
public PageResult(List<T> records, long total, long current, long size) {
|
||||
this.records = records;
|
||||
this.total = total;
|
||||
this.current = current;
|
||||
this.size = size;
|
||||
this.pages = (total + size - 1) / size;
|
||||
this.hasPrevious = current > 1;
|
||||
this.hasNext = current < pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从MyBatis-Plus的IPage转换
|
||||
*/
|
||||
public static <T> PageResult<T> of(IPage<T> page) {
|
||||
return new PageResult<>(
|
||||
page.getRecords(),
|
||||
page.getTotal(),
|
||||
page.getCurrent(),
|
||||
page.getSize()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建空的分页结果
|
||||
*/
|
||||
public static <T> PageResult<T> empty() {
|
||||
return new PageResult<>(List.of(), 0, 1, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建空的分页结果(指定页码和大小)
|
||||
*/
|
||||
public static <T> PageResult<T> empty(long current, long size) {
|
||||
return new PageResult<>(List.of(), 0, current, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建单页结果
|
||||
*/
|
||||
public static <T> PageResult<T> of(List<T> records) {
|
||||
return new PageResult<>(records, records.size(), 1, records.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取开始记录索引(从1开始)
|
||||
*/
|
||||
public long getStartIndex() {
|
||||
return (current - 1) * size + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取结束记录索引
|
||||
*/
|
||||
public long getEndIndex() {
|
||||
long end = current * size;
|
||||
return Math.min(end, total);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为空结果
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return records == null || records.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为第一页
|
||||
*/
|
||||
public boolean isFirst() {
|
||||
return current == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为最后一页
|
||||
*/
|
||||
public boolean isLast() {
|
||||
return current == pages;
|
||||
}
|
||||
}
|
||||
53
src/main/java/com/aida/lanecarford/config/CacheConfig.java
Normal file
53
src/main/java/com/aida/lanecarford/config/CacheConfig.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package com.aida.lanecarford.config;
|
||||
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
|
||||
/**
|
||||
* 缓存配置类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
public class CacheConfig {
|
||||
|
||||
/**
|
||||
* 缓存名称常量
|
||||
*/
|
||||
public static final String STYLE_OUTFITS_CACHE = "styleOutfits";
|
||||
public static final String MODEL_PHOTOS_CACHE = "modelPhotos";
|
||||
public static final String VIRTUAL_TRYONS_CACHE = "virtualTryOns";
|
||||
public static final String FAVORITE_OUTFITS_CACHE = "favoriteOutfits";
|
||||
public static final String CUSTOMER_CACHE = "customers";
|
||||
public static final String SALES_ADVISOR_CACHE = "salesAdvisors";
|
||||
|
||||
/**
|
||||
* 默认缓存管理器
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public CacheManager cacheManager() {
|
||||
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
|
||||
|
||||
// 预创建缓存
|
||||
cacheManager.setCacheNames(java.util.Arrays.asList(
|
||||
STYLE_OUTFITS_CACHE,
|
||||
MODEL_PHOTOS_CACHE,
|
||||
VIRTUAL_TRYONS_CACHE,
|
||||
FAVORITE_OUTFITS_CACHE,
|
||||
CUSTOMER_CACHE,
|
||||
SALES_ADVISOR_CACHE
|
||||
));
|
||||
|
||||
// 允许空值缓存
|
||||
cacheManager.setAllowNullValues(true);
|
||||
|
||||
return cacheManager;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.aida.lanecarford.config;
|
||||
|
||||
import org.springframework.boot.web.servlet.MultipartConfigFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.unit.DataSize;
|
||||
|
||||
import jakarta.servlet.MultipartConfigElement;
|
||||
|
||||
/**
|
||||
* 文件上传配置类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
public class FileUploadConfig {
|
||||
|
||||
/**
|
||||
* 配置文件上传参数
|
||||
*/
|
||||
@Bean
|
||||
public MultipartConfigElement multipartConfigElement() {
|
||||
MultipartConfigFactory factory = new MultipartConfigFactory();
|
||||
|
||||
// 设置单个文件最大大小(10MB)
|
||||
factory.setMaxFileSize(DataSize.ofMegabytes(10));
|
||||
|
||||
// 设置总上传数据最大大小(50MB)
|
||||
factory.setMaxRequestSize(DataSize.ofMegabytes(50));
|
||||
|
||||
// 设置内存临界值(1MB)
|
||||
factory.setFileSizeThreshold(DataSize.ofMegabytes(1));
|
||||
|
||||
return factory.createMultipartConfig();
|
||||
}
|
||||
}
|
||||
36
src/main/java/com/aida/lanecarford/config/LoggingConfig.java
Normal file
36
src/main/java/com/aida/lanecarford/config/LoggingConfig.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package com.aida.lanecarford.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.filter.CommonsRequestLoggingFilter;
|
||||
|
||||
/**
|
||||
* 日志配置类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
public class LoggingConfig {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LoggingConfig.class);
|
||||
|
||||
/**
|
||||
* 配置请求日志过滤器
|
||||
*/
|
||||
@Bean
|
||||
public CommonsRequestLoggingFilter requestLoggingFilter() {
|
||||
CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
|
||||
filter.setIncludeClientInfo(true);
|
||||
filter.setIncludeQueryString(true);
|
||||
filter.setIncludePayload(true);
|
||||
filter.setIncludeHeaders(false);
|
||||
filter.setMaxPayloadLength(1000);
|
||||
filter.setAfterMessagePrefix("REQUEST DATA : ");
|
||||
|
||||
logger.info("请求日志过滤器已配置");
|
||||
return filter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.aida.lanecarford.config;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus配置类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
public class MyBatisPlusConfig implements MetaObjectHandler {
|
||||
|
||||
/**
|
||||
* 插入时自动填充
|
||||
*/
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
this.strictInsertFill(metaObject, "createdTime", LocalDateTime.class, LocalDateTime.now());
|
||||
this.strictInsertFill(metaObject, "updatedTime", LocalDateTime.class, LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新时自动填充
|
||||
*/
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
this.strictUpdateFill(metaObject, "updatedTime", LocalDateTime.class, LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页插件配置
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
|
||||
return interceptor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.aida.lanecarford.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* 性能配置类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class PerformanceConfig {
|
||||
|
||||
/**
|
||||
* 配置异步任务执行器
|
||||
*/
|
||||
@Bean(name = "taskExecutor")
|
||||
public Executor taskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
|
||||
// 核心线程数
|
||||
executor.setCorePoolSize(5);
|
||||
|
||||
// 最大线程数
|
||||
executor.setMaxPoolSize(20);
|
||||
|
||||
// 队列容量
|
||||
executor.setQueueCapacity(100);
|
||||
|
||||
// 线程名前缀
|
||||
executor.setThreadNamePrefix("LaneCarford-Async-");
|
||||
|
||||
// 线程空闲时间(秒)
|
||||
executor.setKeepAliveSeconds(60);
|
||||
|
||||
// 拒绝策略:由调用线程处理
|
||||
executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
|
||||
|
||||
// 等待所有任务结束后再关闭线程池
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
|
||||
// 等待时间(秒)
|
||||
executor.setAwaitTerminationSeconds(60);
|
||||
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置文件处理专用线程池
|
||||
*/
|
||||
@Bean(name = "fileTaskExecutor")
|
||||
public Executor fileTaskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
|
||||
// 文件处理通常是IO密集型,可以设置更多线程
|
||||
executor.setCorePoolSize(3);
|
||||
executor.setMaxPoolSize(10);
|
||||
executor.setQueueCapacity(50);
|
||||
executor.setThreadNamePrefix("LaneCarford-File-");
|
||||
executor.setKeepAliveSeconds(60);
|
||||
executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
executor.setAwaitTerminationSeconds(60);
|
||||
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置AI服务调用专用线程池
|
||||
*/
|
||||
@Bean(name = "aiServiceExecutor")
|
||||
public Executor aiServiceExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
|
||||
// AI服务调用可能耗时较长,设置较少的线程数
|
||||
executor.setCorePoolSize(2);
|
||||
executor.setMaxPoolSize(5);
|
||||
executor.setQueueCapacity(20);
|
||||
executor.setThreadNamePrefix("LaneCarford-AI-");
|
||||
executor.setKeepAliveSeconds(120);
|
||||
executor.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
|
||||
executor.setWaitForTasksToCompleteOnShutdown(true);
|
||||
executor.setAwaitTerminationSeconds(120);
|
||||
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.aida.lanecarford.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* RestTemplate配置类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
/**
|
||||
* 创建RestTemplate Bean
|
||||
*
|
||||
* @param builder RestTemplate构建器
|
||||
* @return RestTemplate实例
|
||||
*/
|
||||
@Bean
|
||||
public RestTemplate restTemplate(RestTemplateBuilder builder) {
|
||||
return builder
|
||||
.setConnectTimeout(Duration.ofSeconds(30))
|
||||
.setReadTimeout(Duration.ofSeconds(60))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.aida.lanecarford.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
/**
|
||||
* Spring Security配置类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
/**
|
||||
* 密码编码器
|
||||
*/
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证管理器
|
||||
*/
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||
return config.getAuthenticationManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全过滤器链配置
|
||||
*/
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// 禁用CSRF保护
|
||||
.csrf(csrf -> csrf.disable())
|
||||
|
||||
// 配置授权规则 - 允许所有请求访问(基础架构模式)
|
||||
.authorizeHttpRequests(authz -> authz
|
||||
.requestMatchers("/actuator/**", "/api-docs/**", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||
.anyRequest().permitAll()
|
||||
);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
172
src/main/java/com/aida/lanecarford/config/SwaggerConfig.java
Normal file
172
src/main/java/com/aida/lanecarford/config/SwaggerConfig.java
Normal file
@@ -0,0 +1,172 @@
|
||||
package com.aida.lanecarford.config;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.ExternalDocumentation;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import io.swagger.v3.oas.models.tags.Tag;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Swagger配置类
|
||||
* 提供完整的API文档配置,包括安全认证、服务器信息和标签分类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Value("${server.port:8080}")
|
||||
private String serverPort;
|
||||
|
||||
@Value("${spring.application.name:lanecarford}")
|
||||
private String applicationName;
|
||||
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(createApiInfo())
|
||||
.servers(createServers())
|
||||
.components(createComponents())
|
||||
.security(createSecurityRequirements())
|
||||
.tags(createTags())
|
||||
.externalDocs(createExternalDocumentation());
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建API信息
|
||||
*/
|
||||
private Info createApiInfo() {
|
||||
return new Info()
|
||||
.title("Lane Carford 虚拟试衣系统 API文档")
|
||||
.description("""
|
||||
## Lane Carford 虚拟试衣系统后端接口文档
|
||||
|
||||
### 系统功能
|
||||
- **客户管理**: 客户信息的创建、查询和管理
|
||||
- **风格分析**: 基于AI的服装风格分析和推荐
|
||||
- **虚拟试衣**: 虚拟试衣效果展示和管理
|
||||
- **收藏管理**: 客户喜欢的搭配收藏和管理
|
||||
- **模特照片**: 模特照片的上传和管理
|
||||
- **导购系统**: 导购员登录和客户服务管理
|
||||
|
||||
### 认证说明
|
||||
系统使用Session认证,需要先通过登录接口获取认证状态。
|
||||
|
||||
### 响应格式
|
||||
所有API响应都遵循统一格式:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"code": "SUCCESS",
|
||||
"message": "操作成功",
|
||||
"data": {},
|
||||
"timestamp": 1640995200000
|
||||
}
|
||||
```
|
||||
|
||||
### 错误处理
|
||||
系统提供统一的错误处理机制,详细的错误信息会在响应中返回。
|
||||
""")
|
||||
.version("1.0.0")
|
||||
.contact(new Contact()
|
||||
.name("Lane Carford 开发团队")
|
||||
.email("dev@lanecarford.com")
|
||||
.url("https://www.lanecarford.com"))
|
||||
.license(new License()
|
||||
.name("Apache 2.0")
|
||||
.url("http://www.apache.org/licenses/LICENSE-2.0"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建服务器信息
|
||||
*/
|
||||
private List<Server> createServers() {
|
||||
return Arrays.asList(
|
||||
new Server()
|
||||
.url("http://localhost:" + serverPort)
|
||||
.description("本地开发环境"),
|
||||
new Server()
|
||||
.url("https://api.lanecarford.com")
|
||||
.description("生产环境"),
|
||||
new Server()
|
||||
.url("https://test-api.lanecarford.com")
|
||||
.description("测试环境")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建组件配置(安全方案等)
|
||||
*/
|
||||
private Components createComponents() {
|
||||
return new Components()
|
||||
.addSecuritySchemes("sessionAuth", new SecurityScheme()
|
||||
.type(SecurityScheme.Type.APIKEY)
|
||||
.in(SecurityScheme.In.COOKIE)
|
||||
.name("JSESSIONID")
|
||||
.description("Session认证,通过登录接口获取"))
|
||||
.addSecuritySchemes("basicAuth", new SecurityScheme()
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme("basic")
|
||||
.description("基础认证(仅用于开发测试)"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建安全要求
|
||||
*/
|
||||
private List<SecurityRequirement> createSecurityRequirements() {
|
||||
return Arrays.asList(
|
||||
new SecurityRequirement().addList("sessionAuth"),
|
||||
new SecurityRequirement().addList("basicAuth")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建API标签分类
|
||||
*/
|
||||
private List<Tag> createTags() {
|
||||
return Arrays.asList(
|
||||
new Tag()
|
||||
.name("认证管理")
|
||||
.description("用户登录、登出和认证相关接口"),
|
||||
new Tag()
|
||||
.name("客户管理")
|
||||
.description("客户信息的创建、查询、更新和删除"),
|
||||
new Tag()
|
||||
.name("风格分析")
|
||||
.description("基于AI的服装风格分析和推荐系统"),
|
||||
new Tag()
|
||||
.name("虚拟试衣")
|
||||
.description("虚拟试衣效果的生成和管理"),
|
||||
new Tag()
|
||||
.name("收藏管理")
|
||||
.description("客户喜欢的搭配收藏和个人化管理"),
|
||||
new Tag()
|
||||
.name("模特照片")
|
||||
.description("模特照片的上传、管理和展示"),
|
||||
new Tag()
|
||||
.name("系统监控")
|
||||
.description("系统健康检查和状态监控接口")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建外部文档链接
|
||||
*/
|
||||
private ExternalDocumentation createExternalDocumentation() {
|
||||
return new ExternalDocumentation()
|
||||
.description("Lane Carford 系统文档")
|
||||
.url("https://docs.lanecarford.com");
|
||||
}
|
||||
}
|
||||
46
src/main/java/com/aida/lanecarford/config/WebConfig.java
Normal file
46
src/main/java/com/aida/lanecarford/config/WebConfig.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package com.aida.lanecarford.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.CorsRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* Web配置类 - 纯API后端服务
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Configuration
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 配置跨域 - 支持前后端分离
|
||||
*/
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true)
|
||||
.maxAge(3600);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置资源处理 - 仅保留API文档和文件上传
|
||||
*/
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
// 配置上传文件的访问路径
|
||||
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/");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.aida.lanecarford.controller;
|
||||
|
||||
import com.aida.lanecarford.service.CustomerService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 顾客控制器
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/customers")
|
||||
@RequiredArgsConstructor
|
||||
public class CustomerController {
|
||||
|
||||
private final CustomerService customerService;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.aida.lanecarford.controller;
|
||||
|
||||
import com.aida.lanecarford.service.CustomerPhotoService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 顾客照片控制器
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/customer-photos")
|
||||
@RequiredArgsConstructor
|
||||
public class CustomerPhotoController {
|
||||
|
||||
private final CustomerPhotoService customerPhotoService;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.aida.lanecarford.controller;
|
||||
|
||||
import com.aida.lanecarford.service.ModelPhotoService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 模特照片控制器
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/model-photos")
|
||||
@RequiredArgsConstructor
|
||||
public class ModelPhotoController {
|
||||
|
||||
private final ModelPhotoService modelPhotoService;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.aida.lanecarford.controller;
|
||||
|
||||
import com.aida.lanecarford.service.SalesService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 导购控制器
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/sales")
|
||||
@RequiredArgsConstructor
|
||||
public class SalesController {
|
||||
|
||||
private final SalesService salesService;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.aida.lanecarford.controller;
|
||||
|
||||
import com.aida.lanecarford.service.StyleService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 风格配置控制器
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/styles")
|
||||
@RequiredArgsConstructor
|
||||
public class StyleController {
|
||||
|
||||
private final StyleService styleService;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.aida.lanecarford.controller;
|
||||
|
||||
import com.aida.lanecarford.service.TryOnEffectService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 试穿效果控制器
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/try-on-effects")
|
||||
@RequiredArgsConstructor
|
||||
public class TryOnEffectController {
|
||||
|
||||
private final TryOnEffectService tryOnEffectService;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.aida.lanecarford.controller;
|
||||
|
||||
import com.aida.lanecarford.service.VisitRecordService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 进店记录控制器
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/visit-records")
|
||||
@RequiredArgsConstructor
|
||||
public class VisitRecordController {
|
||||
|
||||
private final VisitRecordService visitRecordService;
|
||||
|
||||
}
|
||||
44
src/main/java/com/aida/lanecarford/dto/BaseDTO.java
Normal file
44
src/main/java/com/aida/lanecarford/dto/BaseDTO.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.aida.lanecarford.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 数据传输对象基类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "数据传输对象基类")
|
||||
public abstract class BaseDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "记录ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "创建者")
|
||||
private String createBy;
|
||||
|
||||
@Schema(description = "更新者")
|
||||
private String updateBy;
|
||||
|
||||
@Schema(description = "版本号(用于乐观锁)")
|
||||
private Integer version;
|
||||
|
||||
@Schema(description = "是否删除(0-未删除,1-已删除)")
|
||||
private Integer deleted;
|
||||
}
|
||||
98
src/main/java/com/aida/lanecarford/dto/BaseRequest.java
Normal file
98
src/main/java/com/aida/lanecarford/dto/BaseRequest.java
Normal file
@@ -0,0 +1,98 @@
|
||||
package com.aida.lanecarford.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
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 {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "当前页码", example = "1")
|
||||
@Min(value = 1, message = "页码必须大于0")
|
||||
private Integer current = 1;
|
||||
|
||||
@Schema(description = "每页大小", example = "10")
|
||||
@Min(value = 1, message = "每页大小必须大于0")
|
||||
@Max(value = 100, message = "每页大小不能超过100")
|
||||
private Integer size = 10;
|
||||
|
||||
@Schema(description = "排序字段", example = "createTime")
|
||||
private String sortField;
|
||||
|
||||
@Schema(description = "排序方向(ASC-升序,DESC-降序)", example = "DESC")
|
||||
private String sortOrder = "DESC";
|
||||
|
||||
@Schema(description = "搜索关键词")
|
||||
private String keyword;
|
||||
|
||||
@Schema(description = "开始时间(格式:yyyy-MM-dd HH:mm:ss)")
|
||||
private String startTime;
|
||||
|
||||
@Schema(description = "结束时间(格式:yyyy-MM-dd HH:mm:ss)")
|
||||
private String endTime;
|
||||
|
||||
@Schema(description = "状态筛选")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 获取偏移量
|
||||
*/
|
||||
public long getOffset() {
|
||||
return (long) (current - 1) * size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取限制数量
|
||||
*/
|
||||
public long getLimit() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有搜索关键词
|
||||
*/
|
||||
public boolean hasKeyword() {
|
||||
return keyword != null && !keyword.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有时间范围筛选
|
||||
*/
|
||||
public boolean hasTimeRange() {
|
||||
return startTime != null && !startTime.trim().isEmpty()
|
||||
&& endTime != null && !endTime.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有状态筛选
|
||||
*/
|
||||
public boolean hasStatus() {
|
||||
return status != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要排序
|
||||
*/
|
||||
public boolean needSort() {
|
||||
return sortField != null && !sortField.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否降序排序
|
||||
*/
|
||||
public boolean isDesc() {
|
||||
return "DESC".equalsIgnoreCase(sortOrder);
|
||||
}
|
||||
}
|
||||
42
src/main/java/com/aida/lanecarford/entity/BaseEntity.java
Normal file
42
src/main/java/com/aida/lanecarford/entity/BaseEntity.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package com.aida.lanecarford.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 基础实体类
|
||||
* 包含通用字段,其他实体类可以继承此类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@Data
|
||||
public abstract class BaseEntity {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(value = "created_time", fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedTime;
|
||||
|
||||
/**
|
||||
* 逻辑删除标志:0-未删除,1-已删除
|
||||
*/
|
||||
@TableLogic
|
||||
@TableField(value = "deleted")
|
||||
private Integer deleted;
|
||||
}
|
||||
73
src/main/java/com/aida/lanecarford/entity/Customer.java
Normal file
73
src/main/java/com/aida/lanecarford/entity/Customer.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package com.aida.lanecarford.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 顾客实体类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("customers")
|
||||
public class Customer {
|
||||
|
||||
/**
|
||||
* 顾客ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 顾客姓名
|
||||
*/
|
||||
@TableField("name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 顾客邮箱
|
||||
*/
|
||||
@TableField("email")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@TableField("phone")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 性别
|
||||
*/
|
||||
@TableField("gender")
|
||||
private String gender;
|
||||
|
||||
/**
|
||||
* 年龄段
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
67
src/main/java/com/aida/lanecarford/entity/CustomerPhoto.java
Normal file
67
src/main/java/com/aida/lanecarford/entity/CustomerPhoto.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package com.aida.lanecarford.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 顾客照片实体类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("customer_photos")
|
||||
public class CustomerPhoto {
|
||||
|
||||
/**
|
||||
* 顾客照片ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 顾客ID
|
||||
*/
|
||||
@TableField("customer_id")
|
||||
private Long customerId;
|
||||
|
||||
/**
|
||||
* 进店记录ID
|
||||
*/
|
||||
@TableField("visit_record_id")
|
||||
private Long visitRecordId;
|
||||
|
||||
/**
|
||||
* 照片URL
|
||||
*/
|
||||
@TableField("photo_url")
|
||||
private String photoUrl;
|
||||
|
||||
/**
|
||||
* 是否为主照片(0-否,1-是)
|
||||
*/
|
||||
@TableField("is_primary")
|
||||
private Integer isPrimary;
|
||||
|
||||
/**
|
||||
* 上传时间
|
||||
*/
|
||||
@TableField("upload_time")
|
||||
private LocalDateTime uploadTime;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(value = "created_time", fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdTime;
|
||||
}
|
||||
73
src/main/java/com/aida/lanecarford/entity/ModelPhoto.java
Normal file
73
src/main/java/com/aida/lanecarford/entity/ModelPhoto.java
Normal file
@@ -0,0 +1,73 @@
|
||||
package com.aida.lanecarford.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 模特照片实体类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("model_photos")
|
||||
public class ModelPhoto {
|
||||
|
||||
/**
|
||||
* 模特照片ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 模特照片URL
|
||||
*/
|
||||
@TableField("photo_url")
|
||||
private String photoUrl;
|
||||
|
||||
/**
|
||||
* 照片名称
|
||||
*/
|
||||
@TableField("photo_name")
|
||||
private String photoName;
|
||||
|
||||
/**
|
||||
* 性别
|
||||
*/
|
||||
@TableField("gender")
|
||||
private String gender;
|
||||
|
||||
/**
|
||||
* 是否启用(0-禁用,1-启用)
|
||||
*/
|
||||
@TableField("is_active")
|
||||
private Integer isActive;
|
||||
|
||||
/**
|
||||
* 排序权重
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
97
src/main/java/com/aida/lanecarford/entity/Sales.java
Normal file
97
src/main/java/com/aida/lanecarford/entity/Sales.java
Normal file
@@ -0,0 +1,97 @@
|
||||
package com.aida.lanecarford.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 导购实体类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("sales")
|
||||
public class Sales {
|
||||
|
||||
/**
|
||||
* 导购ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@TableField("username")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码(加密后)
|
||||
*/
|
||||
@TableField("password")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 真实姓名
|
||||
*/
|
||||
@TableField("real_name")
|
||||
private String realName;
|
||||
|
||||
/**
|
||||
* 员工编号
|
||||
*/
|
||||
@TableField("employee_id")
|
||||
private String employeeId;
|
||||
|
||||
/**
|
||||
* 门店ID
|
||||
*/
|
||||
@TableField("store_id")
|
||||
private String storeId;
|
||||
|
||||
/**
|
||||
* 门店名称
|
||||
*/
|
||||
@TableField("store_name")
|
||||
private String storeName;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@TableField("phone")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@TableField("email")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 是否启用(0-禁用,1-启用)
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
85
src/main/java/com/aida/lanecarford/entity/Style.java
Normal file
85
src/main/java/com/aida/lanecarford/entity/Style.java
Normal file
@@ -0,0 +1,85 @@
|
||||
package com.aida.lanecarford.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 风格配置实体类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("styles")
|
||||
public class Style {
|
||||
|
||||
/**
|
||||
* 风格配置ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 顾客ID
|
||||
*/
|
||||
@TableField("customer_id")
|
||||
private Long customerId;
|
||||
|
||||
/**
|
||||
* 进店记录ID
|
||||
*/
|
||||
@TableField("visit_record_id")
|
||||
private Long visitRecordId;
|
||||
|
||||
/**
|
||||
* 是否选中(0-未选中,1-已选中)
|
||||
*/
|
||||
@TableField("is_selected")
|
||||
private Integer isSelected;
|
||||
|
||||
/**
|
||||
* 风格图片URL
|
||||
*/
|
||||
@TableField("style_image_url")
|
||||
private String styleImageUrl;
|
||||
|
||||
/**
|
||||
* Python请求ID
|
||||
*/
|
||||
@TableField("python_request_id")
|
||||
private String pythonRequestId;
|
||||
|
||||
/**
|
||||
* 生成状态(0-处理中,1-已完成,2-失败)
|
||||
*/
|
||||
@TableField("generation_status")
|
||||
private Integer generationStatus;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
109
src/main/java/com/aida/lanecarford/entity/TryOnEffect.java
Normal file
109
src/main/java/com/aida/lanecarford/entity/TryOnEffect.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package com.aida.lanecarford.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 试穿效果实体类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("try_on_effects")
|
||||
public class TryOnEffect {
|
||||
|
||||
/**
|
||||
* 试穿效果ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 顾客ID
|
||||
*/
|
||||
@TableField("customer_id")
|
||||
private Long customerId;
|
||||
|
||||
/**
|
||||
* 进店记录ID
|
||||
*/
|
||||
@TableField("visit_record_id")
|
||||
private Long visitRecordId;
|
||||
|
||||
/**
|
||||
* 顾客照片ID
|
||||
*/
|
||||
@TableField("customer_photo_id")
|
||||
private Long customerPhotoId;
|
||||
|
||||
/**
|
||||
* 模特照片ID
|
||||
*/
|
||||
@TableField("model_photo_id")
|
||||
private Long modelPhotoId;
|
||||
|
||||
/**
|
||||
* 试穿效果图URL
|
||||
*/
|
||||
@TableField("try_on_image_url")
|
||||
private String tryOnImageUrl;
|
||||
|
||||
/**
|
||||
* 提示词
|
||||
*/
|
||||
@TableField("prompt")
|
||||
private String prompt;
|
||||
|
||||
/**
|
||||
* Python请求ID
|
||||
*/
|
||||
@TableField("python_request_id")
|
||||
private String pythonRequestId;
|
||||
|
||||
/**
|
||||
* 生成状态(0-处理中,1-已完成,2-失败)
|
||||
*/
|
||||
@TableField("generation_status")
|
||||
private Integer generationStatus;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
*/
|
||||
@TableField("error_message")
|
||||
private String errorMessage;
|
||||
|
||||
/**
|
||||
* 是否收藏(0-否,1-是)
|
||||
*/
|
||||
@TableField("is_favorite")
|
||||
private Integer isFavorite;
|
||||
|
||||
/**
|
||||
* 原始试穿效果ID(用于重新生成)
|
||||
*/
|
||||
@TableField("original_try_on_id")
|
||||
private Long originalTryOnId;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@TableField(value = "created_time", fill = FieldFill.INSERT)
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
|
||||
private LocalDateTime updatedTime;
|
||||
}
|
||||
86
src/main/java/com/aida/lanecarford/entity/VisitRecord.java
Normal file
86
src/main/java/com/aida/lanecarford/entity/VisitRecord.java
Normal file
@@ -0,0 +1,86 @@
|
||||
package com.aida.lanecarford.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 进店记录实体类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Accessors(chain = true)
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("visit_records")
|
||||
public class VisitRecord {
|
||||
|
||||
/**
|
||||
* 进店记录ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 顾客ID
|
||||
*/
|
||||
@TableField("customer_id")
|
||||
private Long customerId;
|
||||
|
||||
/**
|
||||
* 导购ID
|
||||
*/
|
||||
@TableField("sales_id")
|
||||
private Long salesId;
|
||||
|
||||
/**
|
||||
* 进店日期
|
||||
*/
|
||||
@TableField("visit_date")
|
||||
private LocalDate visitDate;
|
||||
|
||||
/**
|
||||
* 进店时间
|
||||
*/
|
||||
@TableField("visit_time")
|
||||
private LocalDateTime visitTime;
|
||||
|
||||
/**
|
||||
* 会话ID
|
||||
*/
|
||||
@TableField("session_id")
|
||||
private String sessionId;
|
||||
|
||||
/**
|
||||
* 状态(0-已结束,1-进行中)
|
||||
*/
|
||||
@TableField("status")
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.aida.lanecarford.exception;
|
||||
|
||||
/**
|
||||
* 业务异常类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private String code;
|
||||
private String message;
|
||||
|
||||
public BusinessException(String code, String message) {
|
||||
super(message);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public BusinessException(String message) {
|
||||
super(message);
|
||||
this.code = "BUSINESS_ERROR";
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public BusinessException(String code, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
// 常用的业务异常静态方法
|
||||
public static BusinessException customerNotFound() {
|
||||
return new BusinessException("CUSTOMER_NOT_FOUND", "客户不存在");
|
||||
}
|
||||
|
||||
public static BusinessException styleOutfitNotFound() {
|
||||
return new BusinessException("STYLE_OUTFIT_NOT_FOUND", "风格搭配不存在");
|
||||
}
|
||||
|
||||
public static BusinessException modelPhotoNotFound() {
|
||||
return new BusinessException("MODEL_PHOTO_NOT_FOUND", "模特照片不存在");
|
||||
}
|
||||
|
||||
public static BusinessException virtualTryOnNotFound() {
|
||||
return new BusinessException("VIRTUAL_TRYON_NOT_FOUND", "虚拟试穿记录不存在");
|
||||
}
|
||||
|
||||
public static BusinessException favoriteNotFound() {
|
||||
return new BusinessException("FAVORITE_NOT_FOUND", "收藏记录不存在");
|
||||
}
|
||||
|
||||
public static BusinessException fileUploadFailed() {
|
||||
return new BusinessException("FILE_UPLOAD_FAILED", "文件上传失败");
|
||||
}
|
||||
|
||||
public static BusinessException invalidFileFormat() {
|
||||
return new BusinessException("INVALID_FILE_FORMAT", "文件格式不支持");
|
||||
}
|
||||
|
||||
public static BusinessException fileSizeExceeded() {
|
||||
return new BusinessException("FILE_SIZE_EXCEEDED", "文件大小超过限制");
|
||||
}
|
||||
|
||||
public static BusinessException emailAlreadyExists() {
|
||||
return new BusinessException("EMAIL_ALREADY_EXISTS", "邮箱已存在");
|
||||
}
|
||||
|
||||
public static BusinessException phoneAlreadyExists() {
|
||||
return new BusinessException("PHONE_ALREADY_EXISTS", "手机号已存在");
|
||||
}
|
||||
|
||||
public static BusinessException invalidCredentials() {
|
||||
return new BusinessException("INVALID_CREDENTIALS", "用户名或密码错误");
|
||||
}
|
||||
|
||||
public static BusinessException accessDenied() {
|
||||
return new BusinessException("ACCESS_DENIED", "访问被拒绝");
|
||||
}
|
||||
|
||||
public static BusinessException operationFailed() {
|
||||
return new BusinessException("OPERATION_FAILED", "操作失败");
|
||||
}
|
||||
|
||||
public static BusinessException dataNotFound() {
|
||||
return new BusinessException("DATA_NOT_FOUND", "数据不存在");
|
||||
}
|
||||
|
||||
public static BusinessException duplicateData() {
|
||||
return new BusinessException("DUPLICATE_DATA", "数据重复");
|
||||
}
|
||||
|
||||
public static BusinessException invalidParameter(String paramName) {
|
||||
return new BusinessException("INVALID_PARAMETER", "参数 " + paramName + " 无效");
|
||||
}
|
||||
|
||||
public static BusinessException serviceUnavailable() {
|
||||
return new BusinessException("SERVICE_UNAVAILABLE", "服务暂时不可用");
|
||||
}
|
||||
|
||||
public static BusinessException externalServiceError() {
|
||||
return new BusinessException("EXTERNAL_SERVICE_ERROR", "外部服务调用失败");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
package com.aida.lanecarford.exception;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.multipart.MaxUploadSizeExceededException;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
* 统一处理应用程序中的各种异常,提供一致的错误响应格式
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
// 错误代码常量
|
||||
private static final String BUSINESS_ERROR = "BUSINESS_ERROR";
|
||||
private static final String VALIDATION_ERROR = "VALIDATION_ERROR";
|
||||
private static final String BIND_ERROR = "BIND_ERROR";
|
||||
private static final String FILE_SIZE_EXCEEDED = "FILE_SIZE_EXCEEDED";
|
||||
private static final String ILLEGAL_ARGUMENT = "ILLEGAL_ARGUMENT";
|
||||
private static final String NULL_POINTER = "NULL_POINTER";
|
||||
private static final String RUNTIME_ERROR = "RUNTIME_ERROR";
|
||||
private static final String UNKNOWN_ERROR = "UNKNOWN_ERROR";
|
||||
private static final String DATABASE_ERROR = "DATABASE_ERROR";
|
||||
private static final String METHOD_NOT_ALLOWED = "METHOD_NOT_ALLOWED";
|
||||
private static final String NOT_FOUND = "NOT_FOUND";
|
||||
private static final String MEDIA_TYPE_NOT_SUPPORTED = "MEDIA_TYPE_NOT_SUPPORTED";
|
||||
private static final String MESSAGE_NOT_READABLE = "MESSAGE_NOT_READABLE";
|
||||
private static final String MISSING_PARAMETER = "MISSING_PARAMETER";
|
||||
private static final String TYPE_MISMATCH = "TYPE_MISMATCH";
|
||||
private static final String CONSTRAINT_VIOLATION = "CONSTRAINT_VIOLATION";
|
||||
|
||||
/**
|
||||
* 处理业务异常
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleBusinessException(BusinessException e, HttpServletRequest request) {
|
||||
logger.warn("业务异常 [{}]: {} - 请求路径: {}", e.getCode(), e.getMessage(), request.getRequestURI());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(createErrorResponse(BUSINESS_ERROR, e.getMessage(), request.getRequestURI(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数验证异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleValidationException(MethodArgumentNotValidException e, HttpServletRequest request) {
|
||||
logger.warn("参数验证异常: {} - 请求路径: {}", e.getMessage(), request.getRequestURI());
|
||||
|
||||
Map<String, String> errors = e.getBindingResult().getAllErrors().stream()
|
||||
.collect(Collectors.toMap(
|
||||
error -> ((FieldError) error).getField(),
|
||||
error -> error.getDefaultMessage(),
|
||||
(existing, replacement) -> existing
|
||||
));
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(createErrorResponse(VALIDATION_ERROR, "参数验证失败", request.getRequestURI(), errors));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理绑定异常
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleBindException(BindException e, HttpServletRequest request) {
|
||||
logger.warn("绑定异常: {} - 请求路径: {}", e.getMessage(), request.getRequestURI());
|
||||
|
||||
Map<String, String> errors = e.getBindingResult().getAllErrors().stream()
|
||||
.collect(Collectors.toMap(
|
||||
error -> ((FieldError) error).getField(),
|
||||
error -> error.getDefaultMessage(),
|
||||
(existing, replacement) -> existing
|
||||
));
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(createErrorResponse(BIND_ERROR, "数据绑定失败", request.getRequestURI(), errors));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理约束违反异常
|
||||
*/
|
||||
@ExceptionHandler(ConstraintViolationException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
|
||||
logger.warn("约束违反异常: {} - 请求路径: {}", e.getMessage(), request.getRequestURI());
|
||||
|
||||
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
|
||||
Map<String, String> errors = violations.stream()
|
||||
.collect(Collectors.toMap(
|
||||
violation -> violation.getPropertyPath().toString(),
|
||||
ConstraintViolation::getMessage,
|
||||
(existing, replacement) -> existing
|
||||
));
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(createErrorResponse(CONSTRAINT_VIOLATION, "约束验证失败", request.getRequestURI(), errors));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理文件上传大小超限异常
|
||||
*/
|
||||
@ExceptionHandler(MaxUploadSizeExceededException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e, HttpServletRequest request) {
|
||||
logger.warn("文件上传大小超限: {} - 请求路径: {}", e.getMessage(), request.getRequestURI());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(createErrorResponse(FILE_SIZE_EXCEEDED, "上传文件大小超过限制", request.getRequestURI(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理非法参数异常
|
||||
*/
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException e, HttpServletRequest request) {
|
||||
logger.warn("非法参数异常: {} - 请求路径: {}", e.getMessage(), request.getRequestURI());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(createErrorResponse(ILLEGAL_ARGUMENT, "参数错误:" + e.getMessage(), request.getRequestURI(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理缺少请求参数异常
|
||||
*/
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleMissingServletRequestParameterException(MissingServletRequestParameterException e, HttpServletRequest request) {
|
||||
logger.warn("缺少请求参数异常: {} - 请求路径: {}", e.getMessage(), request.getRequestURI());
|
||||
|
||||
String message = String.format("缺少必需的请求参数: %s", e.getParameterName());
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(createErrorResponse(MISSING_PARAMETER, message, request.getRequestURI(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数类型不匹配异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
|
||||
logger.warn("参数类型不匹配异常: {} - 请求路径: {}", e.getMessage(), request.getRequestURI());
|
||||
|
||||
String message = String.format("参数 %s 的值 %s 类型不正确", e.getName(), e.getValue());
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(createErrorResponse(TYPE_MISMATCH, message, request.getRequestURI(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理HTTP请求方法不支持异常
|
||||
*/
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
|
||||
logger.warn("HTTP请求方法不支持异常: {} - 请求路径: {}", e.getMessage(), request.getRequestURI());
|
||||
|
||||
String message = String.format("不支持的请求方法: %s", e.getMethod());
|
||||
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED)
|
||||
.body(createErrorResponse(METHOD_NOT_ALLOWED, message, request.getRequestURI(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理媒体类型不支持异常
|
||||
*/
|
||||
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleHttpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException e, HttpServletRequest request) {
|
||||
logger.warn("媒体类型不支持异常: {} - 请求路径: {}", e.getMessage(), request.getRequestURI());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
|
||||
.body(createErrorResponse(MEDIA_TYPE_NOT_SUPPORTED, "不支持的媒体类型", request.getRequestURI(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理HTTP消息不可读异常
|
||||
*/
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleHttpMessageNotReadableException(HttpMessageNotReadableException e, HttpServletRequest request) {
|
||||
logger.warn("HTTP消息不可读异常: {} - 请求路径: {}", e.getMessage(), request.getRequestURI());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
|
||||
.body(createErrorResponse(MESSAGE_NOT_READABLE, "请求体格式错误", request.getRequestURI(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理404异常
|
||||
*/
|
||||
@ExceptionHandler(NoHandlerFoundException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleNoHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) {
|
||||
logger.warn("404异常: {} - 请求路径: {}", e.getMessage(), request.getRequestURI());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.NOT_FOUND)
|
||||
.body(createErrorResponse(NOT_FOUND, "请求的资源不存在", request.getRequestURI(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数据库相关异常
|
||||
*/
|
||||
@ExceptionHandler({DataAccessException.class, SQLException.class, DataIntegrityViolationException.class})
|
||||
public ResponseEntity<Map<String, Object>> handleDatabaseException(Exception e, HttpServletRequest request) {
|
||||
logger.error("数据库异常: {} - 请求路径: {}", e.getMessage(), request.getRequestURI(), e);
|
||||
|
||||
String message = "数据库操作失败";
|
||||
if (e instanceof DataIntegrityViolationException) {
|
||||
message = "数据完整性约束违反";
|
||||
}
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(createErrorResponse(DATABASE_ERROR, message, request.getRequestURI(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理空指针异常
|
||||
*/
|
||||
@ExceptionHandler(NullPointerException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleNullPointerException(NullPointerException e, HttpServletRequest request) {
|
||||
logger.error("空指针异常 - 请求路径: {}", request.getRequestURI(), e);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(createErrorResponse(NULL_POINTER, "系统内部错误", request.getRequestURI(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理运行时异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
||||
logger.error("运行时异常 - 请求路径: {}", request.getRequestURI(), e);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(createErrorResponse(RUNTIME_ERROR, "系统运行时错误", request.getRequestURI(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有其他异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<Map<String, Object>> handleException(Exception e, HttpServletRequest request) {
|
||||
logger.error("未知异常 - 请求路径: {}", request.getRequestURI(), e);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body(createErrorResponse(UNKNOWN_ERROR, "系统内部错误", request.getRequestURI(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建统一的错误响应格式
|
||||
*/
|
||||
private Map<String, Object> createErrorResponse(String code, String message, String path, Map<String, String> errors) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("success", false);
|
||||
response.put("code", code);
|
||||
response.put("message", message);
|
||||
response.put("timestamp", System.currentTimeMillis());
|
||||
response.put("path", path);
|
||||
|
||||
if (errors != null && !errors.isEmpty()) {
|
||||
response.put("errors", errors);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.aida.lanecarford.mapper;
|
||||
|
||||
import com.aida.lanecarford.entity.Customer;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 顾客Mapper接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Mapper
|
||||
public interface CustomerMapper extends BaseMapper<Customer> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.aida.lanecarford.mapper;
|
||||
|
||||
import com.aida.lanecarford.entity.CustomerPhoto;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 顾客照片Mapper接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Mapper
|
||||
public interface CustomerPhotoMapper extends BaseMapper<CustomerPhoto> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.aida.lanecarford.mapper;
|
||||
|
||||
import com.aida.lanecarford.entity.ModelPhoto;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 模特照片Mapper接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Mapper
|
||||
public interface ModelPhotoMapper extends BaseMapper<ModelPhoto> {
|
||||
|
||||
}
|
||||
16
src/main/java/com/aida/lanecarford/mapper/SalesMapper.java
Normal file
16
src/main/java/com/aida/lanecarford/mapper/SalesMapper.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.aida.lanecarford.mapper;
|
||||
|
||||
import com.aida.lanecarford.entity.Sales;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 导购Mapper接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Mapper
|
||||
public interface SalesMapper extends BaseMapper<Sales> {
|
||||
|
||||
}
|
||||
16
src/main/java/com/aida/lanecarford/mapper/StyleMapper.java
Normal file
16
src/main/java/com/aida/lanecarford/mapper/StyleMapper.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package com.aida.lanecarford.mapper;
|
||||
|
||||
import com.aida.lanecarford.entity.Style;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 风格配置Mapper接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Mapper
|
||||
public interface StyleMapper extends BaseMapper<Style> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.aida.lanecarford.mapper;
|
||||
|
||||
import com.aida.lanecarford.entity.TryOnEffect;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 试穿效果Mapper接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Mapper
|
||||
public interface TryOnEffectMapper extends BaseMapper<TryOnEffect> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.aida.lanecarford.mapper;
|
||||
|
||||
import com.aida.lanecarford.entity.VisitRecord;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 进店记录Mapper接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Mapper
|
||||
public interface VisitRecordMapper extends BaseMapper<VisitRecord> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.aida.lanecarford.service;
|
||||
|
||||
import com.aida.lanecarford.entity.CustomerPhoto;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* 顾客照片服务接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface CustomerPhotoService extends IService<CustomerPhoto> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.aida.lanecarford.service;
|
||||
|
||||
import com.aida.lanecarford.entity.Customer;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* 顾客服务接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface CustomerService extends IService<Customer> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.aida.lanecarford.service;
|
||||
|
||||
import com.aida.lanecarford.entity.ModelPhoto;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* 模特照片服务接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface ModelPhotoService extends IService<ModelPhoto> {
|
||||
|
||||
}
|
||||
14
src/main/java/com/aida/lanecarford/service/SalesService.java
Normal file
14
src/main/java/com/aida/lanecarford/service/SalesService.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.aida.lanecarford.service;
|
||||
|
||||
import com.aida.lanecarford.entity.Sales;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* 导购服务接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface SalesService extends IService<Sales> {
|
||||
|
||||
}
|
||||
14
src/main/java/com/aida/lanecarford/service/StyleService.java
Normal file
14
src/main/java/com/aida/lanecarford/service/StyleService.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.aida.lanecarford.service;
|
||||
|
||||
import com.aida.lanecarford.entity.Style;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* 风格配置服务接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface StyleService extends IService<Style> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.aida.lanecarford.service;
|
||||
|
||||
import com.aida.lanecarford.entity.TryOnEffect;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* 试穿效果服务接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface TryOnEffectService extends IService<TryOnEffect> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.aida.lanecarford.service;
|
||||
|
||||
import com.aida.lanecarford.entity.VisitRecord;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
/**
|
||||
* 进店记录服务接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface VisitRecordService extends IService<VisitRecord> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.aida.lanecarford.service.impl;
|
||||
|
||||
import com.aida.lanecarford.entity.CustomerPhoto;
|
||||
import com.aida.lanecarford.mapper.CustomerPhotoMapper;
|
||||
import com.aida.lanecarford.service.CustomerPhotoService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 顾客照片服务实现类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CustomerPhotoServiceImpl extends ServiceImpl<CustomerPhotoMapper, CustomerPhoto> implements CustomerPhotoService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.aida.lanecarford.service.impl;
|
||||
|
||||
import com.aida.lanecarford.entity.Customer;
|
||||
import com.aida.lanecarford.mapper.CustomerMapper;
|
||||
import com.aida.lanecarford.service.CustomerService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 顾客服务实现类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> implements CustomerService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.aida.lanecarford.service.impl;
|
||||
|
||||
import com.aida.lanecarford.entity.ModelPhoto;
|
||||
import com.aida.lanecarford.mapper.ModelPhotoMapper;
|
||||
import com.aida.lanecarford.service.ModelPhotoService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 模特照片服务实现类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ModelPhotoServiceImpl extends ServiceImpl<ModelPhotoMapper, ModelPhoto> implements ModelPhotoService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.aida.lanecarford.service.impl;
|
||||
|
||||
import com.aida.lanecarford.entity.Sales;
|
||||
import com.aida.lanecarford.mapper.SalesMapper;
|
||||
import com.aida.lanecarford.service.SalesService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 导购服务实现类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SalesServiceImpl extends ServiceImpl<SalesMapper, Sales> implements SalesService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.aida.lanecarford.service.impl;
|
||||
|
||||
import com.aida.lanecarford.entity.Style;
|
||||
import com.aida.lanecarford.mapper.StyleMapper;
|
||||
import com.aida.lanecarford.service.StyleService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 风格配置服务实现类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements StyleService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.aida.lanecarford.service.impl;
|
||||
|
||||
import com.aida.lanecarford.entity.TryOnEffect;
|
||||
import com.aida.lanecarford.mapper.TryOnEffectMapper;
|
||||
import com.aida.lanecarford.service.TryOnEffectService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 试穿效果服务实现类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TryOnEffectServiceImpl extends ServiceImpl<TryOnEffectMapper, TryOnEffect> implements TryOnEffectService {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.aida.lanecarford.service.impl;
|
||||
|
||||
import com.aida.lanecarford.entity.VisitRecord;
|
||||
import com.aida.lanecarford.mapper.VisitRecordMapper;
|
||||
import com.aida.lanecarford.service.VisitRecordService;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 进店记录服务实现类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class VisitRecordServiceImpl extends ServiceImpl<VisitRecordMapper, VisitRecord> implements VisitRecordService {
|
||||
|
||||
}
|
||||
194
src/main/java/com/aida/lanecarford/util/BeanUtil.java
Normal file
194
src/main/java/com/aida/lanecarford/util/BeanUtil.java
Normal file
@@ -0,0 +1,194 @@
|
||||
package com.aida.lanecarford.util;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Bean转换工具类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public class BeanUtil {
|
||||
|
||||
/**
|
||||
* 单个对象转换
|
||||
*
|
||||
* @param source 源对象
|
||||
* @param targetClass 目标类型
|
||||
* @param <T> 目标类型
|
||||
* @return 转换后的对象
|
||||
*/
|
||||
public static <T> T convert(Object source, Class<T> targetClass) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
T target = targetClass.getDeclaredConstructor().newInstance();
|
||||
BeanUtils.copyProperties(source, target);
|
||||
return target;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("对象转换失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个对象转换(使用Supplier)
|
||||
*
|
||||
* @param source 源对象
|
||||
* @param targetSupplier 目标对象供应商
|
||||
* @param <T> 目标类型
|
||||
* @return 转换后的对象
|
||||
*/
|
||||
public static <T> T convert(Object source, Supplier<T> targetSupplier) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
T target = targetSupplier.get();
|
||||
BeanUtils.copyProperties(source, target);
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表对象转换
|
||||
*
|
||||
* @param sourceList 源列表
|
||||
* @param targetClass 目标类型
|
||||
* @param <T> 目标类型
|
||||
* @return 转换后的列表
|
||||
*/
|
||||
public static <T> List<T> convertList(List<?> sourceList, Class<T> targetClass) {
|
||||
if (CollectionUtils.isEmpty(sourceList)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<T> targetList = new ArrayList<>(sourceList.size());
|
||||
for (Object source : sourceList) {
|
||||
T target = convert(source, targetClass);
|
||||
if (target != null) {
|
||||
targetList.add(target);
|
||||
}
|
||||
}
|
||||
|
||||
return targetList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表对象转换(使用Supplier)
|
||||
*
|
||||
* @param sourceList 源列表
|
||||
* @param targetSupplier 目标对象供应商
|
||||
* @param <T> 目标类型
|
||||
* @return 转换后的列表
|
||||
*/
|
||||
public static <T> List<T> convertList(List<?> sourceList, Supplier<T> targetSupplier) {
|
||||
if (CollectionUtils.isEmpty(sourceList)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<T> targetList = new ArrayList<>(sourceList.size());
|
||||
for (Object source : sourceList) {
|
||||
T target = convert(source, targetSupplier);
|
||||
if (target != null) {
|
||||
targetList.add(target);
|
||||
}
|
||||
}
|
||||
|
||||
return targetList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制属性(忽略null值)
|
||||
*
|
||||
* @param source 源对象
|
||||
* @param target 目标对象
|
||||
*/
|
||||
public static void copyPropertiesIgnoreNull(Object source, Object target) {
|
||||
if (source == null || target == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
BeanUtils.copyProperties(source, target, getNullPropertyNames(source));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取对象中为null的属性名
|
||||
*
|
||||
* @param source 源对象
|
||||
* @return null属性名数组
|
||||
*/
|
||||
private static String[] getNullPropertyNames(Object source) {
|
||||
final java.beans.BeanInfo beanInfo;
|
||||
try {
|
||||
beanInfo = java.beans.Introspector.getBeanInfo(source.getClass());
|
||||
} catch (java.beans.IntrospectionException e) {
|
||||
throw new RuntimeException("获取Bean信息失败", e);
|
||||
}
|
||||
|
||||
final java.beans.PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
|
||||
final java.util.Set<String> emptyNames = new java.util.HashSet<>();
|
||||
|
||||
for (java.beans.PropertyDescriptor pd : propertyDescriptors) {
|
||||
final java.lang.reflect.Method readMethod = pd.getReadMethod();
|
||||
if (readMethod != null) {
|
||||
try {
|
||||
final Object value = readMethod.invoke(source);
|
||||
if (value == null) {
|
||||
emptyNames.add(pd.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 忽略异常
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return emptyNames.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断对象是否为空
|
||||
*
|
||||
* @param obj 对象
|
||||
* @return 是否为空
|
||||
*/
|
||||
public static boolean isEmpty(Object obj) {
|
||||
if (obj == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (obj instanceof String) {
|
||||
return ((String) obj).trim().isEmpty();
|
||||
}
|
||||
|
||||
if (obj instanceof java.util.Collection) {
|
||||
return ((java.util.Collection<?>) obj).isEmpty();
|
||||
}
|
||||
|
||||
if (obj instanceof java.util.Map) {
|
||||
return ((java.util.Map<?, ?>) obj).isEmpty();
|
||||
}
|
||||
|
||||
if (obj.getClass().isArray()) {
|
||||
return java.lang.reflect.Array.getLength(obj) == 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断对象是否不为空
|
||||
*
|
||||
* @param obj 对象
|
||||
* @return 是否不为空
|
||||
*/
|
||||
public static boolean isNotEmpty(Object obj) {
|
||||
return !isEmpty(obj);
|
||||
}
|
||||
}
|
||||
262
src/main/java/com/aida/lanecarford/util/FileUtil.java
Normal file
262
src/main/java/com/aida/lanecarford/util/FileUtil.java
Normal file
@@ -0,0 +1,262 @@
|
||||
package com.aida.lanecarford.util;
|
||||
|
||||
import com.aida.lanecarford.exception.BusinessException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 文件工具类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public class FileUtil {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FileUtil.class);
|
||||
|
||||
/**
|
||||
* 支持的图片格式
|
||||
*/
|
||||
private static final List<String> SUPPORTED_IMAGE_FORMATS = Arrays.asList(
|
||||
"jpg", "jpeg", "png", "gif", "bmp", "webp"
|
||||
);
|
||||
|
||||
/**
|
||||
* 最大文件大小(10MB)
|
||||
*/
|
||||
private static final long MAX_FILE_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
/**
|
||||
* 上传根目录
|
||||
*/
|
||||
private static final String UPLOAD_ROOT_DIR = "uploads";
|
||||
|
||||
/**
|
||||
* 验证文件是否为图片
|
||||
*/
|
||||
public static boolean isImageFile(MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
if (originalFilename == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String extension = getFileExtension(originalFilename).toLowerCase();
|
||||
return SUPPORTED_IMAGE_FORMATS.contains(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证文件大小
|
||||
*/
|
||||
public static boolean isValidFileSize(MultipartFile file) {
|
||||
return file != null && file.getSize() <= MAX_FILE_SIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件扩展名
|
||||
*/
|
||||
public static String getFileExtension(String filename) {
|
||||
if (filename == null || filename.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int lastDotIndex = filename.lastIndexOf('.');
|
||||
if (lastDotIndex == -1 || lastDotIndex == filename.length() - 1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return filename.substring(lastDotIndex + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成唯一文件名
|
||||
*/
|
||||
public static String generateUniqueFileName(String originalFilename) {
|
||||
String extension = getFileExtension(originalFilename);
|
||||
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
|
||||
String uuid = UUID.randomUUID().toString().replace("-", "");
|
||||
|
||||
return timestamp + "_" + uuid + "." + extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建目录结构
|
||||
*/
|
||||
public static String createDirectoryStructure(String category) {
|
||||
String dateDir = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
|
||||
String fullPath = UPLOAD_ROOT_DIR + File.separator + category + File.separator + dateDir;
|
||||
|
||||
try {
|
||||
Path path = Paths.get(fullPath);
|
||||
Files.createDirectories(path);
|
||||
return fullPath;
|
||||
} catch (IOException e) {
|
||||
logger.error("创建目录失败: {}", fullPath, e);
|
||||
throw BusinessException.fileUploadFailed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存文件
|
||||
*/
|
||||
public static String saveFile(MultipartFile file, String category) {
|
||||
// 验证文件
|
||||
validateFile(file);
|
||||
|
||||
// 创建目录
|
||||
String directoryPath = createDirectoryStructure(category);
|
||||
|
||||
// 生成文件名
|
||||
String filename = generateUniqueFileName(file.getOriginalFilename());
|
||||
|
||||
// 完整文件路径
|
||||
String fullPath = directoryPath + File.separator + filename;
|
||||
|
||||
try {
|
||||
// 保存文件
|
||||
Path filePath = Paths.get(fullPath);
|
||||
Files.write(filePath, file.getBytes());
|
||||
|
||||
logger.info("文件保存成功: {}", fullPath);
|
||||
|
||||
// 返回相对路径(用于数据库存储和访问)
|
||||
return fullPath.replace(File.separator, "/");
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error("文件保存失败: {}", fullPath, e);
|
||||
throw BusinessException.fileUploadFailed();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*/
|
||||
public static boolean deleteFile(String filePath) {
|
||||
if (filePath == null || filePath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Path path = Paths.get(filePath.replace("/", File.separator));
|
||||
boolean deleted = Files.deleteIfExists(path);
|
||||
|
||||
if (deleted) {
|
||||
logger.info("文件删除成功: {}", filePath);
|
||||
} else {
|
||||
logger.warn("文件不存在或删除失败: {}", filePath);
|
||||
}
|
||||
|
||||
return deleted;
|
||||
} catch (IOException e) {
|
||||
logger.error("文件删除失败: {}", filePath, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
*/
|
||||
public static boolean fileExists(String filePath) {
|
||||
if (filePath == null || filePath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Path path = Paths.get(filePath.replace("/", File.separator));
|
||||
return Files.exists(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件大小(字节)
|
||||
*/
|
||||
public static long getFileSize(String filePath) {
|
||||
if (!fileExists(filePath)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
Path path = Paths.get(filePath.replace("/", File.separator));
|
||||
return Files.size(path);
|
||||
} catch (IOException e) {
|
||||
logger.error("获取文件大小失败: {}", filePath, e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化文件大小
|
||||
*/
|
||||
public static String formatFileSize(long size) {
|
||||
if (size < 1024) {
|
||||
return size + " B";
|
||||
} else if (size < 1024 * 1024) {
|
||||
return String.format("%.1f KB", size / 1024.0);
|
||||
} else if (size < 1024 * 1024 * 1024) {
|
||||
return String.format("%.1f MB", size / (1024.0 * 1024.0));
|
||||
} else {
|
||||
return String.format("%.1f GB", size / (1024.0 * 1024.0 * 1024.0));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证文件
|
||||
*/
|
||||
private static void validateFile(MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
throw BusinessException.invalidParameter("file");
|
||||
}
|
||||
|
||||
if (!isImageFile(file)) {
|
||||
throw BusinessException.invalidFileFormat();
|
||||
}
|
||||
|
||||
if (!isValidFileSize(file)) {
|
||||
throw BusinessException.fileSizeExceeded();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件的MIME类型
|
||||
*/
|
||||
public static String getContentType(String filename) {
|
||||
String extension = getFileExtension(filename).toLowerCase();
|
||||
|
||||
switch (extension) {
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
return "image/jpeg";
|
||||
case "png":
|
||||
return "image/png";
|
||||
case "gif":
|
||||
return "image/gif";
|
||||
case "bmp":
|
||||
return "image/bmp";
|
||||
case "webp":
|
||||
return "image/webp";
|
||||
default:
|
||||
return "application/octet-stream";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期文件(可用于定时任务)
|
||||
*/
|
||||
public static void cleanupExpiredFiles(String directoryPath, int daysToKeep) {
|
||||
// TODO: 实现文件清理逻辑
|
||||
logger.info("清理过期文件: {} 天前的文件", daysToKeep);
|
||||
}
|
||||
}
|
||||
246
src/main/java/com/aida/lanecarford/util/ValidationUtil.java
Normal file
246
src/main/java/com/aida/lanecarford/util/ValidationUtil.java
Normal file
@@ -0,0 +1,246 @@
|
||||
package com.aida.lanecarford.util;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* 验证工具类
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public class ValidationUtil {
|
||||
|
||||
/**
|
||||
* 邮箱正则表达式
|
||||
*/
|
||||
private static final Pattern EMAIL_PATTERN = Pattern.compile(
|
||||
"^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"
|
||||
);
|
||||
|
||||
/**
|
||||
* 手机号正则表达式(中国大陆)
|
||||
*/
|
||||
private static final Pattern PHONE_PATTERN = Pattern.compile(
|
||||
"^1[3-9]\\d{9}$"
|
||||
);
|
||||
|
||||
/**
|
||||
* 密码正则表达式(至少8位,包含字母和数字)
|
||||
*/
|
||||
private static final Pattern PASSWORD_PATTERN = Pattern.compile(
|
||||
"^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d@$!%*#?&]{8,}$"
|
||||
);
|
||||
|
||||
/**
|
||||
* 用户名正则表达式(4-20位字母、数字、下划线)
|
||||
*/
|
||||
private static final Pattern USERNAME_PATTERN = Pattern.compile(
|
||||
"^[a-zA-Z0-9_]{4,20}$"
|
||||
);
|
||||
|
||||
/**
|
||||
* 验证邮箱格式
|
||||
*/
|
||||
public static boolean isValidEmail(String email) {
|
||||
return email != null && EMAIL_PATTERN.matcher(email).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证手机号格式
|
||||
*/
|
||||
public static boolean isValidPhone(String phone) {
|
||||
return phone != null && PHONE_PATTERN.matcher(phone).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证密码强度
|
||||
*/
|
||||
public static boolean isValidPassword(String password) {
|
||||
return password != null && PASSWORD_PATTERN.matcher(password).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户名格式
|
||||
*/
|
||||
public static boolean isValidUsername(String username) {
|
||||
return username != null && USERNAME_PATTERN.matcher(username).matches();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证字符串是否为空或null
|
||||
*/
|
||||
public static boolean isEmpty(String str) {
|
||||
return str == null || str.trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证字符串是否不为空
|
||||
*/
|
||||
public static boolean isNotEmpty(String str) {
|
||||
return !isEmpty(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证字符串长度是否在指定范围内
|
||||
*/
|
||||
public static boolean isValidLength(String str, int minLength, int maxLength) {
|
||||
if (str == null) {
|
||||
return false;
|
||||
}
|
||||
int length = str.length();
|
||||
return length >= minLength && length <= maxLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数字是否在指定范围内
|
||||
*/
|
||||
public static boolean isInRange(Number number, Number min, Number max) {
|
||||
if (number == null || min == null || max == null) {
|
||||
return false;
|
||||
}
|
||||
double value = number.doubleValue();
|
||||
double minValue = min.doubleValue();
|
||||
double maxValue = max.doubleValue();
|
||||
return value >= minValue && value <= maxValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证是否为正整数
|
||||
*/
|
||||
public static boolean isPositiveInteger(Number number) {
|
||||
if (number == null) {
|
||||
return false;
|
||||
}
|
||||
return number.longValue() > 0 && number.doubleValue() == number.longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证是否为非负整数
|
||||
*/
|
||||
public static boolean isNonNegativeInteger(Number number) {
|
||||
if (number == null) {
|
||||
return false;
|
||||
}
|
||||
return number.longValue() >= 0 && number.doubleValue() == number.longValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证身份证号格式(简单验证)
|
||||
*/
|
||||
public static boolean isValidIdCard(String idCard) {
|
||||
if (isEmpty(idCard)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 18位身份证号
|
||||
if (idCard.length() == 18) {
|
||||
return Pattern.matches("^[1-9]\\d{5}(18|19|20)\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}[0-9Xx]$", idCard);
|
||||
}
|
||||
|
||||
// 15位身份证号
|
||||
if (idCard.length() == 15) {
|
||||
return Pattern.matches("^[1-9]\\d{5}\\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\\d{3}$", idCard);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证URL格式
|
||||
*/
|
||||
public static boolean isValidUrl(String url) {
|
||||
if (isEmpty(url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Pattern.matches("^(https?|ftp)://[^\\s/$.?#].[^\\s]*$", url);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证IP地址格式
|
||||
*/
|
||||
public static boolean isValidIpAddress(String ip) {
|
||||
if (isEmpty(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Pattern.matches("^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$", ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证颜色代码格式(十六进制)
|
||||
*/
|
||||
public static boolean isValidColorCode(String colorCode) {
|
||||
if (isEmpty(colorCode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Pattern.matches("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", colorCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证日期格式(yyyy-MM-dd)
|
||||
*/
|
||||
public static boolean isValidDateFormat(String date) {
|
||||
if (isEmpty(date)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Pattern.matches("^\\d{4}-\\d{2}-\\d{2}$", date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证时间格式(HH:mm:ss)
|
||||
*/
|
||||
public static boolean isValidTimeFormat(String time) {
|
||||
if (isEmpty(time)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Pattern.matches("^([01]?\\d|2[0-3]):[0-5]\\d:[0-5]\\d$", time);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数字字符串
|
||||
*/
|
||||
public static boolean isNumeric(String str) {
|
||||
if (isEmpty(str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Pattern.matches("^-?\\d+(\\.\\d+)?$", str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证只包含字母
|
||||
*/
|
||||
public static boolean isAlpha(String str) {
|
||||
if (isEmpty(str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Pattern.matches("^[a-zA-Z]+$", str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证只包含字母和数字
|
||||
*/
|
||||
public static boolean isAlphanumeric(String str) {
|
||||
if (isEmpty(str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Pattern.matches("^[a-zA-Z0-9]+$", str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证中文字符
|
||||
*/
|
||||
public static boolean isChinese(String str) {
|
||||
if (isEmpty(str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Pattern.matches("^[\\u4e00-\\u9fa5]+$", str);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user