first commit

This commit is contained in:
zchengrong
2025-10-20 16:13:39 +08:00
parent 8cf82c52c1
commit af8fbd90e3
67 changed files with 5127 additions and 0 deletions

View File

@@ -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.");
}
}

View 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();
}
}

View 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);
}
}
}
}

View 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);
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -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();
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}

View 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");
}
}

View 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/");
}
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View 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;
}

View 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);
}
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View 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;
}

View File

@@ -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", "外部服务调用失败");
}
}

View File

@@ -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;
}
}

View File

@@ -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> {
}

View File

@@ -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> {
}

View File

@@ -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> {
}

View 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> {
}

View 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> {
}

View File

@@ -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> {
}

View File

@@ -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> {
}

View File

@@ -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> {
}

View File

@@ -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> {
}

View File

@@ -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> {
}

View 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> {
}

View 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> {
}

View File

@@ -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> {
}

View File

@@ -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> {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View 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);
}
}

View 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);
}
}

View 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);
}
}