TASK:1.请求穿搭并获取穿搭结果(初步测试)2.对话
This commit is contained in:
@@ -2,11 +2,13 @@ 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 REQUEST_OUTFIT = "http://18.167.251.121:10004/api/v1/agent";
|
||||
|
||||
public static final String CHAT = "http://18.167.251.121:10004/api/v1/agent";
|
||||
public static final String CHAT = "http://18.167.251.121:10004/api/v1/chatbot";
|
||||
|
||||
public static final int MINIO_PATH_TIMEOUT = 7 * 24 * 60 * 60; // minio图片临时访问地址 7 天过期(second)
|
||||
|
||||
public static final int CONN_TIMEOUT = 30000; // (milliseconds)
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
package com.aida.lanecarford.common.enums;
|
||||
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum StatusEnum {
|
||||
SUCCEEDED,
|
||||
PENDING(0),
|
||||
|
||||
FAILED,
|
||||
SUCCEEDED(1),
|
||||
|
||||
FAILED(2),
|
||||
|
||||
RUNNING(3);
|
||||
|
||||
private int code;
|
||||
|
||||
public static StatusEnum of(int code) {
|
||||
return Stream.of(StatusEnum.values()).filter(v -> v.getCode() == code).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
PENDING,
|
||||
|
||||
RUNNING
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public enum StylistPathEnum {
|
||||
private String path;
|
||||
|
||||
public static StylistPathEnum of(String name) {
|
||||
return Stream.of(StylistPathEnum.values()).filter(v -> v.name().equals(name)).findFirst().orElse(null);
|
||||
return Stream.of(StylistPathEnum.values()).filter(v -> v.getName().equals(name)).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -40,8 +40,7 @@ public class WebConfig implements WebMvcConfigurer {
|
||||
registry.addInterceptor(jwtInterceptor)
|
||||
.addPathPatterns("/api/**/**") // 保护这些路径
|
||||
.excludePathPatterns(Arrays.asList("/api/auth/precheckAndSendEmail", "/api/auth/register",
|
||||
"/api/auth/login", "/api/auth/forgotPwd", "/api/style/callback","/api/try-on-effects","/api/customer-photos","/api/visit-records")); // 排除登录接口
|
||||
|
||||
"/api/auth/login", "/api/auth/forgotPwd", "/api/style/callback")); // 排除登录接口
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.aida.lanecarford.controller;
|
||||
|
||||
import com.aida.lanecarford.service.ChatService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/llm")
|
||||
public class ChatController {
|
||||
|
||||
@Resource
|
||||
private ChatService chatService;
|
||||
|
||||
@CrossOrigin
|
||||
@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 );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
package com.aida.lanecarford.controller;
|
||||
|
||||
import com.aida.lanecarford.common.ApiResponse;
|
||||
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 jakarta.validation.Valid;
|
||||
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;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 风格配置控制器
|
||||
@@ -23,9 +26,19 @@ public class StyleController {
|
||||
|
||||
private final StyleService styleService;
|
||||
|
||||
@PostMapping("/requestOutfit")
|
||||
public ApiResponse<List<String>> requestOutfit(@Valid @RequestBody RequestOutfitDTO requestOutfitDTO) {
|
||||
return ApiResponse.success(styleService.requestOutfit(requestOutfitDTO));
|
||||
}
|
||||
|
||||
@PostMapping("/callback")
|
||||
public void callback(@RequestBody OutfitCallbackDTO callbackDTO) {
|
||||
styleService.callback(callbackDTO);
|
||||
}
|
||||
|
||||
@GetMapping("/getOutfitResult")
|
||||
public ApiResponse<List<OutfitResultVO>> getOutfitResult(@RequestParam List<String> requestIDs) {
|
||||
return ApiResponse.success(styleService.getOutfitResult(requestIDs));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.aida.lanecarford.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@TableName("outfit_request")
|
||||
public class OutfitRequest extends BaseEntity{
|
||||
|
||||
/**
|
||||
@@ -28,5 +30,5 @@ public class OutfitRequest extends BaseEntity{
|
||||
/**
|
||||
* 当前任务状态
|
||||
*/
|
||||
private String status;
|
||||
private int status;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class Style extends BaseEntity {
|
||||
* 生成状态(pending-等待中,processing-处理中,completed-已完成,failed-失败)
|
||||
*/
|
||||
@TableField("generation_status")
|
||||
private String generationStatus;
|
||||
private int generationStatus;
|
||||
|
||||
/**
|
||||
* 错误信息
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
package com.aida.lanecarford.service;
|
||||
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
public interface ChatService {
|
||||
|
||||
SseEmitter streamChat(String message, Long sessionId, String gender);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
package com.aida.lanecarford.service;
|
||||
|
||||
import com.aida.lanecarford.dto.OutfitCallbackDTO;
|
||||
import com.aida.lanecarford.dto.RequestOutfitDTO;
|
||||
import com.aida.lanecarford.entity.Style;
|
||||
import com.aida.lanecarford.vo.OutfitResultVO;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 风格配置服务接口
|
||||
*
|
||||
* @author AI Assistant
|
||||
* @since 2024-01-01
|
||||
*/
|
||||
public interface StyleService extends IService<Style> {
|
||||
|
||||
List<String> requestOutfit(RequestOutfitDTO requestOutfitDTO);
|
||||
|
||||
void callback(OutfitCallbackDTO callbackDTO);
|
||||
|
||||
List<OutfitResultVO> getOutfitResult(List<String> requestIDs);
|
||||
|
||||
}
|
||||
@@ -1,10 +1,216 @@
|
||||
package com.aida.lanecarford.service.impl;
|
||||
|
||||
import com.aida.lanecarford.common.constant.CommonConstants;
|
||||
import com.aida.lanecarford.service.ChatService;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ChatServiceImpl implements ChatService {
|
||||
|
||||
/**
|
||||
* 流式对话方法 - 集成第三方AI接口
|
||||
*/
|
||||
public SseEmitter streamChat(String message, Long sessionId, String gender) {
|
||||
SseEmitter emitter = new SseEmitter(0L);
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
// 连接第三方AI服务
|
||||
processAIStream(message, sessionId.toString(), gender, emitter);
|
||||
emitter.complete();
|
||||
} catch (Exception e) {
|
||||
log.error("AI流式对话处理失败", e);
|
||||
sendError(emitter, "AI服务处理失败: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理第三方AI服务的流式响应
|
||||
*/
|
||||
private void processAIStream(String message, String sessionId, String gender, SseEmitter emitter) {
|
||||
HttpURLConnection connection = null;
|
||||
try {
|
||||
// 1. 创建AI服务连接
|
||||
connection = createAIConnection();
|
||||
|
||||
// 2. 发送请求到AI服务
|
||||
sendToAI(connection, message, sessionId, gender);
|
||||
|
||||
// 3. 流式读取并转发响应
|
||||
streamAIResponse(connection, emitter, sessionId);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("AI服务调用失败", e);
|
||||
} finally {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建AI服务连接
|
||||
*/
|
||||
private HttpURLConnection createAIConnection() throws IOException {
|
||||
URL url = new URL(CommonConstants.CHAT);
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setDoOutput(true);
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Accept", "text/event-stream");
|
||||
conn.setConnectTimeout(CommonConstants.CONN_TIMEOUT);
|
||||
conn.setReadTimeout(CommonConstants.CONN_TIMEOUT);
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送请求到AI服务
|
||||
*/
|
||||
private void sendToAI(HttpURLConnection connection, String message, String sessionId, String gender)
|
||||
throws IOException {
|
||||
|
||||
// 构建AI服务请求体
|
||||
JSONObject requestBody = new JSONObject();
|
||||
requestBody.put("user_message", message);
|
||||
requestBody.put("user_id", sessionId);
|
||||
requestBody.put("gender", gender); // 启用流式输出
|
||||
|
||||
log.info("发送请求到AI服务: sessionId={}, messageLength={}", sessionId, message.length());
|
||||
|
||||
try (OutputStream os = connection.getOutputStream()) {
|
||||
byte[] input = requestBody.toJSONString().getBytes(StandardCharsets.UTF_8);
|
||||
os.write(input, 0, input.length);
|
||||
os.flush();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 流式读取AI响应并转发
|
||||
*/
|
||||
private void streamAIResponse(HttpURLConnection connection, SseEmitter emitter, String sessionId)
|
||||
throws IOException {
|
||||
|
||||
int responseCode = connection.getResponseCode();
|
||||
if (responseCode != 200) {
|
||||
throw new RuntimeException("AI服务响应错误: " + responseCode);
|
||||
}
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
|
||||
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
|
||||
String output = JSON.parseObject(line).getString("output");
|
||||
// 不用存储数据,直接发送消息
|
||||
sendRawData(output, emitter, sessionId);
|
||||
}
|
||||
|
||||
// 发送结束信号
|
||||
sendEndSignal(emitter, sessionId);
|
||||
|
||||
} catch (IOException e) {
|
||||
// 检查是否是连接超时或中断
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送错误信息
|
||||
*/
|
||||
private void sendErrorChunk(String content, SseEmitter emitter, String sessionId) {
|
||||
Map<String, Object> response = createResponse("error", content, sessionId);
|
||||
sendToClient(emitter, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送原始数据
|
||||
*/
|
||||
private void sendRawChunk(JSONObject data, SseEmitter emitter, String sessionId) {
|
||||
Map<String, Object> response = createResponse("raw", data.toJSONString(), sessionId);
|
||||
sendToClient(emitter, response);
|
||||
}
|
||||
|
||||
private void sendRawData(String rawData, SseEmitter emitter, String sessionId) {
|
||||
Map<String, Object> response = createResponse("raw", rawData, sessionId);
|
||||
sendToClient(emitter, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送数据到客户端
|
||||
*/
|
||||
private void sendToClient(SseEmitter emitter, Map<String, Object> data) {
|
||||
try {
|
||||
emitter.send(SseEmitter.event()
|
||||
.data(data)
|
||||
.id(UUID.randomUUID().toString()));
|
||||
} catch (IOException e) {
|
||||
log.warn("发送数据到客户端失败,可能连接已关闭");
|
||||
throw new RuntimeException("客户端连接已断开", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送错误信息
|
||||
*/
|
||||
private void sendError(SseEmitter emitter, String errorMessage) {
|
||||
try {
|
||||
Map<String, Object> errorData = createResponse("error", errorMessage, null);
|
||||
emitter.send(SseEmitter.event()
|
||||
.data(errorData)
|
||||
.name("error"));
|
||||
} catch (IOException e) {
|
||||
log.error("发送错误信息失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送结束信号
|
||||
*/
|
||||
private void sendEndSignal(SseEmitter emitter, String sessionId) {
|
||||
try {
|
||||
Map<String, Object> endData = createResponse("end", "对话结束", sessionId);
|
||||
emitter.send(SseEmitter.event()
|
||||
.data(endData)
|
||||
.name("end"));
|
||||
} catch (IOException e) {
|
||||
log.warn("发送结束信号失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建标准响应格式
|
||||
*/
|
||||
private Map<String, Object> createResponse(String type, String content, String sessionId) {
|
||||
Map<String, Object> response = new HashMap<>();
|
||||
response.put("type", type);
|
||||
response.put("content", content);
|
||||
response.put("sessionId", sessionId);
|
||||
response.put("timestamp", System.currentTimeMillis());
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ 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.fastjson.JSONObject;
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
@@ -45,7 +46,7 @@ public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements
|
||||
|
||||
|
||||
// 请求获取搭配
|
||||
public void requestOutfit(RequestOutfitDTO requestOutfitDTO) {
|
||||
public List<String> requestOutfit(RequestOutfitDTO requestOutfitDTO) {
|
||||
// 请求需要顾客id, 生成的数量,风格
|
||||
|
||||
StylistPathEnum stylistPathEnum = StylistPathEnum.of(requestOutfitDTO.getStylist());
|
||||
@@ -58,17 +59,16 @@ 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));
|
||||
if (Objects.isNull(response)) {
|
||||
outfitRequest.setStatus(StatusEnum.FAILED.name());
|
||||
|
||||
JSONObject jsonObject = JSONObject.parseObject(response); // todo 确认这里的status的取值
|
||||
if (Objects.isNull(response) /*|| !jsonObject.getString("status").equals("ok")*/) {
|
||||
outfitRequest.setStatus(StatusEnum.FAILED.getCode());
|
||||
outfitRequestMapper.updateById(outfitRequest);
|
||||
throw new BusinessException("System error (External interface failure)");
|
||||
}
|
||||
// todo 接收返回的requestID
|
||||
// JSONObject.parse(response, )
|
||||
|
||||
List<String> requestIds = new ArrayList<>();
|
||||
List<String> requestIds = jsonObject.getJSONArray("outfit_ids").toJavaList(String.class);
|
||||
|
||||
for(String requestId : requestIds) {
|
||||
// 生成需要 6~8s, 所以这里可以先请求再存储
|
||||
@@ -78,7 +78,7 @@ public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements
|
||||
style.setOutfitRequestId(outfitRequest.getId());
|
||||
style.setIsSelected(0);
|
||||
style.setPythonRequestId(requestId);
|
||||
style.setGenerationStatus(StatusEnum.PENDING.name());
|
||||
style.setGenerationStatus(StatusEnum.PENDING.getCode());
|
||||
style.setCreatedTime(LocalDateTime.now());
|
||||
|
||||
save(style);
|
||||
@@ -87,12 +87,13 @@ public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements
|
||||
OutfitResultVO outfitResultVO = new OutfitResultVO(style.getId(), requestId, StatusEnum.PENDING.name());
|
||||
cacheUtil.setCache(key, outfitResultVO, RedisURIConstants.verifyCodeTimeout);
|
||||
}
|
||||
return requestIds;
|
||||
|
||||
}
|
||||
|
||||
private Map<String, Object> setRequestOutfitParams(Long customerId, int num, String stylistPath) {
|
||||
HashMap<String, Object> params = new HashMap<>();
|
||||
params.put("user_id", customerId);
|
||||
params.put("user_id", customerId.toString());
|
||||
params.put("num_outfits", num);
|
||||
params.put("stylist_path", stylistPath);
|
||||
|
||||
@@ -147,7 +148,7 @@ public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements
|
||||
return;
|
||||
}
|
||||
|
||||
String status = "stop".equals(callbackDTO.getStatus()) ? StatusEnum.SUCCEEDED.name() : StatusEnum.FAILED.name();
|
||||
int status = "stop".equals(callbackDTO.getStatus()) ? StatusEnum.SUCCEEDED.getCode() : StatusEnum.FAILED.getCode();
|
||||
outfit.setGenerationStatus(status);
|
||||
outfit.setStyleImageUrl(callbackDTO.getPath());
|
||||
outfit.setItems(callbackDTO.getItems());
|
||||
@@ -159,33 +160,38 @@ public class StyleServiceImpl extends ServiceImpl<StyleMapper, Style> implements
|
||||
|
||||
public List<OutfitResultVO> getOutfitResult(List<String> requestIDs) {
|
||||
ArrayList<OutfitResultVO> resultVOS = new ArrayList<>();
|
||||
ArrayList<String> reQueryIds = new ArrayList<>();
|
||||
// 优先从redis中获取结果,没有再从数据库中查询
|
||||
for (String requestID : requestIDs) {
|
||||
String key = RedisURIConstants.outfitResultCache + requestID;
|
||||
Object outfit = cacheUtil.getCache(key);
|
||||
|
||||
if (Objects.isNull(outfit)){
|
||||
break;
|
||||
reQueryIds.add(requestID);
|
||||
continue;
|
||||
}
|
||||
resultVOS.add((OutfitResultVO) outfit);
|
||||
}
|
||||
|
||||
LambdaQueryWrapper<Style> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.in(Style::getPythonRequestId, requestIDs);
|
||||
if (!reQueryIds.isEmpty()) {
|
||||
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, null));
|
||||
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(StatusEnum.of(style.getGenerationStatus()).name());
|
||||
if (!StringUtil.isNullOrEmpty(style.getStyleImageUrl())) {
|
||||
outfitResultVO.setPath(minioUtil.getPresignedUrl(style.getStyleImageUrl(), CommonConstants.MINIO_PATH_TIMEOUT, null));
|
||||
}
|
||||
resultVOS.add(outfitResultVO);
|
||||
}
|
||||
resultVOS.add(outfitResultVO);
|
||||
}
|
||||
}
|
||||
|
||||
return resultVOS;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user