Merge branch 'dev/dev-xp' into dev/dev

This commit is contained in:
2025-10-24 16:23:02 +08:00
33 changed files with 678 additions and 61 deletions

View File

@@ -0,0 +1,12 @@
package com.aida.lanecarford.common.constant;
public class CommonConstants {
public static final String REQUEST_OUTFIT = "http://18.167.251.121:10004/api/v1/chatbot";
public static final String CHAT = "http://18.167.251.121:10004/api/v1/agent";
public static final int MINIO_PATH_TIMEOUT = 7 * 24 * 60 * 60; // minio图片临时访问地址 7 天过期second
}

View File

@@ -8,5 +8,9 @@ public class RedisURIConstants {
// 验证码 10分钟过期
public static final Long verifyCodeTimeout = 10 * 60L;
public static final String outfitResultCache = "OutfitResultCache:";
public static final String minioPathCache = "MinioPathCache:";
}

View File

@@ -0,0 +1,13 @@
package com.aida.lanecarford.common.enums;
public enum StatusEnum {
SUCCEEDED,
FAILED,
PENDING,
RUNNING
}

View File

@@ -0,0 +1,25 @@
package com.aida.lanecarford.common.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.stream.Stream;
@Getter
@AllArgsConstructor
public enum StylistPathEnum {
STYLIST_ONE("crystal", "lanecarford/stylist_guide/crystal_en.md"),
STYLIST_TWO("mini", "lanecarford/stylist_guide/mini_en.md");
private String name;
private String path;
public static StylistPathEnum of(String name) {
return Stream.of(StylistPathEnum.values()).filter(v -> v.name().equals(name)).findFirst().orElse(null);
}
}

View File

