Merge branch 'dev/dev' into dev/dev-ltx

This commit is contained in:
litianxiang
2025-10-28 17:23:31 +08:00
15 changed files with 158 additions and 61 deletions

1
.gitignore vendored
View File

@@ -31,3 +31,4 @@ build/
### VS Code ###
.vscode/
/logs/

View File

@@ -10,11 +10,12 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
/**
* 日志切面
*
*
* @author AI Assistant
* @since 2024-01-01
*/
@@ -28,13 +29,15 @@ public class LoggingAspect {
* 定义切点所有Controller方法
*/
@Pointcut("execution(* com.aida.lanecarford.controller..*(..))")
public void controllerMethods() {}
public void controllerMethods() {
}
/**
* 定义切点所有Service方法
*/
@Pointcut("execution(* com.aida.lanecarford.service..*(..))")
public void serviceMethods() {}
public void serviceMethods() {
}
/**
* Controller方法执行前记录日志
@@ -44,12 +47,13 @@ public class LoggingAspect {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
logger.info("=== 请求开始 ===");
logger.info("请求URL: {}", request.getRequestURL().toString());
logger.info("请求方法: {}", request.getMethod());
logger.info("请求IP: {}", getClientIpAddress(request));
logger.info("调用方法: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
// logger.info("调用方法: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
logger.info("调用方法: {}.{}", joinPoint.getSignature().getDeclaringType().getSimpleName(), joinPoint.getSignature().getName());
logger.info("请求参数: {}", Arrays.toString(joinPoint.getArgs()));
}
}
@@ -59,7 +63,7 @@ public class LoggingAspect {
*/
@AfterReturning(pointcut = "controllerMethods()", returning = "result")
public void logControllerAfterReturning(JoinPoint joinPoint, Object result) {
logger.info("方法执行成功: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
logger.info("方法执行成功: {}.{}", joinPoint.getSignature().getDeclaringType().getSimpleName(), joinPoint.getSignature().getName());
logger.info("返回结果: {}", result);
logger.info("=== 请求结束 ===");
}
@@ -69,7 +73,7 @@ public class LoggingAspect {
*/
@AfterThrowing(pointcut = "controllerMethods()", throwing = "exception")
public void logControllerAfterThrowing(JoinPoint joinPoint, Throwable exception) {
logger.error("方法执行异常: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
logger.error("方法执行异常: {}.{}", joinPoint.getSignature().getDeclaringType().getSimpleName(), joinPoint.getSignature().getName());
logger.error("异常信息: ", exception);
logger.info("=== 请求异常结束 ===");
}
@@ -80,15 +84,15 @@ public class LoggingAspect {
@Around("serviceMethods()")
public Object logServiceAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
// String methodName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
String methodName = joinPoint.getSignature().getDeclaringType().getSimpleName() + "." + joinPoint.getSignature().getName();
try {
logger.debug("Service方法开始执行: {}", methodName);
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
logger.debug("Service方法执行成功: {}, 耗时: {}ms", methodName, (endTime - startTime));
return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
@@ -106,22 +110,22 @@ public class LoggingAspect {
if (xForwardedFor != null && !xForwardedFor.isEmpty() && !"unknown".equalsIgnoreCase(xForwardedFor)) {
return xForwardedFor.split(",")[0];
}
String xRealIp = request.getHeader("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty() && !"unknown".equalsIgnoreCase(xRealIp)) {
return xRealIp;
}
String proxyClientIp = request.getHeader("Proxy-Client-IP");
if (proxyClientIp != null && !proxyClientIp.isEmpty() && !"unknown".equalsIgnoreCase(proxyClientIp)) {
return proxyClientIp;
}
String wlProxyClientIp = request.getHeader("WL-Proxy-Client-IP");
if (wlProxyClientIp != null && !wlProxyClientIp.isEmpty() && !"unknown".equalsIgnoreCase(wlProxyClientIp)) {
return wlProxyClientIp;
}
return request.getRemoteAddr();
}
}

View File

@@ -9,7 +9,7 @@ import org.springframework.stereotype.Component;
/**
* 性能监控切面
*
*
* @author AI Assistant
* @since 2024-01-01
*/
@@ -22,7 +22,7 @@ public class PerformanceAspect {
/**
* 监控Controller方法性能
*/
@Around("execution(* com.aida.lanecarford.controller..*(..))")
// @Around("execution(* com.aida.lanecarford.controller..*(..))")
public Object monitorControllerPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
return monitorMethodPerformance(joinPoint, "Controller");
}
@@ -30,7 +30,7 @@ public class PerformanceAspect {
/**
* 监控Service方法性能
*/
@Around("execution(* com.aida.lanecarford.service..*(..))")
// @Around("execution(* com.aida.lanecarford.service..*(..))")
public Object monitorServicePerformance(ProceedingJoinPoint joinPoint) throws Throwable {
return monitorMethodPerformance(joinPoint, "Service");
}
@@ -38,7 +38,7 @@ public class PerformanceAspect {
/**
* 监控数据库操作性能
*/
@Around("execution(* com.aida.lanecarford.mapper..*(..))")
// @Around("execution(* com.aida.lanecarford.mapper..*(..))")
public Object monitorMapperPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
return monitorMethodPerformance(joinPoint, "Mapper");
}
@@ -49,28 +49,28 @@ public class PerformanceAspect {
private Object monitorMethodPerformance(ProceedingJoinPoint joinPoint, String layer) throws Throwable {
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
try {
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
// 记录性能日志
logPerformance(layer, methodName, executionTime, true);
// 如果执行时间过长,记录警告
if (executionTime > getWarningThreshold(layer)) {
logger.warn("{}方法执行时间过长: {} - {}ms", layer, methodName, executionTime);
}
return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
// 记录异常性能日志
logPerformance(layer, methodName, executionTime, false);
throw e;
}
}
@@ -80,10 +80,10 @@ public class PerformanceAspect {
*/
private void logPerformance(String layer, String methodName, long executionTime, boolean success) {
if (logger.isDebugEnabled()) {
logger.debug("性能监控 - {}: {} - {}ms - {}",
layer, methodName, executionTime, success ? "成功" : "失败");
logger.debug("性能监控 - {}: {} - {}ms - {}",
layer, methodName, executionTime, success ? "成功" : "失败");
}
// 可以在这里添加性能数据收集逻辑,比如发送到监控系统
collectPerformanceMetrics(layer, methodName, executionTime, success);
}
@@ -114,7 +114,7 @@ public class PerformanceAspect {
// - 发送到时序数据库
// - 更新内存中的统计信息
// - 发送到监控系统
// 示例:简单的内存统计
PerformanceMetrics.recordExecution(layer, methodName, executionTime, success);
}
@@ -123,7 +123,7 @@ public class PerformanceAspect {
* 简单的性能指标收集器
*/
private static class PerformanceMetrics {
public static void recordExecution(String layer, String methodName, long executionTime, boolean success) {
// 简单的日志记录,实际项目中可以替换为更复杂的指标收集
if (executionTime > 1000) { // 超过1秒的操作

View File

@@ -1,6 +1,7 @@
package com.aida.lanecarford.common.enums;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
@@ -8,13 +9,18 @@ import java.util.stream.Stream;
@Getter
@AllArgsConstructor
@Schema(description = "生成状态枚举")
public enum StatusEnum {
@Schema(description = "等待中")
PENDING(0),
@Schema(description = "成功")
SUCCEEDED(1),
@Schema(description = "失败")
FAILED(2),
@Schema(description = "运行中")
RUNNING(3);
private int code;
@@ -24,5 +30,4 @@ public enum StatusEnum {
}
}

View File

@@ -9,9 +9,9 @@ import java.util.stream.Stream;
@AllArgsConstructor
public enum StylistPathEnum {
STYLIST_ONE("crystal", "lanecarford/stylist_guide/crystal_en.md"),
STYLIST_ONE("crystal", "lanecarford/stylist_guide/latest/crystal_en.md"),
STYLIST_TWO("mini", "lanecarford/stylist_guide/mini_en.md");
STYLIST_TWO("mini", "lanecarford/stylist_guide/latest/mini_en.md");
private String name;

View File

@@ -2,6 +2,7 @@ package com.aida.lanecarford.controller;
import com.aida.lanecarford.common.ApiResponse;
import com.aida.lanecarford.dto.LoginRequest;
import com.aida.lanecarford.entity.User;
import com.aida.lanecarford.service.LoginService;
import com.aida.lanecarford.vo.LoginVO;
import io.swagger.v3.oas.annotations.Hidden;
@@ -23,8 +24,8 @@ public class LoginController {
private final LoginService loginService;
@Operation(
summary = "预检查并发送邮箱验证码",
description = "根据操作类型验证邮箱有效性并发送验证码。支持注册、登录、忘记密码三种操作类型。"
summary = "预检查并发送邮箱验证码",
description = "根据操作类型验证邮箱有效性并发送验证码。支持注册、登录、忘记密码三种操作类型。"
)
@PostMapping("/precheckAndSendEmail")
@Hidden
@@ -44,8 +45,8 @@ public class LoginController {
}
@Operation(
summary = "用户注册或登录",
description = "通过验证码完成用户注册或登录返回JWT令牌和用户信息。"
summary = "用户注册或登录",
description = "通过验证码完成用户注册或登录返回JWT令牌和用户信息。"
)
@PostMapping("/registerOrLogin")
public ApiResponse<LoginVO> registerOrLogin(@Valid @RequestBody LoginRequest loginRequest) {
@@ -53,8 +54,8 @@ public class LoginController {
}
@Operation(
summary = "用户登出",
description = "清除用户登录状态使当前JWT令牌失效。"
summary = "用户登出",
description = "清除用户登录状态使当前JWT令牌失效。"
)
@GetMapping("/logout")
public ApiResponse<String> logout() {
@@ -63,8 +64,8 @@ public class LoginController {
}
@Operation(
summary = "忘记密码",
description = "通过邮箱验证码重置用户密码。需要先获取验证码,然后提供新密码。"
summary = "忘记密码",
description = "通过邮箱验证码重置用户密码。需要先获取验证码,然后提供新密码。"
)
@PostMapping("/forgotPwd")
public ApiResponse<String> forgotPwd(@Valid @RequestBody LoginRequest loginRequest) {
@@ -73,8 +74,8 @@ public class LoginController {
}
@Operation(
summary = "检查登录状态",
description = "验证当前用户的登录状态是否有效检查JWT令牌是否过期。"
summary = "检查登录状态",
description = "验证当前用户的登录状态是否有效检查JWT令牌是否过期。"
)
@GetMapping("/checkLoginStatus")
public ApiResponse<String> checkLoginStatus() {
@@ -86,4 +87,13 @@ public class LoginController {
}
}
@Operation(
summary = "获取用户信息",
description = "通过token获取当前用户信息"
)
@GetMapping("/getUserInfo")
public ApiResponse<User> getUserInfo() {
return ApiResponse.success(loginService.getUserInfo());
}
}

View File

@@ -5,17 +5,15 @@ import lombok.Data;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import java.io.Serializable;
/**
* 请求参数基类
*
* @author AI Assistant
* @since 2024-01-01
*/
@Data
@Schema(description = "请求参数基类")
public abstract class BaseRequest implements Serializable {
public class BaseRequest implements Serializable {
private static final long serialVersionUID = 1L;
@@ -71,8 +69,8 @@ public abstract class BaseRequest implements Serializable {
* 是否有时间范围筛选
*/
public boolean hasTimeRange() {
return startTime != null && !startTime.trim().isEmpty()
&& endTime != null && !endTime.trim().isEmpty();
return startTime != null && !startTime.trim().isEmpty()
&& endTime != null && !endTime.trim().isEmpty();
}
/**

View File

@@ -3,6 +3,7 @@ package com.aida.lanecarford.dto;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class OutfitCallbackDTO {
@@ -14,5 +15,5 @@ public class OutfitCallbackDTO {
private String path;
private List<String> items;
private List<Map<String, String>> items;
}

View File

@@ -26,4 +26,6 @@ public interface LoginService extends IService<User> {
void forgotPwd(LoginRequest loginRequest);
boolean checkLoginStatus();
User getUserInfo();
}

View File

@@ -3,6 +3,7 @@ package com.aida.lanecarford.service.impl;
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.response.ResultEnum;
import com.aida.lanecarford.common.security.JwtUtil;
import com.aida.lanecarford.common.security.context.UserContext;
import com.aida.lanecarford.dto.LoginRequest;
@@ -246,11 +247,18 @@ public class LoginServiceImpl extends ServiceImpl<UserMapper, User> implements L
return false;
}
// 谷歌登录
// todo 谷歌登录
// 获取用户信息
public User getUserInfo() {
AuthPrincipalVO userHolder = UserContext.getUserHolder();
User user = getById(userHolder.getId());
if (Objects.isNull(user)) {
throw new BusinessException("User information cannot be found", ResultEnum.ERROR.getCode());
}
return user;
}
}

View File

@@ -64,7 +64,7 @@ public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements
outfitRequest.setGender(requestOutfitDTO.getGender());
outfitRequestMapper.insert(outfitRequest);
String response = SendRequestUtil.sendPost(CommonConstants.REQUEST_OUTFIT, JSON.toJSONString(params));
String response = SendRequestUtil.sendPostWithRetry(CommonConstants.REQUEST_OUTFIT, JSON.toJSONString(params));
JSONObject jsonObject = JSONObject.parseObject(response); // todo 确认这里的status的取值
if (Objects.isNull(response) /*|| !jsonObject.getString("status").equals("ok")*/) {
@@ -75,7 +75,7 @@ public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements
List<String> requestIds = jsonObject.getJSONArray("outfit_ids").toJavaList(String.class);
for(String requestId : requestIds) {
for (String requestId : requestIds) {
// 生成需要 6~8s, 所以这里可以先请求再存储
Style style = new Style();
style.setCustomerId(requestOutfitDTO.getCustomerId());
@@ -175,7 +175,7 @@ public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements
String key = RedisURIConstants.outfitResultCache + requestID;
Object outfit = cacheUtil.getCache(key);
if (Objects.isNull(outfit)){
if (Objects.isNull(outfit)) {
reQueryIds.add(requestID);
continue;
}

View File

@@ -12,7 +12,7 @@ import java.util.Map;
@Component
public class SendRequestUtil {
public static String sendPost(String url, String requestBodyStr){
public static String sendPost(String url, String requestBodyStr) {
int status;
String body;
try (HttpResponse execute = HttpRequest.post(url)
@@ -53,6 +53,61 @@ public class SendRequestUtil {
throw new BusinessException("System error (External interface failure)");
}
public static String sendPostWithRetry(String url, String requestBodyStr) {
return sendPost(url, requestBodyStr, 3, 1000); // 默认重试3次间隔1秒
}
public static String sendPost(String url, String requestBodyStr, int maxRetries, long retryInterval) {
int status = 0;
String body = null;
int retryCount = 0;
while (retryCount <= maxRetries) {
try {
log.debug("发送POST请求URL: {}, 重试次数: {}/{}", url, retryCount, maxRetries);
HttpResponse execute = HttpRequest.post(url)
.header("Content-Type", "application/json")
.body(requestBodyStr)
.timeout(180000)
.execute();
status = execute.getStatus();
body = execute.body();
if (status == 200) {
log.debug("请求成功URL: {}, 状态码: {}", url, status);
return body;
} else {
log.warn("请求返回非200状态码URL: {}, 状态码: {}, Body: {}", url, status, body);
}
} catch (Exception e) {
log.warn("请求发生异常URL: {}, 异常信息: {}, 重试次数: {}/{}",
url, e.getMessage(), retryCount, maxRetries);
}
// 判断是否继续重试
if (retryCount < maxRetries) {
retryCount++;
try {
log.debug("等待 {}ms 后重试...", retryInterval);
Thread.sleep(retryInterval);
// 可选:递增重试间隔(指数退避)
retryInterval = (long) (retryInterval * 1.5);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("重试被中断", ie);
}
} else {
break;
}
}
log.error("请求最终失败URL: {}, 最大重试次数: {}, 最后状态码: {}, 最后响应: {}",
url, maxRetries, status, body);
return null;
}
}

View File

@@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Slf4j
public class StringListConverter {
@@ -16,7 +17,7 @@ public class StringListConverter {
/**
* 将 List<String> 转换为 JSON 字符串
*/
public static String listToJson(List<String> list) {
public static String listToJson(List<Map<String, String>> list) {
if (list == null || list.isEmpty()) {
return "[]";
}
@@ -31,12 +32,13 @@ public class StringListConverter {
/**
* 将 JSON 字符串转换为 List<String>
*/
public static List<String> jsonToList(String json) {
public static List<Map<String, String>> jsonToList(String json) {
if (json == null || json.trim().isEmpty()) {
return new ArrayList<>();
}
try {
return objectMapper.readValue(json, new TypeReference<List<String>>() {});
return objectMapper.readValue(json, new TypeReference<>() {
});
} catch (JsonProcessingException e) {
log.error("JSON转List失败: {}", json, e);
throw new RuntimeException("JSON转List失败", e);

View File

@@ -1,18 +1,29 @@
package com.aida.lanecarford.vo;
import com.aida.lanecarford.common.enums.StatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@Schema(description = "AI穿搭推荐结果响应参数")
public class OutfitResultVO {
@Schema(description = "记录ID", example = "21")
private Long id;
@Schema(description = "请求ID", example = "7c30e8f6-fdc6-4699-9239-ae6a9d3cf948")
private String requestId;
@Schema(description = "图片路径", example = "https://example.com/images/outfit_123.jpg")
private String path;
@Schema(
description = "处理状态",
implementation = StatusEnum.class,
requiredMode = Schema.RequiredMode.REQUIRED
)
private String status;
public OutfitResultVO(Long id, String requestId, String status) {

View File

@@ -45,7 +45,7 @@ spring:
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted