TASK:1.登录接口联调修改 2.搭配生成优化 3.搭配生成、聊天接口文档添加

This commit is contained in:
2025-10-27 16:39:39 +08:00
parent f1c4b50435
commit ec5054d238
18 changed files with 371 additions and 177 deletions

View File

@@ -90,6 +90,11 @@
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Swagger for API Documentation -->
<dependency>

View File

@@ -7,6 +7,8 @@ public class RedisURIConstants {
public static final String verifyCodeCache = "VerifyCodeCache:";
// 验证码 10分钟过期
public static final Long verifyCodeTimeout = 10 * 60L;
// outfit result 结果30分钟过期
public static final Long outfitResultTimeout = 30 * 60L;
public static final String outfitResultCache = "OutfitResultCache:";

View File

@@ -20,7 +20,7 @@ import java.util.List;
/**
* Swagger配置类
* 提供完整的API文档配置包括安全认证、服务器信息和标签分类
*
*
* @author AI Assistant
* @since 2024-01-01
*/
@@ -116,9 +116,10 @@ public class SwaggerConfig {
.in(SecurityScheme.In.COOKIE)
.name("JSESSIONID")
.description("Session认证通过登录接口获取"))
.addSecuritySchemes("basicAuth", new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("basic")
.addSecuritySchemes("CustomAuth", new SecurityScheme()
.type(SecurityScheme.Type.APIKEY)
.name("Authorization")
.in(SecurityScheme.In.HEADER)
.description("基础认证(仅用于开发测试)"));
}
@@ -128,7 +129,7 @@ public class SwaggerConfig {
private List<SecurityRequirement> createSecurityRequirements() {
return Arrays.asList(
new SecurityRequirement().addList("sessionAuth"),
new SecurityRequirement().addList("basicAuth")
new SecurityRequirement().addList("CustomAuth")
);
}

View File

@@ -39,8 +39,8 @@ public class WebConfig implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/api/**/**") // 保护这些路径
.excludePathPatterns(Arrays.asList("/api/auth/precheckAndSendEmail", "/api/auth/register",
"/api/auth/login", "/api/auth/forgotPwd", "/api/style/callback")); // 排除登录接口
.excludePathPatterns(Arrays.asList("/api/auth/precheckEmail", "/api/auth/registerOrLogin",
"/api/auth/forgotPwd", "/api/style/callback")); // 排除登录接口
}
/**

View File

@@ -1,6 +1,9 @@
package com.aida.lanecarford.controller;
import com.aida.lanecarford.service.ChatService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
@@ -11,17 +14,27 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@Slf4j
@RestController
@RequestMapping("/api/llm")
@Tag(name = "LLM对话管理", description = "大语言模型流式对话相关API接口")
public class ChatController {
@Resource
private ChatService chatService;
@CrossOrigin
@Operation(
summary = "流式对话",
description = "与大语言模型进行流式对话返回Server-Sent Events数据流"
)
@GetMapping(value = "/streamChat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter streamChat(@RequestParam(required = false) String message,
@RequestParam Long sessionId,
@RequestParam String gender) {
return chatService.streamChat(message, sessionId, gender );
}
public SseEmitter streamChat(
@Parameter(description = "用户输入的消息内容", example = "你好,请介绍一下自己")
@RequestParam(required = false) String message,
@Parameter(description = "会话ID", example = "123456", required = true)
@RequestParam Long sessionId,
@Parameter(description = "性别", example = "male | female", required = true)
@RequestParam String gender) {
return chatService.streamChat(message, sessionId, gender);
}
}

View File

@@ -4,9 +4,9 @@ import com.aida.lanecarford.common.ApiResponse;
import com.aida.lanecarford.dto.LoginRequest;
import com.aida.lanecarford.service.LoginService;
import com.aida.lanecarford.vo.LoginVO;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@@ -27,11 +27,22 @@ public class LoginController {
description = "根据操作类型验证邮箱有效性并发送验证码。支持注册、登录、忘记密码三种操作类型。"
)
@PostMapping("/precheckAndSendEmail")
@Hidden
public ApiResponse<String> preCheckAndSendEmail(@Valid @RequestBody LoginRequest loginRequest) {
loginService.preCheckAndSendEmail(loginRequest);
return ApiResponse.success("验证码已发送到您的邮箱");
}
@Operation(
summary = "检查邮箱",
description = "根据操作类型验证邮箱有效性并发送验证码。仅支持忘记密码。"
)
@GetMapping("/precheckEmail")
public ApiResponse<String> precheckForgotPwdAndSendEmail(@Valid @RequestParam String email) {
loginService.precheckForgotPwdAndSendEmail(email);
return ApiResponse.success("验证码已发送到您的邮箱");
}
@Operation(
summary = "用户注册或登录",
description = "通过验证码完成用户注册或登录返回JWT令牌和用户信息。"

View File

@@ -5,6 +5,10 @@ import com.aida.lanecarford.dto.OutfitCallbackDTO;
import com.aida.lanecarford.dto.RequestOutfitDTO;
import com.aida.lanecarford.service.StyleService;
import com.aida.lanecarford.vo.OutfitResultVO;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -22,22 +26,55 @@ import java.util.List;
@RestController
@RequestMapping("/api/style")
@RequiredArgsConstructor
@Tag(name = "穿搭风格管理", description = "AI穿搭推荐和结果查询相关API接口")
public class StyleController {
private final StyleService styleService;
/**
* 请求AI穿搭推荐
* 提交穿搭需求给AI模型异步生成穿搭方案
*
* @param requestOutfitDTO 穿搭请求参数DTO
* @return 包含请求ID列表的响应结果
*/
@Operation(
summary = "请求AI穿搭推荐",
description = "提交用户的穿搭需求给AI模型进行异步处理返回请求ID用于后续结果查询"
)
@PostMapping("/requestOutfit")
public ApiResponse<List<String>> requestOutfit(@Valid @RequestBody RequestOutfitDTO requestOutfitDTO) {
return ApiResponse.success(styleService.requestOutfit(requestOutfitDTO));
}
/**
* AI服务回调接口
* 接收AI服务处理完成后的回调通知更新穿搭结果状态
* 注意此接口为内部接口供AI服务调用不对外暴露文档
*
* @param callbackDTO AI回调数据DTO
*/
@Hidden
@PostMapping("/callback")
public void callback(@RequestBody OutfitCallbackDTO callbackDTO) {
styleService.callback(callbackDTO);
}
/**
* 获取穿搭结果
* 根据请求ID列表查询AI生成的穿搭方案结果
*
* @param requestIDs 请求ID列表通过requestOutfit接口获取
* @return 穿搭结果视图对象列表
*/
@Operation(
summary = "获取穿搭结果",
description = "根据请求ID列表查询AI生成的穿搭方案结果支持批量查询"
)
@GetMapping("/getOutfitResult")
public ApiResponse<List<OutfitResultVO>> getOutfitResult(@RequestParam List<String> requestIDs) {
public ApiResponse<List<OutfitResultVO>> getOutfitResult(
@Parameter(description = "请求ID列表", required = true, example = "[a22019ac-9db2-4076-9953-42d65e8120ec, 20217a09-435d-4b34-962a-3af59881c6d9]")
@RequestParam List<String> requestIDs) {
return ApiResponse.success(styleService.getOutfitResult(requestIDs));
}

View File

@@ -1,28 +1,55 @@
package com.aida.lanecarford.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
@Schema(description = "AI穿搭推荐请求参数")
public class RequestOutfitDTO {
// 顾客id
@Schema(
description = "顾客ID",
example = "1",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotNull(message = "customer id cannot be empty")
private Long customerId;
// 进店id
@Schema(
description = "顾客进店记录ID",
example = "1",
requiredMode = Schema.RequiredMode.REQUIRED
)
@NotNull(message = "customer check-in id cannot be empty")
private Long checkInId;
// 选择的设计师
@Schema(
description = "选择的设计师风格",
example = "mini",
requiredMode = Schema.RequiredMode.REQUIRED,
allowableValues = {"mini", " crystal"}
)
@NotNull(message = "please select a stylist")
private String stylist;
@Schema(
description = "性别",
example = "female",
requiredMode = Schema.RequiredMode.REQUIRED,
allowableValues = {"male", "female"/*, "unisex"*/}
)
@NotNull(message = "please select gender")
private String gender;
// 生成数量
private int num;
@Schema(
description = "生成穿搭方案的数量",
example = "4",
defaultValue = "1",
minimum = "1",
maximum = "4"
)
private int num = 1;
}

View File

@@ -61,7 +61,7 @@ public class Style extends BaseEntity {
/**
* 单品的唯一id
*/
private List<String> items;
private String items;
/**
* 生成状态(pending-等待中,processing-处理中,completed-已完成,failed-失败)

View File

@@ -17,6 +17,8 @@ public interface LoginService extends IService<User> {
void preCheckAndSendEmail(LoginRequest loginRequest);
void precheckForgotPwdAndSendEmail(String email);
LoginVO registerOrLogin(LoginRequest loginRequest);
void logout();

View File

@@ -155,7 +155,7 @@ public class ChatServiceImpl implements ChatService {
}
private void sendRawData(String rawData, SseEmitter emitter, String sessionId) {
Map<String, Object> response = createResponse("raw", rawData, sessionId);
Map<String, Object> response = createResponse("text", rawData, sessionId);
sendToClient(emitter, response);
}

View File

@@ -64,9 +64,6 @@ public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements L
case LOGIN:
precheckLogin(user, loginRequest);
break;
case FORGET_PWD:
precheckForgotPwd(user, loginRequest);
break;
default:
throw new BusinessException("Unknown authentication operation type.");
@@ -77,11 +74,11 @@ public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements L
if (Objects.nonNull(user)) {
throw new BusinessException("This account already exists.");
}
String verifyCode = getCodeAndSetCache(REGISTER.name(), email);
/*String verifyCode = getCodeAndSetCache(REGISTER.name(), email);
Boolean sent = sendEmailUtil.send(email, REGISTER.name(), verifyCode);
if (!sent) {
throw new BusinessException("Failed to send verification code");
}
}*/
}
private void precheckLogin(User user, LoginRequest loginRequest) {
@@ -91,35 +88,41 @@ public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements L
if (!user.getPassword().equals(loginRequest.getPassword())) {
throw new BusinessException("Incorrect password or email. Please try again.");
}
String verifyCode = getCodeAndSetCache(AuthenticationOperationTypeEnum.LOGIN.name(), loginRequest.getEmail());
/*String verifyCode = getCodeAndSetCache(AuthenticationOperationTypeEnum.LOGIN.name(), loginRequest.getEmail());
Boolean sent = sendEmailUtil.send(loginRequest.getEmail(), LOGIN.name(), verifyCode);
if (!sent) {
throw new BusinessException("Failed to send verification code");
}
}*/
}
private void precheckForgotPwd(User user, LoginRequest loginRequest) {
@Override
public void precheckForgotPwdAndSendEmail(String email) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(User::getEmail, email);
User user = getOne(queryWrapper);
if (Objects.isNull(user)) {
throw new BusinessException("Account does not exist. Please register.");
}
if (StringUtil.isNullOrEmpty(loginRequest.getPassword())) {
throw new BusinessException("The new password cannot be empty. Please enter a new password.");
}
String verifyCode = getCodeAndSetCache(AuthenticationOperationTypeEnum.FORGET_PWD.name(), loginRequest.getEmail());
Boolean sent = sendEmailUtil.send(loginRequest.getEmail(), FORGET_PWD.name(), verifyCode);
String verifyCode = getCodeAndSetCache(AuthenticationOperationTypeEnum.FORGET_PWD.name(), email);
Boolean sent = sendEmailUtil.send(email, FORGET_PWD.name(), verifyCode);
if (!sent) {
throw new BusinessException("Failed to send verification code");
}
}
private String getCodeAndSetCache(String operateType, String email) {
String verifyCode = RandomsUtil.generateSecureSixDigitRandom();
String verifyCode = RandomsUtil.generateSecureFiveDigitRandom();
String key = RedisURIConstants.verifyCodeCache + operateType + "_" + email;
cacheUtil.setCache(key, verifyCode, RedisURIConstants.verifyCodeTimeout);
return verifyCode;
}
private void checkVerifyCode(String verifyCode, String operateType, String email) {
if (StringUtil.isNullOrEmpty(verifyCode)) {
throw new BusinessException("Verification code cannot be empty.");
}
String key = RedisURIConstants.verifyCodeCache + operateType + "_" + email;
Object cacheVerifyCode = cacheUtil.getCache(key);
if (Objects.isNull(cacheVerifyCode)) {
@@ -140,6 +143,8 @@ public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements L
public LoginVO registerOrLogin(LoginRequest loginRequest) {
LoginVO loginVO;
preCheckAndSendEmail(loginRequest);
// 2. 获取当前的操作类型
AuthenticationOperationTypeEnum operationTypeEnum = AuthenticationOperationTypeEnum.of(loginRequest.getOperationType());
@@ -156,7 +161,7 @@ public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements L
// 注册
private LoginVO register(LoginRequest loginRequest) {
// 1. 验证邮箱
checkVerifyCode(loginRequest.getVerifyCode(), REGISTER.name(), loginRequest.getEmail());
// checkVerifyCode(loginRequest.getVerifyCode(), REGISTER.name(), loginRequest.getEmail());
// 2. 通过验证,添加账号
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
@@ -183,7 +188,7 @@ public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements L
// 登录
private LoginVO login(LoginRequest loginRequest) {
// 1. 验证邮箱
checkVerifyCode(loginRequest.getVerifyCode(), LOGIN.name(), loginRequest.getEmail());
// checkVerifyCode(loginRequest.getVerifyCode(), LOGIN.name(), loginRequest.getEmail());
// 2. 获取用户信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
@@ -217,7 +222,12 @@ public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements L
// 1. 验证邮箱
checkVerifyCode(loginRequest.getVerifyCode(), FORGET_PWD.name(), loginRequest.getEmail());
// 2. 重置密码
// 2. 验证新密码是否为空
if (StringUtil.isNullOrEmpty(loginRequest.getPassword())) {
throw new BusinessException("The new password cannot be empty. Please enter a new password.");
}
// 3. 重置密码
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.lambda()
.set(User::getPassword, loginRequest.getPassword())

View File

@@ -15,6 +15,7 @@ import com.aida.lanecarford.service.StyleService;
import com.aida.lanecarford.util.CacheUtil;
import com.aida.lanecarford.util.MinioUtil;
import com.aida.lanecarford.util.SendRequestUtil;
import com.aida.lanecarford.util.StringListConverter;
import com.aida.lanecarford.vo.OutfitResultVO;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSON;
@@ -24,6 +25,7 @@ import io.netty.util.internal.StringUtil;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@@ -44,6 +46,9 @@ public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements
@Resource
private OutfitRequestMapper outfitRequestMapper;
@Value("${webhook.domain}")
private String webhookDomain;
// 请求获取搭配
public List<String> requestOutfit(RequestOutfitDTO requestOutfitDTO) {
@@ -96,6 +101,7 @@ public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements
params.put("user_id", customerId.toString());
params.put("num_outfits", num);
params.put("stylist_path", stylistPath);
params.put("callback_url", webhookDomain);
return params;
}
@@ -130,9 +136,10 @@ public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements
// 3.更新path, items, 状态
// 由于数据变化较频繁考虑存到redis
if (outfitResult instanceof OutfitResultVO) {
((OutfitResultVO) outfitResult).setPath(minioUtil.getPresignedUrl(callbackDTO.getPath(), CommonConstants.MINIO_PATH_TIMEOUT, null));
((OutfitResultVO) outfitResult).setStatus(StatusEnum.SUCCEEDED.name());
cacheUtil.setCache(key, outfitResult, RedisURIConstants.verifyCodeTimeout);
((OutfitResultVO) outfitResult).setPath(minioUtil.getPresignedUrl(callbackDTO.getPath(), CommonConstants.MINIO_PATH_TIMEOUT));
String status = "ok".equals(callbackDTO.getStatus()) ? StatusEnum.RUNNING.name() : StatusEnum.SUCCEEDED.name();
((OutfitResultVO) outfitResult).setStatus(status);
cacheUtil.setCache(key, outfitResult, RedisURIConstants.outfitResultTimeout);
}
}
@@ -151,9 +158,11 @@ public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements
int status = "stop".equals(callbackDTO.getStatus()) ? StatusEnum.SUCCEEDED.getCode() : StatusEnum.FAILED.getCode();
outfit.setGenerationStatus(status);
outfit.setStyleImageUrl(callbackDTO.getPath());
outfit.setItems(callbackDTO.getItems());
outfit.setUpdatedTime(LocalDateTime.now());
String itemsJson = StringListConverter.listToJson(callbackDTO.getItems());
outfit.setItems(itemsJson);
updateById(outfit);
}
}
@@ -185,7 +194,7 @@ public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements
outfitResultVO.setRequestId(style.getPythonRequestId());
outfitResultVO.setStatus(StatusEnum.of(style.getGenerationStatus()).name());
if (!StringUtil.isNullOrEmpty(style.getStyleImageUrl())) {
outfitResultVO.setPath(minioUtil.getPresignedUrl(style.getStyleImageUrl(), CommonConstants.MINIO_PATH_TIMEOUT, null));
outfitResultVO.setPath(minioUtil.getPresignedUrl(style.getStyleImageUrl(), CommonConstants.MINIO_PATH_TIMEOUT));
}
resultVOS.add(outfitResultVO);
}

View File

@@ -2,6 +2,7 @@ package com.aida.lanecarford.util;
import com.aida.lanecarford.config.MinioConfig;
import com.aida.lanecarford.common.constant.MinioFileConstants;
import com.aida.lanecarford.exception.BusinessException;
import com.aida.lanecarford.exception.MinioException;
import io.minio.*;
import io.minio.http.Method;
@@ -298,6 +299,23 @@ public class MinioUtil {
}
}
/**
* 获取预签名URL路径中包含了桶名
*
* @param path 对象名称(逻辑路径)
* @param expires 过期时间(秒)
* @return 预签名URL
*/
public String getPresignedUrl(String path, int expires) {
if (!path.contains("/")) {
throw new BusinessException("unknown path");
}
int index = path.indexOf("/");
String bucketName = path.substring(0, index);
String fileName = path.substring(index + 1);
return getPresignedUrl(fileName, expires, bucketName);
}
/**
* 批量获取预签名URL
*

View File

@@ -9,11 +9,11 @@ import java.security.SecureRandom;
public class RandomsUtil {
/**
* 使用ThreadLocalRandom生成6位随机数
* 使用ThreadLocalRandom生成5位随机数
*/
public static String generateSecureSixDigitRandom() {
public static String generateSecureFiveDigitRandom() {
SecureRandom secureRandom = new SecureRandom();
return String.format("%06d", secureRandom.nextInt(1000000));
return String.format("%05d", secureRandom.nextInt(100000));
}
}

View File

@@ -0,0 +1,45 @@
package com.aida.lanecarford.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class StringListConverter {
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 将 List<String> 转换为 JSON 字符串
*/
public static String listToJson(List<String> list) {
if (list == null || list.isEmpty()) {
return "[]";
}
try {
return objectMapper.writeValueAsString(list);
} catch (JsonProcessingException e) {
log.error("List转JSON失败: {}", list, e);
throw new RuntimeException("List转JSON失败", e);
}
}
/**
* 将 JSON 字符串转换为 List<String>
*/
public static List<String> jsonToList(String json) {
if (json == null || json.trim().isEmpty()) {
return new ArrayList<>();
}
try {
return objectMapper.readValue(json, new TypeReference<List<String>>() {});
} catch (JsonProcessingException e) {
log.error("JSON转List失败: {}", json, e);
throw new RuntimeException("JSON转List失败", e);
}
}
}

View File

@@ -80,3 +80,5 @@ tencent:
secret-key: XqujLlywhHfrqcCYfYVHtNgmeIiwxkKf
sender: info@aida.com.hk
webhook:
domain: https://0dd6f6504aff.ngrok-free.app

View File

@@ -1,157 +1,169 @@
-- Lane Carford AI系统基础架构数据库表结构
-- 创建数据库
CREATE DATABASE IF NOT EXISTS lanecarford CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE lanecarford;
USE lanecrawford;
-- 1. 导购
CREATE TABLE sales (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '导购ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
password VARCHAR(255) NOT NULL COMMENT '密码(加密后)',
real_name VARCHAR(100) NOT NULL COMMENT '真实姓名',
employee_id VARCHAR(50) UNIQUE COMMENT '员工编号',
store_id VARCHAR(50) COMMENT '门店ID',
store_name VARCHAR(100) COMMENT '门店名称',
phone VARCHAR(20) COMMENT '手机号',
email VARCHAR(100) COMMENT '邮箱',
is_active TINYINT DEFAULT 1 COMMENT '是否启用(0-禁用,1-启用)',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标志(0-未删除,1-已删除)',
INDEX idx_username (username),
INDEX idx_employee_id (employee_id),
INDEX idx_store_id (store_id),
INDEX idx_deleted (deleted)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='导购表';
-- 1. 用户
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户名',
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮箱',
`password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码(加密后)',
`gender` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '性别',
`employee_id` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '员工编号',
`store_id` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '门店ID',
`store_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '门店名称',
`phone` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号',
`avatar` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '头像地址',
`language` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '系统语言',
`is_active` tinyint DEFAULT '1' COMMENT '是否启用(0-禁用,1-启用)',
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint DEFAULT '0' COMMENT '逻辑删除标志(0-未删除,1-已删除)',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `employee_id` (`employee_id`),
KEY `idx_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
-- 2. 顾客表
CREATE TABLE customers (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '顾客ID',
name VARCHAR(100) NOT NULL COMMENT '顾客姓名',
email VARCHAR(100) NOT NULL COMMENT '顾客邮箱',
phone VARCHAR(20) COMMENT '手机号',
gender VARCHAR(10) COMMENT '性别',
age_range VARCHAR(20) COMMENT '年龄段',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标志(0-未删除,1-已删除)',
INDEX idx_email (email),
INDEX idx_phone (phone),
INDEX idx_name (name),
INDEX idx_deleted (deleted)
CREATE TABLE `customers` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '顾客ID',
`name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '顾客姓名',
`email` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '顾客邮箱',
`phone` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机号',
`gender` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '性别',
`age_range` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '年龄段',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint DEFAULT '0' COMMENT '逻辑删除标志(0-未删除,1-已删除)',
PRIMARY KEY (`id`),
KEY `idx_email` (`email`),
KEY `idx_phone` (`phone`),
KEY `idx_name` (`name`),
KEY `idx_deleted` (`deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='顾客表';
-- 3. 进店记录表
CREATE TABLE visit_records (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '进店记录ID',
customer_id BIGINT NOT NULL COMMENT '顾客ID',
sales_id BIGINT NOT NULL COMMENT '导购ID',
visit_date DATE NOT NULL COMMENT '进店日期',
visit_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '进店时间',
notes TEXT COMMENT '备注',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标志(0-未删除,1-已删除)',
FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE,
FOREIGN KEY (sales_id) REFERENCES sales(id) ON DELETE CASCADE,
INDEX idx_customer_id (customer_id),
INDEX idx_sales_id (sales_id),
INDEX idx_deleted (deleted)
CREATE TABLE `visit_records` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '进店记录ID',
`customer_id` bigint NOT NULL COMMENT '顾客ID',
`user_id` bigint NOT NULL COMMENT '导购ID',
`visit_date` date NOT NULL COMMENT '进店日期',
`visit_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '进店时间',
`notes` text COLLATE utf8mb4_unicode_ci COMMENT '备注',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint DEFAULT '0' COMMENT '逻辑删除标志(0-未删除,1-已删除)',
PRIMARY KEY (`id`),
KEY `idx_customer_id` (`customer_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_deleted` (`deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='进店记录表';
-- 4. 风格配置表
CREATE TABLE styles (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '风格配置ID',
customer_id BIGINT NOT NULL COMMENT '顾客ID',
visit_record_id BIGINT NOT NULL COMMENT '进店记录ID',
is_selected TINYINT DEFAULT 0 COMMENT '是否选中(0-未选中,1-已选中)',
style_image_url VARCHAR(500) COMMENT '风格图片URL',
python_request_id VARCHAR(100) COMMENT 'Python请求ID',
generation_status TINYINT DEFAULT 0 COMMENT '生成状态(0-处理中,1-已完成,2-失败)',
error_message TEXT COMMENT '错误信息',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标志(0-未删除,1-已删除)',
FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE,
FOREIGN KEY (visit_record_id) REFERENCES visit_records(id) ON DELETE CASCADE,
INDEX idx_customer_id (customer_id),
INDEX idx_visit_record_id (visit_record_id),
INDEX idx_python_request_id (python_request_id),
INDEX idx_is_selected (is_selected),
INDEX idx_generation_status (generation_status),
INDEX idx_deleted (deleted)
CREATE TABLE `styles` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '风格配置ID',
`customer_id` bigint NOT NULL COMMENT '顾客ID',
`visit_record_id` bigint NOT NULL COMMENT '进店记录ID',
`outfit_request_id` bigint NOT NULL COMMENT '请求id',
`is_selected` tinyint DEFAULT '0' COMMENT '是否选中(0-未选中,1-已选中)',
`style_image_url` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '风格图片URL',
`python_request_id` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Python请求ID',
`generation_status` tinyint DEFAULT '0' COMMENT '生成状态(0-处理中,1-已完成,2-失败)',
`items` json DEFAULT NULL COMMENT '单品唯一标识',
`error_message` text COLLATE utf8mb4_unicode_ci COMMENT '错误信息',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint DEFAULT '0' COMMENT '逻辑删除标志(0-未删除,1-已删除)',
PRIMARY KEY (`id`),
KEY `idx_customer_id` (`customer_id`),
KEY `idx_visit_record_id` (`visit_record_id`),
KEY `idx_python_request_id` (`python_request_id`),
KEY `idx_is_selected` (`is_selected`),
KEY `idx_generation_status` (`generation_status`),
KEY `idx_deleted` (`deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='风格配置表';
-- 5. 模特照片表
CREATE TABLE model_photos (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '模特照片ID',
photo_url VARCHAR(500) NOT NULL COMMENT '模特照片URL',
photo_name VARCHAR(200) COMMENT '照片名称',
gender VARCHAR(10) NOT NULL COMMENT '性别',
is_active TINYINT DEFAULT 1 COMMENT '是否启用(0-禁用,1-启用)',
sort_order INT DEFAULT 0 COMMENT '排序权重',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标志(0-未删除,1-已删除)',
INDEX idx_gender (gender),
INDEX idx_is_active (is_active),
INDEX idx_sort_order (sort_order),
INDEX idx_deleted (deleted)
CREATE TABLE `model_photos` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '模特照片ID',
`photo_url` varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '模特照片URL',
`photo_name` varchar(200) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '照片名称',
`gender` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '性别',
`is_active` tinyint DEFAULT '1' COMMENT '是否启用(0-禁用,1-启用)',
`sort_order` int DEFAULT '0' COMMENT '排序权重',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint DEFAULT '0' COMMENT '逻辑删除标志(0-未删除,1-已删除)',
PRIMARY KEY (`id`),
KEY `idx_gender` (`gender`),
KEY `idx_is_active` (`is_active`),
KEY `idx_sort_order` (`sort_order`),
KEY `idx_deleted` (`deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='模特照片表';
-- 6. 顾客照片表
CREATE TABLE customer_photos (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '顾客照片ID',
customer_id BIGINT NOT NULL COMMENT '顾客ID',
visit_record_id BIGINT NOT NULL COMMENT '进店记录ID',
photo_url VARCHAR(500) NOT NULL COMMENT '照片URL',
is_primary TINYINT DEFAULT 0 COMMENT '是否为主照片(0-否,1-是)',
upload_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标志(0-未删除,1-已删除)',
FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE,
FOREIGN KEY (visit_record_id) REFERENCES visit_records(id) ON DELETE CASCADE,
INDEX idx_customer_id (customer_id),
INDEX idx_visit_record_id (visit_record_id),
INDEX idx_is_primary (is_primary),
INDEX idx_deleted (deleted)
CREATE TABLE `customer_photos` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '顾客照片ID',
`customer_id` bigint NOT NULL COMMENT '顾客ID',
`visit_record_id` bigint NOT NULL COMMENT '进店记录ID',
`photo_url` varchar(500) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '照片URL',
`is_primary` tinyint DEFAULT '0' COMMENT '是否为主照片(0-否,1-是)',
`upload_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '上传时间',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint DEFAULT '0' COMMENT '逻辑删除标志(0-未删除,1-已删除)',
PRIMARY KEY (`id`),
KEY `idx_customer_id` (`customer_id`),
KEY `idx_visit_record_id` (`visit_record_id`),
KEY `idx_is_primary` (`is_primary`),
KEY `idx_deleted` (`deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='顾客照片表';
-- 8. 试穿效果表
CREATE TABLE try_on_effects (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '试穿效果ID',
customer_id BIGINT NOT NULL COMMENT '顾客ID',
visit_record_id BIGINT NOT NULL COMMENT '进店记录ID',
style_id BIGINT NOT NULL COMMENT '风格ID',
model_photo_id BIGINT COMMENT '模特照片ID',
customer_photo_id BIGINT COMMENT '顾客照片ID',
prompt VARCHAR(500) COMMENT '提示词,当is_regenerated为1时才会有值',
original_try_on_id BIGINT COMMENT '原试穿效果ID,当is_regenerated为1时才会有值',
is_regenerated TINYINT DEFAULT 0 COMMENT '是否由生成结果重新生成(0-否,1-是)',
result_image_url VARCHAR(500) COMMENT '试穿结果图片URL',
request_id VARCHAR(100) COMMENT '请求ID',
generation_status VARCHAR(20) DEFAULT 'pending' COMMENT '生成状态(pending-等待中,processing-处理中,completed-已完成,failed-失败)',
error_message TEXT COMMENT '错误信息',
is_favorite TINYINT DEFAULT 0 COMMENT '是否喜欢的最终造型(0-否,1-是)',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标志(0-未删除,1-已删除)',
FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE CASCADE,
FOREIGN KEY (visit_record_id) REFERENCES visit_records(id) ON DELETE CASCADE,
FOREIGN KEY (style_id) REFERENCES styles(id) ON DELETE CASCADE,
FOREIGN KEY (model_photo_id) REFERENCES model_photos(id) ON DELETE SET NULL,
FOREIGN KEY (customer_photo_id) REFERENCES customer_photos(id) ON DELETE SET NULL,
INDEX idx_customer_id (customer_id),
INDEX idx_visit_record_id (visit_record_id),
INDEX idx_style_id (style_id),
INDEX idx_request_id (request_id),
INDEX idx_generation_status (generation_status),
INDEX idx_is_favorite (is_favorite),
INDEX idx_deleted (deleted)
-- 7. 试穿效果表
CREATE TABLE `try_on_effects` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '试穿效果ID',
`customer_id` bigint NOT NULL COMMENT '顾客ID',
`visit_record_id` bigint NOT NULL COMMENT '进店记录ID',
`style_id` bigint NOT NULL COMMENT '风格ID',
`model_photo_id` bigint DEFAULT NULL COMMENT '模特照片ID',
`customer_photo_id` bigint DEFAULT NULL COMMENT '顾客照片ID',
`prompt` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '提示词,当is_regenerated为1时才会有值',
`original_try_on_id` bigint DEFAULT NULL COMMENT '原试穿效果ID,当is_regenerated为1时才会有值',
`is_regenerated` tinyint DEFAULT '0' COMMENT '是否由生成结果重新生成(0-否,1-是)',
`result_image_url` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '试穿结果图片URL',
`request_id` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '请求ID',
`generation_status` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT 'pending' COMMENT '生成状态(pending-等待中,processing-处理中,completed-已完成,failed-失败)',
`error_message` text COLLATE utf8mb4_unicode_ci COMMENT '错误信息',
`is_favorite` tinyint DEFAULT '0' COMMENT '是否喜欢的最终造型(0-否,1-是)',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint DEFAULT '0' COMMENT '逻辑删除标志(0-未删除,1-已删除)',
PRIMARY KEY (`id`),
KEY `idx_customer_id` (`customer_id`),
KEY `idx_visit_record_id` (`visit_record_id`),
KEY `idx_style_id` (`style_id`),
KEY `idx_request_id` (`request_id`),
KEY `idx_generation_status` (`generation_status`),
KEY `idx_is_favorite` (`is_favorite`),
KEY `idx_deleted` (`deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='试穿效果表';
-- 8. 穿搭请求表
CREATE TABLE `outfit_request` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`customer_id` bigint NOT NULL COMMENT '顾客id',
`visit_record_id` bigint NOT NULL COMMENT '进店记录id',
`stylist` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '选择的设计师风格',
`gender` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '选择的性别',
`status` tinyint(1) DEFAULT '0' COMMENT '当前任务状态 0-处理中 1-成功 2-失败',
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除标志0-未删除1-已删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='穿搭请求表';