@@ -30,17 +30,18 @@ public class JwtUtil {
// 生成JWT token
public String generateToken(AuthPrincipalVO principal) {
return Jwts.builder()
String token = Jwts.builder()
.subject(JSONObject.toJSONString(principal))
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + jwtProperties.getJwtExpiration()))
.signWith(getSigningKey())
.compact();
return jwtProperties.getJwtTokenPrefix() + token;
}
// 从token中提取用户信息
public String extractUserinfo(String token) {
return parseClaims(token).getSubject();
return parser(token).getSubject();
}
// 验证token是否有效
@@ -62,11 +63,7 @@ public class JwtUtil {
token = token.substring(jwtProperties.getJwtTokenPrefix().length()).trim();
}
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
return parseClaims(token);
} catch (ExpiredJwtException e) {
log.error("Token已过期: {}", e.getMessage());

View File

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

View File

@@ -1,15 +1,17 @@
package com.aida.lanecarford.controller;
import com.aida.lanecarford.common.ApiResponse;
import com.aida.lanecarford.dto.BaseRequest;
import com.aida.lanecarford.service.CustomerService;
import com.aida.lanecarford.vo.CustomerCheckInVO;
import com.aida.lanecarford.vo.CustomerVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
/**
* 顾客控制器
*
* @author AI Assistant
* @since 2024-01-01
*/
@RestController
@RequestMapping("/api/customers")
@@ -18,4 +20,14 @@ public class CustomerController {
private final CustomerService customerService;
@GetMapping("/checkIn")
public ApiResponse<CustomerCheckInVO> customerCheckIn(@RequestParam String name, @RequestParam String email) {
return ApiResponse.success(customerService.customerCheckIn(name, email));
}
@PostMapping("/getAllCustomer")
public ApiResponse<IPage<CustomerVO>> getAllCustomer(@Valid @RequestBody BaseRequest request) {
return ApiResponse.success(customerService.getAllCustomer(request));
}
}

View File

@@ -6,10 +6,7 @@ import com.aida.lanecarford.service.LoginService;
import com.aida.lanecarford.vo.LoginVO;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
@@ -24,14 +21,15 @@ public class LoginController {
return ApiResponse.success();
}
@PostMapping("/register")
public ApiResponse<LoginVO> register(@Valid @RequestBody LoginRequest loginRequest) {
return ApiResponse.success(loginService.register(loginRequest));
@PostMapping("/registerOrLogin")
public ApiResponse<LoginVO> registerOrLogin(@Valid @RequestBody LoginRequest loginRequest) {
return ApiResponse.success(loginService.registerOrLogin(loginRequest));
}
@PostMapping("/login")
public ApiResponse<LoginVO> login(@Valid @RequestBody LoginRequest loginRequest) {
return ApiResponse.success(loginService.login(loginRequest));
@GetMapping("/logout")
public ApiResponse<String> logout() {
loginService.logout();
return ApiResponse.success();
}
@PostMapping("/forgotPwd")
@@ -40,5 +38,14 @@ public class LoginController {
return ApiResponse.success();
}
@GetMapping("/checkLoginStatus")
public ApiResponse<String> checkLoginStatus() {
boolean isLogin = loginService.checkLoginStatus();
if (isLogin){
return ApiResponse.success();
} else {
return ApiResponse.error("Please log in again.");
}
}
}

View File

@@ -1,7 +1,11 @@
package com.aida.lanecarford.controller;
import com.aida.lanecarford.dto.OutfitCallbackDTO;
import com.aida.lanecarford.service.StyleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -11,11 +15,17 @@ import org.springframework.web.bind.annotation.RestController;
* @author AI Assistant
* @since 2024-01-01
*/
@Slf4j
@RestController
@RequestMapping("/api/styles")
@RequestMapping("/api/style")
@RequiredArgsConstructor
public class StyleController {
private final StyleService styleService;
@PostMapping("/callback")
public void callback(@RequestBody OutfitCallbackDTO callbackDTO) {
styleService.callback(callbackDTO);
}
}

View File

@@ -9,14 +9,15 @@ import lombok.Data;
public class LoginRequest {
private String name;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
@NotBlank(message = "email cannot be empty")
@Email(message = "Email format is incorrect.")
private String email;
@NotBlank(message = "密码不能为空")
@Size(min = 6, message = "密码至少6位")
@NotBlank(message = "password cannot be empty")
@Size(min = 6, message = "Password must be at least 6 characters.")
private String password;
@NotBlank(message = "operation type cannot be empty")
private String operationType;
private String verifyCode;

View File

@@ -0,0 +1,18 @@
package com.aida.lanecarford.dto;
import lombok.Data;
import java.util.List;
@Data
public class OutfitCallbackDTO {
private String outfit_id;
// 取值范围ok || failed || stop
private String status;
private String path;
private List<String> items;
}

View File

@@ -0,0 +1,28 @@
package com.aida.lanecarford.dto;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
public class RequestOutfitDTO {
// 顾客id
@NotNull(message = "customer id cannot be empty")
private Long customerId;
// 进店id
@NotNull(message = "customer check-in id cannot be empty")
private Long checkInId;
// 选择的设计师
@NotNull(message = "please select a stylist")
private String stylist;
@NotNull(message = "please select gender")
private String gender;
// 生成数量
private int num;
}

View File

@@ -11,9 +11,6 @@ import java.time.LocalDateTime;
/**
* 顾客实体类
*
* @author AI Assistant
* @since 2024-01-01
*/
@Data
@NoArgsConstructor

View File

@@ -0,0 +1,32 @@
package com.aida.lanecarford.entity;
import lombok.Data;
@Data
public class OutfitRequest extends BaseEntity{
/**
* 顾客id
*/
private Long customerId;
/**
* 进店记录id
*/
private Long visitRecordId;
/**
* 选择的设计师风格
*/
private String stylist;
/**
* 选择的性别
*/
private String gender;
/**
* 当前任务状态
*/
private String status;
}

View File

@@ -7,7 +7,7 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
import java.util.List;
/**
* 风格配置实体类
@@ -35,6 +35,11 @@ public class Style extends BaseEntity {
@TableField("visit_record_id")
private Long visitRecordId;
/**
* 请求搭配id
*/
private Long outfitRequestId;
/**
* 是否选中(0-未选中,1-已选中)
*/
@@ -53,6 +58,11 @@ public class Style extends BaseEntity {
@TableField("python_request_id")
private String pythonRequestId;
/**
* 单品的唯一id
*/
private List<String> items;
/**
* 生成状态(pending-等待中,processing-处理中,completed-已完成,failed-失败)
*/

View File

@@ -33,8 +33,8 @@ public class VisitRecord extends BaseEntity {
/**
* 导购ID
*/
@TableField("sales_id")
private Long salesId;
@TableField("user_id")
private Long userId;
/**
* 进店日期

View File

@@ -0,0 +1,9 @@
package com.aida.lanecarford.mapper;
import com.aida.lanecarford.entity.OutfitRequest;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OutfitRequestMapper extends BaseMapper<OutfitRequest> {
}

View File

@@ -0,0 +1,4 @@
package com.aida.lanecarford.service;
public interface ChatService {
}

View File

@@ -1,14 +1,19 @@
package com.aida.lanecarford.service;
import com.aida.lanecarford.dto.BaseRequest;
import com.aida.lanecarford.entity.Customer;
import com.aida.lanecarford.vo.CustomerCheckInVO;
import com.aida.lanecarford.vo.CustomerVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* 顾客服务接口
*
* @author AI Assistant
* @since 2024-01-01
*/
public interface CustomerService extends IService<Customer> {
CustomerCheckInVO customerCheckIn(String name, String email);
IPage<CustomerVO> getAllCustomer(BaseRequest request);
}

View File

@@ -17,9 +17,11 @@ public interface LoginService extends IService<User> {
void preCheckAndSendEmail(LoginRequest loginRequest);
LoginVO register(LoginRequest loginRequest);
LoginVO registerOrLogin(LoginRequest loginRequest);
LoginVO login(LoginRequest loginRequest);
void logout();
void forgotPwd(LoginRequest loginRequest);
boolean checkLoginStatus();
}

View File

@@ -1,5 +1,6 @@
package com.aida.lanecarford.service;
import com.aida.lanecarford.dto.OutfitCallbackDTO;
import com.aida.lanecarford.entity.Style;
import com.baomidou.mybatisplus.extension.service.IService;
@@ -11,4 +12,6 @@ import com.baomidou.mybatisplus.extension.service.IService;
*/
public interface StyleService extends IService<Style> {
void callback(OutfitCallbackDTO callbackDTO);
}

View File

@@ -8,13 +8,12 @@ import java.util.List;
/**
* 进店记录服务接口
*
* @author AI Assistant
* @since 2024-01-01
*/
public interface VisitRecordService extends IService<VisitRecord> {
Boolean delete(Long id);
List<LibraryVo> getByCustomerId(Long customerId);
VisitRecord addRecord(Long customerId, Long userId);
}

View File

@@ -0,0 +1,10 @@
package com.aida.lanecarford.service.impl;
import com.aida.lanecarford.service.ChatService;
import org.springframework.stereotype.Service;
@Service
public class ChatServiceImpl implements ChatService {
}

View File

@@ -1,20 +1,78 @@
package com.aida.lanecarford.service.impl;
import com.aida.lanecarford.common.security.context.UserContext;
import com.aida.lanecarford.dto.BaseRequest;
import com.aida.lanecarford.entity.Customer;
import com.aida.lanecarford.entity.VisitRecord;
import com.aida.lanecarford.mapper.CustomerMapper;
import com.aida.lanecarford.service.CustomerService;
import com.aida.lanecarford.service.VisitRecordService;
import com.aida.lanecarford.util.CopyUtil;
import com.aida.lanecarford.vo.CustomerCheckInVO;
import com.aida.lanecarford.vo.CustomerVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* 顾客服务实现类
*
* @author AI Assistant
* @since 2024-01-01
*/
@Service
@RequiredArgsConstructor
public class CustomerServiceImpl extends ServiceImpl<CustomerMapper, Customer> implements CustomerService {
@Resource
private VisitRecordService visitRecordService;
// 选择顾客登录并添加入店记录
public CustomerCheckInVO customerCheckIn(String name, String email) {
// 1. 判断当前顾客信息在数据库中是否有存储
LambdaQueryWrapper<Customer> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Customer::getName, name).eq(Customer::getEmail, email);
Customer customer = getOne(queryWrapper);
// 2. 没有,获取连卡佛数据库中的顾客信息,查询有没有当前用户
if (Objects.isNull(customer)) {
// todo 从连卡佛数据库查数据
// 先假设都找不到
// throw new BusinessException("This customer does not currently have a registered VIP account.");
// 如果找到了,则添加到数据库
// 3. 添加当前顾客到本系统数据库
customer = new Customer();
customer.setName(name);
customer.setEmail(email);
customer.setCreatedTime(LocalDateTime.now());
save(customer);
}
// 4. 添加入店记录
VisitRecord visitRecord = visitRecordService.addRecord(customer.getId(), UserContext.getUserHolder().getId());
return new CustomerCheckInVO(customer.getId(), visitRecord.getId());
}
// 获取所有的顾客名单
public IPage<CustomerVO> getAllCustomer(BaseRequest request) {
LambdaQueryWrapper<Customer> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.select(Customer::getName, Customer::getEmail);
Page<Customer> page = page(new Page<>(request.getCurrent(), request.getSize()), queryWrapper);
return page.convert(customer -> {
if (customer != null) {
return CopyUtil.copyObject(customer, CustomerVO.class);
}
return null;
});
}
}

View File

@@ -4,6 +4,7 @@ import com.aida.lanecarford.common.constant.RedisURIConstants;
import com.aida.lanecarford.common.enums.AuthenticationOperationTypeEnum;
import com.aida.lanecarford.common.enums.LanguageEnum;
import com.aida.lanecarford.common.security.JwtUtil;
import com.aida.lanecarford.common.security.context.UserContext;
import com.aida.lanecarford.dto.LoginRequest;
import com.aida.lanecarford.entity.User;
import com.aida.lanecarford.exception.BusinessException;
@@ -135,9 +136,25 @@ public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements L
}
}
// 注册
@Override
public LoginVO register(LoginRequest loginRequest) {
public LoginVO registerOrLogin(LoginRequest loginRequest) {
LoginVO loginVO;
// 2. 获取当前的操作类型
AuthenticationOperationTypeEnum operationTypeEnum = AuthenticationOperationTypeEnum.of(loginRequest.getOperationType());
// 3. 根据操作类型进行后续处理
loginVO = switch (operationTypeEnum) {
case REGISTER -> register(loginRequest);
case LOGIN -> login(loginRequest);
default -> throw new BusinessException("Unknown authentication operation type.");
};
return loginVO;
}
// 注册
private LoginVO register(LoginRequest loginRequest) {
// 1. 验证邮箱
checkVerifyCode(loginRequest.getVerifyCode(), REGISTER.name(), loginRequest.getEmail());
@@ -146,7 +163,7 @@ public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements L
queryWrapper.lambda().eq(User::getEmail, loginRequest.getEmail());
User user = getOne(queryWrapper);
if (Objects.isNull(user)){
if (Objects.isNull(user)) {
user = new User();
user.setUsername(loginRequest.getName());
user.setEmail(loginRequest.getEmail());
@@ -157,15 +174,14 @@ public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements L
}
// 3. 生成token,添加到缓存,返回
String token = jwtUtil.generateToken(new AuthPrincipalVO(user.getId(), user.getUsername(), user.getLanguage()));
String token = jwtUtil.generateToken(new AuthPrincipalVO(user.getId(), user.getUsername()));
cacheUtil.setToken(user.getId(), token);
return new LoginVO(token, user, null);
}
// 登录
@Override
public LoginVO login(LoginRequest loginRequest) {
private LoginVO login(LoginRequest loginRequest) {
// 1. 验证邮箱
checkVerifyCode(loginRequest.getVerifyCode(), LOGIN.name(), loginRequest.getEmail());
@@ -176,12 +192,25 @@ public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements L
User user = getOne(queryWrapper);
// 3. 生成token,添加到缓存,返回
String token = jwtUtil.generateToken(new AuthPrincipalVO(user.getId(), user.getUsername(), user.getLanguage()));
String token = jwtUtil.generateToken(new AuthPrincipalVO(user.getId(), user.getUsername()));
cacheUtil.setToken(user.getId(), token);
return new LoginVO(token, user, null);
}
// 登出
@Override
public void logout() {
AuthPrincipalVO userHolder = UserContext.getUserHolder();
if (Objects.nonNull(userHolder)) {
Object token = cacheUtil.getToken(userHolder.getId());
if (Objects.nonNull(token)) {
cacheUtil.deleteToken(userHolder.getId());
}
}
}
// 忘记密码
@Override
public void forgotPwd(LoginRequest loginRequest) {
@@ -196,5 +225,22 @@ public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements L
update(updateWrapper);
}
// 检查登录状态
public boolean checkLoginStatus() {
AuthPrincipalVO userHolder = UserContext.getUserHolder();
if (Objects.nonNull(userHolder)) {
Object token = cacheUtil.getToken(userHolder.getId());
return Objects.nonNull(token);
}
return false;
}
// 谷歌登录
}

View File

@@ -1,20 +1,192 @@
package com.aida.lanecarford.service.impl;
import com.aida.lanecarford.common.constant.CommonConstants;
import com.aida.lanecarford.common.constant.RedisURIConstants;
import com.aida.lanecarford.common.enums.StatusEnum;
import com.aida.lanecarford.common.enums.StylistPathEnum;
import com.aida.lanecarford.dto.OutfitCallbackDTO;
import com.aida.lanecarford.dto.RequestOutfitDTO;
import com.aida.lanecarford.entity.OutfitRequest;
import com.aida.lanecarford.entity.Style;
import com.aida.lanecarford.exception.BusinessException;
import com.aida.lanecarford.mapper.OutfitRequestMapper;
import com.aida.lanecarford.mapper.StyleMapper;
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.vo.OutfitResultVO;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import io.netty.util.internal.StringUtil;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.*;
/**
* 风格配置服务实现类
*
* @author AI Assistant
* @since 2024-01-01
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements StyleService {
@Resource
private CacheUtil cacheUtil;
@Resource
private MinioUtil minioUtil;
@Resource
private OutfitRequestMapper outfitRequestMapper;
// 请求获取搭配
public void requestOutfit(RequestOutfitDTO requestOutfitDTO) {
// 请求需要顾客id, 生成的数量,风格
StylistPathEnum stylistPathEnum = StylistPathEnum.of(requestOutfitDTO.getStylist());
Map<String, Object> params = setRequestOutfitParams(requestOutfitDTO.getCustomerId(), requestOutfitDTO.getNum(), stylistPathEnum.getPath());
OutfitRequest outfitRequest = new OutfitRequest();
outfitRequest.setCustomerId(requestOutfitDTO.getCustomerId());
outfitRequest.setVisitRecordId(requestOutfitDTO.getCheckInId());
outfitRequest.setStylist(requestOutfitDTO.getStylist());
outfitRequest.setGender(requestOutfitDTO.getGender());
outfitRequestMapper.insert(outfitRequest);
String response = SendRequestUtil.sendPost(CommonConstants.REQUEST_OUTFIT, JSON.toJSONString(params));
if (Objects.isNull(response)) {
outfitRequest.setStatus(StatusEnum.FAILED.name());
outfitRequestMapper.updateById(outfitRequest);
throw new BusinessException("System error (External interface failure)");
}
// todo 接收返回的requestID
// JSONObject.parse(response, )
List<String> requestIds = new ArrayList<>();
for(String requestId : requestIds) {
// 生成需要 6~8s, 所以这里可以先请求再存储
Style style = new Style();
style.setCustomerId(requestOutfitDTO.getCustomerId());
style.setVisitRecordId(requestOutfitDTO.getCheckInId());
style.setOutfitRequestId(outfitRequest.getId());
style.setIsSelected(0);
style.setPythonRequestId(requestId);
style.setGenerationStatus(StatusEnum.PENDING.name());
style.setCreatedTime(LocalDateTime.now());
save(style);
String key = RedisURIConstants.outfitResultCache + requestId;
OutfitResultVO outfitResultVO = new OutfitResultVO(style.getId(), requestId, StatusEnum.PENDING.name());
cacheUtil.setCache(key, outfitResultVO, RedisURIConstants.verifyCodeTimeout);
}
}
private Map<String, Object> setRequestOutfitParams(Long customerId, int num, String stylistPath) {
HashMap<String, Object> params = new HashMap<>();
params.put("user_id", customerId);
params.put("num_outfits", num);
params.put("stylist_path", stylistPath);
return params;
}
// 搭配完成后的回调通知处理
public void callback(OutfitCallbackDTO callbackDTO) {
if (Objects.isNull(callbackDTO.getStatus())) {
return;
}
if ("ok".equals(callbackDTO.getStatus()) || "stop".equals(callbackDTO.getStatus())) {
// 1. 判断path是否为空是 -> 不做任何处理
if (StringUtil.isNullOrEmpty(callbackDTO.getPath())) {
return;
}
if (StringUtil.isNullOrEmpty(callbackDTO.getOutfit_id())) {
log.error("回调参数中outfit_id为空");
return;
}
String requestId = callbackDTO.getOutfit_id();
// 2. 获取outfit_id,查询数据库
String key = RedisURIConstants.outfitResultCache + requestId;
Object outfitResult = cacheUtil.getCache(key);
if (Objects.isNull(outfitResult)) {
log.error("未知搭配请求");
return;
}
// 3.更新path, items, 状态
// 由于数据变化较频繁考虑存到redis
if (outfitResult instanceof OutfitResultVO) {
((OutfitResultVO) outfitResult).setPath(minioUtil.getPresignedUrl(callbackDTO.getPath(), CommonConstants.MINIO_PATH_TIMEOUT));
((OutfitResultVO) outfitResult).setStatus(StatusEnum.SUCCEEDED.name());
cacheUtil.setCache(key, outfitResult, RedisURIConstants.verifyCodeTimeout);
}
}
// 生成结束或失败时更新数据库
if ("stop".equals(callbackDTO.getStatus()) || "failed".equals(callbackDTO.getStatus())) {
String requestId = callbackDTO.getOutfit_id();
LambdaQueryWrapper<Style> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Style::getPythonRequestId, requestId);
Style outfit = getOne(queryWrapper);
if (Objects.isNull(outfit)) {
log.error("未知搭配请求");
return;
}
String status = "stop".equals(callbackDTO.getStatus()) ? StatusEnum.SUCCEEDED.name() : StatusEnum.FAILED.name();
outfit.setGenerationStatus(status);
outfit.setStyleImageUrl(callbackDTO.getPath());
outfit.setItems(callbackDTO.getItems());
outfit.setUpdatedTime(LocalDateTime.now());
updateById(outfit);
}
}
public List<OutfitResultVO> getOutfitResult(List<String> requestIDs) {
ArrayList<OutfitResultVO> resultVOS = new ArrayList<>();
// 优先从redis中获取结果没有再从数据库中查询
for (String requestID : requestIDs) {
String key = RedisURIConstants.outfitResultCache + requestID;
Object outfit = cacheUtil.getCache(key);
if (Objects.isNull(outfit)){
break;
}
resultVOS.add((OutfitResultVO) outfit);
}
LambdaQueryWrapper<Style> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(Style::getPythonRequestId, requestIDs);
List<Style> list = list(queryWrapper);
if (!list.isEmpty()) {
for (Style style : list) {
OutfitResultVO outfitResultVO = new OutfitResultVO();
outfitResultVO.setId(style.getId());
outfitResultVO.setRequestId(style.getPythonRequestId());
outfitResultVO.setStatus(style.getGenerationStatus());
if (!StringUtil.isNullOrEmpty(style.getStyleImageUrl())) {
outfitResultVO.setPath(minioUtil.getPresignedUrl(style.getStyleImageUrl(), CommonConstants.MINIO_PATH_TIMEOUT));
}
resultVOS.add(outfitResultVO);
}
}
return resultVOS;
}
}

View File

@@ -13,17 +13,19 @@ import com.aida.lanecarford.vo.LibraryVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* 进店记录服务实现类
*
* @author AI Assistant
* @since 2024-01-01
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class VisitRecordServiceImpl extends ServiceImpl<VisitRecordMapper, VisitRecord> implements VisitRecordService {
@@ -87,4 +89,36 @@ public class VisitRecordServiceImpl extends ServiceImpl<VisitRecordMapper, Visit
}
return libraryVos;
}
@Override
public VisitRecord addRecord(Long customerId, Long userId) {
// 参数验证
if (customerId == null) {
throw new IllegalArgumentException("customer ID cannot be empty");
}
if (userId == null) {
throw new IllegalArgumentException("user ID cannot be empty");
}
try {
VisitRecord visitRecord = new VisitRecord();
visitRecord.setCustomerId(customerId);
visitRecord.setUserId(userId);
visitRecord.setVisitDate(LocalDate.now());
visitRecord.setVisitTime(LocalDateTime.now());
visitRecord.setCreatedTime(LocalDateTime.now());
save(visitRecord);
log.info("顾客进店记录保存成功 recordId={}, customerId={}", visitRecord.getId(), customerId);
return visitRecord;
} catch (DataIntegrityViolationException e) {
log.error("数据完整性约束违反 customerId={}, userId={}", customerId, userId, e);
throw new BusinessException("Save failed: Data constraint violation.");
} catch (Exception e) {
log.error("保存进店记录失败 customerId={}, userId={}", customerId, userId, e);
throw new BusinessException("Save failed: System exception.");
}
}
}

View File

@@ -58,6 +58,10 @@ public class CacheUtil {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
public void setCache(String key, Object value, Long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
public Object getCache(String key) {
return redisTemplate.opsForValue().get(key);
}

View File

@@ -0,0 +1,58 @@
package com.aida.lanecarford.util;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import com.aida.lanecarford.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Map;
@Slf4j
@Component
public class SendRequestUtil {
public static String sendPost(String url, String requestBodyStr){
int status;
String body;
try (HttpResponse execute = HttpRequest.post(url)
.header("Content-Type", "application/json") // 必须设置 Content-Type
.body(requestBodyStr) // Hutool 会自动处理 JSON 序列化
.timeout(180000) // 设置超时(毫秒)
.execute()) {
status = execute.getStatus();
body = execute.body();
if (status == 200) {
return body;
}
}
log.warn("请求失败,接口地址:{} 返回状态码为 {}, body: {}", url, status, body);
// throw new BusinessException("System error (External interface failure)");
return null;
}
public static String sendGet(String url, Map<String, Object> params) {
int status;
String body;
try (HttpResponse execute = HttpRequest.get(url)
.form(params) // 直接传入MapHutool会正确处理
.timeout(180000)
.execute()) {
status = execute.getStatus();
body = execute.body();
if (status == 200) {
return body;
}
} catch (Exception e) {
log.error("请求发生异常: {}", e.getMessage(), e);
return null;
}
log.warn("请求失败,接口地址:{} 返回状态码为 {}, body: {}", url, status, body);
throw new BusinessException("System error (External interface failure)");
}
}

View File

@@ -9,9 +9,9 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor
public class AuthPrincipalVO {
// 用户id
private Long id;
// 用户名
private String username;
private String language;
}

View File

@@ -0,0 +1,13 @@
package com.aida.lanecarford.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class CustomerCheckInVO {
private Long customerId;
private Long checkInId;
}

View File

@@ -0,0 +1,11 @@
package com.aida.lanecarford.vo;
import lombok.Data;
@Data
public class CustomerVO {
private String name;
private String email;
}

View File

@@ -0,0 +1,23 @@
package com.aida.lanecarford.vo;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class OutfitResultVO {
private Long id;
private String requestId;
private String path;
private String status;
public OutfitResultVO(Long id, String requestId, String status) {
this.id = id;
this.requestId = requestId;
this.status = status;
}
}